feat : first step request tagging
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:rasadyar_livestock/presentation/widgets/base_page/logic.dart';
|
||||
|
||||
RAppBar liveStockAppBar({
|
||||
bool hasBack = true,
|
||||
bool hasFilter = true,
|
||||
bool hasSearch = true,
|
||||
bool isBase = false,
|
||||
VoidCallback? onBackPressed,
|
||||
GestureTapCallback? onFilterTap,
|
||||
GestureTapCallback? onSearchTap,
|
||||
}) {
|
||||
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.appBarInspectionSvg.svg(width: 24, height: 24),
|
||||
],
|
||||
),
|
||||
additionalActions: [
|
||||
if (!isBase && hasSearch) searchWidget(onSearchTap),
|
||||
SizedBox(width: 8),
|
||||
if (!isBase && hasFilter) filterWidget(onFilterTap),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
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<BaseLogic>();
|
||||
return Visibility(
|
||||
visible: controller.searchValue.value != null,
|
||||
child: Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: const BoxDecoration(color: Colors.red, shape: BoxShape.circle),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
class BaseLogic extends GetxController {
|
||||
final RxBool isFilterSelected = false.obs;
|
||||
final RxBool isSearchSelected = false.obs;
|
||||
final TextEditingController searchTextController = TextEditingController();
|
||||
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 toggleFilter() {
|
||||
isFilterSelected.value = !isFilterSelected.value;
|
||||
}
|
||||
|
||||
void toggleSearch() {
|
||||
isSearchSelected.value = !isSearchSelected.value;
|
||||
}
|
||||
}
|
||||
143
packages/livestock/lib/presentation/widgets/base_page/view.dart
Normal file
143
packages/livestock/lib/presentation/widgets/base_page/view.dart
Normal file
@@ -0,0 +1,143 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:rasadyar_livestock/presentation/widgets/app_bar/i_app_bar.dart';
|
||||
import 'package:rasadyar_livestock/presentation/widgets/search.dart';
|
||||
|
||||
import 'logic.dart';
|
||||
|
||||
class BasePage extends StatefulWidget {
|
||||
const BasePage({
|
||||
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.defaultSearch = true,
|
||||
this.onBackPressed,
|
||||
this.onFilterTap,
|
||||
this.onSearchTap,
|
||||
this.filteringWidget,
|
||||
});
|
||||
|
||||
final List<String>? routes;
|
||||
final Widget? routesWidget;
|
||||
final bool defaultSearch;
|
||||
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<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) {
|
||||
final currentRoute = ModalRoute.of(context);
|
||||
if (currentRoute?.isCurrent != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
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: liveStockAppBar(
|
||||
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 ? () => controller.toggleSearch() : null,
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
//widget.routesWidget != null ? widget.routesWidget! : buildPageRoute(widget.routes!),
|
||||
if (!widget.isBase && widget.hasSearch && widget.defaultSearch) ...{
|
||||
SearchWidget(onSearchChanged: widget.onSearchChanged),
|
||||
},
|
||||
...widget.widgets,
|
||||
],
|
||||
),
|
||||
floatingActionButtonLocation:
|
||||
widget.floatingActionButtonLocation ?? FloatingActionButtonLocation.startFloat,
|
||||
floatingActionButton: widget.floatingActionButton,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
82
packages/livestock/lib/presentation/widgets/search.dart
Normal file
82
packages/livestock/lib/presentation/widgets/search.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
import 'base_page/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 BaseLogic controller;
|
||||
final TextEditingController textEditingController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = Get.find<BaseLogic>();
|
||||
controller.setSearchCallback(widget.onSearchChanged);
|
||||
}
|
||||
|
||||
@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: () {
|
||||
textEditingController.clear();
|
||||
controller.searchValue.value = null;
|
||||
controller.isSearchSelected.value = false;
|
||||
widget.onSearchChanged?.call(null);
|
||||
},
|
||||
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: textEditingController,
|
||||
onChanged: (val) => controller.searchValue.value = val,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}, controller.isSearchSelected);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user