refactor : base page

This commit is contained in:
2025-09-24 16:24:45 +03:30
parent d2c495bfb1
commit 19802e913c
23 changed files with 562 additions and 568 deletions

View File

@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
class BaseLogic extends GetxController {
final RxBool isFilterSelected = false.obs;
final RxBool isSearchSelected = false.obs;
final RxnString searchValue = RxnString();
final TextEditingController textEditingController = TextEditingController();
void setSearchCallback(void Function(String?)? onSearchChanged) {
debounce<String?>(searchValue, (val) {
if (val != null && val.trim().isNotEmpty) {
onSearchChanged?.call(val);
}
}, time: const Duration(milliseconds: 600));
}
void toggleSearch() {
isSearchSelected.value = !isSearchSelected.value;
}
void clearSearch() {
textEditingController.clear();
searchValue.value = null;
isSearchSelected.value = false;
}
void toggleFilter() {
isFilterSelected.value = !isFilterSelected.value;
}
}

View File

@@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
class BasePage extends GetView<BaseLogic> {
const BasePage({
super.key,
this.routes,
this.routesWidget,
this.widgets,
this.child,
this.scrollable = false,
this.floatingActionButtonLocation,
this.floatingActionButton,
this.appBar,
this.backGroundWidget,
}) : assert(
(routes != null) || routesWidget != null,
'Either routes or routesWidget must be provided.',
);
final List<String>? routes;
final Breadcrumb? routesWidget;
final List<Widget>? widgets;
final Widget? child;
final bool scrollable;
final RAppBar? appBar;
final BackGroundWidget? backGroundWidget;
final FloatingActionButtonLocation? floatingActionButtonLocation;
final Widget? floatingActionButton;
Widget _buildHeader() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
routesWidget ?? TextBreadcrumb(routes: routes!),
if (controller.isSearchSelected.value) ...{SearchWidget()},
],
);
}
Widget _buildBody() {
final content = [_buildHeader(), if (child != null) Expanded(child: child!), ...?widgets];
if (scrollable) {
if (backGroundWidget != null) {
return Stack(
children: [
?backGroundWidget,
SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Column(children: content),
),
],
);
}
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Column(children: content),
);
}
if (backGroundWidget != null) {
return Stack(
children: [
?backGroundWidget,
Column(children: content),
],
);
}
return Column(children: content);
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
if (!didPop) appBar?.onBackTap?.call();
},
child: Scaffold(
backgroundColor: AppColor.bgLight,
appBar: appBar,
body: _buildBody(),
floatingActionButtonLocation: floatingActionButtonLocation,
floatingActionButton: floatingActionButton,
),
);
}
}

View File

@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/presentation/common/assets.gen.dart';
class BackGroundWidget extends StatelessWidget {
const BackGroundWidget({super.key, required this.gradient, required this.vecPath});
final Gradient gradient;
final String vecPath;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(gradient: gradient),
child: SvgGenImage.vec(vecPath).svg(fit: BoxFit.cover),
);
}
}
/*Container chickenBackground() {
return Container(
decoration: BoxDecoration(
gradient:
gradient ??
LinearGradient(
begin: Alignment(1.00, 0.01),
end: Alignment(0.04, 0.99),
colors: [
const Color(0xFFD6DCEF).withValues(alpha: .8),
const Color(0xFFD6E6E9).withValues(alpha: .8),
const Color(0xFFD6E4E3).withValues(alpha: .8),
],
),
),
child: Assets.vec.chickenPatternSvg.svg(fit: BoxFit.cover),
);
}*/

View File

@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
abstract class Breadcrumb extends StatelessWidget {
const Breadcrumb({super.key, this.routes});
final List<String>? routes;
}
class TextBreadcrumb extends Breadcrumb {
const TextBreadcrumb({super.key, super.routes});
@override
Widget build(BuildContext context) {
if (routes?.isEmpty ?? true) {
return SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.fromLTRB(0, 4, 7, 4),
child: Text(routes!.join(" > "), style: AppFonts.yekan14.copyWith(color: AppColor.bgDark)),
);
}
}
class ContainerBreadcrumb extends Breadcrumb {
const ContainerBreadcrumb({super.key, super.routes});
@override
Widget build(BuildContext context) {
if (routes?.isEmpty ?? true) {
return SizedBox.shrink();
}
return buildContainerPageRoute(routes!);
}
Widget buildContainerPageRoute(List<String> route) {
return Container(
height: 24.h,
margin: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(color: Color(0xFFE3E3E3), borderRadius: BorderRadius.circular(2.r)),
padding: EdgeInsets.symmetric(horizontal: 6.w),
child: ListView.separated(
scrollDirection: Axis.horizontal,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) => Center(
child: Text(route[index], style: AppFonts.yekan14.copyWith(color: AppColor.labelTextColor)),
),
separatorBuilder: (context, index) =>
Assets.vec.arrowLeftSvg.svg(height: 24.h, fit: BoxFit.fitHeight),
itemCount: route.length,
),
);
}
}

View File

@@ -1,34 +1,50 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
class RAppBar extends StatelessWidget implements PreferredSizeWidget {
final String? title;
final String? iconTitle;
final Color backgroundColor;
final Color iconColor;
final bool hasBack;
final List<Widget>? children;
final bool centerTitle;
final TextStyle? titleTextStyle;
final VoidCallback? onBackPressed;
final List<Widget>? additionalActions;
final double? leadingWidth;
final Widget? leading;
final Color backgroundColor;
final bool isBase;
final bool hasBack;
final VoidCallback? onBackTap;
final int? backId;
final bool hasSearch;
final VoidCallback? onSearchTap;
final bool hasNotification;
final VoidCallback? onNotificationTap;
final bool hasNews;
final VoidCallback? onNewsTap;
/// Preferred size widget for the AppBar bottom.
final PreferredSizeWidget? bottom;
const RAppBar({
super.key,
this.title,
this.iconTitle,
this.children,
this.backgroundColor = AppColor.blueNormal,
this.iconColor = Colors.white,
this.titleTextStyle,
this.onBackPressed,
this.additionalActions,
this.leading,
this.hasBack = true,
this.hasSearch = false,
this.hasNews = false,
this.hasNotification= false,
this.isBase = false,
this.centerTitle = false,
this.leadingWidth,
this.bottom,
this.backId,
this.onBackTap,
this.onSearchTap,
this.onNewsTap,
this.onNotificationTap,
});
@override
@@ -39,46 +55,64 @@ class RAppBar extends StatelessWidget implements PreferredSizeWidget {
elevation: 0,
excludeHeaderSemantics: true,
scrolledUnderElevation: 0,
centerTitle: centerTitle,
titleTextStyle: titleTextStyle ?? AppFonts.yekan16.copyWith(color: Colors.white),
title: title != null
? Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(title!),
if (iconTitle != null) ...{const SizedBox(width: 8)},
if (iconTitle != null) ...{SvgGenImage.vec(iconTitle!).svg(width: 24, height: 24)},
],
)
: null,
leadingWidth: leadingWidth?.toDouble(),
leading: leading != null
? Padding(padding: const EdgeInsets.only(right: 6), child: leading)
: null,
titleSpacing: 8,
actions: [
if (additionalActions != null) ...additionalActions!,
if (hasBack) ...{
GestureDetector(
onTap: onBackPressed ?? () => Get.back(),
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 2, 0),
child: Assets.vec.arrowLeftSvg.svg(
width: 24.w,
height: 24.h,
colorFilter: ColorFilter.mode(iconColor ?? Colors.white, BlendMode.srcIn),
title: Row(
mainAxisAlignment: _getMainAxisAlignment(),
mainAxisSize: MainAxisSize.min,
textDirection: TextDirection.rtl, // Support for RTL languages
children: [
if (children != null) ...children!,
if (hasNews || hasBack || hasSearch || hasNotification) const Spacer(),
if (hasSearch) SearchWidget(),
if (hasSearch) SizedBox(width: 8.w),
if (hasNews)
GestureDetector(
onTap: onNewsTap,
child: Badge.count(
count: 5,
child: Icon(CupertinoIcons.news_solid, color: Colors.white),
),
),
),
},
],
if (hasNews) SizedBox(width: 8.w),
if (hasNotification)
Badge.count(count: 2, child: Icon(CupertinoIcons.bell_fill, color: Colors.white)),
if (hasNotification) SizedBox(width: 8.w),
if (hasBack)
GestureDetector(
onTap: onBackTap ?? () => Get.back(id: backId),
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 2, 0),
child: Assets.vec.arrowLeftSvg.svg(
width: 24.w,
height: 24.h,
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
),
if (hasBack) SizedBox(width: 8.w),
],
),
titleSpacing: 2,
bottom: bottom,
);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
Size get preferredSize => Size.fromHeight(kToolbarHeight + (bottom?.preferredSize.height ?? 0));
MainAxisAlignment _getMainAxisAlignment() {
if (centerTitle) {
return MainAxisAlignment.center;
} else if (children != null && children!.isNotEmpty) {
return MainAxisAlignment.start;
} else {
return MainAxisAlignment.end;
}
}
}
class RAppBar2 extends StatelessWidget implements PreferredSizeWidget {

View File

@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
class SearchWidget extends GetView<BaseLogic> {
const SearchWidget({super.key});
@override
Widget build(BuildContext context) {
return ObxValue((data) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
height: data.value ? 40 : 0,
child: Visibility(
visible: data.value,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: RTextField(
height: 40,
borderColor: AppColor.blackLight,
suffixIcon: ObxValue(
(data) => Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: (data.value == null)
? Assets.vec.searchSvg.svg(
width: 10,
height: 10,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
)
: IconButton(
onPressed: () {
controller.clearSearch();
},
enableFeedback: true,
padding: EdgeInsets.zero,
iconSize: 24,
splashRadius: 50,
icon: Assets.vec.closeCircleSvg.svg(
width: 20,
height: 20,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
),
controller.searchValue,
),
hintText: 'جستجو کنید ...',
hintStyle: AppFonts.yekan16.copyWith(color: AppColor.blueNormal),
filledColor: Colors.white,
filled: true,
controller: controller.textEditingController,
onChanged: (val) => controller.searchValue.value = val,
),
),
),
);
}, controller.isSearchSelected);
}
}

View File

@@ -1,9 +1,17 @@
export 'app_bar/r_app_bar.dart';
export 'base_page/widgets/r_app_bar.dart';
export 'bottom_navigation/r_bottom_navigation.dart';
export 'bottom_navigation/wave_bottom_navigation.dart';
export 'bottom_sheet/base_bottom_sheet.dart';
export 'bottom_sheet/date_picker_bottom_sheet.dart';
export 'check_box/check_box_widget.dart';
//base page
export 'base_page/view.dart';
export 'base_page/logic.dart';
export 'base_page/widgets/back_ground_widget.dart';
export 'base_page/widgets/breadcrumb.dart';
export 'base_page/widgets/search_widget.dart';
//buttons
export 'buttons/buttons.dart';
export 'card/card_icon_widget.dart';