feat : new ui for steward

This commit is contained in:
2025-10-01 13:16:16 +03:30
parent 20a4a59c2e
commit 3d957b8aee
12 changed files with 573 additions and 136 deletions

View File

@@ -28,123 +28,122 @@ class HomePage extends GetView<HomeLogic> {
InkWell mainInformation() {
return InkWell(
onTap: () {
controller.isExpanded.value = !controller.isExpanded.value;
},
onTap: () {
controller.isExpanded.value = !controller.isExpanded.value;
},
child: Stack(
clipBehavior: Clip.none,
children: [
Container(
margin: EdgeInsetsGeometry.all(6),
child: Stack(
clipBehavior: Clip.none,
children: [
Container(
margin: EdgeInsetsGeometry.all(6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.50, color: const Color(0xFFA9A9A9)),
),
child: ObxValue((data) {
return AnimatedSize(
duration: Duration(milliseconds: 300),
child: data.value
? Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.end,
children: [
AnimatedRotation(
turns: 180,
duration: Duration(milliseconds: 3000),
child: Icon(CupertinoIcons.chevron_up, size: 18),
),
],
),
SizedBox(height: 8),
_todayShipmentWidget(),
_todayShipmentWidget2(),
_inventoryWidget(),
Row(
children: [
Text(
'اطلاعات بارها',
textAlign: TextAlign.right,
style: AppFonts.yekan16,
),
],
),
_informationShipment(),
Row(
children: [
Text(
'اطلاعات توزیع',
textAlign: TextAlign.right,
style: AppFonts.yekan16,
),
],
),
distributionInformationWidget(),
],
),
)
: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.end,
children: [Icon(CupertinoIcons.chevron_down, size: 18)],
),
_todayShipmentWidget(),
_todayShipmentWidget2(),
_inventoryWidget(),
],
),
),
);
}, controller.isExpanded),
),
Positioned(
top: -10,
right: 20,
child: Container(
height: 32.h,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.50, color: const Color(0xFFA9A9A9)),
),
child: ObxValue((data) {
return AnimatedSize(
duration: Duration(milliseconds: 300),
child: data.value
? Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.end,
children: [
AnimatedRotation(
turns: 180,
duration: Duration(milliseconds: 3000),
child: Icon(CupertinoIcons.chevron_up, size: 18),
),
],
),
SizedBox(height: 8),
_todayShipmentWidget(),
_inventoryWidget(),
Row(
children: [
Text(
'اطلاعات بارها',
textAlign: TextAlign.right,
style: AppFonts.yekan16,
),
],
),
_informationShipment(),
Row(
children: [
Text(
'اطلاعات توزیع',
textAlign: TextAlign.right,
style: AppFonts.yekan16,
),
],
),
distributionInformationWidget(),
],
),
)
: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(CupertinoIcons.chevron_down, size: 18),
],
),
_todayShipmentWidget(),
_inventoryWidget(),
],
),
),
);
}, controller.isExpanded),
),
Positioned(
top: -10,
right: 20,
child: Container(
height: 32.h,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.50, color: const Color(0xFFA9A9A9)),
),
padding: EdgeInsets.symmetric(horizontal: 8),
child: Row(
spacing: 8,
children: [
Assets.vec.chicken2Svg.svg(
width: 24.w,
height: 24.h,
colorFilter: ColorFilter.mode(AppColor.blueDark, BlendMode.srcIn),
),
Text(
'اطلاعات مرغ گرم',
textAlign: TextAlign.right,
style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor),
),
],
),
padding: EdgeInsets.symmetric(horizontal: 8),
child: Row(
spacing: 8,
children: [
Assets.vec.chicken2Svg.svg(
width: 24.w,
height: 24.h,
colorFilter: ColorFilter.mode(AppColor.blueDark, BlendMode.srcIn),
),
Text(
'اطلاعات مرغ گرم',
textAlign: TextAlign.right,
style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor),
),
],
),
),
],
),
);
),
],
),
);
}
Widget distributionInformationWidget() {
@@ -278,10 +277,10 @@ class HomePage extends GetView<HomeLogic> {
(data) => _informationLabelCard(
title: 'بارهای امروز',
titleColor: AppColor.blueNormal,
borderColor: Color(0xFF77A6FF),
isLoading: data.value == null,
description: data.value?.separatedByCommaFa ?? '0',
iconPath: Assets.vec.cubeSearchSvg.path,
iconColor: AppColor.blueNormal,
iconPath: Assets.vec.cubeScanWithLabelSvg.path,
bgDescriptionColor: Colors.white,
gradient: LinearGradient(
begin: Alignment.topCenter,
@@ -297,16 +296,67 @@ class HomePage extends GetView<HomeLogic> {
child: ObxValue((data) {
return _informationLabelCard(
title: 'درانتظار تایید',
borderColor: AppColor.greenNormal,
isLoading: data.value == null,
description: data.value?.totalNotEnteredBars.separatedByCommaFa ?? '0',
unit:
'(${data.value?.totalNotEnteredKillHouseRequestsWeight.separatedByCommaFa})\nکیلوگرم',
iconPath: Assets.vec.cubeWattingSvg.path,
iconPath: Assets.vec.cubeCardSvg.path,
bgDescriptionColor: Colors.white,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [const Color(0xFFFFE7BB), Colors.white],
colors: [const Color(0xFFD9F7F0), Colors.white],
),
);
}, controller.barInformation),
),
],
),
);
}
Widget _todayShipmentWidget2() {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 13),
child: Row(
spacing: 8,
children: [
Expanded(
child: ObxValue(
(data) => _informationLabelCard(
title: 'مانده دولتی',
titleColor: AppColor.blueNormal,
borderColor: Color(0xFFFFAE00),
isLoading: data.value == null,
description: data.value?.separatedByCommaFa ?? '0',
iconPath: Assets.vec.cubeCardGovermentSvg.path,
bgDescriptionColor: Colors.white,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFFFFD47A), Colors.white],
),
),
controller.totalWeightTodayBars,
),
),
Expanded(
child: ObxValue((data) {
return _informationLabelCard(
title: 'مانده آزاد',
borderColor: const Color(0xFF9758FF),
isLoading: data.value == null,
description: data.value?.totalNotEnteredBars.separatedByCommaFa ?? '0',
unit:
'(${data.value?.totalNotEnteredKillHouseRequestsWeight.separatedByCommaFa})\nکیلوگرم',
iconPath: Assets.vec.cubeCardFreeSvg.path,
bgDescriptionColor: Colors.white,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [const Color(0xFFD3B9FF), Colors.white],
),
);
}, controller.barInformation),
@@ -321,6 +371,7 @@ class HomePage extends GetView<HomeLogic> {
required String description,
required String iconPath,
required Color bgDescriptionColor,
Color? borderColor,
String unit = 'کیلوگرم',
bool isLoading = false,
Color? iconColor,
@@ -330,7 +381,10 @@ class HomePage extends GetView<HomeLogic> {
}) {
return Container(
height: 82,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: borderColor != null ? Border.all(width: 1, color: borderColor) : null,
),
clipBehavior: Clip.hardEdge,
child: Row(
children: [

View File

@@ -31,6 +31,45 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
child: Column(
children: [
inventoryWidget(controller.rootLogic),
/*Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
spacing: 8,
children: [
Expanded(
child: _informationLabelCard(
title: 'مانده دولتی',
titleColor: AppColor.textColor,
borderColor: Color(0xFFFFAE00),
description: 356952222222.separatedByCommaFa,
iconPath: Assets.vec.cubeCardGovermentSvg.path,
bgDescriptionColor: Colors.white,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFFFFD47A), Colors.white],
),
),
),
Expanded(
child: _informationLabelCard(
title: 'مانده آزاد',
borderColor: const Color(0xFF9758FF),
description: 9658554788.separatedByCommaFa,
unit: 'KG',
iconPath: Assets.vec.cubeCardFreeSvg.path,
bgDescriptionColor: Colors.white,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [const Color(0xFFD3B9FF), Colors.white],
),
),
),
],
),
),*/
ObxValue((data) {
return RPaginatedListView(
onLoadMore: () async => controller.getOutProvinceSales(true),
@@ -533,4 +572,99 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
),
);
}
Container _informationLabelCard({
required String title,
required String description,
required String iconPath,
required Color bgDescriptionColor,
Color? borderColor,
String unit = 'KG',
bool isLoading = false,
Color? iconColor,
Color? titleColor,
Color? bgLabelColor,
LinearGradient? gradient,
}) {
return Container(
height: 40.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: borderColor != null ? Border.all(width: 1, color: borderColor) : null,
),
clipBehavior: Clip.hardEdge,
child: Row(
children: [
// Left side with icon and title
Expanded(
child: Container(
height: 82,
decoration: BoxDecoration(
color: gradient == null ? bgLabelColor : null,
borderRadius: BorderRadius.only(
topRight: Radius.circular(8),
bottomRight: Radius.circular(8),
),
gradient: gradient,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
SvgGenImage.vec(iconPath).svg(
width: 24,
height: 24,
colorFilter: iconColor != null
? ColorFilter.mode(iconColor, BlendMode.srcIn)
: null,
),
Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(
color: titleColor ?? AppColor.mediumGreyDarkActive,
),
),
],
),
),
),
// Right side with description and unit
Expanded(
child: Container(
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
color: bgDescriptionColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
bottomLeft: Radius.circular(8),
),
),
child: isLoading
? Center(child: CupertinoActivityIndicator())
: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
description,
textAlign: TextAlign.left,
maxLines: 1,
textDirection: TextDirection.ltr,
overflow: TextOverflow.ellipsis,
style: AppFonts.yekan14.copyWith(color: AppColor.mediumGreyDarkActive),
),
Text(
unit,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.mediumGreyDarkActive),
),
],
),
),
),
],
),
);
}
}

View File

@@ -413,7 +413,6 @@ class SegmentationPage extends GetView<SegmentationLogic> {
return OverlayDropdownWidget<GuildModel>(
key: ValueKey(item?.user?.fullname ?? ''),
items: controller.guildsModel,
isDisabled: controller.saleType.value == 1,
onChanged: (value) {
controller.selectedGuildModel.value = value;
},

View File

@@ -137,6 +137,12 @@ class $AssetsIconsGen {
/// File path: assets/icons/cube_card.svg
SvgGenImage get cubeCard => const SvgGenImage('assets/icons/cube_card.svg');
/// File path: assets/icons/cube_card_free.svg
SvgGenImage get cubeCardFree => const SvgGenImage('assets/icons/cube_card_free.svg');
/// File path: assets/icons/cube_card_goverment.svg
SvgGenImage get cubeCardGoverment => const SvgGenImage('assets/icons/cube_card_goverment.svg');
/// File path: assets/icons/cube_rotate.svg
SvgGenImage get cubeRotate => const SvgGenImage('assets/icons/cube_rotate.svg');
@@ -396,6 +402,8 @@ class $AssetsIconsGen {
cube,
cubeBottomRotation,
cubeCard,
cubeCardFree,
cubeCardGoverment,
cubeRotate,
cubeScan,
cubeScanWithLabel,
@@ -623,6 +631,12 @@ class $AssetsVecGen {
/// File path: assets/vec/cube_card.svg.vec
SvgGenImage get cubeCardSvg => const SvgGenImage.vec('assets/vec/cube_card.svg.vec');
/// File path: assets/vec/cube_card_free.svg.vec
SvgGenImage get cubeCardFreeSvg => const SvgGenImage.vec('assets/vec/cube_card_free.svg.vec');
/// File path: assets/vec/cube_card_goverment.svg.vec
SvgGenImage get cubeCardGovermentSvg => const SvgGenImage.vec('assets/vec/cube_card_goverment.svg.vec');
/// File path: assets/vec/cube_rotate.svg.vec
SvgGenImage get cubeRotateSvg => const SvgGenImage.vec('assets/vec/cube_rotate.svg.vec');
@@ -882,6 +896,8 @@ class $AssetsVecGen {
cubeSvg,
cubeBottomRotationSvg,
cubeCardSvg,
cubeCardFreeSvg,
cubeCardGovermentSvg,
cubeRotateSvg,
cubeScanSvg,
cubeScanWithLabelSvg,

View File

@@ -0,0 +1,71 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rasadyar_core/presentation/common/app_color.dart';
import 'multi_select_dropdown_logic.dart';
class MultiSelectDropdown<T> extends StatelessWidget {
final List<T> items;
final T? selectedItem;
final T? initialValue;
final Widget Function(T item) itemBuilder;
final Widget Function(T? selected) labelBuilder;
final void Function(T selected)? onChanged;
final EdgeInsets? contentPadding;
final String Function(T item)? itemToString;
const MultiSelectDropdown({
super.key,
required this.items,
required this.itemBuilder,
required this.labelBuilder,
this.initialValue,
this.onChanged,
this.selectedItem,
this.contentPadding,
this.itemToString,
});
@override
Widget build(BuildContext context) {
return GetBuilder<MultiSelectDropdownLogic<T>>(
init: MultiSelectDropdownLogic<T>(
items: items,
selectedItem: selectedItem,
initialValue: initialValue,
itemToString: itemToString,
onChanged: onChanged,
contentPadding: contentPadding,
itemBuilder: itemBuilder,
),
builder: (controller) {
return GestureDetector(
onTap: () {
controller.isOpen.value ? controller.removeOverlay() : controller.showOverlay(context);
},
child: Container(
height: 40,
width: Get.width,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: items.isEmpty ? Colors.grey.shade200 : AppColor.bgLight,
border: Border.all(color: AppColor.darkGreyLight),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: labelBuilder(controller.selectedItem.value)),
Icon(
controller.isOpen.value ? CupertinoIcons.chevron_up : CupertinoIcons.chevron_down,
size: 14,
),
],
),
),
);
},
);
}
}

View File

@@ -0,0 +1,145 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rasadyar_core/presentation/common/app_color.dart';
class MultiSelectDropdownLogic<T> extends GetxController {
final List<T> items;
final T? initialValue;
final String Function(T item)? itemToString;
final void Function(T selected)? onChanged;
RxBool isOpen = false.obs;
Rx<T?> selectedItem;
late TextEditingController searchController;
late RxList<T> filteredItems;
late EdgeInsets? contentPadding;
late Widget Function(T item) itemBuilder;
MultiSelectDropdownLogic({
required this.items,
this.initialValue,
this.itemToString,
this.onChanged,
T? selectedItem,
this.contentPadding,
required this.itemBuilder,
}) : selectedItem = Rx(selectedItem ?? initialValue);
@override
void onInit() {
super.onInit();
searchController = TextEditingController();
filteredItems = RxList<T>(items);
}
void showOverlay(BuildContext context) {
final RenderBox renderBox = (context.findRenderObject() as RenderBox);
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
final screenHeight = MediaQuery.of(context).size.height;
final bool openUp = offset.dy + size.height + 300 > screenHeight;
searchController.clear();
filteredItems.value = items;
OverlayEntry overlayEntry = OverlayEntry(
builder: (_) => GestureDetector(
onTap: () {
removeOverlay();
},
child: Stack(
children: [
Positioned(
left: offset.dx,
top: openUp ? offset.dy - 300 - 4 : offset.dy + size.height + 4,
width: size.width,
child: Material(
elevation: 4,
borderRadius: BorderRadius.circular(8),
child: Obx(
() => Container(
decoration: BoxDecoration(
color: AppColor.bgLight,
border: Border.all(color: AppColor.darkGreyLight),
borderRadius: BorderRadius.circular(8),
),
constraints: BoxConstraints(maxHeight: 300),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: TextField(
controller: searchController,
decoration: const InputDecoration(
hintText: 'جستجو...',
isDense: true,
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
border: OutlineInputBorder(),
),
onChanged: (query) {
filteredItems.value = items
.where(
(item) =>
itemToString
?.call(item)
.toLowerCase()
.contains(query.toLowerCase()) ??
false,
)
.toList();
},
),
),
if (filteredItems.isEmpty)
const Padding(
padding: EdgeInsets.all(16.0),
child: Text("نتیجه‌ای یافت نشد."),
),
if (filteredItems.isNotEmpty)
Flexible(
child: ListView.builder(
itemCount: filteredItems.length,
itemBuilder: (context, index) {
var item = filteredItems[index];
return InkWell(
onTap: () {
onChanged?.call(item);
selectedItem.value = item;
removeOverlay();
},
child: Padding(
padding:
contentPadding ??
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: itemBuilder(item),
),
);
},
),
),
],
),
),
),
),
),
],
),
),
);
Overlay.of(context).insert(overlayEntry);
isOpen.value = true;
}
void removeOverlay() {
isOpen.value = false;
}
@override
void onClose() {
searchController.dispose();
super.onClose();
}
}

