feat : profile and map

This commit is contained in:
2025-07-28 15:57:30 +03:30
parent 6057976b46
commit d9724f681c
67 changed files with 2835 additions and 444 deletions

View File

@@ -2,10 +2,12 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_core/data/model/request/login_request/login_request_model.dart';
import 'package:rasadyar_core/data/model/response/user_info/user_info_model.dart';
import 'package:rasadyar_core/data/model/response/user_profile_model/user_profile_model.dart';
import 'package:rasadyar_inspection/data/repositories/auth_repository_imp.dart';
import 'package:rasadyar_inspection/data/model/request/login_request/login_request_model.dart';
import 'package:rasadyar_inspection/data/model/response/auth/auth_response_model.dart';
import 'package:rasadyar_inspection/data/repositories/auth/auth_repository_imp.dart';
import 'package:rasadyar_inspection/data/utils/dio_exception_handeler.dart';
import 'package:rasadyar_inspection/injection/inspection_di.dart';
import 'package:rasadyar_inspection/presentation/routes/app_routes.dart';
import 'package:rasadyar_inspection/presentation/widget/captcha/logic.dart';
enum AuthType { useAndPass, otp }
@@ -38,7 +40,7 @@ class AuthLogic extends GetxController {
RxInt secondsRemaining = 120.obs;
Timer? _timer;
//AuthRepositoryImpl authRepository = diAuth.get<AuthRepositoryImpl>();
AuthRepositoryImpl authRepository = diInspection.get<AuthRepositoryImpl>();
final Module _module = Get.arguments;
@@ -97,33 +99,34 @@ class AuthLogic extends GetxController {
);
}
/*Future<void> submitLoginForm() async {
Future<void> submitLoginForm() async {
if (!_isFormValid()) return;
iLog('module222 : ${_module.toString()}');
final loginRequestModel = _buildLoginRequest();
isLoading.value = true;
await safeCall<AuthResponseModel?>(
call: () => authRepository.login(authRequest: loginRequestModel.toJson()),
call: () async => authRepository.login(authRequest: loginRequestModel.toJson()),
onSuccess: (result) async {
await tokenStorageService.saveModule(_module);
await tokenStorageService.saveRefreshToken(result?.refresh ?? '');
await tokenStorageService.saveAccessToken(result?.access ?? '');
Get.offAllNamed(InspectionRoutes.init);
},
onError: (error, stackTrace) {
if (error is DioException) {
diAuth.get<DioErrorHandler>().handle(error);
diInspection.get<DioErrorHandler>().handle(error);
}
captchaController.getCaptcha();
},
);
isLoading.value = false;
}*/
}
Future<void> submitLoginForm2() async {
if (!_isFormValid()) return;
//AuthRepositoryImpl authTmp = diAuth.get<AuthRepositoryImpl>(instanceName: 'newUrl');
isLoading.value = true;
/* await safeCall<UserProfileModel?>(
/* await safeCall<UserProfileModel?>(
call: () => authTmp.login(
authRequest: {
"username": usernameController.value.text,

View File

@@ -184,7 +184,7 @@ class AuthPage extends GetView<AuthLogic> {
onPressed: controller.isDisabled.value
? null
: () async {
await controller.submitLoginForm2();
await controller.submitLoginForm();
},
width: Get.width,
height: 48,

View File

@@ -1,20 +1,23 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_inspection/data/utils/marker_generator.dart';
import 'package:rasadyar_inspection/data/model/response/poultry_location/poultry_location_model.dart';
import 'package:rasadyar_inspection/data/repositories/inspection/inspection_repository_imp.dart';
import 'package:rasadyar_inspection/injection/inspection_di.dart';
import 'package:rasadyar_inspection/presentation/widget/base_page/logic.dart';
import '../filter/view.dart';
class InspectionMapLogic extends GetxController with GetTickerProviderStateMixin {
final BaseLogic baseLogic = Get.find<BaseLogic>();
Rx<LatLng> currentLocation = LatLng(35.824891, 50.948025).obs;
RxList<LatLng> allMarkers = <LatLng>[].obs;
RxList<LatLng> markers = <LatLng>[].obs;
Rx<LatLng> currentLocation = LatLng(34.798315281272544, 48.51479142983491).obs;
Rx<Resource<List<PoultryLocationModel>>> allPoultryLocation =
Resource<List<PoultryLocationModel>>.loading().obs;
RxList<PoultryLocationModel> markers = <PoultryLocationModel>[].obs;
Timer? _debounceTimer;
RxBool isLoading = false.obs;
RxBool isSelectedDetailsLocation = false.obs;
@@ -33,55 +36,7 @@ class InspectionMapLogic extends GetxController with GetTickerProviderStateMixin
late DraggableBottomSheetController detailsLocationBottomSheetController;
late final BottomSheetManager bottomSheetManager;
Future<void> determineCurrentPosition() async {
isLoading.value = true;
final position = await Geolocator.getCurrentPosition(
locationSettings: AndroidSettings(accuracy: LocationAccuracy.best),
);
final latLng = LatLng(position.latitude, position.longitude);
currentLocation.value = latLng;
markers.add(latLng);
animatedMapController.animateTo(
dest: latLng,
zoom: 18,
curve: Curves.easeInOut,
duration: const Duration(seconds: 1),
);
isLoading.value = false;
}
void debouncedUpdateVisibleMarkers({required LatLng center}) {
_debounceTimer?.cancel();
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
final filtered = filterNearbyMarkers({
'markers': allMarkers,
'centerLat': center.latitude,
'centerLng': center.longitude,
'radius': 2000.0,
});
markers.addAll(filtered);
});
}
List<LatLng> filterNearbyMarkers(Map<String, dynamic> args) {
final List<LatLng> rawMarkers = args['markers'];
final double centerLat = args['centerLat'];
final double centerLng = args['centerLng'];
final double radiusInMeters = args['radius'];
final center = LatLng(centerLat, centerLng);
final distance = Distance();
return rawMarkers
.where((marker) => distance(center, marker) <= radiusInMeters)
.toList();
}
Future<void> generatedMarkers() async {
final generatedMarkers = await generateLocationsUsingCompute(100000);
allMarkers.value = generatedMarkers;
}
InspectionRepositoryImp inspectionRepository = diInspection.get<InspectionRepositoryImp>();
@override
void onInit() {
@@ -92,6 +47,7 @@ class InspectionMapLogic extends GetxController with GetTickerProviderStateMixin
curve: Curves.easeInOut,
cancelPreviousAnimations: true,
);
fetchAllPoultryLocations();
filterBottomSheetController = DraggableBottomSheetController(
initialHeight: 350,
@@ -105,7 +61,6 @@ class InspectionMapLogic extends GetxController with GetTickerProviderStateMixin
maxHeight: 200,
);
detailsLocationBottomSheetController = DraggableBottomSheetController(
initialHeight: Get.height * 0.5,
minHeight: Get.height * 0.37,
@@ -114,13 +69,10 @@ class InspectionMapLogic extends GetxController with GetTickerProviderStateMixin
slidController = SlidableController(this).obs;
bottomSheetManager = BottomSheetManager({
filterBottomSheetController:
() => filterWidget(filterIndex: filterIndex, showIndex: showIndex),
selectedLocationBottomSheetController:
() => selectedLocationWidget(
showHint:
selectedLocationBottomSheetController.isVisible.value &&
showSlideHint,
filterBottomSheetController: () =>
filterWidget(filterIndex: filterIndex, showIndex: showIndex),
selectedLocationBottomSheetController: () => selectedLocationWidget(
showHint: selectedLocationBottomSheetController.isVisible.value && showSlideHint,
sliderController: slidController.value,
trigger: triggerSlidableAnimation,
toggle: selectedLocationBottomSheetController.toggle,
@@ -132,8 +84,63 @@ class InspectionMapLogic extends GetxController with GetTickerProviderStateMixin
@override
void onReady() {
super.onReady();
determineCurrentPosition();
generatedMarkers();
//determineCurrentPosition();
}
@override
void onClose() {
slidController.close();
super.onClose();
}
Future<void> determineCurrentPosition() async {
isLoading.value = true;
final position = await Geolocator.getCurrentPosition(
locationSettings: AndroidSettings(accuracy: LocationAccuracy.best),
);
final latLng = LatLng(position.latitude, position.longitude);
/*currentLocation.value = latLng;
markers.add(PoultryLocationModel(
lat: latLng.latitude,
long: latLng.longitude
));*/
animatedMapController.animateTo(
dest: latLng,
zoom: 18,
curve: Curves.easeInOut,
duration: const Duration(seconds: 1),
);
isLoading.value = false;
}
void debouncedUpdateVisibleMarkers({required LatLng center}) {
_debounceTimer?.cancel();
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
final filtered = filterNearbyMarkers(
allPoultryLocation.value.data ?? [],
center.latitude,
center.longitude,
10000, // Radius in meters
);
markers.addAll(filtered);
});
}
List<PoultryLocationModel> filterNearbyMarkers(
List<PoultryLocationModel> allMarkers,
double centerLat,
double centerLng,
double radiusInMeters,
) {
final center = LatLng(centerLat, centerLng);
final distance = Distance();
return allMarkers.where((marker) {
var tmp =LatLng(marker.lat ?? 0, marker.long ?? 0);
return distance(center,tmp ) <= radiusInMeters;
}).toList();
}
Future<void> triggerSlidableAnimation() async {
@@ -144,9 +151,23 @@ class InspectionMapLogic extends GetxController with GetTickerProviderStateMixin
showSlideHint = false;
}
@override
void onClose() {
slidController.close();
super.onClose();
Future<void> fetchAllPoultryLocations() async {
isLoading.value = true;
allPoultryLocation.value = Resource<List<PoultryLocationModel>>.loading();
await safeCall(
call: () => inspectionRepository.getNearbyLocation(),
onSuccess: (result) {
if (result != null) {
allPoultryLocation.value = Resource<List<PoultryLocationModel>>.success(result);
} else {
allPoultryLocation.value = Resource<List<PoultryLocationModel>>.error(
'No locations found',
);
}
},
onError: (error, stackTrace) {
allPoultryLocation.value = Resource<List<PoultryLocationModel>>.error(error.toString());
},
);
}
}

View File

@@ -54,7 +54,7 @@ class InspectionMapPage extends GetView<InspectionMapLogic> {
markers: markers
.map(
(e) => markerWidget(
marker: e,
marker: LatLng(e.lat??0, e.long??0),
onTap: () {
Get.bottomSheet(
selectedLocationWidget2(
@@ -295,6 +295,7 @@ class InspectionMapPage extends GetView<InspectionMapLogic> {
}
Marker markerWidget({required LatLng marker, required VoidCallback onTap}) {
iLog('lat: ${marker.latitude}, lng: ${marker.longitude}');
return Marker(
point: marker,
child: GestureDetector(

View File

@@ -1,28 +1,27 @@
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_inspection/data/model/response/user_profile/user_profile_model.dart';
import 'package:rasadyar_inspection/data/repositories/user/user_repository_imp.dart';
import 'package:rasadyar_inspection/injection/inspection_di.dart';
import 'package:rasadyar_inspection/inspection.dart';
class ProfileLogic extends GetxController {
List<String> roles = <String>[
'کاربر عادی',
'کاربر ویژه',
'کاربر VIP',
'کاربر نقره ای',
'کاربر طلایی',
];
RootLogic rootLogic = Get.find<RootLogic>();
UserRepositoryImp userRepository = diInspection.get<UserRepositoryImp>();
Rx<Resource<UserProfileModel>> userProfile = Resource<UserProfileModel>.loading().obs;
RxInt selectedRole = 0.obs;
RxInt selectedInformationType = 0.obs;
@override
void onReady() {
// TODO: implement onReady
super.onReady();
rootLogic.userProfile.listen((data) {
if (data != null) {
userProfile.value = Resource<UserProfileModel>.success(data);
} else {
userProfile.value = Resource<UserProfileModel>.error('Failed to load user profile');
}
});
}
@override
@@ -30,4 +29,26 @@ class ProfileLogic extends GetxController {
// TODO: implement onClose
super.onClose();
}
Future<void> fetchUserProfile() async {
userProfile.value = Resource<UserProfileModel>.loading();
await safeCall(
call: () => userRepository.fetchUserProfile(
token: rootLogic.tokenStorageService.accessToken.value ?? '',
),
onSuccess: (profile) {
if (profile != null) {
userProfile.value = Resource<UserProfileModel>.success(profile);
} else {
userProfile.value = Resource<UserProfileModel>.error('Failed to load user profile');
}
},
onError: (error, stackTrace) {
userProfile.value = Resource<UserProfileModel>.error(
'Failed to load user profile: ${error.toString()}',
);
},
);
}
}

View File

@@ -1,5 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_inspection/data/model/response/user_profile/user_profile_model.dart';
import 'logic.dart';
@@ -9,124 +11,96 @@ class ProfilePage extends GetView<ProfileLogic> {
@override
Widget build(BuildContext context) {
return Column(
spacing: 30,
children: [
SizedBox(
height: Get.height * 0.3,
child: Stack(
fit: StackFit.expand,
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
Assets.vec.bgHeaderUserProfileSvg.svg(),
Positioned(
bottom: -20,
left: 0,
right: 0,
child: SizedBox(
width: 110,
height: 110,
child: CircleAvatar(
backgroundColor: AppColor.blueLightHover,
child: FaIcon(
FontAwesomeIcons.user,
size: 45,
color: Colors.white,
Expanded(
flex: 1,
child: Container(
color: AppColor.blueNormal,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(),
ObxValue((data) {
final status = data.value.status;
if (status == ResourceStatus.loading) {
return Container(
width: 128.w,
height: 128.h,
child: Center(child: CupertinoActivityIndicator(color: AppColor.greenNormal)),
);
}
if (status == ResourceStatus.error) {
return Container(
width: 128.w,
height: 128.h,
child: Center(child: Text('خطا در دریافت اطلاعات')),
);
}
// Default UI
return Container(
width: 128.w,
height: 128.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.blueLightActive,
),
),
),
),
],
child: Center(
child: CircleAvatar(
radius: 64.w,
backgroundImage: NetworkImage(data.value.data?.user.photo ?? ''),
),
),
);
}, controller.userProfile),
],
),
),
),
Expanded(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 16,
children: [
SizedBox(
height: 75,
child: ListView.separated(
shrinkWrap: true,
padding: EdgeInsets.all(16),
scrollDirection: Axis.horizontal,
itemBuilder:
(context, index) => ObxValue((data) {
return ChoiceChip(
onSelected: (value) {
data.value = index;
},
selectedColor: AppColor.blueNormal,
labelStyle:
data.value == index
? AppFonts.yekan13.copyWith(
color: AppColor.whiteLight,
)
: AppFonts.yekan12.copyWith(
color: AppColor.darkGreyNormalActive,
),
checkmarkColor: Colors.white,
label: Text(controller.roles[index]),
selected: index == data.value,
);
}, controller.selectedRole),
separatorBuilder: (context, index) => SizedBox(width: 8),
itemCount: controller.roles.length,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 30,
vertical: 10,
),
child: ObxValue((data) {
return switch (data.value) {
0 => userProfileInformation(),
1 => bankInformationWidget(),
2 => invoiceIssuanceInformation(),
int() => Placeholder(),
};
}, controller.selectedInformationType),
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 10),
child: userProfileInformation(),
),
),
ObxValue((data) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Wrap(
spacing: 20,
runSpacing: 10,
children: [
cardActionWidget(
title: 'اطلاعات کاربری',
onPressed: () {
data.value = 0;
},
icon: Assets.vec.profileUserSvg.path,
selected: data.value == 0,
),
cardActionWidget(
title: 'اطلاعات بانکی',
onPressed: () {
data.value = 1;
},
icon: Assets.vec.informationSvg.path,
selected: data.value == 1,
),
cardActionWidget(
title: 'اطلاعات \nصدور فاکتور',
onPressed: () {
data.value = 2;
},
icon: Assets.vec.receiptDiscountSvg.path,
selected: data.value == 2,
),
],
),
);
}, controller.selectedInformationType),
Center(
child: Wrap(
alignment: WrapAlignment.center,
spacing: 20,
runSpacing: 10,
children: [
cardActionWidget(
title: 'تغییر رمز عبور',
selected: true,
onPressed: () {
Get.bottomSheet(changePasswordBottomSheet(), isScrollControlled: true);
},
icon: Assets.vec.lockSvg.path,
),
cardActionWidget(
title: 'خروج',
selected: true,
color: ColorFilter.mode(Colors.redAccent, BlendMode.srcIn),
cardColor: Color(0xFFEFEFEF),
textColor: AppColor.redDarkerText,
onPressed: () {
Get.bottomSheet(exitBottomSheet(), isScrollControlled: true);
},
icon: Assets.vec.logoutSvg.path,
),
],
),
),
SizedBox(height: 100),
],
@@ -149,72 +123,113 @@ class ProfilePage extends GetView<ProfileLogic> {
],
);
Column userProfileInformation() {
return Column(
spacing: 10,
children: [
itemList(
title: 'نام و نام خانوادگی',
content: 'آیدا گل محمدی',
icon: Assets.vec.userSvg.path,
),
itemList(
title: 'موبایل',
content: '09302654896',
icon: Assets.vec.callSvg.path,
),
itemList(
title: 'کدملی',
content: 'نا مشخص',
icon: Assets.vec.tagUserSvg.path,
),
itemList(
title: 'شماره شناسنامه',
content: 'نا مشخص',
icon: Assets.vec.userSquareSvg.path,
),
itemList(
title: 'تاریخ تولد',
content: '1404/10/12',
icon: Assets.vec.calendarSvg.path,
),
itemList(
title: 'استان',
content: 'لرستان',
icon: Assets.vec.pictureFrameSvg.path,
),
itemList(
title: 'شهر',
content: 'خرم آباد',
icon: Assets.vec.mapSvg.path,
),
],
);
Widget userProfileInformation() {
return ObxValue((data) {
if (data.value.status == ResourceStatus.loading) {
return LoadingWidget();
} else if (data.value.status == ResourceStatus.error) {
return ErrorWidget('خطا در دریافت اطلاعات کاربر');
} else if (data.value.status == ResourceStatus.success) {
User item = data.value.data!.user;
return Column(
spacing: 6,
children: [
buildRowOnTapped(
onTap: () {
Get.bottomSheet(
userInformationBottomSheet(),
isScrollControlled: true,
ignoreSafeArea: false,
);
},
titleWidget: Column(
spacing: 3,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'اطلاعات هویتی',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
Container(width: 37.w, height: 1.h, color: AppColor.greenNormal),
],
),
valueWidget: Assets.vec.editSvg.svg(
width: 24.w,
height: 24.h,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
itemList(
title: 'نام و نام خانوادگی',
content: '${item.firstName} ${item.lastName}',
icon: Assets.vec.userSvg.path,
hasColoredBox: true,
),
itemList(
title: 'موبایل',
content: item.mobile ?? 'نامشخص',
icon: Assets.vec.callSvg.path,
),
itemList(
title: 'کدملی',
content: item.nationalCode ?? 'نامشخص',
icon: Assets.vec.tagUserSvg.path,
),
itemList(
title: 'شماره شناسنامه',
content: item.nationalCode ?? 'نامشخص',
icon: Assets.vec.userSquareSvg.path,
),
itemList(
title: 'استان',
content: item.provinceName ?? 'نامشخص',
icon: Assets.vec.pictureFrameSvg.path,
),
itemList(
title: 'شهر',
content: item.cityName ?? 'نامشخص',
icon: Assets.vec.mapSvg.path,
),
],
);
} else {
return SizedBox.shrink();
}
}, controller.userProfile);
}
Widget itemList({
required String title,
required String content,
String? icon,
}) => Row(
spacing: 4,
children: [
if (icon != null)
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: SvgGenImage.vec(icon).svg(
width: 20,
height: 20,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
bool hasColoredBox = false,
}) => Container(
padding: EdgeInsets.symmetric(horizontal: 12.h, vertical: 6.h),
decoration: BoxDecoration(
color: hasColoredBox ? AppColor.greenLight : Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: hasColoredBox
? Border.all(width: 0.25, color: AppColor.bgDark)
: Border.all(width: 0, color: Colors.transparent),
),
child: Row(
spacing: 4,
children: [
if (icon != null)
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: SvgGenImage.vec(icon).svg(
width: 20.w,
height: 20.h,
colorFilter: ColorFilter.mode(AppColor.mediumGreyNormalActive, BlendMode.srcIn),
),
),
),
Text(title, style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal)),
Spacer(),
Text(
content,
style: AppFonts.yekan13.copyWith(color: AppColor.darkGreyNormalHover),
),
],
Text(title, style: AppFonts.yekan12.copyWith(color: AppColor.mediumGreyNormalActive)),
Spacer(),
Text(content, style: AppFonts.yekan13.copyWith(color: AppColor.mediumGreyNormalHover)),
],
),
);
Widget cardActionWidget({
@@ -222,6 +237,9 @@ class ProfilePage extends GetView<ProfileLogic> {
required VoidCallback onPressed,
required String icon,
bool selected = false,
ColorFilter? color,
Color? cardColor,
Color? textColor,
}) {
return GestureDetector(
onTap: onPressed,
@@ -233,25 +251,25 @@ class ProfilePage extends GetView<ProfileLogic> {
height: 52,
padding: EdgeInsets.all(8),
decoration: ShapeDecoration(
color: selected ? AppColor.blueLightActive : AppColor.blueLight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
color: cardColor ?? AppColor.blueLight,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: SvgGenImage.vec(icon).svg(
width: 40,
height: 40,
colorFilter: ColorFilter.mode(
selected ? AppColor.whiteLight : AppColor.blueNormal,
BlendMode.srcIn,
),
colorFilter:
color ??
ColorFilter.mode(
selected ? AppColor.blueNormal : AppColor.whiteLight,
BlendMode.srcIn,
),
),
),
SizedBox(height: 2),
Text(
title,
style: AppFonts.yekan10.copyWith(
color: selected ? AppColor.blueNormal : AppColor.blueLightActive,
color: textColor ?? (selected ? AppColor.blueNormal : AppColor.blueLightActive),
),
textAlign: TextAlign.center,
),
@@ -259,4 +277,359 @@ class ProfilePage extends GetView<ProfileLogic> {
),
);
}
Widget userInformationBottomSheet() {
return Container(color: Colors.red);
/* return BaseBottomSheet(
height: 750.h,
child: SingleChildScrollView(
child: Column(
spacing: 8,
children: [
Text(
'ویرایش اطلاعات هویتی',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover),
),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
RTextField(
controller: controller.nameController,
label: 'نام',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.lastNameController,
label: 'نام خانوادگی',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.nationalCodeController,
label: 'شماره شناسنامه',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.nationalIdController,
label: 'کد ملی',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
ObxValue((data) {
return RTextField(
controller: controller.birthdayController,
label: 'تاریخ تولد',
initText: data.value?.formatCompactDate() ?? '',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
onTap: () {},
);
}, controller.birthDate),
SizedBox(),
],
),
),
SizedBox(),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 8,
children: [
Text(
'عکس پروفایل',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
ObxValue((data) {
return Container(
width: Get.width,
height: 270,
decoration: BoxDecoration(
color: AppColor.lightGreyNormal,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: AppColor.blackLight),
),
child: Center(
child: data.value == null
? Padding(
padding: const EdgeInsets.fromLTRB(30, 10, 10, 30),
child: Image.network(
controller.userProfile.value.data?.image ?? '',
),
)
: Image.file(File(data.value!.path), fit: BoxFit.cover),
),
);
}, controller.selectedImage),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
text: 'گالری',
width: 150.w,
height: 40.h,
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
onPressed: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
),
SizedBox(width: 16),
ROutlinedElevated(
text: 'دوربین',
width: 150.w,
height: 40.h,
textStyle: AppFonts.yekan20.copyWith(color: AppColor.blueNormal),
onPressed: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.camera,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
),
],
),
],
),
),
Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ObxValue((data) {
return RElevated(
height: 40.h,
text: 'ویرایش',
isLoading: data.value,
onPressed: () async {
await controller.updateUserProfile();
controller.getUserProfile();
Get.back();
},
);
}, controller.isOnLoading),
ROutlinedElevated(
height: 40.h,
text: 'انصراف',
borderColor: AppColor.blueNormal,
onPressed: () {
Get.back();
},
),
],
),
],
),
),
);*/
}
/* Widget _provinceWidget() {
return Obx(() {
return OverlayDropdownWidget<IranProvinceCityModel>(
items: controller.rootLogic.provinces,
onChanged: (value) {
controller.selectedProvince.value = value;
},
selectedItem: controller.selectedProvince.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Text(item?.name ?? 'انتخاب استان'),
);
});
}
Widget _cityWidget() {
return ObxValue((data) {
return OverlayDropdownWidget<IranProvinceCityModel>(
items: data,
onChanged: (value) {
controller.selectedCity.value = value;
},
selectedItem: controller.selectedCity.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Text(item?.name ?? 'انتخاب شهر'),
);
}, controller.cites);
}*/
Widget changePasswordBottomSheet() {
return Container(color: Colors.red);
/* return BaseBottomSheet(
height: 400.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 8,
children: [
Text(
'تغییر رمز عبور',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover),
),
SizedBox(),
RTextField(
controller: controller.oldPasswordController,
hintText: 'رمز عبور قبلی',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'رمز عبور را وارد کنید';
} else if (controller.userProfile.value.data?.password != value) {
return 'رمز عبور صحیح نیست';
}
return null;
},
),
RTextField(
controller: controller.newPasswordController,
hintText: 'رمز عبور جدید',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'رمز عبور را وارد کنید';
} else if (value.length < 6) {
return 'رمز عبور باید بیش از 6 کارکتر باشد.';
}
return null;
},
),
RTextField(
controller: controller.retryNewPasswordController,
hintText: 'تکرار رمز عبور جدید',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'رمز عبور را وارد کنید';
} else if (value.length < 6) {
return 'رمز عبور باید بیش از 6 کارکتر باشد.';
} else if (controller.newPasswordController.text != value) {
return 'رمز عبور جدید یکسان نیست';
}
return null;
},
),
SizedBox(),
Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
height: 40.h,
text: 'ویرایش',
onPressed: () async {
if (controller.formKey.currentState?.validate() != true) {
return;
}
await controller.updatePassword();
controller.getUserProfile();
controller.clearPasswordForm();
Get.back();
},
),
ROutlinedElevated(
height: 40.h,
text: 'انصراف',
borderColor: AppColor.blueNormal,
onPressed: () {
Get.back();
},
),
],
),
],
),
),
),
);*/
}
Widget exitBottomSheet() {
return Container(color: Colors.red);
/* return BaseBottomSheet(
height: 220.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 8,
children: [
Text('خروج', style: AppFonts.yekan16Bold.copyWith(color: AppColor.error)),
SizedBox(),
Text(
'آیا مطمئن هستید که می‌خواهید از حساب کاربری خود خارج شوید؟',
textAlign: TextAlign.center,
style: AppFonts.yekan16Bold.copyWith(color: AppColor.textColor),
),
SizedBox(),
Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
height: 40.h,
text: 'خروج',
backgroundColor: AppColor.error,
onPressed: () async {
await controller.rootLogic.tokenService.deleteTokens().then((value) {
Get.back();
Get.offAllNamed(ChickenRoutes.auth, arguments: Module.chicken);
});
},
),
ROutlinedElevated(
height: 40.h,
text: 'انصراف',
borderColor: AppColor.blueNormal,
onPressed: () {
Get.back();
},
),
],
),
],
),
),
),
);*/
}
}

View File

@@ -1,16 +1,60 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_inspection/data/model/response/user_profile/user_profile_model.dart';
import 'package:rasadyar_inspection/data/repositories/user/user_repository_imp.dart';
import 'package:rasadyar_inspection/injection/inspection_di.dart';
import 'package:rasadyar_inspection/presentation/pages/action/view.dart';
import 'package:rasadyar_inspection/presentation/pages/inspection_map/view.dart';
import 'package:rasadyar_inspection/presentation/pages/profile/view.dart';
enum ErrorLocationType { serviceDisabled, permissionDenied, none }
class RootLogic extends GetxController {
RxInt currentIndex = 0.obs;
List<Widget> pages = [InspectionMapPage(), ActionPage(), ProfilePage()];
RxList<ErrorLocationType> errorLocationType = RxList();
TokenStorageService tokenStorageService = Get.find<TokenStorageService>();
UserRepositoryImp userRepository = diInspection.get<UserRepositoryImp>();
Rxn<UserProfileModel> userProfile = Rxn<UserProfileModel>();
@override
void onInit() {
super.onInit();
userRepository
.fetchUserProfile(token: tokenStorageService.accessToken.value ?? '')
.then((value) => userProfile.value = value);
}
@override
void onReady() {
super.onReady();
locationServiceEnabled().then((value) {
if (!value) {
errorLocationType.add(ErrorLocationType.serviceDisabled);
}
});
checkPermission().then((value) {
if (!value) {
errorLocationType.add(ErrorLocationType.permissionDenied);
}
});
listenToLocationServiceStatus().listen((event) {
if (!event) {
errorLocationType.add(ErrorLocationType.serviceDisabled);
} else {
errorLocationType.remove(ErrorLocationType.serviceDisabled);
}
});
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
Stream<bool> listenToLocationServiceStatus() {
return Geolocator.getServiceStatusStream().map((status) {
@@ -52,38 +96,7 @@ class RootLogic extends GetxController {
}
}
@override
void onReady() {
super.onReady();
locationServiceEnabled().then((value) {
if (!value) {
errorLocationType.add(ErrorLocationType.serviceDisabled);
}
});
checkPermission().then((value) {
if (!value) {
errorLocationType.add(ErrorLocationType.permissionDenied);
}
});
listenToLocationServiceStatus().listen((event) {
if (!event) {
errorLocationType.add(ErrorLocationType.serviceDisabled);
} else {
errorLocationType.remove(ErrorLocationType.serviceDisabled);
}
});
}
void changePage(int index) {
currentIndex.value = index;
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
}

View File

@@ -9,46 +9,46 @@ class RootPage extends GetView<RootLogic> {
@override
Widget build(BuildContext context) {
return ObxValue((currentIndex) {
return Scaffold(
body: ObxValue(
(currentIndex) => IndexedStack(index: currentIndex.value, children: controller.pages),
controller.currentIndex,
),
bottomNavigationBar: RBottomNavigation(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
items: [
RBottomNavigationItem(
label: 'نقشه',
icon: Assets.vec.mapSvg.path,
isSelected: currentIndex.value == 0,
onTap: () {
Get.nestedKey(1)?.currentState?.popUntil((route) => route.isFirst);
return PopScope(
canPop: false,
child: Scaffold(
backgroundColor: AppColor.bgLight,
body: IndexedStack(index: currentIndex.value, children: controller.pages),
bottomNavigationBar: RBottomNavigation(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
items: [
RBottomNavigationItem(
label: 'نقشه',
icon: Assets.vec.mapSvg.path,
isSelected: currentIndex.value == 0,
onTap: () {
Get.nestedKey(1)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(0);
},
),
RBottomNavigationItem(
label: 'اقدام',
icon: Assets.vec.settingSvg.path,
isSelected: currentIndex.value == 1,
onTap: () {
Get.nestedKey(0)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(1);
},
),
controller.changePage(0);
},
),
RBottomNavigationItem(
label: 'اقدام',
icon: Assets.vec.settingSvg.path,
isSelected: currentIndex.value == 1,
onTap: () {
Get.nestedKey(0)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(1);
},
),
RBottomNavigationItem(
label: 'پروفایل',
icon: Assets.vec.profileCircleSvg.path,
isSelected: currentIndex.value == 2,
onTap: () {
Get.nestedKey(1)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(0)?.currentState?.popUntil((route) => route.isFirst);
RBottomNavigationItem(
label: 'پروفایل',
icon: Assets.vec.profileCircleSvg.path,
isSelected: currentIndex.value == 4,
onTap: () {
Get.nestedKey(1)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(0)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(4);
},
),
],
controller.changePage(2);
},
),
],
),
),
);
}, controller.currentIndex);