save geolocation

This commit is contained in:
Vadim 2025-07-10 15:18:47 +02:00
parent 1412ce2b3e
commit 39501ee4ac
16 changed files with 163 additions and 14 deletions

View file

@ -47,6 +47,9 @@
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" /> <uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" /> <uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" />
<uses-permission android:name="android.permission.MICROPHONE" tools:node="remove" /> <uses-permission android:name="android.permission.MICROPHONE" tools:node="remove" />
<uses-feature android:name="android.hardware.microphone" android:required="false" /> <uses-feature android:name="android.hardware.microphone" android:required="false" />

View file

@ -53,5 +53,9 @@
<false/> <false/>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Provide camera permissions in order to make measurements</string> <string>Provide camera permissions in order to make measurements</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Provide location permissions to save coordinates with your photos</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Provide location permissions to save coordinates with your photos</string>
</dict> </dict>
</plist> </plist>

View file

@ -6,6 +6,7 @@ import 'package:lightmeter/data/analytics/analytics.dart';
import 'package:lightmeter/data/analytics/api/analytics_firebase.dart'; import 'package:lightmeter/data/analytics/api/analytics_firebase.dart';
import 'package:lightmeter/data/caffeine_service.dart'; import 'package:lightmeter/data/caffeine_service.dart';
import 'package:lightmeter/data/camera_info_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/haptics_service.dart';
import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart';
import 'package:lightmeter/data/permissions_service.dart'; import 'package:lightmeter/data/permissions_service.dart';
@ -71,6 +72,7 @@ class _ApplicationWrapperState extends State<ApplicationWrapper> {
analytics: const LightmeterAnalytics(api: LightmeterAnalyticsFirebase()), analytics: const LightmeterAnalytics(api: LightmeterAnalyticsFirebase()),
caffeineService: const CaffeineService(), caffeineService: const CaffeineService(),
environment: widget.env.copyWith(hasLightSensor: hasLightSensor), environment: widget.env.copyWith(hasLightSensor: hasLightSensor),
geolocationService: const GeolocationService(),
hapticsService: const HapticsService(), hapticsService: const HapticsService(),
lightSensorService: const LightSensorService(LocalPlatform()), lightSensorService: const LightSensorService(LocalPlatform()),
permissionsService: const PermissionsService(), permissionsService: const PermissionsService(),

View file

@ -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<Coordinates?> 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<bool> isLocationServiceEnabled() async {
return await Geolocator.isLocationServiceEnabled();
}
/// Checks current location permission status
Future<LocationPermission> checkPermission() async {
return await Geolocator.checkPermission();
}
/// Requests location permission
Future<LocationPermission> requestPermission() async {
return await Geolocator.requestPermission();
}
}

View file

@ -171,5 +171,7 @@
"ndFilter": "ND Filter", "ndFilter": "ND Filter",
"film": "Film", "film": "Film",
"note": "Notiz", "note": "Notiz",
"notSet": "Nicht gesetzt" "notSet": "Nicht gesetzt",
"location": "Standort",
"noMapsAppFound": "Keine Kartenanwendung gefunden."
} }

View file

@ -171,5 +171,7 @@
"ndFilter": "ND Filter", "ndFilter": "ND Filter",
"film": "Film", "film": "Film",
"note": "Note", "note": "Note",
"notSet": "Not set" "notSet": "Not set",
"location": "Location",
"noMapsAppFound": "No maps application found."
} }

View file

@ -162,5 +162,7 @@
"ndFilter": "Filtre ND", "ndFilter": "Filtre ND",
"film": "Film", "film": "Film",
"note": "Note", "note": "Note",
"notSet": "Non défini" "notSet": "Non défini",
"location": "Emplacement",
"noMapsAppFound": "Aucune application de cartes trouvée."
} }

View file

@ -161,5 +161,7 @@
"ndFilter": "ND фильтр", "ndFilter": "ND фильтр",
"film": "Плёнка", "film": "Плёнка",
"note": "Заметка", "note": "Заметка",
"notSet": "Не задано" "notSet": "Не задано",
"location": "Местоположение",
"noMapsAppFound": "Приложение карт не найдено."
} }

View file

@ -159,5 +159,7 @@
"ndFilter": "ND 滤镜", "ndFilter": "ND 滤镜",
"film": "胶片", "film": "胶片",
"note": "备注", "note": "备注",
"notSet": "未设置" "notSet": "未设置",
"location": "位置",
"noMapsAppFound": "未找到地图应用程序。"
} }

View file

@ -2,7 +2,7 @@ import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.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:lightmeter/utils/context_utils.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@ -63,6 +63,10 @@ class LogbookPhotosProviderState extends State<LogbookPhotosProvider> {
required int nd, required int nd,
}) async { }) async {
if (context.isPro) { if (context.isPro) {
// Get coordinates from geolocation service
final geolocationService = ServicesProvider.of(context).geolocationService;
final coordinates = await geolocationService.getCurrentPosition();
final photo = LogbookPhoto( final photo = LogbookPhoto(
id: const UuidV8().generate(), id: const UuidV8().generate(),
name: path, name: path,
@ -70,9 +74,9 @@ class LogbookPhotosProviderState extends State<LogbookPhotosProvider> {
ev: ev100, ev: ev100,
iso: iso, iso: iso,
nd: nd, nd: nd,
coordinates: null, // TODO coordinates: coordinates,
); );
//await widget.storageService.addPhoto(photo); await widget.storageService.addPhoto(photo);
_photos[photo.id] = photo; _photos[photo.id] = photo;
setState(() {}); setState(() {});
} else { } else {

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lightmeter/data/analytics/analytics.dart'; import 'package:lightmeter/data/analytics/analytics.dart';
import 'package:lightmeter/data/caffeine_service.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/haptics_service.dart';
import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart';
import 'package:lightmeter/data/permissions_service.dart'; import 'package:lightmeter/data/permissions_service.dart';
@ -13,6 +14,7 @@ class ServicesProvider extends InheritedWidget {
final LightmeterAnalytics analytics; final LightmeterAnalytics analytics;
final CaffeineService caffeineService; final CaffeineService caffeineService;
final Environment environment; final Environment environment;
final GeolocationService geolocationService;
final HapticsService hapticsService; final HapticsService hapticsService;
final LightSensorService lightSensorService; final LightSensorService lightSensorService;
final PermissionsService permissionsService; final PermissionsService permissionsService;
@ -23,6 +25,7 @@ class ServicesProvider extends InheritedWidget {
required this.analytics, required this.analytics,
required this.caffeineService, required this.caffeineService,
required this.environment, required this.environment,
required this.geolocationService,
required this.hapticsService, required this.hapticsService,
required this.lightSensorService, required this.lightSensorService,
required this.permissionsService, required this.permissionsService,

View file

@ -1,14 +1,10 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/navigation/routes.dart'; import 'package:lightmeter/navigation/routes.dart';
import 'package:lightmeter/platform_config.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/providers/logbook_photos_provider.dart';
import 'package:lightmeter/res/dimens.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:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';

View file

@ -23,8 +23,6 @@ class LogbookPhotoEditBloc extends Bloc<LogbookPhotoEditEvent, LogbookPhotoEditS
iso: photo.iso, iso: photo.iso,
nd: photo.nd, nd: photo.nd,
coordinates: photo.coordinates, coordinates: photo.coordinates,
aperture: null,
shutterSpeed: null,
note: photo.note, note: photo.note,
canSave: false, canSave: false,
), ),

View file

@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/logbook_photo_edit/bloc_logbook_photo_edit.dart';
import 'package:lightmeter/screens/logbook_photo_edit/state_logbook_photo_edit.dart';
import 'package:map_launcher/map_launcher.dart';
import 'package:url_launcher/url_launcher.dart';
class LogbookPhotoCoordinatesListTile extends StatefulWidget {
const LogbookPhotoCoordinatesListTile();
@override
State<LogbookPhotoCoordinatesListTile> createState() => LogbookPhotoCoordinatesListTileState();
}
class LogbookPhotoCoordinatesListTileState extends State<LogbookPhotoCoordinatesListTile> {
@override
Widget build(BuildContext context) {
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
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<void> _showSnackBar() async {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(S.of(context).youDontHaveMailApp),
behavior: SnackBarBehavior.floating,
),
);
}
}

View file

@ -6,6 +6,7 @@ import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/platform_config.dart'; import 'package:lightmeter/platform_config.dart';
import 'package:lightmeter/res/dimens.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/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/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/event_logbook_photo_edit.dart';
import 'package:lightmeter/screens/logbook_photo_edit/state_logbook_photo_edit.dart'; import 'package:lightmeter/screens/logbook_photo_edit/state_logbook_photo_edit.dart';
@ -77,6 +78,7 @@ class _LogbookPhotoEditScreenState extends State<LogbookPhotoEditScreen> {
child: const Column( child: const Column(
children: [ children: [
_DateListTile(), _DateListTile(),
LogbookPhotoCoordinatesListTile(),
_NoteListTile(), _NoteListTile(),
_EvListTile(), _EvListTile(),
_IsoListTile(), _IsoListTile(),

View file

@ -26,6 +26,7 @@ dependencies:
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
flutter_native_splash: 2.4.4 flutter_native_splash: 2.4.4
geolocator: 13.0.1
intl: 0.19.0 intl: 0.19.0
intl_utils: 2.8.7 intl_utils: 2.8.7
light_sensor: 3.0.1 light_sensor: 3.0.1
@ -46,6 +47,7 @@ dependencies:
url_launcher_ios: 6.3.2 url_launcher_ios: 6.3.2
uuid: 4.5.1 uuid: 4.5.1
vibration: 2.0.1 vibration: 2.0.1
map_launcher: 3.2.0
dev_dependencies: dev_dependencies:
args: 2.6.0 args: 2.6.0
@ -65,6 +67,11 @@ dev_dependencies:
test: 1.25.7 test: 1.25.7
dependency_overrides: 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 material_color_utilities: 0.11.1
flutter: flutter: