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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 249 KiB

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

View File

@@ -1,14 +1,10 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rasadyar_chicken/data/models/response/hatching/hatching_models.dart';
import 'package:rasadyar_chicken/data/models/response/hatching_report/hatching_report.dart';
import 'package:rasadyar_chicken/presentation/pages/poultry_science/active_hatching/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/page_route.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_core/presentation/widget/list_item/list_item2.dart';
import 'package:rasadyar_core/presentation/widget/list_view/r_paginated_list_view.dart';
class ActiveHatchingPage extends GetView<ActiveHatchingLogic> {
const ActiveHatchingPage({super.key});
@@ -18,11 +14,22 @@ class ActiveHatchingPage extends GetView<ActiveHatchingLogic> {
return BasePage(
hasSearch: false,
hasFilter: false,
routesWidget: buildContainerPageRoute(controller.routesName),
routesWidget: buildContainerPageRoute(controller.routesName),
onBackPressed: () => Get.back(id: poultryFirstKey),
widgets: [
child: Stack(
children: [
Positioned.fill(
child: Assets.vec.chickenPatternSvg.svg(
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(Colors.grey.shade400, BlendMode.srcIn),
),
),
Positioned.fill(child: Column(children: [hatchingWidget()])),
],
),
/*widgets: [
hatchingWidget()
],
],*/
);
}
@@ -99,8 +106,10 @@ class ActiveHatchingPage extends GetView<ActiveHatchingLogic> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('نژاد:${item.breed?.first.breed ?? 'N/A'}', style: AppFonts.yekan14.copyWith(color: AppColor.textColor)),
Text(
'نژاد:${item.breed?.first.breed ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
Text(
' سن${item.age} (روز)',
@@ -120,8 +129,12 @@ class ActiveHatchingPage extends GetView<ActiveHatchingLogic> {
value: item.quantity.separatedByCommaFa,
unit: '(قطعه)',
),
buildUnitRow(title: 'مانده در سالن', value: item.leftOver.separatedByCommaFa,unit: '(قطعه)',),
buildUnitRow(title: 'تلفات', value: item.losses.separatedByCommaFa,unit: '(قطعه)',),
buildUnitRow(
title: 'مانده در سالن',
value: item.leftOver.separatedByCommaFa,
unit: '(قطعه)',
),
buildUnitRow(title: 'تلفات', value: item.losses.separatedByCommaFa, unit: '(قطعه)'),
buildRow(
title: 'دامپزشک فارم',
value: '${item.vetFarm?.vetFarmFullName}(${item.vetFarm?.vetFarmMobile})',
@@ -136,7 +149,6 @@ class ActiveHatchingPage extends GetView<ActiveHatchingLogic> {
color: (item.reportInfo?.image ?? false) ? AppColor.greenNormal : AppColor.redDark,
),
),
],
),
);

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/poultry_farm/poultry_farm.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_core/presentation/widget/base_page/widgets/back_ground_widget.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/page_route.dart';
import 'package:rasadyar_core/core.dart';
@@ -16,9 +16,15 @@ class FarmPage extends GetView<FarmLogic> {
return BasePage(
hasFilter: false,
hasSearch: false,
routesWidget: buildContainerPageRoute(controller.routesName),
isBase: true,
routes: [],
onBackPressed: () => Get.back(id: poultryFirstKey),
widgets: [firstTagInformation(), farmListWidget()],
child: Stack(
children: [
Positioned.fill(child: chickenBackground()),
Positioned.fill(child: Column(children: [firstTagInformation(), farmListWidget()])),
],
),
);
}
@@ -134,7 +140,11 @@ class FarmPage extends GetView<FarmLogic> {
title: 'دامپزشک فارم',
value: '${item.vetFarm?.fullName} (${item.vetFarm?.mobile ?? '-'})',
),
buildUnitRow(title: 'ظرفیت فارم', value: item.totalCapacity.separatedByCommaFa, unit: '(قطعه)'),
buildUnitRow(
title: 'ظرفیت فارم',
value: item.totalCapacity.separatedByCommaFa,
unit: '(قطعه)',
),
buildRow(
title: 'جوجه ریزی فعال (تعداد دوره) ',
value:

View File

@@ -19,13 +19,7 @@ class PoultryScienceHomePage extends GetView<PoultryScienceHomeLogic> {
hasBack: false,
hasFilter: false,
hasSearch: false,
additionalActions: [
SizedBox(width: 12),
Icon(CupertinoIcons.envelope_fill, color: Colors.white),
SizedBox(width: 12),
Icon(CupertinoIcons.bell_fill, color: Colors.white),
SizedBox(width: 12),
],
),
body: SingleChildScrollView(
physics: BouncingScrollPhysics(),

View File

@@ -15,22 +15,32 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
@override
Widget build(BuildContext context) {
return BasePage(
return ChickenBasePage(
hasBack: true,
hasFilter: true,
hasSearch: true,
filteringWidget: filterBottomSheet(),
onSearchChanged: (data) => controller.setSearchValue(data),
backId: poultryFirstKey,
routes: controller.routesName,
//routesWidget: ObxValue((route) => buildContainerPageRoute(route), controller.routesName),
child: Stack(
children: [
Positioned.fill(
child: Assets.vec.chickenPatternSvg.svg(
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(Color(0xFFC3CBDA), BlendMode.srcIn),
),
),
SizedBox(height: 50, child: segmentWidget()),
routesWidget: ObxValue((route) => buildContainerPageRoute(route), controller.routesName),
onBackPressed: () => Get.back(id: poultryFirstKey),
widgets: [
segmentWidget(),
ObxValue((data) {
return data.value == 0 ? hatchingWidget() : reportWidget();
}, controller.selectedSegmentIndex),
],
Expanded(
child: ObxValue((data) {
return data.value == 0 ? hatchingWidget() : reportWidget();
}, controller.selectedSegmentIndex),
),
],
),
);
}
@@ -109,9 +119,10 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('نژاد:${ item.breed?.first.breed ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor)),
Text(
'نژاد:${item.breed?.first.breed ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
Text(
' سن ${item.age} (روزه)',
@@ -133,12 +144,14 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
unit: '(قطعه)',
),
buildUnitRow(
title: 'مانده در سالن', value: item.leftOver.separatedByCommaFa, unit: '(قطعه)',),
buildUnitRow(title: 'تلفات', value: item.losses.separatedByCommaFa, unit: '(قطعه)',),
title: 'مانده در سالن',
value: item.leftOver.separatedByCommaFa,
unit: '(قطعه)',
),
buildUnitRow(title: 'تلفات', value: item.losses.separatedByCommaFa, unit: '(قطعه)'),
buildRow(
title: 'دامپزشک فارم',
value: '${item.vetFarm?.vetFarmFullName}(${item.vetFarm?.vetFarmMobile})',
),
buildRow(
title: 'شرح بازرسی',
@@ -188,77 +201,77 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
shrinkWrap: true,
itemCount: controller.pickedImages.length + 1,
itemBuilder: (context, index) {
if (index + 1 < 7&& index == data.length) {
return GestureDetector(
onTap: () async {
await controller.pickImages();
},
child: Container(
width: 80.h,
height: 80.h,
decoration: BoxDecoration(
color: AppColor.lightGreyNormal,
borderRadius: BorderRadius.circular(8.r),
),
child: Center(
child: Icon(
Icons.add_a_photo,
color: AppColor.lightGreyDarker,
size: 32.h,
),
),
),
);
} else {
return Container(
if (index + 1 < 7 && index == data.length) {
return GestureDetector(
onTap: () async {
await controller.pickImages();
},
child: Container(
width: 80.h,
height: 80.h,
decoration: BoxDecoration(
color: AppColor.lightGreyNormal,
borderRadius: BorderRadius.circular(8.r),
),
child: Stack(
children: [
Positioned.fill(
child: Image.file(File(data[index].path), fit: BoxFit.cover),
),
child: Center(
child: Icon(
Icons.add_a_photo,
color: AppColor.lightGreyDarker,
size: 32.h,
),
),
),
);
} else {
return Container(
width: 80.h,
height: 80.h,
decoration: BoxDecoration(
color: AppColor.lightGreyNormal,
borderRadius: BorderRadius.circular(8.r),
),
child: Stack(
children: [
Positioned.fill(
child: Image.file(File(data[index].path), fit: BoxFit.cover),
),
Positioned(
top: 4,
left: 4,
child: GestureDetector(
onTap: () {
controller.removeImage(index);
},
child: Container(
width: 24.w,
height: 24.h,
clipBehavior: Clip.antiAlias,
padding: EdgeInsets.all(4),
decoration: ShapeDecoration(
color: Colors.white.withValues(alpha: 0.80),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
Positioned(
top: 4,
left: 4,
child: GestureDetector(
onTap: () {
controller.removeImage(index);
},
child: Container(
width: 24.w,
height: 24.h,
clipBehavior: Clip.antiAlias,
padding: EdgeInsets.all(4),
decoration: ShapeDecoration(
color: Colors.white.withValues(alpha: 0.80),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
child: Assets.vec.trashSvg.svg(
width: 8.w,
height: 8.h,
colorFilter: ColorFilter.mode(
AppColor.redNormal,
BlendMode.srcIn,
),
),
child: Assets.vec.trashSvg.svg(
width: 8.w,
height: 8.h,
colorFilter: ColorFilter.mode(
AppColor.redNormal,
BlendMode.srcIn,
),
),
),
),
],
),
);
}
}, );
},controller.pickedImages),
),
],
),
);
}
},
);
}, controller.pickedImages),
SizedBox(height: 35.h),
Text(
@@ -305,7 +318,7 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
),
).whenComplete(() {
controller.pickedImages.clear();
},);
});
}
Padding segmentWidget() {
@@ -410,8 +423,6 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
item.hatching?.chickenBreed ?? 'N/A',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
Text(
@@ -428,23 +439,25 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
buildRow(title: 'شماره مجوز جوجه ریزی', value: item.hatching?.licenceNumber ?? 'N/A'),
buildUnitRow(
title: 'حجم جوجه ریزی',
value: item.hatching?.quantity.separatedByCommaFa ?? 'N/A',
title: 'حجم جوجه ریزی',
value: item.hatching?.quantity.separatedByCommaFa ?? 'N/A',
unit: '(قطعه)'
unit: '(قطعه)',
),
buildUnitRow(
title: 'مانده در سالن',
value: item.hatching?.leftOver.separatedByCommaFa ?? 'N/A',
unit: '(قطعه)'
title: 'مانده در سالن',
value: item.hatching?.leftOver.separatedByCommaFa ?? 'N/A',
unit: '(قطعه)',
),
buildUnitRow(
title: 'تلفات',
value: item.hatching?.losses.separatedByCommaFa ?? 'N/A',
unit: '(قطعه)',
),
buildUnitRow(title: 'تلفات',
value: item.hatching?.losses.separatedByCommaFa ?? 'N/A',
unit: '(قطعه)'),
buildRow(
title: 'دامپزشک فارم',
value:
'${item.hatching?.vetFarm?.vetFarmFullname}(${item.hatching?.vetFarm?.vetFarmMobile})',
'${item.hatching?.vetFarm?.vetFarmFullname}(${item.hatching?.vetFarm?.vetFarmMobile})',
),
buildRow(
title: 'شرح بازرسی',
@@ -457,12 +470,16 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
Visibility(
visible: item.realQuantityAi != null,
child: buildRow(
title: 'تعداد تاییده هوش مصنوعی', value: item.realQuantityAi.separatedByComma),
title: 'تعداد تاییده هوش مصنوعی',
value: item.realQuantityAi.separatedByComma,
),
),
Visibility(
visible: item.realQuantity != null,
child: buildRow(
title: 'تعداد تاییده', value: item.realQuantity.separatedByComma ?? '-'),
title: 'تعداد تاییده',
value: item.realQuantity.separatedByComma ?? '-',
),
),
},
@@ -493,7 +510,6 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
visible: item.messageRegistererRole != null,
child: buildRow(title: 'نقش کننده گزارش', value: item.messageRegistererRole ?? '-'),
),
},
SizedBox(
@@ -506,18 +522,17 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
itemBuilder: (context, index) =>
Container(
height: 100.h,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
image: DecorationImage(
image: NetworkImage(item.image?[index] ?? ''),
fit: BoxFit.cover,
),
),
itemBuilder: (context, index) => Container(
height: 100.h,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
image: DecorationImage(
image: NetworkImage(item.image?[index] ?? ''),
fit: BoxFit.cover,
),
),
),
),
),
],
@@ -639,7 +654,6 @@ class InspectionPoultrySciencePage extends GetView<InspectionPoultryScienceLogic
);
}
Widget filterBottomSheet() {
return BaseBottomSheet(
height: 200,

View File

@@ -1,18 +1,13 @@
import 'package:rasadyar_chicken/presentation/pages/common/profile/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/poultry_science/root/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_chicken/presentation/widget/search/logic.dart';
import 'package:rasadyar_core/core.dart';
import '../widget/base_page/logic.dart';
class GlobalBinding extends Bindings {
@override
void dependencies() {
Get.put(BaseLogic(), permanent: true);
Get.lazyPut(() => ProfileLogic(), fenix: true);
Get.lazyPut(() => SearchLogic(), fenix: true);
//root logics
}

View File

@@ -1,7 +1,5 @@
import 'package:rasadyar_chicken/presentation/pages/common/auth/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/common/auth/view.dart';
import 'package:rasadyar_chicken/presentation/pages/common/profile/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/common/profile/view.dart';
import 'package:rasadyar_chicken/presentation/pages/common/role/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/common/role/view.dart';
import 'package:rasadyar_chicken/presentation/pages/poultry_science/active_hatching/logic.dart';
@@ -22,9 +20,7 @@ import 'package:rasadyar_chicken/presentation/pages/poultry_science/root/view.da
import 'package:rasadyar_chicken/presentation/pages/steward/steward.dart';
import 'package:rasadyar_chicken/presentation/routes/global_binding.dart';
import 'package:rasadyar_chicken/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart';
import 'package:rasadyar_chicken/presentation/widget/captcha/logic.dart';
import 'package:rasadyar_chicken/presentation/widget/search/logic.dart';
import 'package:rasadyar_core/core.dart';
sealed class ChickenPages {
@@ -93,7 +89,6 @@ sealed class ChickenPages {
page: () => SalesOutOfProvincePage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => SearchLogic());
Get.lazyPut(() => SalesOutOfProvinceLogic());
Get.lazyPut(() => SalesOutOfProvinceBuyersLogic());
Get.lazyPut(() => SalesOutOfProvinceSalesListLogic());
@@ -104,7 +99,6 @@ sealed class ChickenPages {
page: () => SalesOutOfProvinceBuyersPage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => SearchLogic());
Get.lazyPut(() => SalesOutOfProvinceLogic());
Get.lazyPut(() => SalesOutOfProvinceBuyersLogic());
Get.lazyPut(() => SalesOutOfProvinceSalesListLogic());
@@ -117,7 +111,6 @@ sealed class ChickenPages {
binding: BindingsBuilder(() {
Get.lazyPut(() => BaseLogic());
Get.lazyPut(() => SalesInProvinceLogic());
Get.lazyPut(() => SearchLogic());
}),
),
@@ -137,7 +130,6 @@ sealed class ChickenPages {
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => BaseLogic());
Get.lazyPut(() => SearchLogic());
Get.lazyPut(() => BuyOutOfProvinceLogic());
}),
),
@@ -147,7 +139,6 @@ sealed class ChickenPages {
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => BaseLogic());
Get.lazyPut(() => SearchLogic());
Get.lazyPut(() => BuyInProvinceLogic());
Get.lazyPut(() => BuyInProvinceWaitingLogic());
Get.lazyPut(() => BuyInProvinceAllLogic());
@@ -178,7 +169,7 @@ sealed class ChickenPages {
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => InspectionPoultryScienceLogic());
Get.lazyPut(() => SearchLogic());
}),
),
GetPage(

View File

@@ -1,99 +1,37 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart';
import 'package:rasadyar_chicken/presentation/widget/search/logic.dart';
import 'package:rasadyar_core/core.dart';
/// Creates a customized AppBar for the Rasadyar Chicken app.
RAppBar chickenAppBar({
bool hasBack = true,
bool hasFilter = true,
bool hasSearch = true,
bool isBase = false,
VoidCallback? onBackPressed,
GestureTapCallback? onFilterTap,
GestureTapCallback? onSearchTap,
List<Widget>? additionalActions,
bool hasNotification = false,
bool hasNews = false,
int? backId,
VoidCallback? onBackTap,
VoidCallback? onFilterTap,
VoidCallback? onSearchTap,
VoidCallback? onNewsTap,
VoidCallback? onNotificationTap,
}) {
return RAppBar(
hasBack: isBase == true ? false : hasBack,
onBackPressed: onBackPressed,
leadingWidth: 155,
leading: Row(
mainAxisSize: MainAxisSize.min,
spacing: 6,
children: [
Text('رصدطیور', style: AppFonts.yekan16Bold.copyWith(color: Colors.white)),
Assets.vec.chickenSvg.svg(
width: 24,
height: 24,
),
],
),
additionalActions: [
if (!isBase && hasSearch) searchWidget(onSearchTap),
SizedBox(width: 8),
if (!isBase && hasFilter) filterWidget(onFilterTap),
SizedBox(width: 8),
if (additionalActions != null) ...additionalActions,
hasBack: hasBack,
hasSearch: hasSearch,
hasNotification: hasNotification,
hasNews: hasNews,
isBase: isBase,
backId: backId,
onSearchTap: onSearchTap,
onBackTap: onBackTap,
onNotificationTap: onNotificationTap,
onNewsTap: onNewsTap,
backgroundColor: AppColor.blueNormal,
children: [
Text('رصدطیور', style: AppFonts.yekan16Bold),
const SizedBox(width: 6),
Assets.vec.chickenSvg.svg(height: 24, width: 24),
],
);
}
GestureDetector filterWidget(GestureTapCallback? onFilterTap) {
return GestureDetector(
onTap: onFilterTap,
child: Stack(
alignment: Alignment.topRight,
children: [
Assets.vec.filterOutlineSvg.svg(
width: 20,
height: 20,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Obx(() {
final controller = Get.find<BaseLogic>();
return Visibility(
visible: controller.isFilterSelected.value,
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
);
}),
],
),
);
}
GestureDetector searchWidget(GestureTapCallback? onSearchTap) {
return GestureDetector(
onTap: onSearchTap,
child: Stack(
alignment: Alignment.topRight,
children: [
Assets.vec.searchSvg.svg(
width: 24,
height: 24,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Obx(() {
final controller = Get.find<SearchLogic>();
return Visibility(
visible: controller.searchValue.value!=null,
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
);
}),
],
),
);
}

View File

@@ -1,9 +0,0 @@
import 'package:rasadyar_core/core.dart';
class BaseLogic extends GetxController {
final RxBool isFilterSelected = false.obs;
void toggleFilter() {
isFilterSelected.value = !isFilterSelected.value;
}
}

View File

@@ -1,112 +1,70 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/widget/app_bar.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart';
import 'package:rasadyar_chicken/presentation/widget/page_route.dart';
import 'package:rasadyar_chicken/presentation/widget/search/logic.dart';
import 'package:rasadyar_chicken/presentation/widget/search/view.dart';
import 'package:rasadyar_core/core.dart';
class BasePage extends StatefulWidget {
const BasePage({
class ChickenBasePage extends GetView<BaseLogic> {
const ChickenBasePage({
super.key,
this.routes,
this.widgets,
this.routesWidget,
this.floatingActionButtonLocation,
this.floatingActionButton,
this.onSearchChanged,
this.child,
this.hasBack = true,
this.hasFilter = true,
this.hasSearch = true,
this.isBase = false,
this.onBackPressed,
this.hasNotification = false,
this.hasNews = false,
this.backId,
this.onBackTap,
this.onFilterTap,
this.onSearchTap,
this.onNewsTap,
this.onNotificationTap,
this.onSearchChanged,
this.routes,
this.routesWidget,
this.widgets,
this.child,
this.scrollable = false,
this.floatingActionButtonLocation,
this.floatingActionButton,
this.filteringWidget,
}) : assert(
(routes != null) || routesWidget != null,
'Either routes or routesWidget must be provided.',
);
//AppBar properties`
final bool hasBack;
final bool hasFilter;
final bool hasSearch;
final bool isBase;
final bool hasNotification;
final bool hasNews;
final int? backId;
final VoidCallback? onBackTap;
final VoidCallback? onFilterTap;
final VoidCallback? onSearchTap;
final VoidCallback? onNewsTap;
final VoidCallback? onNotificationTap;
final List<String>? routes;
final Widget? routesWidget;
final Breadcrumb? routesWidget;
final List<Widget>? widgets;
final Widget? child;
final bool scrollable;
final FloatingActionButtonLocation? floatingActionButtonLocation;
final Widget? floatingActionButton;
final Widget? filteringWidget;
final void Function(String?)? onSearchChanged;
final bool hasBack;
final bool hasFilter;
final bool hasSearch;
final bool isBase;
final VoidCallback? onBackPressed;
final GestureTapCallback? onFilterTap;
final GestureTapCallback? onSearchTap;
@override
State<BasePage> createState() => _BasePageState();
}
class _BasePageState extends State<BasePage> {
BaseLogic get controller => Get.find<BaseLogic>();
Worker? filterWorker;
bool _isBottomSheetOpen = false;
@override
void initState() {
super.initState();
/* filterWorker = ever(controller.isFilterSelected, (bool isSelected) {
if (!mounted) return;
if (isSelected && widget.filteringWidget != null) {
// بررسی اینکه آیا bottomSheet از قبل باز است یا نه
if (_isBottomSheetOpen) {
controller.isFilterSelected.value = false;
return;
}
// بررسی اینکه آیا route فعلی current است یا نه
if (ModalRoute.of(context)?.isCurrent != true) {
controller.isFilterSelected.value = false;
return;
}
_isBottomSheetOpen = true;
Get.bottomSheet(
widget.filteringWidget!,
isScrollControlled: true,
isDismissible: true,
enableDrag: true,
).then((_) {
// تنظیم مقدار به false بعد از بسته شدن bottomSheet
if (mounted) {
_isBottomSheetOpen = false;
controller.isFilterSelected.value = false;
}
});
}
});*/
}
@override
void dispose() {
filterWorker?.dispose();
super.dispose();
}
void _onFilterTap() {
if (widget.hasFilter && widget.filteringWidget != null) {
// بررسی اینکه آیا این route در top است یا نه
final currentRoute = ModalRoute.of(context);
if (currentRoute?.isCurrent != true) {
return;
}
if (hasFilter && filteringWidget != null) {
final currentRoute = ModalRoute.of(Get.context!);
if (currentRoute?.isCurrent != true) return;
// مستقیماً bottomSheet را باز کنید
Get.bottomSheet(
widget.filteringWidget!,
filteringWidget!,
isScrollControlled: true,
isDismissible: true,
enableDrag: true,
@@ -116,140 +74,27 @@ class _BasePageState extends State<BasePage> {
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) => widget.onBackPressed,
child: Scaffold(
backgroundColor: AppColor.bgLight,
appBar: chickenAppBar(
hasBack: widget.isBase ? false : widget.hasBack,
onBackPressed: widget.onBackPressed,
hasFilter: widget.hasFilter,
hasSearch: widget.hasSearch,
isBase: widget.isBase,
onFilterTap: widget.hasFilter ? _onFilterTap : null,
onSearchTap: widget.hasSearch ? () => Get.find<SearchLogic>().toggleSearch() : null,
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
widget.routesWidget != null ? widget.routesWidget! : buildPageRoute(widget.routes!),
if (!widget.isBase && widget.hasSearch) ...{
SearchWidget(onSearchChanged: widget.onSearchChanged),
},
if (widget.child != null) ...{Expanded(child: widget.child!)},
...?widget.widgets,
],
),
floatingActionButtonLocation: widget.floatingActionButtonLocation,
floatingActionButton: widget.floatingActionButton,
),
);
}
}
class BasePageWithScroll extends StatefulWidget {
const BasePageWithScroll({
super.key,
this.routes,
required this.widgets,
this.routesWidget,
this.floatingActionButtonLocation,
this.floatingActionButton,
this.onSearchChanged,
this.hasBack = true,
this.hasFilter = true,
this.hasSearch = true,
this.isBase = false,
this.onBackPressed,
this.onFilterTap,
this.onSearchTap,
this.filteringWidget,
}) : assert(
(routes != null) || routesWidget != null,
'Either routes or routesWidget must be provided.',
);
final List<String>? routes;
final Widget? routesWidget;
final List<Widget> widgets;
final FloatingActionButtonLocation? floatingActionButtonLocation;
final Widget? floatingActionButton;
final Widget? filteringWidget;
final void Function(String?)? onSearchChanged;
final bool hasBack;
final bool hasFilter;
final bool hasSearch;
final bool isBase;
final VoidCallback? onBackPressed;
final GestureTapCallback? onFilterTap;
final GestureTapCallback? onSearchTap;
@override
State<BasePageWithScroll> createState() => _BasePageWithScrollState();
}
class _BasePageWithScrollState extends State<BasePageWithScroll> {
BaseLogic get controller => Get.find<BaseLogic>();
Worker? filterWorker;
@override
void dispose() {
filterWorker?.dispose();
super.dispose();
}
void _onFilterTap() {
if (widget.hasFilter && widget.filteringWidget != null) {
// بررسی اینکه آیا این route در top است یا نه
final currentRoute = ModalRoute.of(context);
if (currentRoute?.isCurrent != true) {
return;
}
// مستقیماً bottomSheet را باز کنید
Get.bottomSheet(
widget.filteringWidget!,
isScrollControlled: true,
isDismissible: true,
enableDrag: true,
);
}
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) => widget.onBackPressed,
child: Scaffold(
backgroundColor: AppColor.bgLight,
appBar: chickenAppBar(
hasBack: widget.isBase ? false : widget.hasBack,
onBackPressed: widget.onBackPressed,
hasFilter: widget.hasFilter,
hasSearch: widget.hasSearch,
isBase: widget.isBase,
onFilterTap: widget.hasFilter ? _onFilterTap : null,
onSearchTap: widget.hasSearch ? () => Get.find<SearchLogic>().toggleSearch() : null,
),
body: SingleChildScrollView(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
widget.routesWidget != null ? widget.routesWidget! : buildPageRoute(widget.routes!),
if (!widget.isBase && widget.hasSearch) ...{
SearchWidget(onSearchChanged: widget.onSearchChanged),
},
...widget.widgets,
],
),
),
floatingActionButtonLocation: widget.floatingActionButtonLocation,
floatingActionButton: widget.floatingActionButton,
return BasePage(
routes: routes,
routesWidget: routesWidget,
widgets: widgets,
child: child,
scrollable: scrollable,
floatingActionButtonLocation: floatingActionButtonLocation,
floatingActionButton: floatingActionButton,
appBar: chickenAppBar(
isBase: isBase,
hasBack: isBase ? false : hasBack,
onBackTap: onBackTap,
onNewsTap: onNewsTap,
hasFilter: hasFilter,
hasSearch: hasSearch,
hasNews: hasNews,
hasNotification: hasNotification,
backId: backId,
onNotificationTap: onNotificationTap,
onFilterTap: hasFilter ? _onFilterTap : null,
onSearchTap: hasSearch ? controller.toggleSearch : null,
),
);
}

View File

@@ -2,10 +2,13 @@ import 'package:flutter/cupertino.dart';
import 'package:rasadyar_core/core.dart';
Widget buildPageRoute(List<String> route) {
if(route.isEmpty){
return SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.fromLTRB(0, 4, 7, 4),
child: Text(
route.isEmpty ? 'خانه' : route.join(" > "),
route.join(" > "),
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
);

View File

@@ -1,21 +0,0 @@
import 'package:rasadyar_core/core.dart';
class SearchLogic extends GetxController {
final RxBool isSearchSelected = false.obs;
final RxnString searchValue = RxnString();
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;
}
}

View File

@@ -460,18 +460,12 @@ class $AssetsIconsGen {
class $AssetsImagesGen {
const $AssetsImagesGen();
/// File path: assets/images/bg_chicken_pattern.webp
AssetGenImage get bgChickenPattern => const AssetGenImage('assets/images/bg_chicken_pattern.webp');
/// File path: assets/images/chicken.png
AssetGenImage get chicken => const AssetGenImage('assets/images/chicken.png');
/// File path: assets/images/inner_splash.webp
AssetGenImage get innerSplash => const AssetGenImage('assets/images/inner_splash.webp');
/// File path: assets/images/live_chicken.jpg
AssetGenImage get liveChicken => const AssetGenImage('assets/images/live_chicken.jpg');
/// File path: assets/images/outter_splash.webp
AssetGenImage get outterSplash => const AssetGenImage('assets/images/outter_splash.webp');
@@ -482,15 +476,7 @@ class $AssetsImagesGen {
AssetGenImage get selectRole => const AssetGenImage('assets/images/select_role.webp');
/// List of all assets
List<AssetGenImage> get values => [
bgChickenPattern,
chicken,
innerSplash,
liveChicken,
outterSplash,
placeHolder,
selectRole,
];
List<AssetGenImage> get values => [chicken, innerSplash, outterSplash, placeHolder, selectRole];
}
class $AssetsLogosGen {

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

@@ -1,27 +1,8 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SearchWidget extends StatefulWidget {
const SearchWidget({super.key, this.onSearchChanged});
final void Function(String?)? onSearchChanged;
@override
State<SearchWidget> createState() => _SearchWidgetState();
}
class _SearchWidgetState extends State<SearchWidget> {
late final SearchLogic controller;
final TextEditingController textEditingController = TextEditingController();
@override
void initState() {
super.initState();
controller = Get.find<SearchLogic>();
controller.setSearchCallback(widget.onSearchChanged);
}
class SearchWidget extends GetView<BaseLogic> {
const SearchWidget({super.key});
@override
Widget build(BuildContext context) {
@@ -48,10 +29,7 @@ class _SearchWidgetState extends State<SearchWidget> {
)
: IconButton(
onPressed: () {
textEditingController.clear();
controller.searchValue.value = null;
controller.isSearchSelected.value = false;
widget.onSearchChanged?.call(null);
controller.clearSearch();
},
enableFeedback: true,
padding: EdgeInsets.zero,
@@ -70,7 +48,7 @@ class _SearchWidgetState extends State<SearchWidget> {
hintStyle: AppFonts.yekan16.copyWith(color: AppColor.blueNormal),
filledColor: Colors.white,
filled: true,
controller: textEditingController,
controller: controller.textEditingController,
onChanged: (val) => controller.searchValue.value = val,
),
),

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';