diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 63b97bb..9ac7e41 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,8 @@ + + + + { diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index d90dfca..9b8840a 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -484,4 +484,13 @@ class AppLocalizationsDe extends AppLocalizations { @override String get zurueckgelegteStrecke => 'Zurückgelegte Strecke'; + + @override + String get tracking => 'Tracking'; + + @override + String get couldntDeterminePosition => 'Position konnte nicht ermittelt werden'; + + @override + String get trackingRunningInBackground => 'Die Tracking funktion läuft im Hintergrund'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 2d58dcb..1aeb305 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -484,4 +484,13 @@ class AppLocalizationsEn extends AppLocalizations { @override String get zurueckgelegteStrecke => 'Distance traveled'; + + @override + String get tracking => 'Tracking'; + + @override + String get couldntDeterminePosition => 'couldn\'t determine position'; + + @override + String get trackingRunningInBackground => 'The tracking service is running in the background'; } diff --git a/lib/methods/excursion_db_helper.dart b/lib/methods/excursion_db_helper.dart index 08ca10a..58b053a 100644 --- a/lib/methods/excursion_db_helper.dart +++ b/lib/methods/excursion_db_helper.dart @@ -37,10 +37,10 @@ class ExcursionDBHelper implements IDb { @override onCreateDatabases(Database excursionDB, int version) async { await excursionDB.execute( - 'CREATE TABLE excursion (ID INTEGER PRIMARY KEY AUTOINCREMENT, LogDat TEXT, Rudel TEXT, Teilnehmer TEXT, Jahr TEXT, Dauer TEXT, MHund INTEGER, MLeine TEXT, BLand TEXT, Lkr TEXT, BeiOrt TEXT, BimaName TEXT, Wetter TEXT, Temperat TEXT, RegenVor TEXT, KmRad TEXT, KmAuto TEXT, KmFuss TEXT, KmTotal TEXT, KmAuProz TEXT, KmFuProz TEXT, KmRaProz TEXT, SpGut TEXT, SpMittel, SpSchlecht TEXT, SpurFund TEXT, SpurLang TEXT, SpurTiere Text, SpSicher TEXT, WelpenSp TEXT, WelpenAnz TEXT, WpSicher TEXT, LosungGes TEXT, LosungAnz TEXT, LosungGen TEXT, UrinAnz TEXT, UrinGen TEXT, OestrAnz TEXT, OestrGen TEXT, HaarAnz TEXT, HaarGen TEXT, LosungKm TEXT, GenetiKm TEXT, Hinweise TEXT, Bemerk TEXT, IntKomm TEXT, BimaNr TEXT, BimaNutzer TEXT, BimaAGV TEXT, FallNum INTEGER, Sent INTEGER DEFAULT 0)', + 'CREATE TABLE excursion (ID INTEGER PRIMARY KEY AUTOINCREMENT, LogDat TEXT, Rudel TEXT, Teilnehmer TEXT, Jahr TEXT, Dauer TEXT, MHund INTEGER, MLeine TEXT, BLand TEXT, Lkr TEXT, BeiOrt TEXT, BimaName TEXT, Wetter TEXT, Temperat TEXT, RegenVor TEXT, KmRad TEXT, KmAuto TEXT, KmFuss TEXT, KmTotal TEXT, KmAuProz TEXT, KmFuProz TEXT, KmRaProz TEXT, SpGut TEXT, SpMittel, SpSchlecht TEXT, SpurFund TEXT, SpurLang TEXT, SpurTiere Text, SpSicher TEXT, WelpenSp TEXT, WelpenAnz TEXT, WpSicher TEXT, LosungGes TEXT, LosungAnz TEXT, LosungGen TEXT, UrinAnz TEXT, UrinGen TEXT, OestrAnz TEXT, OestrGen TEXT, HaarAnz TEXT, HaarGen TEXT, LosungKm TEXT, GenetiKm TEXT, Hinweise TEXT, Bemerk TEXT, IntKomm TEXT, BimaNr TEXT, BimaNutzer TEXT, BimaAGV TEXT, FallNum INTEGER, Weg TEXT, Sent INTEGER DEFAULT 0)', ); await excursionDB.execute( - 'CREATE TABLE excursionTemplates (ID INTEGER PRIMARY KEY AUTOINCREMENT, LogDat TEXT, Rudel TEXT, Teilnehmer TEXT, Jahr TEXT, Dauer TEXT, MHund INTEGER, MLeine TEXT, BLand TEXT, Lkr TEXT, BeiOrt TEXT, BimaName TEXT, Wetter TEXT, Temperat TEXT, RegenVor TEXT, KmRad TEXT, KmAuto TEXT, KmFuss TEXT, KmTotal TEXT, KmAuProz TEXT, KmFuProz TEXT, KmRaProz TEXT, SpGut TEXT, SpMittel, SpSchlecht TEXT, SpurFund TEXT, SpurLang TEXT, SpurTiere Text, SpSicher TEXT, WelpenSp TEXT, WelpenAnz TEXT, WpSicher TEXT, LosungGes TEXT, LosungAnz TEXT, LosungGen TEXT, UrinAnz TEXT, UrinGen TEXT, OestrAnz TEXT, OestrGen TEXT, HaarAnz TEXT, HaarGen TEXT, LosungKm TEXT, GenetiKm TEXT, Hinweise TEXT, Bemerk TEXT, IntKomm TEXT, BimaNr TEXT, BimaNutzer TEXT, BimaAGV TEXT, FallNum INTEGER)', + 'CREATE TABLE excursionTemplates (ID INTEGER PRIMARY KEY AUTOINCREMENT, LogDat TEXT, Rudel TEXT, Teilnehmer TEXT, Jahr TEXT, Dauer TEXT, MHund INTEGER, MLeine TEXT, BLand TEXT, Lkr TEXT, BeiOrt TEXT, BimaName TEXT, Wetter TEXT, Temperat TEXT, RegenVor TEXT, KmRad TEXT, KmAuto TEXT, KmFuss TEXT, KmTotal TEXT, KmAuProz TEXT, KmFuProz TEXT, KmRaProz TEXT, SpGut TEXT, SpMittel, SpSchlecht TEXT, SpurFund TEXT, SpurLang TEXT, SpurTiere Text, SpSicher TEXT, WelpenSp TEXT, WelpenAnz TEXT, WpSicher TEXT, LosungGes TEXT, LosungAnz TEXT, LosungGen TEXT, UrinAnz TEXT, UrinGen TEXT, OestrAnz TEXT, OestrGen TEXT, HaarAnz TEXT, HaarGen TEXT, LosungKm TEXT, GenetiKm TEXT, Hinweise TEXT, Bemerk TEXT, IntKomm TEXT, BimaNr TEXT, BimaNutzer TEXT, BimaAGV TEXT, FallNum INTEGER, Weg TEXT)', ); } diff --git a/lib/screens/excursion/excursion_main.dart b/lib/screens/excursion/excursion_main.dart index 93f519e..610b13f 100644 --- a/lib/screens/excursion/excursion_main.dart +++ b/lib/screens/excursion/excursion_main.dart @@ -1,5 +1,8 @@ import 'package:animations/animations.dart'; import 'package:fforte/enums/databases.dart'; +import 'package:fforte/screens/addCam/exceptions/location_disabled_exception.dart'; +import 'package:fforte/screens/addCam/exceptions/location_forbidden_exception.dart'; +import 'package:fforte/screens/addCam/services/geolocator_service.dart'; import 'package:fforte/screens/excursion/widgets/anzahlen.dart'; import 'package:fforte/screens/excursion/widgets/bima_nutzer.dart'; import 'package:fforte/screens/excursion/widgets/hinweise.dart'; @@ -7,13 +10,17 @@ import 'package:fforte/screens/excursion/widgets/hund_u_leine.dart'; import 'package:fforte/screens/excursion/widgets/letzter_niederschlag.dart'; import 'package:fforte/screens/excursion/widgets/spur_gefunden.dart'; import 'package:fforte/screens/excursion/widgets/strecke_u_spurbedingungen.dart'; +import 'package:fforte/screens/excursion/widgets/tracking.dart'; import 'package:fforte/screens/helper/add_entries_dialog_helper.dart'; +import 'package:fforte/screens/helper/snack_bar_helper.dart'; import 'package:fforte/screens/sharedMethods/check_required.dart'; import 'package:fforte/screens/sharedMethods/save_template.dart'; import 'package:fforte/screens/sharedWidgets/datum.dart'; import 'package:fforte/screens/sharedWidgets/var_text_field.dart'; import 'package:fforte/l10n/app_localizations.dart'; import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:latlong2/latlong.dart'; class ExcursionMain extends StatefulWidget { final bool isTemplate; @@ -34,6 +41,18 @@ class ExcursionMain extends StatefulWidget { class _ExcursionMainState extends State { int currentStep = 0; late bool isTemplate; + Position currentPosition = Position( + longitude: 10.0, + latitude: 51.0, + timestamp: DateTime.now(), + accuracy: 0.0, + altitude: 0.0, + heading: 0.0, + speed: 0.0, + speedAccuracy: 0.0, + altitudeAccuracy: 0.0, + headingAccuracy: 0.0, + ); // all TextEditingController because its easier Map> rmap = { @@ -55,6 +74,7 @@ class _ExcursionMainState extends State { "BimaAGV": {"controller": TextEditingController(), "required": false}, // Step 2 + "Weg": {"controller": TextEditingController(), "required": false}, "Wetter": {"controller": TextEditingController(), "required": false}, "Temperat": {"controller": TextEditingController(), "required": false}, "RegenVor": {"controller": TextEditingController(), "required": false}, @@ -100,6 +120,27 @@ class _ExcursionMainState extends State { @override void initState() { + GeolocatorService.deteterminePosition() + .then((result) => currentPosition = result) + .catchError((error) { + if (error is LocationDisabledException) { + if (mounted) { + SnackBarHelper.showSnackBarMessage( + context, + AppLocalizations.of(context)!.locationDisabled, + ); + } + } else if (error is LocationForbiddenException) { + if (mounted) { + SnackBarHelper.showSnackBarMessage( + context, + AppLocalizations.of(context)!.locationForbidden, + ); + } + } + return currentPosition; + }); + if (widget.existingData?.isNotEmpty ?? false) { for (var key in widget.existingData!.keys) { rmap[key]!["controller"]!.text = @@ -130,7 +171,6 @@ class _ExcursionMainState extends State { return puff; } - @override Widget build(BuildContext context) { List getSteps() => [ @@ -253,6 +293,27 @@ class _ExcursionMainState extends State { title: Text(AppLocalizations.of(context)!.umstaendeUndAktionen), content: Column( children: [ + // ---------- Tracking + ElevatedButton( + onPressed: + () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return Tracking( + weg: rmap["Weg"]!["controller"]!, + startPosition: LatLng( + currentPosition.latitude, + currentPosition.longitude, + ), + ); + }, + ), + ), + child: Text(AppLocalizations.of(context)!.tracking), + ), + const SizedBox(height: 10), + // ---------- Weather VarTextField( textController: rmap["Wetter"]!["controller"]!, @@ -390,7 +451,6 @@ class _ExcursionMainState extends State { currentStep += 1; }); } else { - if (widget.isSent) { Navigator.pushNamedAndRemoveUntil( context, diff --git a/lib/screens/excursion/widgets/tracking.dart b/lib/screens/excursion/widgets/tracking.dart new file mode 100644 index 0000000..c1a1352 --- /dev/null +++ b/lib/screens/excursion/widgets/tracking.dart @@ -0,0 +1,186 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:fforte/l10n/app_localizations.dart'; +import 'package:fforte/screens/helper/snack_bar_helper.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:latlong2/latlong.dart'; + +class Tracking extends StatefulWidget { + final LatLng startPosition; + final TextEditingController weg; + const Tracking({super.key, required this.startPosition, required this.weg}); + + @override + State createState() => _TrackingState(); +} + +class _TrackingState extends State { + List pathList = []; + StreamSubscription? positionStream; + bool positionStreamRunning = false; + + Random rand = Random(); + + @override + void initState() { + // TODO debugging (i guess) + pathList.add( + LatLng(widget.startPosition.latitude, widget.startPosition.longitude), + ); + + if (widget.weg.text.isNotEmpty) { + for (var element in widget.weg.text.split(";")) { + List posSplit = element.split(","); + try { + posSplit[0] = posSplit[0].substring(0, 9); + posSplit[1] = posSplit[1].substring(0, 9); + } on RangeError { + // ignore because the double is short enough then + } + + pathList.add( + LatLng(double.parse(posSplit.first), double.parse(posSplit[1])), + ); + } + } + super.initState(); + } + + @override + void dispose() { + if (positionStream != null) positionStream!.cancel(); + bool isFirst = true; + if (pathList.isNotEmpty) { + for (var pos in pathList) { + if (!isFirst) { + widget.weg.text += ";"; + } else { + isFirst = false; + } + + widget.weg.text += "${pos.latitude},${pos.longitude}"; + } + } + super.dispose(); + } + + final LocationSettings streamLocationSettings = LocationSettings( + accuracy: LocationAccuracy.high, + distanceFilter: 10, + ); + + void onTrackingStart() async { + // // notification handling for tracking in background notification + // PermissionStatus permissionStatus = + // await NotificationPermissions.getNotificationPermissionStatus(); + // + // if (permissionStatus != PermissionStatus.granted) { + // await NotificationPermissions.requestNotificationPermissions(); + // } + + positionStream = Geolocator.getPositionStream( + locationSettings: AndroidSettings( + accuracy: LocationAccuracy.high, + distanceFilter: 0, + foregroundNotificationConfig: + mounted + ? ForegroundNotificationConfig( + notificationTitle: + AppLocalizations.of(context)!.trackingRunningInBackground, + notificationText: "", + ) + : null, + ), + ).listen((Position? position) { + if (position != null) { + // pathList.add(LatLng(position.latitude, position.longitude)); + setState(() { + pathList.add(LatLng(rand.nextInt(5) + 40, position.longitude)); + }); + } else { + if (mounted) { + SnackBarHelper.showSnackBarMessage( + context, + AppLocalizations.of(context)!.couldntDeterminePosition, + ); + } + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.tracking), + // leading: IconButton(onPressed: () {}, icon: Icon(Icons.arrow_back_rounded)), + actions: [ + if (positionStreamRunning) + IconButton( + onPressed: () { + setState(() { + positionStreamRunning = false; + positionStream!.cancel(); + }); + }, + icon: Icon(Icons.stop_rounded), + ), + IconButton( + onPressed: () { + if (positionStreamRunning) { + positionStreamRunning = false; + positionStream?.pause(); + } else { + positionStreamRunning = true; + onTrackingStart(); + } + setState(() {}); + }, + icon: + positionStreamRunning + ? Icon(Icons.pause) + : Icon(Icons.play_arrow), + ), + ], + ), + body: FlutterMap( + mapController: MapController(), + options: MapOptions( + interactionOptions: const InteractionOptions( + flags: + InteractiveFlag.pinchZoom | + InteractiveFlag.drag | + InteractiveFlag.pinchMove, + ), + initialCenter: widget.startPosition, + initialZoom: 16.0, + ), + children: [ + TileLayer( + urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'com.example.app', + ), + if (pathList.isNotEmpty) + PolylineLayer( + polylines: [ + Polyline(strokeWidth: 2.0, points: pathList, color: Colors.red), + ], + ), + CircleLayer( + circles: [ + CircleMarker( + color: Colors.blue, + point: pathList.isEmpty ? widget.startPosition : pathList.last, + radius: 5, + useRadiusInMeter: true, + ), + ], + ), + ], + ), + ); + } +} diff --git a/time.txt b/time.txt index a21e47a..4ecbf29 100644 --- a/time.txt +++ b/time.txt @@ -82,3 +82,4 @@ 11.mai 1h 15min 12.mai 5h 30min 13.mai 1h 50min +15.mai 2h 30min