feat : request tagging

This commit is contained in:
2025-08-05 14:48:47 +03:30
parent 7b8cfb5ae9
commit 59e6d621cf
19 changed files with 318 additions and 66 deletions

View File

@@ -0,0 +1,5 @@
import 'package:rasadyar_livestock/data/model/response/address/address.dart';
abstract class LivestockRemoteDataSource {
Future<LocationDetails> getLocationDetailsByLatLng({required double latitude, required double longitude});
}

View File

@@ -0,0 +1,29 @@
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_livestock/data/data_source/remote/livestock/livestock_remote.dart';
import 'package:rasadyar_livestock/data/model/response/address/address.dart';
class LivestockRemoteDataSourceImp implements LivestockRemoteDataSource {
@override
Future<LocationDetails> getLocationDetailsByLatLng({
required double latitude,
required double longitude,
}) async {
try {
Dio dio = Dio();
dio.options.baseUrl = 'https://nominatim.openstreetmap.org/';
dio.options.headers['User-Agent'] = 'RasadyarLivestock/2.0';
final response = await dio.get(
'reverse',
queryParameters: {'lat': latitude, 'lon': longitude, 'format': 'json'},
);
if (response.statusCode == 200) {
final data = response.data;
return LocationDetails.fromJson(data);
} else {
throw Exception('Failed to load address');
}
} catch (e) {
rethrow;
}
}
}

View File

@@ -10,14 +10,14 @@ _LoginRequestModel _$LoginRequestModelFromJson(Map<String, dynamic> json) =>
_LoginRequestModel(
username: json['username'] as String?,
password: json['password'] as String?,
captchaCode: json['captcha_code'] as String?,
captchaKey: json['captcha_key'] as String?,
captchaCode: json['captchaCode'] as String?,
captchaKey: json['captchaKey'] as String?,
);
Map<String, dynamic> _$LoginRequestModelToJson(_LoginRequestModel instance) =>
<String, dynamic>{
'username': instance.username,
'password': instance.password,
'captcha_code': instance.captchaCode,
'captcha_key': instance.captchaKey,
'captchaCode': instance.captchaCode,
'captchaKey': instance.captchaKey,
};

View File

@@ -0,0 +1,49 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'address.freezed.dart';
part 'address.g.dart';
@freezed
abstract class Address with _$Address {
const factory Address({
String? road,
String? neighbourhood,
String? suburb,
String? state,
String? borough,
String? city,
String? district,
String? county,
String? province,
String? ISO3166_2_lvl4,
String? postcode,
String? country,
String? country_code,
}) = _Address;
factory Address.fromJson(Map<String, dynamic> json) => _$AddressFromJson(json);
}
@freezed
abstract class LocationDetails with _$LocationDetails {
const factory LocationDetails({
int? place_id,
String? licence,
String? osm_type,
int? osm_id,
String? lat,
String? lon,
String? class_,
String? type,
int? place_rank,
double? importance,
String? addresstype,
String? name,
String? display_name,
Address? address,
List<String>? boundingbox,
}) = _LocationDetails;
factory LocationDetails.fromJson(Map<String, dynamic> json) => _$LocationDetailsFromJson(json);
}

View File

@@ -10,12 +10,12 @@ _AuthResponseModel _$AuthResponseModelFromJson(Map<String, dynamic> json) =>
_AuthResponseModel(
refresh: json['refresh'] as String?,
access: json['access'] as String?,
otpStatus: json['otp_status'] as bool?,
otpStatus: json['otpStatus'] as bool?,
);
Map<String, dynamic> _$AuthResponseModelToJson(_AuthResponseModel instance) =>
<String, dynamic>{
'refresh': instance.refresh,
'access': instance.access,
'otp_status': instance.otpStatus,
'otpStatus': instance.otpStatus,
};

View File

@@ -9,17 +9,17 @@ part of 'captcha_response_model.dart';
_CaptchaResponseModel _$CaptchaResponseModelFromJson(
Map<String, dynamic> json,
) => _CaptchaResponseModel(
captchaKey: json['captcha_key'] as String?,
captchaImage: json['captcha_image'] as String?,
imageType: json['image_type'] as String?,
imageDecode: json['image_decode'] as String?,
captchaKey: json['captchaKey'] as String?,
captchaImage: json['captchaImage'] as String?,
imageType: json['imageType'] as String?,
imageDecode: json['imageDecode'] as String?,
);
Map<String, dynamic> _$CaptchaResponseModelToJson(
_CaptchaResponseModel instance,
) => <String, dynamic>{
'captcha_key': instance.captchaKey,
'captcha_image': instance.captchaImage,
'image_type': instance.imageType,
'image_decode': instance.imageDecode,
'captchaKey': instance.captchaKey,
'captchaImage': instance.captchaImage,
'imageType': instance.imageType,
'imageDecode': instance.imageDecode,
};

View File

@@ -26,12 +26,12 @@ _User _$UserFromJson(Map<String, dynamic> json) => _User(
id: (json['id'] as num).toInt(),
username: json['username'] as String,
password: json['password'] as String,
firstName: json['first_name'] as String,
lastName: json['last_name'] as String,
isActive: json['is_active'] as bool,
firstName: json['firstName'] as String,
lastName: json['lastName'] as String,
isActive: json['isActive'] as bool,
mobile: json['mobile'] as String,
phone: json['phone'] as String,
nationalCode: json['national_code'] as String,
nationalCode: json['nationalCode'] as String,
birthdate: DateTime.parse(json['birthdate'] as String),
nationality: json['nationality'] as String,
ownership: json['ownership'] as String,
@@ -39,21 +39,21 @@ _User _$UserFromJson(Map<String, dynamic> json) => _User(
photo: json['photo'] as String,
province: (json['province'] as num).toInt(),
city: (json['city'] as num).toInt(),
otpStatus: json['otp_status'] as bool,
cityName: json['city_name'] as String,
provinceName: json['province_name'] as String,
otpStatus: json['otpStatus'] as bool,
cityName: json['cityName'] as String,
provinceName: json['provinceName'] as String,
);
Map<String, dynamic> _$UserToJson(_User instance) => <String, dynamic>{
'id': instance.id,
'username': instance.username,
'password': instance.password,
'first_name': instance.firstName,
'last_name': instance.lastName,
'is_active': instance.isActive,
'firstName': instance.firstName,
'lastName': instance.lastName,
'isActive': instance.isActive,
'mobile': instance.mobile,
'phone': instance.phone,
'national_code': instance.nationalCode,
'nationalCode': instance.nationalCode,
'birthdate': instance.birthdate.toIso8601String(),
'nationality': instance.nationality,
'ownership': instance.ownership,
@@ -61,14 +61,14 @@ Map<String, dynamic> _$UserToJson(_User instance) => <String, dynamic>{
'photo': instance.photo,
'province': instance.province,
'city': instance.city,
'otp_status': instance.otpStatus,
'city_name': instance.cityName,
'province_name': instance.provinceName,
'otpStatus': instance.otpStatus,
'cityName': instance.cityName,
'provinceName': instance.provinceName,
};
_Role _$RoleFromJson(Map<String, dynamic> json) => _Role(
id: (json['id'] as num).toInt(),
roleName: json['role_name'] as String,
roleName: json['roleName'] as String,
description: json['description'] as String,
type: RoleType.fromJson(json['type'] as Map<String, dynamic>),
permissions: json['permissions'] as List<dynamic>,
@@ -76,7 +76,7 @@ _Role _$RoleFromJson(Map<String, dynamic> json) => _Role(
Map<String, dynamic> _$RoleToJson(_Role instance) => <String, dynamic>{
'id': instance.id,
'role_name': instance.roleName,
'roleName': instance.roleName,
'description': instance.description,
'type': instance.type,
'permissions': instance.permissions,
@@ -91,14 +91,14 @@ Map<String, dynamic> _$RoleTypeToJson(_RoleType instance) => <String, dynamic>{
};
_Permission _$PermissionFromJson(Map<String, dynamic> json) => _Permission(
pageName: json['page_name'] as String,
pageAccess: (json['page_access'] as List<dynamic>)
pageName: json['pageName'] as String,
pageAccess: (json['pageAccess'] as List<dynamic>)
.map((e) => e as String)
.toList(),
);
Map<String, dynamic> _$PermissionToJson(_Permission instance) =>
<String, dynamic>{
'page_name': instance.pageName,
'page_access': instance.pageAccess,
'pageName': instance.pageName,
'pageAccess': instance.pageAccess,
};

View File

@@ -1,3 +1,4 @@
import 'package:rasadyar_livestock/data/model/response/address/address.dart';
import 'package:rasadyar_livestock/data/model/response/auth/auth_response_model.dart';
import 'package:rasadyar_livestock/data/model/response/captcha/captcha_response_model.dart';
@@ -11,4 +12,6 @@ abstract class AuthRepository {
Future<bool> hasAuthenticated();
Future<AuthResponseModel?> loginWithRefreshToken({required Map<String, dynamic> authRequest});
}

View File

@@ -1,4 +1,6 @@
import 'package:rasadyar_livestock/data/data_source/remote/auth/auth_remote.dart';
import 'package:rasadyar_livestock/data/data_source/remote/livestock/livestock_remote.dart';
import 'package:rasadyar_livestock/data/model/response/address/address.dart';
import 'package:rasadyar_livestock/data/model/response/auth/auth_response_model.dart';
import 'package:rasadyar_livestock/data/model/response/captcha/captcha_response_model.dart';
@@ -7,7 +9,8 @@ import 'auth_repository.dart';
class AuthRepositoryImp implements AuthRepository {
final AuthRemoteDataSource authRemote;
AuthRepositoryImp(this.authRemote);
AuthRepositoryImp({required this.authRemote});
@override
Future<AuthResponseModel?> login({required Map<String, dynamic> authRequest}) async =>
@@ -34,4 +37,6 @@ class AuthRepositoryImp implements AuthRepository {
Future<bool> hasAuthenticated() async {
return await authRemote.hasAuthenticated();
}
}

View File

@@ -0,0 +1,11 @@
import 'package:rasadyar_livestock/data/model/response/address/address.dart';
import 'package:rasadyar_livestock/data/model/response/auth/auth_response_model.dart';
import 'package:rasadyar_livestock/data/model/response/captcha/captcha_response_model.dart';
abstract class LivestockRepository {
Future<LocationDetails?> getLocationDetails({
required double latitude,
required double longitude,
});
}

View File

@@ -0,0 +1,18 @@
import 'package:rasadyar_livestock/data/data_source/remote/livestock/livestock_remote.dart';
import 'package:rasadyar_livestock/data/model/response/address/address.dart';
import 'livestock_repository.dart';
class LivestockRepositoryImp implements LivestockRepository {
final LivestockRemoteDataSource livestockRemote;
LivestockRepositoryImp({required this.livestockRemote});
@override
Future<LocationDetails?> getLocationDetails({required double latitude, required double longitude}) async {
return await livestockRemote.getLocationDetailsByLatLng(
latitude: latitude,
longitude: longitude,
);
}
}

View File

@@ -2,8 +2,12 @@ import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_livestock/data/common/dio_exception_handeler.dart';
import 'package:rasadyar_livestock/data/data_source/remote/auth/auth_remote.dart';
import 'package:rasadyar_livestock/data/data_source/remote/auth/auth_remote_imp.dart';
import 'package:rasadyar_livestock/data/data_source/remote/livestock/livestock_remote.dart';
import 'package:rasadyar_livestock/data/data_source/remote/livestock/livestock_remote_imp.dart';
import 'package:rasadyar_livestock/data/repository/auth/auth_repository.dart';
import 'package:rasadyar_livestock/data/repository/auth/auth_repository_imp.dart';
import 'package:rasadyar_livestock/data/repository/livestock/livestock_repository.dart';
import 'package:rasadyar_livestock/data/repository/livestock/livestock_repository_imp.dart';
import 'package:rasadyar_livestock/presentation/routes/app_pages.dart';
GetIt get diLiveStock => GetIt.instance;
@@ -17,7 +21,7 @@ Future<void> setupLiveStockDI() async {
await tokenService.saveBaseUrl('https://api.dam.rasadyar.net/');
}
// First register AppInterceptor with lazy callbacks
// First register AppInterceptor with lazy callbacks
diLiveStock.registerLazySingleton<AppInterceptor>(
() => AppInterceptor(
refreshTokenCallback: () async {
@@ -55,13 +59,25 @@ Future<void> setupLiveStockDI() async {
await diLiveStock.get<DioRemote>().init();
// Now register the data source and repository
//region Auth
diLiveStock.registerLazySingleton<AuthRemoteDataSource>(
() => AuthRemoteDataSourceImp(diLiveStock.get<DioRemote>()),
);
diLiveStock.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImp(diLiveStock.get<AuthRemoteDataSource>()),
() => AuthRepositoryImp(authRemote: diLiveStock.get<AuthRemoteDataSource>()),
);
//endregion
//region Livestock
diLiveStock.registerLazySingleton<LivestockRemoteDataSource>(
() => LivestockRemoteDataSourceImp(),
);
diLiveStock.registerLazySingleton<LivestockRepository>(
() => LivestockRepositoryImp(livestockRemote: diLiveStock.get<LivestockRemoteDataSource>()),
);
//endregion
diLiveStock.registerLazySingleton<ImagePicker>(() => ImagePicker());
await diLiveStock.allReady();

View File

@@ -1,22 +1,43 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_livestock/data/model/response/address/address.dart';
import 'package:rasadyar_livestock/data/repository/livestock/livestock_repository.dart';
import 'package:rasadyar_livestock/injection/live_stock_di.dart';
class RequestTaggingLogic extends GetxController {
RxInt currentIndex = 0.obs;
final int maxStep = 3;
final int maxStep = 2;
RxBool nextButtonEnabled = true.obs;
LivestockRepository livestockRepository = diLiveStock.get<LivestockRepository>();
//region First Step
final TextEditingController phoneController = TextEditingController();
final TextEditingController fullNameController = TextEditingController();
final TextEditingController addressController = TextEditingController();
ImagePicker imagePicker = diLiveStock.get<ImagePicker>();
Rxn<XFile> rancherImage = Rxn<XFile>(null);
//endregion
//region Second Step
Rxn<XFile> herdImage = Rxn<XFile>(null);
Rx<Resource<LocationDetails>> addressDetails = Rx<Resource<LocationDetails>>(Resource.loading());
RxnString addressDetailsValue = RxnString(null);
RxnString addressLocationValue = RxnString(null);
RxInt selectedSegment = 0.obs;
RxBool searchIsSelected = false.obs;
RxBool filterIsSelected = false.obs;
RxList<int> filterSelected = <int>[].obs;
RxList isExpandedList = <int>[].obs;
RxBool tst1 = false.obs;
//endregion
@override
void onInit() {
super.onInit();
@@ -25,6 +46,8 @@ class RequestTaggingLogic extends GetxController {
ever(rancherImage, (callback) {
setUpNextButtonListeners();
});
determineCurrentPosition();
}
@override
@@ -52,11 +75,11 @@ class RequestTaggingLogic extends GetxController {
void setUpNextButtonListeners() {
if (currentIndex.value == 0) {
nextButtonEnabled.value =
/* nextButtonEnabled.value =
phoneController.text.isNotEmpty &&
fullNameController.text.isNotEmpty &&
addressController.text.isNotEmpty &&
rancherImage.value != null;
rancherImage.value != null;*/
return;
}
@@ -94,10 +117,47 @@ class RequestTaggingLogic extends GetxController {
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');
Future<void> determineCurrentPosition() async {
final position = await Geolocator.getCurrentPosition(
locationSettings: AndroidSettings(accuracy: LocationAccuracy.best),
);
getLocationDetails(position.latitude, position.longitude);
}
Future<void> getLocationDetails(double latitude, double longitude) async {
safeCall(
call: () => livestockRepository.getLocationDetails(latitude: latitude, longitude: longitude),
onSuccess: (result) {
if (result != null) {
addressDetails.value = Resource.success(result);
buildAddressDetails(result);
addressLocationValue.value = '$latitude - $longitude';
} else {
addressDetails.value = Resource.error('Failed to fetch address');
}
},
onError: (error, stackTrace) {
addressDetails.value = Resource.error('Error fetching address: ${error.toString()}');
},
);
}
void buildAddressDetails(LocationDetails result) {
final address = result.address;
if (address != null) {
final addressParts = [
address.state,
address.county,
address.district,
address.city,
address.suburb,
address.neighbourhood,
address.road,
].where((part) => part != null && part.isNotEmpty).join(', ');
addressDetailsValue.value = addressParts;
} else {
addressDetailsValue.value = 'Address not found';
}
}
}