mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:37:11 -04:00 
			
		
		
		
	* fix: remove unnecessary db operations in map * feat: use user's location for map thumbnails * chore: refactored handleMapEvents * fix: location fails fetching & update geolocator * chore: minor refactor * chore: small style tweak --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
		
			
				
	
	
		
			183 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:math';
 | |
| 
 | |
| import 'package:auto_route/auto_route.dart';
 | |
| import 'package:easy_localization/easy_localization.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:flutter_hooks/flutter_hooks.dart';
 | |
| import 'package:hooks_riverpod/hooks_riverpod.dart';
 | |
| import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
 | |
| import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | |
| import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart';
 | |
| import 'package:immich_mobile/widgets/map/map_theme_override.dart';
 | |
| import 'package:maplibre_gl/maplibre_gl.dart';
 | |
| import 'package:immich_mobile/utils/map_utils.dart';
 | |
| 
 | |
| @RoutePage()
 | |
| class MapLocationPickerPage extends HookConsumerWidget {
 | |
|   final LatLng initialLatLng;
 | |
| 
 | |
|   const MapLocationPickerPage({
 | |
|     super.key,
 | |
|     this.initialLatLng = const LatLng(0, 0),
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context, WidgetRef ref) {
 | |
|     final selectedLatLng = useValueNotifier<LatLng>(initialLatLng);
 | |
|     final controller = useRef<MapLibreMapController?>(null);
 | |
|     final marker = useRef<Symbol?>(null);
 | |
| 
 | |
|     Future<void> onStyleLoaded() async {
 | |
|       marker.value = await controller.value?.addMarkerAtLatLng(initialLatLng);
 | |
|     }
 | |
| 
 | |
|     Future<void> onMapClick(Point<num> point, LatLng centre) async {
 | |
|       selectedLatLng.value = centre;
 | |
|       controller.value?.animateCamera(CameraUpdate.newLatLng(centre));
 | |
|       if (marker.value != null) {
 | |
|         await controller.value
 | |
|             ?.updateSymbol(marker.value!, SymbolOptions(geometry: centre));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     void onClose([LatLng? selected]) {
 | |
|       context.maybePop(selected);
 | |
|     }
 | |
| 
 | |
|     Future<void> getCurrentLocation() async {
 | |
|       var (currentLocation, _) =
 | |
|           await MapUtils.checkPermAndGetLocation(context: context);
 | |
| 
 | |
|       if (currentLocation == null) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       var currentLatLng =
 | |
|           LatLng(currentLocation.latitude, currentLocation.longitude);
 | |
|       selectedLatLng.value = currentLatLng;
 | |
|       controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng));
 | |
|     }
 | |
| 
 | |
|     return MapThemeOverride(
 | |
|       mapBuilder: (style) => Builder(
 | |
|         builder: (ctx) => Scaffold(
 | |
|           backgroundColor: ctx.themeData.cardColor,
 | |
|           appBar: _AppBar(onClose: onClose),
 | |
|           extendBodyBehindAppBar: true,
 | |
|           primary: true,
 | |
|           body: style.widgetWhen(
 | |
|             onData: (style) => Container(
 | |
|               clipBehavior: Clip.antiAliasWithSaveLayer,
 | |
|               decoration: const BoxDecoration(
 | |
|                 borderRadius: BorderRadius.only(
 | |
|                   bottomLeft: Radius.circular(40),
 | |
|                   bottomRight: Radius.circular(40),
 | |
|                 ),
 | |
|               ),
 | |
|               child: MapLibreMap(
 | |
|                 initialCameraPosition:
 | |
|                     CameraPosition(target: initialLatLng, zoom: 12),
 | |
|                 styleString: style,
 | |
|                 onMapCreated: (mapController) =>
 | |
|                     controller.value = mapController,
 | |
|                 onStyleLoadedCallback: onStyleLoaded,
 | |
|                 onMapClick: onMapClick,
 | |
|                 dragEnabled: false,
 | |
|                 tiltGesturesEnabled: false,
 | |
|                 myLocationEnabled: false,
 | |
|                 attributionButtonMargins: const Point(20, 15),
 | |
|               ),
 | |
|             ),
 | |
|           ),
 | |
|           bottomNavigationBar: _BottomBar(
 | |
|             selectedLatLng: selectedLatLng,
 | |
|             onUseLocation: () => onClose(selectedLatLng.value),
 | |
|             onGetCurrentLocation: getCurrentLocation,
 | |
|           ),
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| class _AppBar extends StatelessWidget implements PreferredSizeWidget {
 | |
|   final Function() onClose;
 | |
| 
 | |
|   const _AppBar({required this.onClose});
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return Padding(
 | |
|       padding: const EdgeInsets.only(top: 25),
 | |
|       child: Align(
 | |
|         alignment: Alignment.centerLeft,
 | |
|         child: ElevatedButton(
 | |
|           onPressed: onClose,
 | |
|           style: ElevatedButton.styleFrom(
 | |
|             shape: const CircleBorder(),
 | |
|           ),
 | |
|           child: const Icon(Icons.arrow_back_ios_new_rounded),
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Size get preferredSize => const Size.fromHeight(100);
 | |
| }
 | |
| 
 | |
| class _BottomBar extends StatelessWidget {
 | |
|   final ValueNotifier<LatLng> selectedLatLng;
 | |
|   final Function() onUseLocation;
 | |
|   final Function() onGetCurrentLocation;
 | |
| 
 | |
|   const _BottomBar({
 | |
|     required this.selectedLatLng,
 | |
|     required this.onUseLocation,
 | |
|     required this.onGetCurrentLocation,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return SizedBox(
 | |
|       height: 150 + context.padding.bottom,
 | |
|       child: Padding(
 | |
|         padding: EdgeInsets.only(bottom: context.padding.bottom),
 | |
|         child: Column(
 | |
|           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | |
|           crossAxisAlignment: CrossAxisAlignment.start,
 | |
|           children: [
 | |
|             Row(
 | |
|               mainAxisAlignment: MainAxisAlignment.center,
 | |
|               children: [
 | |
|                 const Icon(Icons.public, size: 18),
 | |
|                 const SizedBox(width: 15),
 | |
|                 ValueListenableBuilder(
 | |
|                   valueListenable: selectedLatLng,
 | |
|                   builder: (_, value, __) => Text(
 | |
|                     "${value.latitude.toStringAsFixed(4)}, ${value.longitude.toStringAsFixed(4)}",
 | |
|                   ),
 | |
|                 ),
 | |
|               ],
 | |
|             ),
 | |
|             Row(
 | |
|               mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | |
|               children: [
 | |
|                 ElevatedButton(
 | |
|                   onPressed: onUseLocation,
 | |
|                   child:
 | |
|                       const Text("map_location_picker_page_use_location").tr(),
 | |
|                 ),
 | |
|                 ElevatedButton(
 | |
|                   onPressed: onGetCurrentLocation,
 | |
|                   child: const Icon(Icons.my_location),
 | |
|                 ),
 | |
|               ],
 | |
|             ),
 | |
|           ],
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 |