View File

@@ -15,6 +15,7 @@ class OverlayDropdownWidget<T> extends StatefulWidget {
final void Function(T selected)? onChanged;
final EdgeInsets? contentPadding;
final bool isDisabled;
const OverlayDropdownWidget({
super.key,
required this.items,
@@ -46,7 +47,6 @@ class _OverlayDropdownState<T> extends State<OverlayDropdownWidget<T>> {
selectedItem = widget.selectedItem ?? widget.initialValue;
}
@override
void didUpdateWidget(covariant OverlayDropdownWidget<T> oldWidget) {
super.didUpdateWidget(oldWidget);
@@ -135,11 +135,11 @@ class _OverlayDropdownState<T> extends State<OverlayDropdownWidget<T>> {
Widget build(BuildContext context) {
return GestureDetector(
key: _key,
onTap: widget.isDisabled
? null
: () {
_isOpen.value ? _removeOverlay() : _showOverlay();
},
onTap: widget.isDisabled
? null
: () {
_isOpen.value ? _removeOverlay() : _showOverlay();
},
child: Container(
height: widget.height?.toDouble() ?? 40,
width: Get.width,
@@ -274,11 +274,11 @@ class _OverlayDropdownState2<T> extends State<OverlayDropdownWidget2<T>> {
child: Text("نتیجه‌ای یافت نشد."),
),
if (_filteredItems.isNotEmpty)
Expanded(
child: ListView(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
children: _filteredItems.map((item) {
Flexible(
child: ListView.builder(
itemCount: _filteredItems.length,
itemBuilder: (context, index) {
var item = _filteredItems[index];
return InkWell(
onTap: () {
widget.onChanged?.call(item);
@@ -294,7 +294,8 @@ class _OverlayDropdownState2<T> extends State<OverlayDropdownWidget2<T>> {
child: widget.itemBuilder(item),
),
);
}).toList(),
},
physics: const BouncingScrollPhysics(),
),
),
],

View File

@@ -1,20 +1,19 @@
export 'base_page/logic.dart';
//base page
export 'base_page/view.dart';
export 'base_page/widgets/back_ground_widget.dart';
export 'base_page/widgets/breadcrumb.dart';
export 'base_page/widgets/r_app_bar.dart';
export 'base_page/widgets/search_widget.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';
export 'buttons/buttons.dart';
//buttons - enhanced core widgets
export 'buttons/core_button.dart';
export 'buttons/buttons.dart';
export 'card/card_icon_widget.dart';
export 'check_box/check_box_widget.dart';
export 'chips/r_chips.dart';
//custom
export 'custom/information_tag_widget.dart';
@@ -38,6 +37,8 @@ export 'loading/loading_widget.dart';
// other
export 'logo_widget.dart';
export 'marquee/r_marquee.dart';
export 'overlay_dropdown_widget/multi_select_dropdown/multi_select_dropdown.dart';
export 'overlay_dropdown_widget/multi_select_dropdown/multi_select_dropdown_logic.dart';
export 'overlay_dropdown_widget/overlay_dropdown.dart';
export 'overlay_dropdown_widget/resource_overlay_dropdown.dart';
export 'pagination/pagination_from_until.dart';