feat : first step request tagging
This commit is contained in:
@@ -44,6 +44,12 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.yalantis.ucrop.UCropActivity"
|
||||
android:screenOrientation="fullSensor"
|
||||
android:theme="@style/Ucrop.CropTheme"/>
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
|
||||
7
android/app/src/main/res/values-v35/styles.xml
Normal file
7
android/app/src/main/res/values-v35/styles.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Ucrop.CropTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -16,7 +16,6 @@ class CustomNavigationObserver extends NavigatorObserver {
|
||||
|
||||
@override
|
||||
void didPush(Route route, Route? previousRoute) async {
|
||||
super.didPush(route, previousRoute);
|
||||
final routeName = route.settings.name;
|
||||
if (!_isWorkDone && (routeName == ChickenRoutes.init || routeName == ChickenRoutes.auth)) {
|
||||
_isWorkDone = true;
|
||||
@@ -31,6 +30,7 @@ class CustomNavigationObserver extends NavigatorObserver {
|
||||
_isWorkDone = true;
|
||||
await setupLiveStockDI();
|
||||
}
|
||||
super.didPush(route, previousRoute);
|
||||
tLog('CustomNavigationObserver: didPush - $routeName');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import 'package:rasadyar_chicken/presentation/routes/routes.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:rasadyar_inspection/inspection.dart';
|
||||
import 'package:rasadyar_livestock/injection/live_stock_di.dart';
|
||||
import 'package:rasadyar_livestock/presentation/routes/app_pages.dart';
|
||||
|
||||
class ModulesLogic extends GetxController {
|
||||
TokenStorageService tokenService = Get.find<TokenStorageService>();
|
||||
RxBool isLoading = false.obs;
|
||||
|
||||
List<ModuleModel> moduleList = [
|
||||
ModuleModel(title: 'بازرسی', icon: Assets.icons.inspection.path, module: Module.inspection),
|
||||
@@ -25,4 +30,23 @@ class ModulesLogic extends GetxController {
|
||||
tokenService.saveModule(module);
|
||||
tokenService.appModule.value = module;
|
||||
}
|
||||
|
||||
Future<void> navigateToModule(Module module) async {
|
||||
if (module == Module.inspection) {
|
||||
Get.offAllNamed(InspectionRoutes.init);
|
||||
} else if (module == Module.liveStocks) {
|
||||
await setupLiveStockDI();
|
||||
Get.offAllNamed(LiveStockRoutes.init);
|
||||
} else if (module == Module.chicken) {
|
||||
Get.offAllNamed(ChickenRoutes.init);
|
||||
}
|
||||
}
|
||||
|
||||
void onTapCard(Module module, int index) async {
|
||||
isLoading.value = true;
|
||||
selectedIndex.value = index;
|
||||
saveModule(module);
|
||||
await Future.delayed(Duration(milliseconds: 800)); // Simulate loading delay
|
||||
navigateToModule(module);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_chicken/chicken.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:rasadyar_inspection/inspection.dart';
|
||||
|
||||
import 'logic.dart';
|
||||
|
||||
@@ -16,39 +15,38 @@ class ModulesPage extends GetView<ModulesLogic> {
|
||||
centerTitle: true,
|
||||
backgroundColor: AppColor.blueNormal,
|
||||
),
|
||||
body: GridView.builder(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 20),
|
||||
|
||||
itemBuilder: (context, index) {
|
||||
final module = controller.moduleList[index];
|
||||
return CardIcon(
|
||||
title: module.title,
|
||||
icon: module.icon,
|
||||
onTap: () {
|
||||
controller.selectedIndex.value = index;
|
||||
controller.saveModule(module.module);
|
||||
|
||||
// Navigate to the appropriate route based on the selected module
|
||||
switch (module.module) {
|
||||
case Module.inspection:
|
||||
Get.toNamed(InspectionRoutes.init);
|
||||
break;
|
||||
case Module.liveStocks:
|
||||
//TODO: Implement liveStocks module navigation
|
||||
case Module.chicken:
|
||||
Get.toNamed(ChickenRoutes.init);
|
||||
break;
|
||||
}
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
GridView.builder(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 20),
|
||||
itemBuilder: (context, index) {
|
||||
final module = controller.moduleList[index];
|
||||
return CardIcon(
|
||||
title: module.title,
|
||||
icon: module.icon,
|
||||
onTap: () => controller.onTapCard(module.module, index),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 10,
|
||||
crossAxisSpacing: 10,
|
||||
),
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemCount: controller.moduleList.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 10,
|
||||
crossAxisSpacing: 10,
|
||||
),
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemCount: controller.moduleList.length,
|
||||
),
|
||||
ObxValue((loading) {
|
||||
if (!controller.isLoading.value) return SizedBox.shrink();
|
||||
return Container(
|
||||
color: Colors.grey.withValues(alpha: 0.5),
|
||||
child: Center(
|
||||
child: CupertinoActivityIndicator(color: AppColor.greenNormal, radius: 30),
|
||||
),
|
||||
);
|
||||
}, controller.isLoading),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export 'package:dio/dio.dart';
|
||||
export 'package:flutter_localizations/flutter_localizations.dart';
|
||||
export 'package:flutter_map/flutter_map.dart';
|
||||
export 'package:flutter_map_animations/flutter_map_animations.dart';
|
||||
export 'package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart';
|
||||
export 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||
export 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
export 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
@@ -21,6 +22,7 @@ export 'package:get/get.dart' hide FormData, MultipartFile, Response;
|
||||
export 'package:get_it/get_it.dart';
|
||||
//local storage
|
||||
export 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
export 'package:image_cropper/image_cropper.dart';
|
||||
///image picker
|
||||
export 'package:image_picker/image_picker.dart';
|
||||
//encryption
|
||||
@@ -36,7 +38,6 @@ export 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||
export 'package:rasadyar_core/presentation/common/common.dart';
|
||||
export 'package:rasadyar_core/presentation/utils/utils.dart';
|
||||
export 'package:rasadyar_core/presentation/widget/widget.dart';
|
||||
export 'package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart';
|
||||
|
||||
//models
|
||||
export 'data/model/model.dart';
|
||||
@@ -57,5 +58,4 @@ export 'utils/map_utils.dart';
|
||||
export 'utils/network/network.dart';
|
||||
export 'utils/route_utils.dart';
|
||||
export 'utils/separator_input_formatter.dart';
|
||||
|
||||
export 'utils/utils.dart';
|
||||
|
||||
@@ -10,7 +10,7 @@ class AppInterceptor extends Interceptor {
|
||||
final RefreshTokenCallback? refreshTokenCallback;
|
||||
final SaveTokenCallback saveTokenCallback;
|
||||
final ClearTokenCallback clearTokenCallback;
|
||||
late final Dio dio;
|
||||
late Dio dio;
|
||||
dynamic authArguments;
|
||||
static Completer<String?>? _refreshCompleter;
|
||||
static bool _isRefreshing = false;
|
||||
@@ -44,7 +44,7 @@ class AppInterceptor extends Interceptor {
|
||||
|
||||
@override
|
||||
Future<void> onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||
if (err.response?.statusCode == 401) {
|
||||
if (err.response?.statusCode == 401 && err.response?.data['detail'] != "No active account found with the given credentials") {
|
||||
final retryResult = await _handleUnauthorizedError(err);
|
||||
if (retryResult != null) {
|
||||
handler.resolve(retryResult);
|
||||
@@ -104,6 +104,7 @@ class AppInterceptor extends Interceptor {
|
||||
return dio.fetch(newOptions);
|
||||
}
|
||||
|
||||
//TODO
|
||||
void _handleRefreshFailure() {
|
||||
ApiHandler.cancelAllRequests("Token refresh failed");
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class DioRemote implements IHttpClient {
|
||||
Future<void> init() async {
|
||||
dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
|
||||
if (interceptors != null) {
|
||||
interceptors!.dio = dio;
|
||||
dio.interceptors.add(interceptors!);
|
||||
}
|
||||
|
||||
|
||||
@@ -725,6 +725,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
image_cropper:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_cropper
|
||||
sha256: "4e9c96c029eb5a23798da1b6af39787f964da6ffc78fd8447c140542a9f7c6fc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.0"
|
||||
image_cropper_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_cropper_for_web
|
||||
sha256: fd81ebe36f636576094377aab32673c4e5d1609b32dec16fad98d2b71f1250a9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
image_cropper_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_cropper_platform_interface
|
||||
sha256: "6ca6b81769abff9a4dcc3bbd3d75f5dfa9de6b870ae9613c8cd237333a4283af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.0"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -18,7 +18,7 @@ dependencies:
|
||||
|
||||
##image_picker
|
||||
image_picker: ^1.1.2
|
||||
|
||||
image_cropper: ^9.1.0
|
||||
|
||||
#UI
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
@@ -32,22 +32,16 @@ class DioErrorHandler {
|
||||
_errorSnackBar(
|
||||
error.response?.data.keys.first == 'is_user'
|
||||
? 'کاربر با این شماره تلفن وجود ندارد'
|
||||
: error.response?.data[error.response?.data.keys.first] ??
|
||||
'خطا در برقراری ارتباط با سرور',
|
||||
: '${error.response?.statusCode} - ${error.response?.data[error.response?.data.keys.first]}' ??
|
||||
'خطا در برقراری ارتباط با سرور',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
GetSnackBar _errorSnackBar(String message) {
|
||||
return GetSnackBar(
|
||||
titleText: Text(
|
||||
'خطا',
|
||||
style: AppFonts.yekan14.copyWith(color: Colors.white),
|
||||
),
|
||||
messageText: Text(
|
||||
message,
|
||||
style: AppFonts.yekan12.copyWith(color: Colors.white),
|
||||
),
|
||||
titleText: Text('خطا', style: AppFonts.yekan14.copyWith(color: Colors.white)),
|
||||
messageText: Text(message, style: AppFonts.yekan12.copyWith(color: Colors.white)),
|
||||
backgroundColor: AppColor.error,
|
||||
margin: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
borderRadius: 12,
|
||||
|
||||
@@ -16,7 +16,6 @@ class AuthRemoteDataSourceImp extends AuthRemoteDataSource {
|
||||
'${_BASE_URL}login/',
|
||||
data: authRequest,
|
||||
fromJson: AuthResponseModel.fromJson,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
@@ -11,27 +11,17 @@ GetIt get diLiveStock => GetIt.instance;
|
||||
Future<void> setupLiveStockDI() async {
|
||||
diLiveStock.registerSingleton(DioErrorHandler());
|
||||
|
||||
|
||||
final tokenService = Get.find<TokenStorageService>();
|
||||
|
||||
|
||||
if (tokenService.baseurl.value == null) {
|
||||
await tokenService.saveBaseUrl('https://api.dam.rasadyar.net/');
|
||||
}
|
||||
|
||||
|
||||
diLiveStock.registerLazySingleton<AuthRemoteDataSource>(
|
||||
() => AuthRemoteDataSourceImp(diLiveStock.get<DioRemote>()),
|
||||
);
|
||||
|
||||
diLiveStock.registerLazySingleton<AuthRepository>(
|
||||
() => AuthRepositoryImp(diLiveStock.get<AuthRemoteDataSource>()),
|
||||
);
|
||||
|
||||
|
||||
// First register AppInterceptor with lazy callbacks
|
||||
diLiveStock.registerLazySingleton<AppInterceptor>(
|
||||
() => AppInterceptor(
|
||||
() => AppInterceptor(
|
||||
refreshTokenCallback: () async {
|
||||
// Use lazy access to avoid circular dependency
|
||||
final authRepository = diLiveStock.get<AuthRepository>();
|
||||
final hasAuthenticated = await authRepository.hasAuthenticated();
|
||||
if (hasAuthenticated) {
|
||||
@@ -53,13 +43,26 @@ Future<void> setupLiveStockDI() async {
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
// Register DioRemote with the interceptor
|
||||
diLiveStock.registerLazySingleton<DioRemote>(
|
||||
() => DioRemote(
|
||||
() => DioRemote(
|
||||
baseUrl: tokenService.baseurl.value,
|
||||
interceptors: diLiveStock.get<AppInterceptor>(),
|
||||
// interceptors: diLiveStock.get<AppInterceptor>(),
|
||||
),
|
||||
);
|
||||
|
||||
// Initialize DioRemote
|
||||
await diLiveStock.get<DioRemote>().init();
|
||||
|
||||
// Now register the data source and repository
|
||||
diLiveStock.registerLazySingleton<AuthRemoteDataSource>(
|
||||
() => AuthRemoteDataSourceImp(diLiveStock.get<DioRemote>()),
|
||||
);
|
||||
|
||||
diLiveStock.registerLazySingleton<AuthRepository>(
|
||||
() => AuthRepositoryImp(diLiveStock.get<AuthRemoteDataSource>()),
|
||||
);
|
||||
|
||||
diLiveStock.registerLazySingleton<ImagePicker>(() => ImagePicker());
|
||||
await diLiveStock.allReady();
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ class AuthLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
final loginRequestModel = _buildLoginRequest();
|
||||
isLoading.value = true;
|
||||
await safeCall<AuthResponseModel?>(
|
||||
call: () async => authRepository.login(authRequest: loginRequestModel.toJson()),
|
||||
call: () async => await authRepository.login(authRequest: loginRequestModel.toJson()),
|
||||
onSuccess: (result) async {
|
||||
await tokenStorageService.saveModule(_module);
|
||||
await tokenStorageService.saveRefreshToken(result?.refresh ?? '');
|
||||
|
||||
@@ -17,27 +17,29 @@ class AuthPage extends GetView<AuthLogic> {
|
||||
children: [
|
||||
Assets.vec.bgAuthSvg.svg(fit: BoxFit.fill),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.r),
|
||||
child: FadeTransition(
|
||||
opacity: controller.textAnimation,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 12,
|
||||
children: [
|
||||
Text(
|
||||
'به سامانه رصدیار خوش آمدید!',
|
||||
textAlign: TextAlign.right,
|
||||
style: AppFonts.yekan25Bold.copyWith(color: Colors.white),
|
||||
),
|
||||
Text(
|
||||
'سامانه رصد و پایش زنجیره تامین، تولید و توزیع کالا های اساسی',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppFonts.yekan16.copyWith(color: Colors.white),
|
||||
),
|
||||
],
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.r),
|
||||
child: FadeTransition(
|
||||
opacity: controller.textAnimation,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 12,
|
||||
children: [
|
||||
Text(
|
||||
'به سامانه رصدیار خوش آمدید!',
|
||||
textAlign: TextAlign.right,
|
||||
style: AppFonts.yekan25Bold.copyWith(color: Colors.white),
|
||||
),
|
||||
Text(
|
||||
'سامانه رصد و پایش زنجیره تامین، تولید و توزیع کالا های اساسی',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppFonts.yekan16.copyWith(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:rasadyar_livestock/presentation/widgets/base_page/logic.dart';
|
||||
|
||||
class MapLogic extends GetxController {
|
||||
|
||||
var ss = Get.find<DraggableBottomSheetController>();
|
||||
|
||||
|
||||
BaseLogic baseLogic = Get.find<BaseLogic>();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:rasadyar_livestock/presentation/page/map/widget/map_widget/view.dart';
|
||||
import 'package:rasadyar_livestock/presentation/widgets/base_page/view.dart';
|
||||
|
||||
import 'logic.dart';
|
||||
|
||||
@@ -9,6 +10,158 @@ class MapPage extends GetView<MapLogic> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(body: Stack(children: [MapWidget()]));
|
||||
return BasePage(
|
||||
hasSearch: true,
|
||||
hasFilter: true,
|
||||
hasBack: false,
|
||||
defaultSearch: false,
|
||||
filteringWidget: filterWidget(showIndex: 3.obs, filterIndex: 5.obs),
|
||||
widgets: [MapWidget()],
|
||||
);
|
||||
}
|
||||
|
||||
Widget filterWidget({required RxInt filterIndex, required RxInt showIndex}) {
|
||||
return BaseBottomSheet(
|
||||
height: Get.height * 0.5,
|
||||
child: Container(color: Colors.red),
|
||||
);
|
||||
}
|
||||
|
||||
BaseBottomSheet searchWidget() {
|
||||
return BaseBottomSheet(
|
||||
height: Get.height * 0.85,
|
||||
rootChild: Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
Expanded(
|
||||
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.baseLogic.searchTextController.clear();
|
||||
controller.baseLogic.searchValue.value = null;
|
||||
controller.baseLogic.isSearchSelected.value = false;
|
||||
//controller.mapLogic.hasFilterOrSearch.value = false;
|
||||
//controller.searchedPoultryLocation.value = Resource.initial();
|
||||
},
|
||||
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.baseLogic.searchValue,
|
||||
),
|
||||
hintText: 'جستجو کنید ...',
|
||||
hintStyle: AppFonts.yekan16.copyWith(color: AppColor.blueNormal),
|
||||
filledColor: Colors.white,
|
||||
filled: true,
|
||||
controller: controller.baseLogic.searchTextController,
|
||||
onChanged: (val) => controller.baseLogic.searchValue.value = val,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Get.back();
|
||||
},
|
||||
child: Assets.vec.mapSvg.svg(
|
||||
width: 24.w,
|
||||
height: 24.h,
|
||||
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
/* Expanded(
|
||||
child: ObxValue((rxData) {
|
||||
final resource = rxData.value;
|
||||
final status = resource.status;
|
||||
final items = resource.data;
|
||||
final message = resource.message ?? 'خطا در بارگذاری';
|
||||
|
||||
if (status == ResourceStatus.initial) {
|
||||
return Center(child: Text('ابتدا جستجو کنید'));
|
||||
}
|
||||
|
||||
if (status == ResourceStatus.loading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (status == ResourceStatus.error) {
|
||||
return Center(child: Text(message));
|
||||
}
|
||||
|
||||
if (items == null || items.isEmpty) {
|
||||
return Center(child: EmptyWidget());
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
itemCount: items.length,
|
||||
separatorBuilder: (context, index) => SizedBox(height: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index]; // اگر item استفاده نمیشه، میتونه حذف بشه
|
||||
return ListItem2(
|
||||
index: index,
|
||||
labelColor: AppColor.blueLight,
|
||||
labelIcon: Assets.vec.cowSvg.path,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
item.unitName ?? 'N/A',
|
||||
style: AppFonts.yekan10.copyWith(color: AppColor.blueNormal),
|
||||
),
|
||||
Text(
|
||||
item.user?.fullname ?? '',
|
||||
style: AppFonts.yekan12.copyWith(color: AppColor.darkGreyDarkHover),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'جوجه ریزی فعال',
|
||||
style: AppFonts.yekan10.copyWith(color: AppColor.blueNormal),
|
||||
),
|
||||
Text(
|
||||
(item.hatching != null && item.hatching!.isNotEmpty)
|
||||
? 'دارد'
|
||||
: 'ندراد',
|
||||
style: AppFonts.yekan12.copyWith(color: AppColor.darkGreyDarkHover),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}, controller.searchedPoultryLocation),
|
||||
),*/
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:rasadyar_livestock/presentation/routes/app_pages.dart';
|
||||
|
||||
import 'logic.dart';
|
||||
|
||||
@@ -172,8 +173,40 @@ class MapWidget extends GetView<MapWidgetLogic> {
|
||||
.map(
|
||||
(element) => Marker(
|
||||
point: element,
|
||||
child: FaIcon(FontAwesomeIcons.locationPin, color: AppColor.error),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
Get.bottomSheet(
|
||||
detailsBottomSheet(),
|
||||
isScrollControlled: true,
|
||||
isDismissible: true,
|
||||
ignoreSafeArea: false,
|
||||
);
|
||||
},
|
||||
icon: FaIcon(FontAwesomeIcons.locationPin, color: AppColor.error),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
Widget detailsBottomSheet() {
|
||||
return BaseBottomSheet(
|
||||
height: 250.h,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 20,
|
||||
children: [
|
||||
Text('مشخصات محل', style: AppFonts.yekan16Bold),
|
||||
// Add more details here
|
||||
RElevated(
|
||||
text: 'ایجاد بازرسی',
|
||||
width: Get.width,
|
||||
height: 40.h,
|
||||
onPressed: () {
|
||||
Get.toNamed(LiveStockRoutes.requestTagging);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,32 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:rasadyar_livestock/presentation/page/root/logic.dart';
|
||||
import 'package:rasadyar_livestock/injection/live_stock_di.dart';
|
||||
|
||||
class RequestTaggingLogic extends GetxController {
|
||||
RxInt currentIndex = 0.obs;
|
||||
final int maxStep = 3;
|
||||
|
||||
RxBool nextButtonEnabled = true.obs;
|
||||
|
||||
final TextEditingController phoneController = TextEditingController();
|
||||
final TextEditingController fullNameController = TextEditingController();
|
||||
final TextEditingController addressController = TextEditingController();
|
||||
|
||||
ImagePicker imagePicker = diLiveStock.get<ImagePicker>();
|
||||
Rxn<XFile> rancherImage = Rxn<XFile>(null);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
setUpTextControllerListeners();
|
||||
setUpNextButtonListeners();
|
||||
ever(rancherImage, (callback) {
|
||||
setUpNextButtonListeners();
|
||||
});
|
||||
}
|
||||
|
||||
final TextEditingController phoneController = TextEditingController();
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
@@ -14,4 +36,68 @@ final TextEditingController phoneController = TextEditingController();
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void onNext() {
|
||||
if (currentIndex.value < maxStep) {
|
||||
if (currentIndex.value == 0) {}
|
||||
currentIndex.value++;
|
||||
}
|
||||
}
|
||||
|
||||
void onPrevious() {
|
||||
if (currentIndex.value > 0) {
|
||||
currentIndex.value--;
|
||||
}
|
||||
}
|
||||
|
||||
void setUpNextButtonListeners() {
|
||||
if (currentIndex.value == 0) {
|
||||
nextButtonEnabled.value =
|
||||
phoneController.text.isNotEmpty &&
|
||||
fullNameController.text.isNotEmpty &&
|
||||
addressController.text.isNotEmpty &&
|
||||
rancherImage.value != null;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void setUpTextControllerListeners() {
|
||||
phoneController.addListener(setUpNextButtonListeners);
|
||||
fullNameController.addListener(setUpNextButtonListeners);
|
||||
addressController.addListener(setUpNextButtonListeners);
|
||||
}
|
||||
|
||||
Future<void> pickImage() async {
|
||||
rancherImage.value = await imagePicker.pickImage(
|
||||
source: ImageSource.camera,
|
||||
imageQuality: 60,
|
||||
maxWidth: 1080,
|
||||
maxHeight: 720,
|
||||
);
|
||||
|
||||
getFileSizeInKB(rancherImage.value?.path ?? '', tag: 'Picked');
|
||||
}
|
||||
|
||||
Future<void> cropImage() async {
|
||||
if (rancherImage.value == null) return;
|
||||
|
||||
final CroppedFile? cropped = await ImageCropper().cropImage(
|
||||
sourcePath: rancherImage.value!.path,
|
||||
maxWidth: 1080,
|
||||
maxHeight: 720,
|
||||
compressQuality: 60,
|
||||
);
|
||||
if (cropped == null) return;
|
||||
rancherImage.value = XFile(cropped.path);
|
||||
|
||||
getFileSizeInKB(rancherImage.value?.path ?? '', tag: 'Cropped');
|
||||
}
|
||||
|
||||
void getFileSizeInKB(String filePath, {String? tag}) {
|
||||
final file = File(filePath);
|
||||
final bytes = file.lengthSync();
|
||||
var size = (bytes / 1024).ceil();
|
||||
iLog('${tag ?? 'Picked'} image Size: $size');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
@@ -19,91 +21,272 @@ class RequestTaggingPage extends GetView<RequestTaggingLogic> {
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
RTextField(
|
||||
controller: controller.phoneController,
|
||||
label: 'تلفن دامدار',
|
||||
child: ObxValue((index) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(child: _buildStep(index.value)),
|
||||
nextOrPreviousWidget(),
|
||||
],
|
||||
);
|
||||
}, controller.currentIndex),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Row nextOrPreviousWidget() {
|
||||
return Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
ObxValue(
|
||||
(data) => Expanded(
|
||||
flex: 2,
|
||||
child: RElevated(
|
||||
height: 40.h,
|
||||
enabled: data.value,
|
||||
onPressed: controller.onNext,
|
||||
child: Text('بعدی'),
|
||||
backgroundColor: AppColor.blueNormal,
|
||||
),
|
||||
),
|
||||
controller.nextButtonEnabled,
|
||||
),
|
||||
Expanded(
|
||||
child: ROutlinedElevated(
|
||||
enabled: controller.currentIndex.value > 0,
|
||||
onPressed: controller.onPrevious,
|
||||
child: Text('قبلی'),
|
||||
borderColor: AppColor.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
SizedBox(
|
||||
width: Get.width,
|
||||
height: 356,
|
||||
child: Card(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: Get.width,
|
||||
Widget _buildStep(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return firstStepWidget();
|
||||
default:
|
||||
return Center(
|
||||
child: Text(
|
||||
'مرحله $index در دست توسعه است',
|
||||
style: AppFonts.yekan16.copyWith(color: AppColor.redNormal),
|
||||
textDirection: TextDirection.rtl,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.lightGreyNormal,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Assets.images.placeHolder.image(
|
||||
height: 150,
|
||||
width: 200,
|
||||
),
|
||||
),
|
||||
),
|
||||
Widget firstStepWidget() {
|
||||
return Form(
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColor.lightGreyNormal, width: 1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Row(children: [Text('اطلاعات دامدار', style: AppFonts.yekan16Bold)]),
|
||||
RTextField(controller: controller.fullNameController, label: 'نام ونام خانوادگی'),
|
||||
RTextField(controller: controller.phoneController, label: 'تلفن'),
|
||||
RTextField(controller: controller.addressController, label: 'ادرس'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
width: Get.width,
|
||||
height: 356.h,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(8.r),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColor.lightGreyNormal, width: 1.w),
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
),
|
||||
child: Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Row(children: [Text('تصویر دامدار', style: AppFonts.yekan16Bold)]),
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: Get.width,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.lightGreyNormal,
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Container(
|
||||
width: Get.width,
|
||||
height: 40,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: ShapeDecoration(
|
||||
color: AppColor.blueNormal,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
' تصویر گله',
|
||||
style: AppFonts.yekan14.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
CupertinoIcons.arrow_up_doc,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: ObxValue((tmpImage) {
|
||||
if (tmpImage.value == null) {
|
||||
return Assets.vec.placeHolderSvg.svg(height: 150.h, width: 200.w);
|
||||
} else {
|
||||
return Image.file(File(tmpImage.value!.path), fit: BoxFit.cover);
|
||||
}
|
||||
}, controller.rancherImage),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await controller.pickImage();
|
||||
await showCropDialog();
|
||||
},
|
||||
child: Container(
|
||||
width: Get.width,
|
||||
height: 40.h,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: ShapeDecoration(
|
||||
color: AppColor.blueNormal,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10.r),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(' دوربین', style: AppFonts.yekan14.copyWith(color: Colors.white)),
|
||||
Icon(CupertinoIcons.arrow_up_doc, color: Colors.white),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Spacer(),
|
||||
Spacer(),
|
||||
|
||||
/* RElevated(
|
||||
text: 'ارسال تصویر گله',
|
||||
onPressed: () {
|
||||
Get.toNamed(LiveStockRoutes.tagging);
|
||||
},
|
||||
height: 40,
|
||||
isFullWidth: true,
|
||||
backgroundColor: AppColor.greenNormal,
|
||||
textStyle: AppFonts.yekan16.copyWith(color: Colors.white),
|
||||
),*/
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showCropDialog() async {
|
||||
await Get.dialog(
|
||||
Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'آیا نیازی به برش تصویر دارید؟',
|
||||
style: AppFonts.yekan16Bold,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
RElevated(
|
||||
text: 'ارسال تصویر گله',
|
||||
onPressed: () {
|
||||
Get.toNamed(LiveStockRoutes.tagging);
|
||||
},
|
||||
height: 40,
|
||||
isFullWidth: true,
|
||||
backgroundColor: AppColor.greenNormal,
|
||||
textStyle: AppFonts.yekan16.copyWith(color: Colors.white),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 24),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
spacing: 12.w,
|
||||
children: [
|
||||
RElevated(
|
||||
height: 40.h,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
await controller.cropImage();
|
||||
},
|
||||
child: const Text('بله'),
|
||||
),
|
||||
ROutlinedElevated(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('خیر'),
|
||||
borderColor: AppColor.error,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Column secondStepWidget() {
|
||||
return Column(
|
||||
children: [
|
||||
RTextField(controller: controller.phoneController, label: 'تلفن دامدار'),
|
||||
|
||||
SizedBox(
|
||||
width: Get.width,
|
||||
height: 356,
|
||||
child: Card(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: Get.width,
|
||||
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.lightGreyNormal,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Assets.images.placeHolder.image(height: 150, width: 200),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Container(
|
||||
width: Get.width,
|
||||
height: 40,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: ShapeDecoration(
|
||||
color: AppColor.blueNormal,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(' تصویر گله', style: AppFonts.yekan14.copyWith(color: Colors.white)),
|
||||
Icon(CupertinoIcons.arrow_up_doc, color: Colors.white),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Spacer(),
|
||||
|
||||
RElevated(
|
||||
text: 'ارسال تصویر گله',
|
||||
onPressed: () {
|
||||
Get.toNamed(LiveStockRoutes.tagging);
|
||||
},
|
||||
height: 40,
|
||||
isFullWidth: true,
|
||||
backgroundColor: AppColor.greenNormal,
|
||||
textStyle: AppFonts.yekan16.copyWith(color: Colors.white),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class RootLogic extends GetxController {
|
||||
|
||||
ProfilePage(),
|
||||
];
|
||||
RxInt currentIndex = 1.obs;
|
||||
RxInt currentIndex = 0.obs;
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:rasadyar_livestock/presentation/page/root/logic.dart';
|
||||
import 'package:rasadyar_livestock/presentation/page/root/view.dart';
|
||||
import 'package:rasadyar_livestock/presentation/page/tagging/logic.dart';
|
||||
import 'package:rasadyar_livestock/presentation/page/tagging/view.dart';
|
||||
import 'package:rasadyar_livestock/presentation/widgets/base_page/logic.dart';
|
||||
import 'package:rasadyar_livestock/presentation/widgets/captcha/logic.dart';
|
||||
|
||||
part 'app_routes.dart';
|
||||
@@ -38,6 +39,7 @@ sealed class LiveStockPages {
|
||||
Get.lazyPut(() => ProfileLogic());
|
||||
Get.lazyPut(() => ProfileLogic());
|
||||
Get.lazyPut(() => MapWidgetLogic());
|
||||
Get.lazyPut(() => BaseLogic());
|
||||
}),
|
||||
children: [
|
||||
/*GetPage(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
24
pubspec.lock
24
pubspec.lock
@@ -749,6 +749,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
image_cropper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_cropper
|
||||
sha256: "4e9c96c029eb5a23798da1b6af39787f964da6ffc78fd8447c140542a9f7c6fc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.0"
|
||||
image_cropper_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_cropper_for_web
|
||||
sha256: fd81ebe36f636576094377aab32673c4e5d1609b32dec16fad98d2b71f1250a9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
image_cropper_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_cropper_platform_interface
|
||||
sha256: "6ca6b81769abff9a4dcc3bbd3d75f5dfa9de6b870ae9613c8cd237333a4283af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.0"
|
||||
image_picker:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user