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-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.MICROPHONE" tools:node="remove" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />

View file

@ -53,5 +53,9 @@
<false/>
<key>NSCameraUsageDescription</key>
<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>
</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/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<ApplicationWrapper> {
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(),

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",
"film": "Film",
"note": "Notiz",
"notSet": "Nicht gesetzt"
"notSet": "Nicht gesetzt",
"location": "Standort",
"noMapsAppFound": "Keine Kartenanwendung gefunden."
}

View file

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

View file

@ -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."
}

View file

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

View file

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

View file

@ -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<LogbookPhotosProvider> {
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<LogbookPhotosProvider> {
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 {

View file

@ -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,

View file

@ -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';

View file

@ -23,8 +23,6 @@ class LogbookPhotoEditBloc extends Bloc<LogbookPhotoEditEvent, LogbookPhotoEditS
iso: photo.iso,
nd: photo.nd,
coordinates: photo.coordinates,
aperture: null,
shutterSpeed: null,
note: photo.note,
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/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<LogbookPhotoEditScreen> {
child: const Column(
children: [
_DateListTile(),
LogbookPhotoCoordinatesListTile(),
_NoteListTile(),
_EvListTile(),
_IsoListTile(),

View file

@ -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: