Update AndroidManifest to include foreground service permissions and implement tracking service in excursion screens. Refactor tracking logic to utilize TrackingService for better state management and streamline position updates.
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<!-- <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> -->
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<!-- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /> -->
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
<application
|
<application
|
||||||
android:label="LUPUS"
|
android:label="LUPUS"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
@@ -37,11 +37,12 @@
|
|||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
|
||||||
<!-- <service -->
|
<service
|
||||||
<!-- android:name="com.dexterous.flutterlocalnotifications.ForegroundService" -->
|
android:name=".TrackingService"
|
||||||
<!-- android:exported="false" -->
|
android:exported="false"
|
||||||
<!-- android:stopWithTask="false" -->
|
android:stopWithTask="false"
|
||||||
<!-- android:foregroundServiceType="<location>"> -->
|
android:foregroundServiceType="location">
|
||||||
|
</service>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
|
|||||||
BIN
assets/icons/tracking_off.png
Normal file
BIN
assets/icons/tracking_off.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/icons/tracking_on.png
Normal file
BIN
assets/icons/tracking_on.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
@@ -19,6 +19,7 @@ import 'package:fforte/screens/sharedMethods/save_template.dart';
|
|||||||
import 'package:fforte/screens/sharedWidgets/datum.dart';
|
import 'package:fforte/screens/sharedWidgets/datum.dart';
|
||||||
import 'package:fforte/screens/sharedWidgets/var_text_field.dart';
|
import 'package:fforte/screens/sharedWidgets/var_text_field.dart';
|
||||||
import 'package:fforte/l10n/app_localizations.dart';
|
import 'package:fforte/l10n/app_localizations.dart';
|
||||||
|
import 'package:fforte/services/tracking_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@@ -147,7 +148,6 @@ class _ExcursionMainState extends State<ExcursionMain> {
|
|||||||
return currentPosition;
|
return currentPosition;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (widget.existingData?.isNotEmpty ?? false) {
|
if (widget.existingData?.isNotEmpty ?? false) {
|
||||||
for (var key in widget.existingData!.keys) {
|
for (var key in widget.existingData!.keys) {
|
||||||
rmap[key]!["controller"]!.text =
|
rmap[key]!["controller"]!.text =
|
||||||
@@ -188,268 +188,288 @@ class _ExcursionMainState extends State<ExcursionMain> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
List<Step> getSteps() => [
|
List<Step> getSteps() => [
|
||||||
Step(
|
Step(
|
||||||
title: Text(AppLocalizations.of(context)!.dateandtime),
|
title: Text(AppLocalizations.of(context)!.dateandtime),
|
||||||
content: Column(
|
content: Column(
|
||||||
children: [
|
children: [
|
||||||
// ---------- Date
|
// ---------- Date
|
||||||
Datum(
|
Datum(
|
||||||
initDatum: DateTime.now(),
|
initDatum: DateTime.now(),
|
||||||
onDateChanged: (date) {
|
onDateChanged: (date) {
|
||||||
rmap["Datum"]!["controller"]!.text = date.toString();
|
rmap["Datum"]!["controller"]!.text = date.toString();
|
||||||
},
|
},
|
||||||
name: AppLocalizations.of(context)!.date,
|
name: AppLocalizations.of(context)!.date,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// ---------- Pack
|
// ---------- Pack
|
||||||
VarTextField(
|
VarTextField(
|
||||||
textController: rmap["Rudel"]!["controller"]!,
|
textController: rmap["Rudel"]!["controller"]!,
|
||||||
localization: AppLocalizations.of(context)!.rudel,
|
localization: AppLocalizations.of(context)!.rudel,
|
||||||
dbName: "Rudel",
|
dbName: "Rudel",
|
||||||
required: false,
|
required: false,
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// ---------- Participants
|
// ---------- Participants
|
||||||
VarTextField(
|
VarTextField(
|
||||||
textController: rmap["Teilnehmer"]!["controller"]!,
|
textController: rmap["Teilnehmer"]!["controller"]!,
|
||||||
localization: AppLocalizations.of(context)!.teilnehmer,
|
localization: AppLocalizations.of(context)!.teilnehmer,
|
||||||
dbName: "Teilnehmer",
|
dbName: "Teilnehmer",
|
||||||
required: false,
|
required: false,
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// ---------- Duration
|
// ---------- Duration
|
||||||
VarTextField(
|
VarTextField(
|
||||||
textController: rmap["Dauer"]!["controller"]!,
|
textController: rmap["Dauer"]!["controller"]!,
|
||||||
localization: AppLocalizations.of(context)!.dauer,
|
localization: AppLocalizations.of(context)!.dauer,
|
||||||
dbName: "Dauer",
|
dbName: "Dauer",
|
||||||
required: false,
|
required: false,
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
// ---------- Dog(leash)
|
// ---------- Dog(leash)
|
||||||
HundULeine(
|
HundULeine(
|
||||||
mHund: rmap["MHund"]!["controller"]!,
|
mHund: rmap["MHund"]!["controller"]!,
|
||||||
mLeine: rmap["MLeine"]!["controller"]!,
|
mLeine: rmap["MLeine"]!["controller"]!,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// ---------- State
|
// ---------- State
|
||||||
VarTextField(
|
VarTextField(
|
||||||
textController: rmap["BLand"]!["controller"]!,
|
textController: rmap["BLand"]!["controller"]!,
|
||||||
localization: AppLocalizations.of(context)!.bland,
|
localization: AppLocalizations.of(context)!.bland,
|
||||||
dbName: "BLand",
|
dbName: "BLand",
|
||||||
required: false,
|
required: false,
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// ---------- Country
|
// ---------- Country
|
||||||
VarTextField(
|
VarTextField(
|
||||||
textController: rmap["Lkr"]!["controller"]!,
|
textController: rmap["Lkr"]!["controller"]!,
|
||||||
localization: AppLocalizations.of(context)!.lkr,
|
localization: AppLocalizations.of(context)!.lkr,
|
||||||
dbName: "Lkr",
|
dbName: "Lkr",
|
||||||
required: false,
|
required: false,
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// ---------- By State
|
// ---------- By State
|
||||||
VarTextField(
|
VarTextField(
|
||||||
textController: rmap["BeiOrt"]!["controller"]!,
|
textController: rmap["BeiOrt"]!["controller"]!,
|
||||||
localization: AppLocalizations.of(context)!.beiort,
|
localization: AppLocalizations.of(context)!.beiort,
|
||||||
dbName: "BeiOrt",
|
dbName: "BeiOrt",
|
||||||
required: false,
|
required: false,
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// ---------- Bima number
|
// ---------- Bima number
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
child: ExpansionPanelList(
|
child: ExpansionPanelList(
|
||||||
expansionCallback:
|
expansionCallback: ((int index, bool isExpanded) =>
|
||||||
((int index, bool isExpanded) =>
|
|
||||||
setState(() => bimaExtended = isExpanded)),
|
setState(() => bimaExtended = isExpanded)),
|
||||||
expandedHeaderPadding: EdgeInsets.all(0),
|
expandedHeaderPadding: EdgeInsets.all(0),
|
||||||
children: [
|
children: [
|
||||||
ExpansionPanel(
|
ExpansionPanel(
|
||||||
isExpanded: bimaExtended,
|
isExpanded: bimaExtended,
|
||||||
canTapOnHeader: true,
|
canTapOnHeader: true,
|
||||||
headerBuilder: (context, bool isOpen) => Padding(
|
headerBuilder: (context, bool isOpen) => Padding(
|
||||||
padding: const EdgeInsets.only(left: 15),
|
padding: const EdgeInsets.only(left: 15),
|
||||||
child: Align(alignment: Alignment.centerLeft, child: Text("BImA", style: Theme.of(context).textTheme.bodyLarge,),),
|
child: Align(
|
||||||
),
|
alignment: Alignment.centerLeft,
|
||||||
body: Padding(
|
child: Text(
|
||||||
padding: const EdgeInsets.all(15),
|
"BImA",
|
||||||
child: Column(
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
children: [
|
),
|
||||||
const SizedBox(height: 10),
|
|
||||||
VarTextField(
|
|
||||||
textController: rmap["BimaNr"]!["controller"]!,
|
|
||||||
localization: AppLocalizations.of(context)!.bimaNr,
|
|
||||||
dbName: "BimaNr",
|
|
||||||
required: false,
|
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
),
|
||||||
// ---------- Bima name
|
body: Padding(
|
||||||
VarTextField(
|
padding: const EdgeInsets.all(15),
|
||||||
textController: rmap["BimaName"]!["controller"]!,
|
child: Column(
|
||||||
localization: AppLocalizations.of(context)!.bimaName,
|
children: [
|
||||||
dbName: "BimaName",
|
const SizedBox(height: 10),
|
||||||
required: false,
|
VarTextField(
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
textController: rmap["BimaNr"]!["controller"]!,
|
||||||
|
localization:
|
||||||
|
AppLocalizations.of(context)!.bimaNr,
|
||||||
|
dbName: "BimaNr",
|
||||||
|
required: false,
|
||||||
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
// ---------- Bima name
|
||||||
|
VarTextField(
|
||||||
|
textController:
|
||||||
|
rmap["BimaName"]!["controller"]!,
|
||||||
|
localization:
|
||||||
|
AppLocalizations.of(context)!.bimaName,
|
||||||
|
dbName: "BimaName",
|
||||||
|
required: false,
|
||||||
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
// ---------- Bima user
|
||||||
|
BimaNutzer(
|
||||||
|
onBimaNutzerChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
rmap["BimaNutzer"]!["controller"]!.text =
|
||||||
|
value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
// ---------- Bima AGV
|
||||||
|
VarTextField(
|
||||||
|
textController: rmap["BimaAGV"]!["controller"]!,
|
||||||
|
localization:
|
||||||
|
AppLocalizations.of(context)!.bimaAGV,
|
||||||
|
dbName: "BimaAGV",
|
||||||
|
required: false,
|
||||||
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
),
|
||||||
// ---------- Bima user
|
|
||||||
BimaNutzer(
|
|
||||||
onBimaNutzerChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
rmap["BimaNutzer"]!["controller"]!.text = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
// ---------- Bima AGV
|
|
||||||
VarTextField(
|
|
||||||
textController: rmap["BimaAGV"]!["controller"]!,
|
|
||||||
localization: AppLocalizations.of(context)!.bimaAGV,
|
|
||||||
dbName: "BimaAGV",
|
|
||||||
required: false,
|
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
Step(
|
||||||
),
|
title: Text(AppLocalizations.of(context)!.umstaendeUndAktionen),
|
||||||
Step(
|
content: Column(
|
||||||
title: Text(AppLocalizations.of(context)!.umstaendeUndAktionen),
|
children: [
|
||||||
content: Column(
|
// ---------- Tracking
|
||||||
children: [
|
ElevatedButton(
|
||||||
// ---------- Tracking
|
onPressed: () async {
|
||||||
ElevatedButton(
|
await Navigator.push(context, MaterialPageRoute(
|
||||||
onPressed:
|
|
||||||
() => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return Tracking(
|
return Tracking(
|
||||||
weg: rmap["Weg"]!["controller"]!,
|
weg: rmap["Weg"]!["controller"]!,
|
||||||
startPosition: currentPosition,
|
startPosition: currentPosition,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
));
|
||||||
),
|
setState(() {});
|
||||||
child: Text(AppLocalizations.of(context)!.trackingAnAusschalten),
|
},
|
||||||
),
|
child:
|
||||||
const SizedBox(height: 10),
|
Text(AppLocalizations.of(context)!.trackingAnAusschalten),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
// ---------- Weather
|
// ---------- Weather
|
||||||
VarTextField(
|
VarTextField(
|
||||||
textController: rmap["Wetter"]!["controller"]!,
|
textController: rmap["Wetter"]!["controller"]!,
|
||||||
localization: AppLocalizations.of(context)!.wetter,
|
localization: AppLocalizations.of(context)!.wetter,
|
||||||
dbName: "Wetter",
|
dbName: "Wetter",
|
||||||
required: false,
|
required: false,
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
// ---------- Temperature
|
||||||
|
VarTextField(
|
||||||
|
textController: rmap["Temperat"]!["controller"]!,
|
||||||
|
localization: AppLocalizations.of(context)!.temperatur,
|
||||||
|
dbName: "Temperat",
|
||||||
|
required: false,
|
||||||
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
// ---------- Last precipitation
|
||||||
|
LetzterNiederschlag(
|
||||||
|
controller: rmap["RegenVor"]!["controller"]!),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// ---------- Track conditions
|
||||||
|
StreckeUSpurbedingungen(
|
||||||
|
kmAutoController: rmap["KmAuto"]!["controller"]!,
|
||||||
|
kmFussController: rmap["KmFuss"]!["controller"]!,
|
||||||
|
kmRadController: rmap["KmRad"]!["controller"]!,
|
||||||
|
spGutController: rmap["SpGut"]!["controller"]!,
|
||||||
|
spMittelController: rmap["SpMittel"]!["controller"]!,
|
||||||
|
spSchlechtController: rmap["SpSchlecht"]!["controller"]!,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Divider(),
|
||||||
|
// ---------- Track found
|
||||||
|
SpurGefunden(
|
||||||
|
spurFund: rmap["SpurFund"]!["controller"]!,
|
||||||
|
spurLang: rmap["SpurLang"]!["controller"]!,
|
||||||
|
spurTiere: rmap["SpurTiere"]!["controller"]!,
|
||||||
|
spSicher: rmap["SpSicher"]!["controller"]!,
|
||||||
|
welpenSp: rmap["WelpenSp"]!["controller"]!,
|
||||||
|
welpenAnz: rmap["WelpenAnz"]!["controller"]!,
|
||||||
|
wpSicher: rmap["WpSicher"]!["controller"]!,
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// ---------- Counts
|
||||||
|
Anzahlen(
|
||||||
|
losungAnz: rmap["LosungAnz"]!["controller"]!,
|
||||||
|
losungGes: rmap["LosungGes"]!["controller"]!,
|
||||||
|
losungGen: rmap["LosungGen"]!["controller"]!,
|
||||||
|
urinAnz: rmap["UrinAnz"]!["controller"]!,
|
||||||
|
urinGen: rmap["UrinGen"]!["controller"]!,
|
||||||
|
oestrAnz: rmap["OestrAnz"]!["controller"]!,
|
||||||
|
oestrGen: rmap["OestrGen"]!["controller"]!,
|
||||||
|
haarAnz: rmap["HaarAnz"]!["controller"]!,
|
||||||
|
haarGen: rmap["HaarGen"]!["controller"]!,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// ---------- Clues
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.hinweise,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Hinweise(hinweise: rmap["Hinweise"]!["controller"]!),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
),
|
||||||
// ---------- Temperature
|
Step(
|
||||||
VarTextField(
|
title: Text(AppLocalizations.of(context)!.intkomm),
|
||||||
textController: rmap["Temperat"]!["controller"]!,
|
content: Column(
|
||||||
localization: AppLocalizations.of(context)!.temperatur,
|
children: [
|
||||||
dbName: "Temperat",
|
// ---------- Remarks
|
||||||
required: false,
|
VarTextField(
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
textController: rmap["Bemerk"]!["controller"]!,
|
||||||
|
localization: AppLocalizations.of(context)!.sonstbemerkungen,
|
||||||
|
dbName: "Bemerk",
|
||||||
|
required: false,
|
||||||
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// ---------- Internal communication
|
||||||
|
VarTextField(
|
||||||
|
textController: rmap["IntKomm"]!["controller"]!,
|
||||||
|
localization: AppLocalizations.of(context)!.intkomm,
|
||||||
|
dbName: "IntKomm",
|
||||||
|
required: false,
|
||||||
|
dbDesignation: DatabasesEnum.excursion,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
),
|
||||||
// ---------- Last precipitation
|
];
|
||||||
LetzterNiederschlag(controller: rmap["RegenVor"]!["controller"]!),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
// ---------- Track conditions
|
|
||||||
StreckeUSpurbedingungen(
|
|
||||||
kmAutoController: rmap["KmAuto"]!["controller"]!,
|
|
||||||
kmFussController: rmap["KmFuss"]!["controller"]!,
|
|
||||||
kmRadController: rmap["KmRad"]!["controller"]!,
|
|
||||||
spGutController: rmap["SpGut"]!["controller"]!,
|
|
||||||
spMittelController: rmap["SpMittel"]!["controller"]!,
|
|
||||||
spSchlechtController: rmap["SpSchlecht"]!["controller"]!,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
const Divider(),
|
|
||||||
// ---------- Track found
|
|
||||||
SpurGefunden(
|
|
||||||
spurFund: rmap["SpurFund"]!["controller"]!,
|
|
||||||
spurLang: rmap["SpurLang"]!["controller"]!,
|
|
||||||
spurTiere: rmap["SpurTiere"]!["controller"]!,
|
|
||||||
spSicher: rmap["SpSicher"]!["controller"]!,
|
|
||||||
welpenSp: rmap["WelpenSp"]!["controller"]!,
|
|
||||||
welpenAnz: rmap["WelpenAnz"]!["controller"]!,
|
|
||||||
wpSicher: rmap["WpSicher"]!["controller"]!,
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
// ---------- Counts
|
|
||||||
Anzahlen(
|
|
||||||
losungAnz: rmap["LosungAnz"]!["controller"]!,
|
|
||||||
losungGes: rmap["LosungGes"]!["controller"]!,
|
|
||||||
losungGen: rmap["LosungGen"]!["controller"]!,
|
|
||||||
urinAnz: rmap["UrinAnz"]!["controller"]!,
|
|
||||||
urinGen: rmap["UrinGen"]!["controller"]!,
|
|
||||||
oestrAnz: rmap["OestrAnz"]!["controller"]!,
|
|
||||||
oestrGen: rmap["OestrGen"]!["controller"]!,
|
|
||||||
haarAnz: rmap["HaarAnz"]!["controller"]!,
|
|
||||||
haarGen: rmap["HaarGen"]!["controller"]!,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
const Divider(),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
// ---------- Clues
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomLeft,
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!.hinweise,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Hinweise(hinweise: rmap["Hinweise"]!["controller"]!),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Step(
|
|
||||||
title: Text(AppLocalizations.of(context)!.intkomm),
|
|
||||||
content: Column(
|
|
||||||
children: [
|
|
||||||
// ---------- Remarks
|
|
||||||
VarTextField(
|
|
||||||
textController: rmap["Bemerk"]!["controller"]!,
|
|
||||||
localization: AppLocalizations.of(context)!.sonstbemerkungen,
|
|
||||||
dbName: "Bemerk",
|
|
||||||
required: false,
|
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
// ---------- Internal communication
|
|
||||||
VarTextField(
|
|
||||||
textController: rmap["IntKomm"]!["controller"]!,
|
|
||||||
localization: AppLocalizations.of(context)!.intkomm,
|
|
||||||
dbName: "IntKomm",
|
|
||||||
required: false,
|
|
||||||
dbDesignation: DatabasesEnum.excursion,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Begin of widget tree
|
// Begin of widget tree
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text(AppLocalizations.of(context)!.excursion)),
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.excursion),
|
||||||
|
actions: [
|
||||||
|
// Text(TrackingService().isTracking ? "Tracking" : "Not tracking")
|
||||||
|
Image.asset(
|
||||||
|
TrackingService().isTracking ? "assets/icons/tracking_on.png" : "assets/icons/tracking_off.png",
|
||||||
|
width: 40,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
body: PageTransitionSwitcher(
|
body: PageTransitionSwitcher(
|
||||||
duration: const Duration(microseconds: 800),
|
duration: const Duration(microseconds: 800),
|
||||||
transitionBuilder: (
|
transitionBuilder: (
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:fforte/l10n/app_localizations.dart';
|
import 'package:fforte/l10n/app_localizations.dart';
|
||||||
import 'package:fforte/screens/addCam/services/geolocator_service.dart';
|
import 'package:fforte/screens/addCam/services/geolocator_service.dart';
|
||||||
import 'package:fforte/screens/helper/add_entries_dialog_helper.dart';
|
import 'package:fforte/screens/helper/add_entries_dialog_helper.dart';
|
||||||
import 'package:fforte/screens/helper/snack_bar_helper.dart';
|
import 'package:fforte/screens/helper/snack_bar_helper.dart';
|
||||||
import 'package:fforte/services/notification_service.dart';
|
import 'package:fforte/services/tracking_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
|
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
|
||||||
@@ -22,20 +21,14 @@ class Tracking extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _TrackingState extends State<Tracking> {
|
class _TrackingState extends State<Tracking> {
|
||||||
List<LatLng> pathList = [];
|
final TrackingService _trackingService = TrackingService();
|
||||||
StreamSubscription<Position>? positionStream;
|
|
||||||
LocationMarkerPosition? locationMarkerPosition;
|
LocationMarkerPosition? locationMarkerPosition;
|
||||||
bool positionStreamRunning = false;
|
|
||||||
MapController mapController = MapController();
|
MapController mapController = MapController();
|
||||||
|
StreamSubscription? _positionSubscription;
|
||||||
Random rand = Random();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
// debugging (i guess)
|
super.initState();
|
||||||
// pathList.add(
|
|
||||||
// LatLng(widget.startPosition.latitude, widget.startPosition.longitude),
|
|
||||||
// );
|
|
||||||
|
|
||||||
if (widget.weg.text.isNotEmpty) {
|
if (widget.weg.text.isNotEmpty) {
|
||||||
for (var element in widget.weg.text.split(";")) {
|
for (var element in widget.weg.text.split(";")) {
|
||||||
@@ -47,7 +40,7 @@ class _TrackingState extends State<Tracking> {
|
|||||||
// ignore because the double is short enough then
|
// ignore because the double is short enough then
|
||||||
}
|
}
|
||||||
|
|
||||||
pathList.add(
|
_trackingService.pathList.add(
|
||||||
LatLng(double.parse(posSplit.first), double.parse(posSplit[1])),
|
LatLng(double.parse(posSplit.first), double.parse(posSplit[1])),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -59,7 +52,15 @@ class _TrackingState extends State<Tracking> {
|
|||||||
accuracy: widget.startPosition.accuracy,
|
accuracy: widget.startPosition.accuracy,
|
||||||
);
|
);
|
||||||
|
|
||||||
super.initState();
|
_positionSubscription = _trackingService.positionStream$.listen((position) {
|
||||||
|
setState(() {
|
||||||
|
locationMarkerPosition = LocationMarkerPosition(
|
||||||
|
latitude: position.latitude,
|
||||||
|
longitude: position.longitude,
|
||||||
|
accuracy: position.accuracy,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
GeolocatorService.alwaysPositionEnabled().then((value) {
|
GeolocatorService.alwaysPositionEnabled().then((value) {
|
||||||
if (!value && mounted) {
|
if (!value && mounted) {
|
||||||
@@ -71,101 +72,30 @@ class _TrackingState extends State<Tracking> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
if (positionStream != null) positionStream!.cancel();
|
_positionSubscription?.cancel();
|
||||||
bool isFirst = true;
|
widget.weg.text = _trackingService.getPathAsString();
|
||||||
if (pathList.isNotEmpty) {
|
|
||||||
for (var pos in pathList) {
|
|
||||||
if (!isFirst) {
|
|
||||||
widget.weg.text += ";";
|
|
||||||
} else {
|
|
||||||
isFirst = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
widget.weg.text += "${pos.latitude},${pos.longitude}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NotificationService().deleteNotification();
|
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
final LocationSettings streamLocationSettings = LocationSettings(
|
|
||||||
accuracy: LocationAccuracy.high,
|
|
||||||
distanceFilter: 10,
|
|
||||||
);
|
|
||||||
|
|
||||||
void onTrackingStart() async {
|
|
||||||
// notification handling for tracking in background notification
|
|
||||||
await NotificationService().initNotification();
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
NotificationService().showNotification(
|
|
||||||
title: AppLocalizations.of(context)!.trackingRunningInBackground,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
setState(() {
|
|
||||||
pathList.add(LatLng(position.latitude, position.longitude));
|
|
||||||
// Random value for debugging
|
|
||||||
// pathList.add(LatLng(rand.nextInt(5) + 40, position.longitude));
|
|
||||||
|
|
||||||
locationMarkerPosition = LocationMarkerPosition(
|
|
||||||
latitude: position.latitude,
|
|
||||||
longitude: position.longitude,
|
|
||||||
accuracy: position.accuracy,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (mounted) {
|
|
||||||
SnackBarHelper.showSnackBarMessage(
|
|
||||||
context,
|
|
||||||
AppLocalizations.of(context)!.couldntDeterminePosition,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
positionStream!.onError((e) {
|
|
||||||
NotificationService().deleteNotification();
|
|
||||||
NotificationService().showNotification(title: "ERROR: $e");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.tracking),
|
title: Text(AppLocalizations.of(context)!.tracking),
|
||||||
leading: IconButton(onPressed: () {
|
leading: IconButton(
|
||||||
Navigator.pop(context);
|
onPressed: () {
|
||||||
}, icon: Icon(Icons.arrow_back_rounded)),
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.arrow_back_rounded)
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
if (!positionStreamRunning)
|
if (!_trackingService.isTracking)
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
bool delete =
|
bool delete = await AddEntriesDialogHelper.deleteCompleteRouteDialog(context);
|
||||||
await AddEntriesDialogHelper.deleteCompleteRouteDialog(
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (delete) {
|
if (delete) {
|
||||||
setState(() {
|
setState(() {
|
||||||
pathList = [];
|
_trackingService.clearPath();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -174,32 +104,32 @@ class _TrackingState extends State<Tracking> {
|
|||||||
color: Theme.of(context).colorScheme.errorContainer,
|
color: Theme.of(context).colorScheme.errorContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (positionStreamRunning)
|
if (_trackingService.isTracking)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
positionStreamRunning = false;
|
_trackingService.stopTracking();
|
||||||
positionStream!.cancel();
|
|
||||||
NotificationService().deleteNotification();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Text(AppLocalizations.of(context)!.trackingStop),
|
child: Text(AppLocalizations.of(context)!.trackingStop),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (positionStreamRunning) {
|
setState(() {
|
||||||
positionStreamRunning = false;
|
if (_trackingService.isTracking) {
|
||||||
positionStream?.pause();
|
_trackingService.pauseTracking();
|
||||||
} else {
|
} else {
|
||||||
positionStreamRunning = true;
|
if (_trackingService.positionStream == null) {
|
||||||
onTrackingStart();
|
_trackingService.startTracking(context);
|
||||||
}
|
} else {
|
||||||
setState(() {});
|
_trackingService.resumeTracking();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
child:
|
child: _trackingService.isTracking
|
||||||
positionStreamRunning
|
? Text(AppLocalizations.of(context)!.trackingPause)
|
||||||
? Text(AppLocalizations.of(context)!.trackingPause)
|
: Text(AppLocalizations.of(context)!.trackingStart),
|
||||||
: Text(AppLocalizations.of(context)!.trackingStart)
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -219,8 +149,7 @@ class _TrackingState extends State<Tracking> {
|
|||||||
mapController: mapController,
|
mapController: mapController,
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
interactionOptions: const InteractionOptions(
|
interactionOptions: const InteractionOptions(
|
||||||
flags:
|
flags: InteractiveFlag.pinchZoom |
|
||||||
InteractiveFlag.pinchZoom |
|
|
||||||
InteractiveFlag.drag |
|
InteractiveFlag.drag |
|
||||||
InteractiveFlag.pinchMove,
|
InteractiveFlag.pinchMove,
|
||||||
),
|
),
|
||||||
@@ -235,22 +164,16 @@ class _TrackingState extends State<Tracking> {
|
|||||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
userAgentPackageName: 'de.lupus.apps',
|
userAgentPackageName: 'de.lupus.apps',
|
||||||
),
|
),
|
||||||
if (pathList.isNotEmpty)
|
if (_trackingService.pathList.isNotEmpty)
|
||||||
PolylineLayer(
|
PolylineLayer(
|
||||||
polylines: [
|
polylines: [
|
||||||
Polyline(strokeWidth: 2.0, points: pathList, color: Colors.red),
|
Polyline(
|
||||||
|
strokeWidth: 2.0,
|
||||||
|
points: _trackingService.pathList,
|
||||||
|
color: Colors.red
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// CircleLayer(
|
|
||||||
// circles: [
|
|
||||||
// CircleMarker(
|
|
||||||
// color: Colors.blue,
|
|
||||||
// point: pathList.isEmpty ? widget.startPosition : pathList.last,
|
|
||||||
// radius: 5,
|
|
||||||
// useRadiusInMeter: true,
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
CurrentLocationLayer(),
|
CurrentLocationLayer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
83
lib/services/tracking_service.dart
Normal file
83
lib/services/tracking_service.dart
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:fforte/l10n/app_localizations.dart';
|
||||||
|
import 'package:fforte/services/notification_service.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:geolocator/geolocator.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
|
class TrackingService {
|
||||||
|
static final TrackingService _instance = TrackingService._internal();
|
||||||
|
factory TrackingService() => _instance;
|
||||||
|
TrackingService._internal();
|
||||||
|
|
||||||
|
List<LatLng> pathList = [];
|
||||||
|
StreamSubscription<Position>? positionStream;
|
||||||
|
bool isTracking = false;
|
||||||
|
final _positionController = StreamController<Position>.broadcast();
|
||||||
|
|
||||||
|
Stream<Position> get positionStream$ => _positionController.stream;
|
||||||
|
|
||||||
|
Future<void> startTracking(BuildContext context) async {
|
||||||
|
if (isTracking) return;
|
||||||
|
|
||||||
|
await NotificationService().initNotification();
|
||||||
|
if (context.mounted) {
|
||||||
|
NotificationService().showNotification(
|
||||||
|
title: AppLocalizations.of(context)!.trackingRunningInBackground,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
positionStream = Geolocator.getPositionStream(
|
||||||
|
locationSettings: AndroidSettings(
|
||||||
|
accuracy: LocationAccuracy.high,
|
||||||
|
distanceFilter: 0,
|
||||||
|
foregroundNotificationConfig: ForegroundNotificationConfig(
|
||||||
|
notificationTitle: context.mounted ? AppLocalizations.of(context)!.trackingRunningInBackground : "Tracking",
|
||||||
|
notificationText: "",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).listen((Position? position) {
|
||||||
|
if (position != null) {
|
||||||
|
pathList.add(LatLng(position.latitude, position.longitude));
|
||||||
|
_positionController.add(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
positionStream!.onError((e) {
|
||||||
|
NotificationService().deleteNotification();
|
||||||
|
NotificationService().showNotification(title: "ERROR: $e");
|
||||||
|
});
|
||||||
|
|
||||||
|
isTracking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pauseTracking() {
|
||||||
|
positionStream?.pause();
|
||||||
|
isTracking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resumeTracking() {
|
||||||
|
positionStream?.resume();
|
||||||
|
isTracking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopTracking() {
|
||||||
|
positionStream?.cancel();
|
||||||
|
NotificationService().deleteNotification();
|
||||||
|
isTracking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearPath() {
|
||||||
|
pathList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPathAsString() {
|
||||||
|
return pathList.map((pos) => "${pos.latitude},${pos.longitude}").join(";");
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
stopTracking();
|
||||||
|
_positionController.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user