added guide that explains to the user how to give always location permission

This commit is contained in:
Nico
2025-06-05 19:32:43 +02:00
parent 9431232e91
commit a599c6fbc1
9 changed files with 238 additions and 30 deletions

View File

@@ -172,5 +172,14 @@
"trackingStart": "Tracking starten", "trackingStart": "Tracking starten",
"trackingStop": "Tracking stoppen", "trackingStop": "Tracking stoppen",
"trackingPause": "Tracking pausieren", "trackingPause": "Tracking pausieren",
"accuracy": "Genauigkeit" "accuracy": "Genauigkeit",
"leavePageTitle": "Seite verlassen?",
"leavePageContent": "Möchten Sie die Seite wirklich verlassen?",
"leaveWithoutSaving": "Seite verlassen ohne zu speichern",
"leaveAndSaveTemplate": "Als Template speichern und verlassen",
"trackingPermissionTitle": "Standort-Berechtigung für Tracking",
"trackingPermissionContent": "Für das Tracking wird die dauerhafte Standort-Berechtigung benötigt. Nach dem Öffnen der Einstellungen:\n\n1. Tippen Sie auf 'Berechtigungen' und dann 'Standort'\n2. Wählen Sie 'immer'\n3. Kehren Sie zur App zurück\n\nDies ermöglicht das Aufzeichnen Ihrer Position auch im Hintergrund.",
"openSettings": "Einstellungen öffnen",
"returnToApp": "Zurück zur App",
"permissionNotGranted": "Berechtigung nicht erteilt. Tracking nicht möglich."
} }

View File

@@ -686,5 +686,15 @@
"accuracy": "Accuracy", "accuracy": "Accuracy",
"@accuracy": { "@accuracy": {
"description": "Accuracy of GPS position" "description": "Accuracy of GPS position"
} },
"leavePageTitle": "Leave page?",
"leavePageContent": "Do you really want to leave the page?",
"leaveWithoutSaving": "Leave without saving",
"leaveAndSaveTemplate": "Save as template and leave",
"trackingPermissionTitle": "Location Permission for Tracking",
"trackingPermissionContent": "Tracking requires permanent location permission. After opening settings:\n\n1. Tap on 'Permissions' and then on 'Location'\n2. Select 'Allow all the time'\n3. Return to the app\n\nThis allows recording your position in the background.",
"openSettings": "Open Settings",
"returnToApp": "Return to App",
"permissionNotGranted": "Permission not granted. Tracking not possible."
} }

View File

@@ -1132,6 +1132,60 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Accuracy'** /// **'Accuracy'**
String get accuracy; String get accuracy;
/// No description provided for @leavePageTitle.
///
/// In en, this message translates to:
/// **'Leave page?'**
String get leavePageTitle;
/// No description provided for @leavePageContent.
///
/// In en, this message translates to:
/// **'Do you really want to leave the page?'**
String get leavePageContent;
/// No description provided for @leaveWithoutSaving.
///
/// In en, this message translates to:
/// **'Leave without saving'**
String get leaveWithoutSaving;
/// No description provided for @leaveAndSaveTemplate.
///
/// In en, this message translates to:
/// **'Save as template and leave'**
String get leaveAndSaveTemplate;
/// No description provided for @trackingPermissionTitle.
///
/// In en, this message translates to:
/// **'Location Permission for Tracking'**
String get trackingPermissionTitle;
/// No description provided for @trackingPermissionContent.
///
/// In en, this message translates to:
/// **'Tracking requires permanent location permission. After opening settings:\n\n1. Tap on \'Permissions\' and then on \'Location\'\n2. Select \'Allow all the time\'\n3. Return to the app\n\nThis allows recording your position in the background.'**
String get trackingPermissionContent;
/// No description provided for @openSettings.
///
/// In en, this message translates to:
/// **'Open Settings'**
String get openSettings;
/// No description provided for @returnToApp.
///
/// In en, this message translates to:
/// **'Return to App'**
String get returnToApp;
/// No description provided for @permissionNotGranted.
///
/// In en, this message translates to:
/// **'Permission not granted. Tracking not possible.'**
String get permissionNotGranted;
} }
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> { class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -526,4 +526,31 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get accuracy => 'Genauigkeit'; String get accuracy => 'Genauigkeit';
@override
String get leavePageTitle => 'Seite verlassen?';
@override
String get leavePageContent => 'Möchten Sie die Seite wirklich verlassen?';
@override
String get leaveWithoutSaving => 'Seite verlassen ohne zu speichern';
@override
String get leaveAndSaveTemplate => 'Als Template speichern und verlassen';
@override
String get trackingPermissionTitle => 'Standort-Berechtigung für Tracking';
@override
String get trackingPermissionContent => 'Für das Tracking wird die dauerhafte Standort-Berechtigung benötigt. Nach dem Öffnen der Einstellungen:\n\n1. Tippen Sie auf \'Berechtigungen\' und dann \'Standort\'\n2. Wählen Sie \'immer\'\n3. Kehren Sie zur App zurück\n\nDies ermöglicht das Aufzeichnen Ihrer Position auch im Hintergrund.';
@override
String get openSettings => 'Einstellungen öffnen';
@override
String get returnToApp => 'Zurück zur App';
@override
String get permissionNotGranted => 'Berechtigung nicht erteilt. Tracking nicht möglich.';
} }

View File

@@ -526,4 +526,31 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get accuracy => 'Accuracy'; String get accuracy => 'Accuracy';
@override
String get leavePageTitle => 'Leave page?';
@override
String get leavePageContent => 'Do you really want to leave the page?';
@override
String get leaveWithoutSaving => 'Leave without saving';
@override
String get leaveAndSaveTemplate => 'Save as template and leave';
@override
String get trackingPermissionTitle => 'Location Permission for Tracking';
@override
String get trackingPermissionContent => 'Tracking requires permanent location permission. After opening settings:\n\n1. Tap on \'Permissions\' and then on \'Location\'\n2. Select \'Allow all the time\'\n3. Return to the app\n\nThis allows recording your position in the background.';
@override
String get openSettings => 'Open Settings';
@override
String get returnToApp => 'Return to App';
@override
String get permissionNotGranted => 'Permission not granted. Tracking not possible.';
} }

View File

@@ -124,7 +124,7 @@ class _ExcursionMainState extends State<ExcursionMain> {
@override @override
void initState() { void initState() {
GeolocatorService.deteterminePosition( GeolocatorService.deteterminePosition(
alwaysOnNeeded: true, alwaysOnNeeded: false,
).then((result) => currentPosition = result).catchError((error) async { ).then((result) => currentPosition = result).catchError((error) async {
if (error is LocationDisabledException) { if (error is LocationDisabledException) {
if (mounted) { if (mounted) {
@@ -142,9 +142,16 @@ class _ExcursionMainState extends State<ExcursionMain> {
} }
} else if (error is NeedAlwaysLocation) { } else if (error is NeedAlwaysLocation) {
if (mounted) { if (mounted) {
bool reload = await AddEntriesDialogHelper.locationSettingsDialog(context); bool reload =
if (reload) GeolocatorService.deteterminePosition().then((res) => currentPosition = res).catchError((error) {return currentPosition;}); await AddEntriesDialogHelper.locationSettingsDialog(context);
} if (reload) {
GeolocatorService.deteterminePosition()
.then((res) => currentPosition = res)
.catchError((error) {
return currentPosition;
});
}
}
} }
return currentPosition; return currentPosition;
}); });
@@ -346,6 +353,62 @@ class _ExcursionMainState extends State<ExcursionMain> {
// ---------- Tracking // ---------- Tracking
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
// Check for always permission before starting tracking
LocationPermission permission = await Geolocator.checkPermission();
if (permission != LocationPermission.always) {
if (mounted) {
bool? shouldContinue = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(AppLocalizations.of(context)!.trackingPermissionTitle),
content: Text(AppLocalizations.of(context)!.trackingPermissionContent),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(AppLocalizations.of(context)!.cancel),
),
TextButton(
onPressed: () async {
await Geolocator.openAppSettings();
Navigator.of(context).pop(true);
},
child: Text(AppLocalizations.of(context)!.openSettings),
),
],
),
);
if (shouldContinue != true) {
return;
}
// Wait for user to change settings and return
// Try checking the permission multiple times
for (int i = 0; i < 5; i++) {
await Future.delayed(const Duration(seconds: 1));
if (!mounted) return;
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.always) {
break;
}
// If this is the last attempt and we still don't have permission
if (i == 4 && permission != LocationPermission.always) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.permissionNotGranted),
duration: const Duration(seconds: 3),
),
);
}
return;
}
}
}
}
await Navigator.push(context, MaterialPageRoute( await Navigator.push(context, MaterialPageRoute(
builder: (context) { builder: (context) {
return Tracking( return Tracking(
@@ -356,8 +419,7 @@ class _ExcursionMainState extends State<ExcursionMain> {
)); ));
setState(() {}); setState(() {});
}, },
child: child: Text(AppLocalizations.of(context)!.trackingAnAusschalten),
Text(AppLocalizations.of(context)!.trackingAnAusschalten),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@@ -461,7 +523,7 @@ class _ExcursionMainState extends State<ExcursionMain> {
return PopScope( return PopScope(
canPop: false, canPop: false,
onPopInvoked: (didPop) async { onPopInvokedWithResult: (bool didPop, Object? res) async {
if (didPop) { if (didPop) {
return; return;
} }
@@ -470,20 +532,23 @@ class _ExcursionMainState extends State<ExcursionMain> {
final result = await showDialog<int>( final result = await showDialog<int>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Seite verlassen?'), title: Text(AppLocalizations.of(context)!.leavePageTitle),
content: const Text('Möchten Sie die Seite wirklich verlassen?'), content: Text(AppLocalizations.of(context)!.leavePageContent),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(0), onPressed: () => Navigator.of(context).pop(0),
child: const Text('Nein'), child: Text(AppLocalizations.of(context)!.nein),
), ),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(1), onPressed: () {
child: const Text('Ja und Template speichern'), saveTemplate(getFieldsText(), DatabasesEnum.excursion);
Navigator.of(context).pop(1);
},
child: Text(AppLocalizations.of(context)!.leaveAndSaveTemplate),
), ),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(2), onPressed: () => Navigator.of(context).pop(2),
child: const Text('Ja ohne Template'), child: Text(AppLocalizations.of(context)!.leaveWithoutSaving),
), ),
], ],
), ),
@@ -517,7 +582,9 @@ class _ExcursionMainState extends State<ExcursionMain> {
actions: [ actions: [
// Text(TrackingService().isTracking ? "Tracking" : "Not tracking") // Text(TrackingService().isTracking ? "Tracking" : "Not tracking")
Image.asset( Image.asset(
TrackingService().isTracking ? "assets/icons/tracking_on.png" : "assets/icons/tracking_off.png", TrackingService().isTracking
? "assets/icons/tracking_on.png"
: "assets/icons/tracking_off.png",
width: 40, width: 40,
), ),
], ],

View File

@@ -258,7 +258,7 @@ class AddEntriesDialogHelper {
); );
} }
} catch (e) { } catch (e) {
print(e.toString()); debugPrint(e.toString());
} }
}, },
child: Text(AppLocalizations.of(context)!.justsave), child: Text(AppLocalizations.of(context)!.justsave),

View File

@@ -181,7 +181,8 @@ class _ViewEntriesState extends State<ViewEntries> {
title: Text( title: Text(
'${widget.dbType == DatabasesEnum.place ? AppLocalizations.of(context)!.justplace : AppLocalizations.of(context)!.excursion} ${mainEntries[index]["ID"]}'), '${widget.dbType == DatabasesEnum.place ? AppLocalizations.of(context)!.justplace : AppLocalizations.of(context)!.excursion} ${mainEntries[index]["ID"]}'),
subtitle: Text( subtitle: Text(
dateFormat.format(DateTime.parse(mainEntries[index]["Datum"])), dateFormat.format(DateTime.parse(
mainEntries[index]["Datum"])),
), ),
trailing: Checkbox( trailing: Checkbox(
value: mainEntries[index]['Sent'] == 0 value: mainEntries[index]['Sent'] == 0
@@ -191,7 +192,7 @@ class _ViewEntriesState extends State<ViewEntries> {
), ),
onTap: () async { onTap: () async {
if (widget.dbType == DatabasesEnum.place) { if (widget.dbType == DatabasesEnum.place) {
Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => AddCamMain( builder: (context) => AddCamMain(
@@ -204,7 +205,7 @@ class _ViewEntriesState extends State<ViewEntries> {
); );
} else if (widget.dbType == } else if (widget.dbType ==
DatabasesEnum.excursion) { DatabasesEnum.excursion) {
Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ExcursionMain( builder: (context) => ExcursionMain(
@@ -215,6 +216,9 @@ class _ViewEntriesState extends State<ViewEntries> {
), ),
), ),
); );
setState(() {
reloadAllEntries();
});
} }
}, },
), ),
@@ -269,9 +273,9 @@ class _ViewEntriesState extends State<ViewEntries> {
], ],
), ),
child: ListTile( child: ListTile(
onTap: () { onTap: () async {
if (widget.dbType == DatabasesEnum.place) { if (widget.dbType == DatabasesEnum.place) {
Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => AddCamMain( builder: (context) => AddCamMain(
@@ -282,7 +286,7 @@ class _ViewEntriesState extends State<ViewEntries> {
); );
} else if (widget.dbType == } else if (widget.dbType ==
DatabasesEnum.excursion) { DatabasesEnum.excursion) {
Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ExcursionMain( builder: (context) => ExcursionMain(
@@ -291,12 +295,16 @@ class _ViewEntriesState extends State<ViewEntries> {
), ),
), ),
); );
setState(() {
reloadAllEntries();
});
} }
}, },
title: Text( title: Text(
'${widget.dbType == DatabasesEnum.place ? AppLocalizations.of(context)!.justplace : AppLocalizations.of(context)!.excursion} ${templates[index]["ID"]}'), '${widget.dbType == DatabasesEnum.place ? AppLocalizations.of(context)!.justplace : AppLocalizations.of(context)!.excursion} ${templates[index]["ID"]}'),
subtitle: Text( subtitle: Text(
dateFormat.format(DateTime.parse(templates[index]["Datum"])), dateFormat.format(
DateTime.parse(templates[index]["Datum"])),
), ),
), ),
); );

View File

@@ -57,12 +57,17 @@ class TrackingService {
Future<void> startTracking(BuildContext context) async { Future<void> startTracking(BuildContext context) async {
if (isTracking) return; if (isTracking) return;
final LocationSettings locationSettings = LocationSettings(
accuracy: LocationAccuracy.high
);
_lastContext = context; _lastContext = context;
await NotificationService().initNotification(); await NotificationService().initNotification();
if (context.mounted) {
NotificationService().showNotification( NotificationService().showNotification(
title: AppLocalizations.of(context)!.trackingRunningInBackground, title: AppLocalizations.of(context)!.trackingRunningInBackground,
); );
}
// Get tracking interval from settings // Get tracking interval from settings
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
@@ -71,8 +76,9 @@ class TrackingService {
// Create a timer that triggers position updates // Create a timer that triggers position updates
_positionTimer = Timer.periodic(Duration(seconds: intervalSeconds), (_) async { _positionTimer = Timer.periodic(Duration(seconds: intervalSeconds), (_) async {
try { try {
final Position position = await Geolocator.getCurrentPosition( final Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high, locationSettings: locationSettings
); );
pathList.add(LatLng(position.latitude, position.longitude)); pathList.add(LatLng(position.latitude, position.longitude));
@@ -89,7 +95,7 @@ class TrackingService {
// Get initial position immediately // Get initial position immediately
try { try {
final Position position = await Geolocator.getCurrentPosition( final Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high, locationSettings: locationSettings
); );
pathList.add(LatLng(position.latitude, position.longitude)); pathList.add(LatLng(position.latitude, position.longitude));