Fix background svc; Add end time logging to trips
This commit is contained in:
parent
1e01384d9b
commit
35863780f6
6 changed files with 99 additions and 15 deletions
|
@ -10,7 +10,9 @@
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
|
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="Time Tracker"
|
android:label="Time Tracker"
|
||||||
|
@ -47,6 +49,11 @@
|
||||||
<provider android:name="sk.fourq.otaupdate.OtaUpdateFileProvider" android:authorities="${applicationId}.ota_update_provider" android:exported="false" android:grantUriPermissions="true">
|
<provider android:name="sk.fourq.otaupdate.OtaUpdateFileProvider" android:authorities="${applicationId}.ota_update_provider" android:exported="false" android:grantUriPermissions="true">
|
||||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
|
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="de.julianassmann.flutter_background.IsolateHolderService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="location" />
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
|
|
|
@ -9,7 +9,7 @@ class TTConsts {
|
||||||
static get SESSION_SERVER =>
|
static get SESSION_SERVER =>
|
||||||
"https://api.zontreck.com/timetrack/$UPDATE_CHANNEL/timetrack.php";
|
"https://api.zontreck.com/timetrack/$UPDATE_CHANNEL/timetrack.php";
|
||||||
|
|
||||||
static const VERSION = "1.0.0-beta.12";
|
static const VERSION = "1.0.0-beta.13";
|
||||||
|
|
||||||
static bool UPDATE_AVAILABLE = false;
|
static bool UPDATE_AVAILABLE = false;
|
||||||
static UpdateChannel UPDATE_CHANNEL = UpdateChannel.beta;
|
static UpdateChannel UPDATE_CHANNEL = UpdateChannel.beta;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'dart:ui';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_background/flutter_background.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:libac_dart/nbt/Stream.dart';
|
import 'package:libac_dart/nbt/Stream.dart';
|
||||||
import 'package:timetrack/consts.dart';
|
import 'package:timetrack/consts.dart';
|
||||||
|
@ -25,6 +26,7 @@ class SessionData {
|
||||||
static String LastSessionID = "";
|
static String LastSessionID = "";
|
||||||
static String DisplayError = "";
|
static String DisplayError = "";
|
||||||
static double? TotalPay;
|
static double? TotalPay;
|
||||||
|
static bool ContainsTripTimes = true;
|
||||||
|
|
||||||
/// Is true if the try-catch is tripped or if not running on Android
|
/// Is true if the try-catch is tripped or if not running on Android
|
||||||
static bool isWeb = false;
|
static bool isWeb = false;
|
||||||
|
@ -51,6 +53,27 @@ class SessionData {
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String GetPaidHours() {
|
||||||
|
return Duration2Notation(_GetPaidHours());
|
||||||
|
}
|
||||||
|
|
||||||
|
static Duration _GetPaidHours() {
|
||||||
|
Duration stamp = Duration();
|
||||||
|
for (var trip in Trips) {
|
||||||
|
stamp += trip.EndTime.difference(trip.StartTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String GetUnpaidHours() {
|
||||||
|
// This is the inverted value of paid hours. We get total hours and subtract it from the paid hours. This gives us the unpaid hours.
|
||||||
|
Duration totalTime = EndTime.difference(StartTime);
|
||||||
|
Duration unpaid = _GetPaidHours() - totalTime;
|
||||||
|
|
||||||
|
return Duration2Notation(unpaid);
|
||||||
|
}
|
||||||
|
|
||||||
static String GetTotalMilesAsString() {
|
static String GetTotalMilesAsString() {
|
||||||
double miles = GetTotalMiles();
|
double miles = GetTotalMiles();
|
||||||
if (miles == 0) return "0.0";
|
if (miles == 0) return "0.0";
|
||||||
|
@ -126,7 +149,21 @@ class SessionData {
|
||||||
}
|
}
|
||||||
//** End AI Generated code */
|
//** End AI Generated code */
|
||||||
|
|
||||||
static Future<void> Login() async {
|
static Future<bool> Login() async {
|
||||||
|
final androidConfig = FlutterBackgroundAndroidConfig(
|
||||||
|
notificationTitle: "Time Tracker",
|
||||||
|
notificationText:
|
||||||
|
"Background notification for keeping TimeTrack running in the background",
|
||||||
|
notificationImportance: AndroidNotificationImportance.normal,
|
||||||
|
);
|
||||||
|
bool success = await FlutterBackground.initialize(
|
||||||
|
androidConfig: androidConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
StartTime = DateTime.now();
|
StartTime = DateTime.now();
|
||||||
IsOnTheClock = true;
|
IsOnTheClock = true;
|
||||||
|
|
||||||
|
@ -136,7 +173,7 @@ class SessionData {
|
||||||
hasGPS = await Geolocator.isLocationServiceEnabled();
|
hasGPS = await Geolocator.isLocationServiceEnabled();
|
||||||
if (!hasGPS) {
|
if (!hasGPS) {
|
||||||
IsOnTheClock = false;
|
IsOnTheClock = false;
|
||||||
return Future.error("Location services are disabled");
|
return await Future.error("Location services are disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
perm = await Geolocator.checkPermission();
|
perm = await Geolocator.checkPermission();
|
||||||
|
@ -144,13 +181,13 @@ class SessionData {
|
||||||
perm = await Geolocator.requestPermission();
|
perm = await Geolocator.requestPermission();
|
||||||
if (perm == LocationPermission.denied) {
|
if (perm == LocationPermission.denied) {
|
||||||
IsOnTheClock = false;
|
IsOnTheClock = false;
|
||||||
return Future.error("Location permissions are denied");
|
return await Future.error("Location permissions are denied");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (perm == LocationPermission.deniedForever) {
|
if (perm == LocationPermission.deniedForever) {
|
||||||
IsOnTheClock = false;
|
IsOnTheClock = false;
|
||||||
return Future.error(
|
return await Future.error(
|
||||||
"Location permissions are denied permanently. Login cannot proceed.",
|
"Location permissions are denied permanently. Login cannot proceed.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -166,6 +203,8 @@ class SessionData {
|
||||||
|
|
||||||
SessionData.Calls.dispatch();
|
SessionData.Calls.dispatch();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> Logout() async {
|
static Future<void> Logout() async {
|
||||||
|
@ -305,11 +344,14 @@ class SessionData {
|
||||||
/// [b] is the end time
|
/// [b] is the end time
|
||||||
static String GetTotalTimeWorked(DateTime a, DateTime b) {
|
static String GetTotalTimeWorked(DateTime a, DateTime b) {
|
||||||
Duration diff = b.difference(a);
|
Duration diff = b.difference(a);
|
||||||
|
return Duration2Notation(diff);
|
||||||
|
}
|
||||||
|
|
||||||
int days = diff.inDays;
|
static String Duration2Notation(Duration time) {
|
||||||
int hours = diff.inHours.remainder(24);
|
int days = time.inDays;
|
||||||
int minutes = diff.inMinutes.remainder(60);
|
int hours = time.inHours.remainder(24);
|
||||||
int seconds = diff.inSeconds.remainder(60);
|
int minutes = time.inMinutes.remainder(60);
|
||||||
|
int seconds = time.inSeconds.remainder(60);
|
||||||
|
|
||||||
List<String> parts = [];
|
List<String> parts = [];
|
||||||
|
|
||||||
|
@ -361,6 +403,7 @@ class Trip {
|
||||||
List<Delivery> deliveries = [];
|
List<Delivery> deliveries = [];
|
||||||
|
|
||||||
DateTime StartTime = DateTime(0);
|
DateTime StartTime = DateTime(0);
|
||||||
|
DateTime EndTime = DateTime(0);
|
||||||
|
|
||||||
Trip() {
|
Trip() {
|
||||||
StartTime = DateTime.now();
|
StartTime = DateTime.now();
|
||||||
|
@ -374,7 +417,10 @@ class Trip {
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJsonMap() {
|
Map<String, dynamic> toJsonMap() {
|
||||||
Map<String, Object> trip = {"start": StartTime.toString()};
|
Map<String, Object> trip = {
|
||||||
|
"start": StartTime.toString(),
|
||||||
|
"end": EndTime.toString(),
|
||||||
|
};
|
||||||
List<Map<String, dynamic>> dropOffs = [];
|
List<Map<String, dynamic>> dropOffs = [];
|
||||||
for (var delivery in deliveries) {
|
for (var delivery in deliveries) {
|
||||||
dropOffs.add(delivery.toJsonMap());
|
dropOffs.add(delivery.toJsonMap());
|
||||||
|
@ -388,6 +434,10 @@ class Trip {
|
||||||
static Trip fromJsonMap(Map<String, dynamic> jsx) {
|
static Trip fromJsonMap(Map<String, dynamic> jsx) {
|
||||||
Trip trip = Trip();
|
Trip trip = Trip();
|
||||||
trip.StartTime = DateTime.parse(jsx['start'] as String);
|
trip.StartTime = DateTime.parse(jsx['start'] as String);
|
||||||
|
if (jsx.containsKey("end")) {
|
||||||
|
trip.EndTime = DateTime.parse(jsx['end'] as String);
|
||||||
|
} else
|
||||||
|
SessionData.ContainsTripTimes = false;
|
||||||
trip.deliveries = [];
|
trip.deliveries = [];
|
||||||
List<dynamic> dropOffs = jsx['deliveries'] as List<dynamic>;
|
List<dynamic> dropOffs = jsx['deliveries'] as List<dynamic>;
|
||||||
|
|
||||||
|
|
|
@ -151,9 +151,17 @@ class _HomePageState extends State<HomePage> {
|
||||||
Center(
|
Center(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
setState(() {
|
var result = await SessionData.Login();
|
||||||
SessionData.Login();
|
if (!result) {
|
||||||
});
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
"Fatal Error: Could not establish the background service for keeping the app alive.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: Text("ENGAGE"),
|
child: Text("ENGAGE"),
|
||||||
),
|
),
|
||||||
|
|
|
@ -28,6 +28,15 @@ class _WorkData extends State<WorkDataPage> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget GetDurationWidgets() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Text("Paid Driving Hours: ${SessionData.GetPaidHours()}"),
|
||||||
|
Text("Unpaid Driving Hours: ${SessionData.GetUnpaidHours()}"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -77,6 +86,15 @@ class _WorkData extends State<WorkDataPage> {
|
||||||
),
|
),
|
||||||
tileColor: LibACFlutterConstants.TITLEBAR_COLOR,
|
tileColor: LibACFlutterConstants.TITLEBAR_COLOR,
|
||||||
),
|
),
|
||||||
|
if (SessionData.ContainsTripTimes) GetDurationWidgets(),
|
||||||
|
if (!SessionData.ContainsTripTimes)
|
||||||
|
ListTile(
|
||||||
|
title: Text("ERROR"),
|
||||||
|
subtitle: Text(
|
||||||
|
"This TTX session file is older than Beta 13. It does not contain trip end times. For that reason, the app cannot display the paid driving hours and unpaid driving hours.",
|
||||||
|
),
|
||||||
|
tileColor: LibACFlutterConstants.TITLEBAR_COLOR,
|
||||||
|
),
|
||||||
if (SessionData.TotalPay != null)
|
if (SessionData.TotalPay != null)
|
||||||
Text(
|
Text(
|
||||||
"Total Pay: \$${SessionData.TotalPay!}",
|
"Total Pay: \$${SessionData.TotalPay!}",
|
||||||
|
|
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.0-beta.12
|
version: 1.0.0-beta.13
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.2
|
sdk: ^3.7.2
|
||||||
|
@ -47,6 +47,7 @@ dependencies:
|
||||||
latlong2: ^0.9.1
|
latlong2: ^0.9.1
|
||||||
flutter_map_tile_caching: ^10.1.1
|
flutter_map_tile_caching: ^10.1.1
|
||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
|
flutter_background: ^1.3.0+1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue