From 693d8cbfabb09a1e9bebf5c4a706302b318d6208 Mon Sep 17 00:00:00 2001 From: "mr.mojtaba" Date: Sun, 3 Aug 2025 14:17:08 +0330 Subject: [PATCH] feat : Map Widget --- .../widget/map/custom_marker.dart | 10 - .../lib/presentation/widget/map/view.dart | 207 ------------------ .../lib/injection/live_stock_di.dart | 44 ++-- .../lib/presentation/page/auth/logic.dart | 4 +- .../lib/presentation/page/map/view.dart | 22 +- .../page/map/widget/map_widget}/logic.dart | 48 ++-- .../page/map/widget/map_widget/view.dart | 179 +++++++++++++++ .../lib/presentation/routes/app_pages.dart | 3 +- .../presentation/widgets/captcha/logic.dart | 4 +- 9 files changed, 232 insertions(+), 289 deletions(-) delete mode 100644 packages/core/lib/presentation/widget/map/custom_marker.dart delete mode 100644 packages/core/lib/presentation/widget/map/view.dart rename packages/{core/lib/presentation/widget/map => livestock/lib/presentation/page/map/widget/map_widget}/logic.dart (81%) create mode 100644 packages/livestock/lib/presentation/page/map/widget/map_widget/view.dart diff --git a/packages/core/lib/presentation/widget/map/custom_marker.dart b/packages/core/lib/presentation/widget/map/custom_marker.dart deleted file mode 100644 index d1e5cae..0000000 --- a/packages/core/lib/presentation/widget/map/custom_marker.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:latlong2/latlong.dart'; - -class CustomMarker { - final LatLng point; - final VoidCallback? onTap; -final int? id; - - CustomMarker({ this.id, required this.point, this.onTap}); -} \ No newline at end of file diff --git a/packages/core/lib/presentation/widget/map/view.dart b/packages/core/lib/presentation/widget/map/view.dart deleted file mode 100644 index 4204919..0000000 --- a/packages/core/lib/presentation/widget/map/view.dart +++ /dev/null @@ -1,207 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:geolocator/geolocator.dart'; -import 'package:get/get.dart'; -import 'package:rasadyar_core/presentation/common/app_color.dart'; -import 'package:rasadyar_core/presentation/common/app_fonts.dart'; -import 'package:rasadyar_core/presentation/common/assets.gen.dart'; -import 'package:rasadyar_core/presentation/widget/buttons/elevated.dart'; -import 'package:rasadyar_core/presentation/widget/buttons/fab.dart'; -import 'package:rasadyar_core/presentation/widget/buttons/outline_elevated.dart'; - -import 'logic.dart'; - -class MapWidget extends GetView { - final VoidCallback? initOnTap; - final Widget? initMarkerWidget; - final Widget markerWidget; - - const MapWidget({ - this.initOnTap, - this.initMarkerWidget, - required this.markerWidget, - super.key, - }); - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - ObxValue((errorType) { - if (errorType.isNotEmpty) { - if (errorType.contains(ErrorLocationType.serviceDisabled)) { - Future.microtask(() { - Get.defaultDialog( - title: 'خطا', - content: const Text('سرویس مکان‌یابی غیرفعال است'), - cancel: ROutlinedElevated( - text: 'بررسی مجدد', - width: 120, - textStyle: AppFonts.yekan16, - onPressed: () async { - var service = await controller.locationServiceEnabled(); - if (service) { - controller.errorLocationType.remove( - ErrorLocationType.serviceDisabled, - ); - Get.back(); - } - // Don't call Get.back() if service is still disabled - }, - ), - confirm: RElevated( - text: 'روشن کردن', - textStyle: AppFonts.yekan16, - width: 120, - onPressed: () async { - var res = await Geolocator.openLocationSettings(); - if (res) { - var service = await controller.locationServiceEnabled(); - if (service) { - controller.errorLocationType.remove( - ErrorLocationType.serviceDisabled, - ); - Get.back(); - } - } - }, - ), - contentPadding: EdgeInsets.all(8), - onWillPop: () async { - return controller.errorLocationType.isEmpty; - }, - barrierDismissible: false, - ); - }); - } else { - Future.microtask(() { - Get.defaultDialog( - title: 'خطا', - content: const Text(' دسترسی به سرویس مکان‌یابی غیرفعال است'), - cancel: ROutlinedElevated( - text: 'بررسی مجدد', - width: 120, - textStyle: AppFonts.yekan16, - onPressed: () async { - await controller.checkPermission(); - }, - ), - confirm: RElevated( - text: 'اجازه دادن', - textStyle: AppFonts.yekan16, - width: 120, - onPressed: () async { - var res = await controller.checkPermission(request: true); - if (res) { - controller.errorLocationType.remove( - ErrorLocationType.permissionDenied, - ); - Get.back(); - } - }, - ), - - contentPadding: EdgeInsets.all(8), - onWillPop: () async { - return controller.errorLocationType.isEmpty; - }, - barrierDismissible: false, - ); - }); - } - } - return const SizedBox.shrink(); - }, controller.errorLocationType), - _buildMap(), - _buildGpsButton(), - _buildFilterButton(), - ], - ); - } - - Widget _buildMap() { - return ObxValue((currentLocation) { - return FlutterMap( - mapController: controller.animatedMapController.mapController, - options: MapOptions( - initialCenter: currentLocation.value, - initialZoom: 18, - onPositionChanged: (camera, hasGesture) { - if (hasGesture) { - controller.debouncedUpdateVisibleMarkers(center: camera.center); - } - //controller.debouncedUpdateVisibleMarkers(center: camera.center); - }, - ), - children: [ - TileLayer(urlTemplate: controller.tileType), - ObxValue((markers) { - return MarkerLayer( - markers: - markers - .map( - (e) => Marker( - point: e.point, - child: GestureDetector( - onTap: e.id != -1 ? e.onTap : initOnTap, - child: - e.id != -1 - ? markerWidget - : initMarkerWidget ?? SizedBox.shrink(), - ), - ), - ) - .toList(), - ); - }, controller.markers), - ], - ); - }, controller.currentLocation); - } - - Widget _buildGpsButton() { - return Positioned( - right: 10, - bottom: 83, - child: ObxValue((data) { - return RFab.small( - backgroundColor: AppColor.greenNormal, - isLoading: data.value, - icon: Assets.vec.gpsSvg.svg(), - onPressed: () async { - controller.isLoading.value = true; - await controller.determineCurrentPosition(); - controller.isLoading.value = false; - }, - ); - }, controller.isLoading), - ); - } - - Widget _buildFilterButton() { - return Positioned( - right: 10, - bottom: 30, - child: RFab.small( - backgroundColor: AppColor.blueNormal, - icon: Assets.vec.filterSvg.svg(width: 24, height: 24), - onPressed: () {}, - ), - ); - } - - /*Marker markerWidget({required LatLng marker, required VoidCallback onTap}) { - return Marker( - point: marker, - child: GestureDetector( - onTap: onTap, - behavior: HitTestBehavior.opaque, - child: SizedBox( - width: 36, - height: 36, - child: Assets.vec.mapMarkerSvg.svg(width: 30, height: 30), - ), - ), - ); - }*/ -} diff --git a/packages/livestock/lib/injection/live_stock_di.dart b/packages/livestock/lib/injection/live_stock_di.dart index 58f1585..10dd71e 100644 --- a/packages/livestock/lib/injection/live_stock_di.dart +++ b/packages/livestock/lib/injection/live_stock_di.dart @@ -2,24 +2,40 @@ 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/repository/auth/auth_repository.dart'; import 'package:rasadyar_livestock/data/repository/auth/auth_repository_imp.dart'; import 'package:rasadyar_livestock/presentation/routes/app_pages.dart'; GetIt get diLiveStock => GetIt.instance; -Future setupLiveStockDI() async { +Future setupLiveStockDI() async { diLiveStock.registerSingleton(DioErrorHandler()); - var tokenService = Get.find(); + + + final tokenService = Get.find(); + + if (tokenService.baseurl.value == null) { await tokenService.saveBaseUrl('https://api.dam.rasadyar.net/'); } + + + diLiveStock.registerLazySingleton( + () => AuthRemoteDataSourceImp(diLiveStock.get()), + ); + + diLiveStock.registerLazySingleton( + () => AuthRepositoryImp(diLiveStock.get()), + ); + + diLiveStock.registerLazySingleton( - () => AppInterceptor( + () => AppInterceptor( refreshTokenCallback: () async { - var authRepository = diLiveStock.get(); - var hasAuthenticated = await authRepository.hasAuthenticated(); + final authRepository = diLiveStock.get(); + final hasAuthenticated = await authRepository.hasAuthenticated(); if (hasAuthenticated) { - var newToken = await authRepository.loginWithRefreshToken( + final newToken = await authRepository.loginWithRefreshToken( authRequest: {'refresh': tokenService.refreshToken.value}, ); return newToken?.access; @@ -37,23 +53,13 @@ Future setupLiveStockDI() async { ), ); - // Register the DioRemote client + diLiveStock.registerLazySingleton( - () => DioRemote( + () => DioRemote( baseUrl: tokenService.baseurl.value, interceptors: diLiveStock.get(), ), ); - var dioRemoteClient = diLiveStock.get(); - await dioRemoteClient.init(); - // Register the AuthRemote data source implementation - diLiveStock.registerLazySingleton( - () => AuthRemoteDataSourceImp(diLiveStock.get()), - ); - - // Register the AuthRepository implementation - diLiveStock.registerLazySingleton( - () => AuthRepositoryImp(diLiveStock.get()), - ); + await diLiveStock.get().init(); } diff --git a/packages/livestock/lib/presentation/page/auth/logic.dart b/packages/livestock/lib/presentation/page/auth/logic.dart index 884edc3..2431423 100644 --- a/packages/livestock/lib/presentation/page/auth/logic.dart +++ b/packages/livestock/lib/presentation/page/auth/logic.dart @@ -5,7 +5,7 @@ import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_livestock/data/common/dio_exception_handeler.dart'; import 'package:rasadyar_livestock/data/model/request/login_request/login_request_model.dart'; import 'package:rasadyar_livestock/data/model/response/auth/auth_response_model.dart'; -import 'package:rasadyar_livestock/data/repository/auth/auth_repository_imp.dart'; +import 'package:rasadyar_livestock/data/repository/auth/auth_repository.dart' show AuthRepository; import 'package:rasadyar_livestock/injection/live_stock_di.dart'; import 'package:rasadyar_livestock/presentation/routes/app_pages.dart'; import 'package:rasadyar_livestock/presentation/widgets/captcha/logic.dart'; @@ -44,7 +44,7 @@ class AuthLogic extends GetxController with GetTickerProviderStateMixin { RxInt secondsRemaining = 120.obs; Timer? _timer; - AuthRepositoryImp authRepository = diLiveStock.get(); + AuthRepository authRepository = diLiveStock.get(); final Module _module = Get.arguments; diff --git a/packages/livestock/lib/presentation/page/map/view.dart b/packages/livestock/lib/presentation/page/map/view.dart index 6935217..451a95e 100644 --- a/packages/livestock/lib/presentation/page/map/view.dart +++ b/packages/livestock/lib/presentation/page/map/view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:rasadyar_core/core.dart'; -import 'package:rasadyar_core/presentation/widget/map/view.dart'; +import 'package:rasadyar_livestock/presentation/page/map/widget/map_widget/view.dart'; + import 'logic.dart'; class MapPage extends GetView { @@ -8,23 +9,6 @@ class MapPage extends GetView { @override Widget build(BuildContext context) { - return Scaffold( - body: Stack( - children: [ - MapWidget( - markerWidget: Icon(Icons.pin_drop_rounded), - initOnTap: () { - - }, - initMarkerWidget: Assets.vec.mapMarkerSvg.svg( - width: 30, - height: 30, - ), - ), - - ], - ), - ); + return Scaffold(body: Stack(children: [MapWidget()])); } } - diff --git a/packages/core/lib/presentation/widget/map/logic.dart b/packages/livestock/lib/presentation/page/map/widget/map_widget/logic.dart similarity index 81% rename from packages/core/lib/presentation/widget/map/logic.dart rename to packages/livestock/lib/presentation/page/map/widget/map_widget/logic.dart index 78ab225..53575a7 100644 --- a/packages/core/lib/presentation/widget/map/logic.dart +++ b/packages/livestock/lib/presentation/page/map/widget/map_widget/logic.dart @@ -1,23 +1,17 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_animations/flutter_map_animations.dart'; -import 'package:geolocator/geolocator.dart'; -import 'package:get/get.dart'; -import 'package:latlong2/latlong.dart'; -import 'package:rasadyar_core/utils/logger_utils.dart'; +import 'package:rasadyar_core/core.dart'; -import 'custom_marker.dart'; enum ErrorLocationType { serviceDisabled, permissionDenied, none } class MapWidgetLogic extends GetxController with GetTickerProviderStateMixin { Rx currentLocation = LatLng(35.824891, 50.948025).obs; String tileType = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; + RxDouble currentZoom = 15.0.obs; - RxList markers = [].obs; RxList allMarkers = [].obs; Rx mapController = MapController().obs; RxList errorLocationType = RxList(); @@ -25,6 +19,20 @@ class MapWidgetLogic extends GetxController with GetTickerProviderStateMixin { Timer? _debounceTimer; RxBool isLoading = false.obs; + RxList markerLocations = [ + LatLng(35.824891, 50.948025), + LatLng(35.825000, 50.949000), + LatLng(35.823000, 50.947000), + LatLng(35.826000, 50.950000), + LatLng(35.827000, 50.951000), + LatLng(35.828000, 50.952000), + LatLng(35.829000, 50.953000), + LatLng(35.830000, 50.954000), + LatLng(35.831000, 50.955000), + LatLng(35.832000, 50.956000), + LatLng(35.832000, 50.956055), + ].obs; + @override void onInit() { super.onInit(); @@ -89,8 +97,7 @@ class MapWidgetLogic extends GetxController with GetTickerProviderStateMixin { switch (permission) { case LocationPermission.denied: - final LocationPermission requestResult = - await Geolocator.requestPermission(); + final LocationPermission requestResult = await Geolocator.requestPermission(); return requestResult != LocationPermission.denied && requestResult != LocationPermission.deniedForever; @@ -117,9 +124,7 @@ class MapWidgetLogic extends GetxController with GetTickerProviderStateMixin { final latLng = LatLng(position.latitude, position.longitude); currentLocation.value = latLng; - markers.add( - CustomMarker(id: -1, point: latLng, ), - ); + animatedMapController.animateTo( dest: latLng, zoom: 18, @@ -138,7 +143,7 @@ class MapWidgetLogic extends GetxController with GetTickerProviderStateMixin { 'radius': 1000.0, }); - // markers.addAll(filtered); + // markers.addAll(filtered); }); } @@ -150,21 +155,8 @@ class MapWidgetLogic extends GetxController with GetTickerProviderStateMixin { final center = LatLng(centerLat, centerLng); final distance = Distance(); - return rawMarkers - .where((marker) => distance(center, marker) <= radiusInMeters) - .toList(); + return rawMarkers.where((marker) => distance(center, marker) <= radiusInMeters).toList(); } - void addMarker(CustomMarker marker) { - markers.add(marker); - } - - void setMarkers(List newMarkers) { - markers.value = newMarkers; - } - - void clearMarkers() { - markers.clear(); - } } diff --git a/packages/livestock/lib/presentation/page/map/widget/map_widget/view.dart b/packages/livestock/lib/presentation/page/map/widget/map_widget/view.dart new file mode 100644 index 0000000..bd2226b --- /dev/null +++ b/packages/livestock/lib/presentation/page/map/widget/map_widget/view.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class MapWidget extends GetView { + const MapWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Expanded( + child: Stack( + fit: StackFit.expand, + children: [ + ObxValue((errorType) { + if (errorType.isNotEmpty) { + if (errorType.contains(ErrorLocationType.serviceDisabled)) { + Future.microtask(() { + Get.defaultDialog( + title: 'خطا', + content: const Text('سرویس مکان‌یابی غیرفعال است'), + cancel: ROutlinedElevated( + text: 'بررسی مجدد', + width: 120, + textStyle: AppFonts.yekan16, + onPressed: () async { + var service = await controller.locationServiceEnabled(); + if (service) { + controller.errorLocationType.remove(ErrorLocationType.serviceDisabled); + Get.back(); + } + // Don't call Get.back() if service is still disabled + }, + ), + confirm: RElevated( + text: 'روشن کردن', + textStyle: AppFonts.yekan16, + width: 120, + onPressed: () async { + var res = await Geolocator.openLocationSettings(); + if (res) { + var service = await controller.locationServiceEnabled(); + if (service) { + controller.errorLocationType.remove(ErrorLocationType.serviceDisabled); + Get.back(); + } + } + }, + ), + contentPadding: EdgeInsets.all(8), + onWillPop: () async { + return controller.errorLocationType.isEmpty; + }, + barrierDismissible: false, + ); + }); + } else { + Future.microtask(() { + Get.defaultDialog( + title: 'خطا', + content: const Text(' دسترسی به سرویس مکان‌یابی غیرفعال است'), + cancel: ROutlinedElevated( + text: 'بررسی مجدد', + width: 120, + textStyle: AppFonts.yekan16, + onPressed: () async { + await controller.checkPermission(); + }, + ), + confirm: RElevated( + text: 'اجازه دادن', + textStyle: AppFonts.yekan16, + width: 120, + onPressed: () async { + var res = await controller.checkPermission(request: true); + if (res) { + controller.errorLocationType.remove(ErrorLocationType.permissionDenied); + Get.back(); + } + }, + ), + + contentPadding: EdgeInsets.all(8), + onWillPop: () async { + return controller.errorLocationType.isEmpty; + }, + barrierDismissible: false, + ); + }); + } + } + return const SizedBox.shrink(); + }, controller.errorLocationType), + + ObxValue((currentLocation) { + return FlutterMap( + mapController: controller.animatedMapController.mapController, + options: MapOptions( + initialCenter: currentLocation.value, + interactionOptions: const InteractionOptions( + flags: InteractiveFlag.all & ~InteractiveFlag.rotate, + ), + initialZoom: 15, + onPositionChanged: (camera, hasGesture) { + controller.currentZoom.value = camera.zoom; + /* controller.debouncedUpdateVisibleMarkers( + center: camera.center, + zoom: camera.zoom, + );*/ + }, + ), + + children: [ + TileLayer( + urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'ir.mnpc.rasadyar', + ), + + ObxValue((markers) { + return MarkerClusterLayerWidget( + options: MarkerClusterLayerOptions( + maxClusterRadius: 80, + size: const Size(40, 40), + alignment: Alignment.center, + padding: const EdgeInsets.all(50), + maxZoom: 18, + markers: buildMarkers(markers), + builder: (context, clusterMarkers) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.blue, + ), + child: Center( + child: Text( + clusterMarkers.length.toString(), + style: const TextStyle(color: Colors.white), + ), + ), + ); + }, + ), + ); + }, controller.markerLocations), + ], + ); + }, controller.currentLocation), + + // Uncomment the following lines to enable the search widget + /* Positioned( + top: 10, + left: 20, + right: 20, + child: ObxValue((data) { + if (data.value) { + return SearchWidget( + onSearchChanged: (data) { + controller.baseLogic.searchValue.value = data; + }, + ); + } else { + return SizedBox.shrink(); + } + }, controller.baseLogic.isSearchSelected), + ),*/ + ], + ), + ); + } + + List buildMarkers(RxList latLng) => latLng + .map( + (element) => Marker( + point: element, + child: FaIcon(FontAwesomeIcons.locationPin, color: AppColor.error), + ), + ) + .toList(); +} diff --git a/packages/livestock/lib/presentation/routes/app_pages.dart b/packages/livestock/lib/presentation/routes/app_pages.dart index 4ada648..fa24e46 100644 --- a/packages/livestock/lib/presentation/routes/app_pages.dart +++ b/packages/livestock/lib/presentation/routes/app_pages.dart @@ -1,8 +1,8 @@ import 'package:rasadyar_core/core.dart'; -import 'package:rasadyar_core/presentation/widget/map/logic.dart'; import 'package:rasadyar_livestock/presentation/page/auth/logic.dart'; import 'package:rasadyar_livestock/presentation/page/auth/view.dart'; import 'package:rasadyar_livestock/presentation/page/map/logic.dart'; +import 'package:rasadyar_livestock/presentation/page/map/widget/map_widget/logic.dart'; import 'package:rasadyar_livestock/presentation/page/profile/logic.dart'; import 'package:rasadyar_livestock/presentation/page/request_tagging/logic.dart'; import 'package:rasadyar_livestock/presentation/page/request_tagging/view.dart'; @@ -38,7 +38,6 @@ sealed class LiveStockPages { Get.lazyPut(() => ProfileLogic()); Get.lazyPut(() => ProfileLogic()); Get.lazyPut(() => MapWidgetLogic()); - Get.lazyPut(() => DraggableBottomSheetController()); }), children: [ /*GetPage( diff --git a/packages/livestock/lib/presentation/widgets/captcha/logic.dart b/packages/livestock/lib/presentation/widgets/captcha/logic.dart index fe8cfdc..b35df6d 100644 --- a/packages/livestock/lib/presentation/widgets/captcha/logic.dart +++ b/packages/livestock/lib/presentation/widgets/captcha/logic.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_livestock/data/model/response/captcha/captcha_response_model.dart'; -import 'package:rasadyar_livestock/data/repository/auth/auth_repository_imp.dart'; +import 'package:rasadyar_livestock/data/repository/auth/auth_repository.dart'; import 'package:rasadyar_livestock/injection/live_stock_di.dart'; class CaptchaWidgetLogic extends GetxController with StateMixin { TextEditingController textController = TextEditingController(); RxnString captchaKey = RxnString(); GlobalKey formKey = GlobalKey(); - AuthRepositoryImp authRepository = diLiveStock.get(); + AuthRepository authRepository = diLiveStock.get(); @override void onInit() {