diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9b498cc..5d3d43c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -47,6 +47,9 @@ + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 290af5b..b2cf09d 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -53,5 +53,9 @@ NSCameraUsageDescription Provide camera permissions in order to make measurements + NSLocationWhenInUseUsageDescription + Provide location permissions to save coordinates with your photos + NSLocationAlwaysAndWhenInUseUsageDescription + Provide location permissions to save coordinates with your photos diff --git a/lib/application_wrapper.dart b/lib/application_wrapper.dart index 62fbe83..c7e0176 100644 --- a/lib/application_wrapper.dart +++ b/lib/application_wrapper.dart @@ -6,6 +6,7 @@ import 'package:lightmeter/data/analytics/analytics.dart'; import 'package:lightmeter/data/analytics/api/analytics_firebase.dart'; import 'package:lightmeter/data/caffeine_service.dart'; import 'package:lightmeter/data/camera_info_service.dart'; +import 'package:lightmeter/data/geolocation_service.dart'; import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/permissions_service.dart'; @@ -71,6 +72,7 @@ class _ApplicationWrapperState extends State { analytics: const LightmeterAnalytics(api: LightmeterAnalyticsFirebase()), caffeineService: const CaffeineService(), environment: widget.env.copyWith(hasLightSensor: hasLightSensor), + geolocationService: const GeolocationService(), hapticsService: const HapticsService(), lightSensorService: const LightSensorService(LocalPlatform()), permissionsService: const PermissionsService(), diff --git a/lib/data/geolocation_service.dart b/lib/data/geolocation_service.dart new file mode 100644 index 0000000..0659914 --- /dev/null +++ b/lib/data/geolocation_service.dart @@ -0,0 +1,55 @@ +import 'package:geolocator/geolocator.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; + +class GeolocationService { + const GeolocationService(); + + /// Gets the current position and returns Coordinates if successful + /// Returns null if location services are disabled or permission is denied + Future getCurrentPosition() async { + try { + // Check if location services are enabled + final isLocationServiceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!isLocationServiceEnabled) { + return null; + } + + // Check location permission + final permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + final requestedPermission = await Geolocator.requestPermission(); + if (requestedPermission == LocationPermission.denied || + requestedPermission == LocationPermission.deniedForever) { + return null; + } + } + + // Get current position + final position = await Geolocator.getCurrentPosition( + locationSettings: const LocationSettings( + timeLimit: Duration(seconds: 10), + ), + ); + + return Coordinates(position.latitude, position.longitude); + } catch (e) { + // Return null if any error occurs (timeout, no GPS signal, etc.) + return null; + } + } + + /// Checks if location services are enabled + Future isLocationServiceEnabled() async { + return await Geolocator.isLocationServiceEnabled(); + } + + /// Checks current location permission status + Future checkPermission() async { + return await Geolocator.checkPermission(); + } + + /// Requests location permission + Future requestPermission() async { + return await Geolocator.requestPermission(); + } +} diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 87ad0c3..5812d14 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -171,5 +171,7 @@ "ndFilter": "ND Filter", "film": "Film", "note": "Notiz", - "notSet": "Nicht gesetzt" + "notSet": "Nicht gesetzt", + "location": "Standort", + "noMapsAppFound": "Keine Kartenanwendung gefunden." } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 66f3ca9..8e0e718 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -171,5 +171,7 @@ "ndFilter": "ND Filter", "film": "Film", "note": "Note", - "notSet": "Not set" + "notSet": "Not set", + "location": "Location", + "noMapsAppFound": "No maps application found." } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 115b11a..9d50c60 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -162,5 +162,7 @@ "ndFilter": "Filtre ND", "film": "Film", "note": "Note", - "notSet": "Non défini" + "notSet": "Non défini", + "location": "Emplacement", + "noMapsAppFound": "Aucune application de cartes trouvée." } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 2aca4df..06d0e34 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -161,5 +161,7 @@ "ndFilter": "ND фильтр", "film": "Плёнка", "note": "Заметка", - "notSet": "Не задано" + "notSet": "Не задано", + "location": "Местоположение", + "noMapsAppFound": "Приложение карт не найдено." } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index b9d5531..c35e01e 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -159,5 +159,7 @@ "ndFilter": "ND 滤镜", "film": "胶片", "note": "备注", - "notSet": "未设置" + "notSet": "未设置", + "location": "位置", + "noMapsAppFound": "未找到地图应用程序。" } \ No newline at end of file diff --git a/lib/providers/logbook_photos_provider.dart b/lib/providers/logbook_photos_provider.dart index d91b638..0e396ce 100644 --- a/lib/providers/logbook_photos_provider.dart +++ b/lib/providers/logbook_photos_provider.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:lightmeter/providers/films_provider.dart'; +import 'package:lightmeter/providers/services_provider.dart'; import 'package:lightmeter/utils/context_utils.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; @@ -63,6 +63,10 @@ class LogbookPhotosProviderState extends State { required int nd, }) async { if (context.isPro) { + // Get coordinates from geolocation service + final geolocationService = ServicesProvider.of(context).geolocationService; + final coordinates = await geolocationService.getCurrentPosition(); + final photo = LogbookPhoto( id: const UuidV8().generate(), name: path, @@ -70,9 +74,9 @@ class LogbookPhotosProviderState extends State { ev: ev100, iso: iso, nd: nd, - coordinates: null, // TODO + coordinates: coordinates, ); - //await widget.storageService.addPhoto(photo); + await widget.storageService.addPhoto(photo); _photos[photo.id] = photo; setState(() {}); } else { diff --git a/lib/providers/services_provider.dart b/lib/providers/services_provider.dart index 1db019e..820c339 100644 --- a/lib/providers/services_provider.dart +++ b/lib/providers/services_provider.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/analytics/analytics.dart'; import 'package:lightmeter/data/caffeine_service.dart'; +import 'package:lightmeter/data/geolocation_service.dart'; import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/permissions_service.dart'; @@ -13,6 +14,7 @@ class ServicesProvider extends InheritedWidget { final LightmeterAnalytics analytics; final CaffeineService caffeineService; final Environment environment; + final GeolocationService geolocationService; final HapticsService hapticsService; final LightSensorService lightSensorService; final PermissionsService permissionsService; @@ -23,6 +25,7 @@ class ServicesProvider extends InheritedWidget { required this.analytics, required this.caffeineService, required this.environment, + required this.geolocationService, required this.hapticsService, required this.lightSensorService, required this.permissionsService, diff --git a/lib/screens/logbook/screen_logbook.dart b/lib/screens/logbook/screen_logbook.dart index 34b4aa8..232b48f 100644 --- a/lib/screens/logbook/screen_logbook.dart +++ b/lib/screens/logbook/screen_logbook.dart @@ -1,14 +1,10 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/navigation/routes.dart'; import 'package:lightmeter/platform_config.dart'; -import 'package:lightmeter/providers/equipment_profile_provider.dart'; import 'package:lightmeter/providers/logbook_photos_provider.dart'; import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/screens/equipment_profile_edit/flow_equipment_profile_edit.dart'; -import 'package:lightmeter/screens/shared/sliver_placeholder/widget_sliver_placeholder.dart'; import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; diff --git a/lib/screens/logbook_photo_edit/bloc_logbook_photo_edit.dart b/lib/screens/logbook_photo_edit/bloc_logbook_photo_edit.dart index 2a794d9..5993b4a 100644 --- a/lib/screens/logbook_photo_edit/bloc_logbook_photo_edit.dart +++ b/lib/screens/logbook_photo_edit/bloc_logbook_photo_edit.dart @@ -23,8 +23,6 @@ class LogbookPhotoEditBloc extends Bloc createState() => LogbookPhotoCoordinatesListTileState(); +} + +class LogbookPhotoCoordinatesListTileState extends State { + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.note != current.note, + builder: (context, state) { + final coords = state.coordinates; + final hasCoords = coords != null; + final text = hasCoords ? '${coords.latitude.toStringAsFixed(6)}, ${coords.longitude.toStringAsFixed(6)}' : '-'; + return ListTile( + leading: const Icon(Icons.location_on_outlined), + title: Text(S.of(context).location), + trailing: Text(text), + onTap: hasCoords + ? () async { + final lat = coords.latitude; + final lng = coords.longitude; + final availableMaps = await MapLauncher.installedMaps; + if (availableMaps.isEmpty) { + // Fallback to Google Maps in browser + final url = Uri.parse('https://www.google.com/maps/search/?api=1&query=$lat,$lng'); + if (await canLaunchUrl(url)) { + await launchUrl(url, mode: LaunchMode.externalApplication); + } else if (mounted) { + _showSnackBar(); + } + return; + } + await MapLauncher.showMarker( + mapType: availableMaps.first.mapType, + coords: Coords(lat, lng), + title: text, + description: state.note, + ); + } + : null, + ); + }, + ); + } + + Future _showSnackBar() async { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(S.of(context).youDontHaveMailApp), + behavior: SnackBarBehavior.floating, + ), + ); + } +} diff --git a/lib/screens/logbook_photo_edit/screen_logbook_photo_edit.dart b/lib/screens/logbook_photo_edit/screen_logbook_photo_edit.dart index 51e95c7..0431520 100644 --- a/lib/screens/logbook_photo_edit/screen_logbook_photo_edit.dart +++ b/lib/screens/logbook_photo_edit/screen_logbook_photo_edit.dart @@ -6,6 +6,7 @@ import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/platform_config.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/logbook_photo_edit/bloc_logbook_photo_edit.dart'; +import 'package:lightmeter/screens/logbook_photo_edit/components/coordinates_list_tile/widget_list_tile_coordinates_logbook_photo.dart'; import 'package:lightmeter/screens/logbook_photo_edit/components/picker_list_tile/widget_list_tile_picker.dart'; import 'package:lightmeter/screens/logbook_photo_edit/event_logbook_photo_edit.dart'; import 'package:lightmeter/screens/logbook_photo_edit/state_logbook_photo_edit.dart'; @@ -77,6 +78,7 @@ class _LogbookPhotoEditScreenState extends State { child: const Column( children: [ _DateListTile(), + LogbookPhotoCoordinatesListTile(), _NoteListTile(), _EvListTile(), _IsoListTile(), diff --git a/pubspec.yaml b/pubspec.yaml index 395d9f3..8b6f49b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: flutter_localizations: sdk: flutter flutter_native_splash: 2.4.4 + geolocator: 13.0.1 intl: 0.19.0 intl_utils: 2.8.7 light_sensor: 3.0.1 @@ -46,6 +47,7 @@ dependencies: url_launcher_ios: 6.3.2 uuid: 4.5.1 vibration: 2.0.1 + map_launcher: 3.2.0 dev_dependencies: args: 2.6.0 @@ -65,6 +67,11 @@ dev_dependencies: test: 1.25.7 dependency_overrides: + geolocator_android: 4.6.1 + m3_lightmeter_iap: + path: /Users/vodemn/Documents/GitHub/Vodemn/m3_lightmeter_iap + m3_lightmeter_resources: + path: /Users/vodemn/Documents/GitHub/Vodemn/m3_lightmeter_resources material_color_utilities: 0.11.1 flutter: