Fix background svc; Add end time logging to trips

This commit is contained in:
zontreck 2025-05-20 09:21:13 -07:00
parent 1e01384d9b
commit 35863780f6
6 changed files with 99 additions and 15 deletions

View file

@ -10,7 +10,9 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.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
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">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
</provider>
<service
android:name="de.julianassmann.flutter_background.IsolateHolderService"
android:exported="false"
android:foregroundServiceType="location" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and

View file

@ -9,7 +9,7 @@ class TTConsts {
static get SESSION_SERVER =>
"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 UpdateChannel UPDATE_CHANNEL = UpdateChannel.beta;

View file

@ -5,6 +5,7 @@ import 'dart:ui';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_background/flutter_background.dart';
import 'package:geolocator/geolocator.dart';
import 'package:libac_dart/nbt/Stream.dart';
import 'package:timetrack/consts.dart';
@ -25,6 +26,7 @@ class SessionData {
static String LastSessionID = "";
static String DisplayError = "";
static double? TotalPay;
static bool ContainsTripTimes = true;
/// Is true if the try-catch is tripped or if not running on Android
static bool isWeb = false;
@ -51,6 +53,27 @@ class SessionData {
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() {
double miles = GetTotalMiles();
if (miles == 0) return "0.0";
@ -126,7 +149,21 @@ class SessionData {
}
//** 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();
IsOnTheClock = true;
@ -136,7 +173,7 @@ class SessionData {
hasGPS = await Geolocator.isLocationServiceEnabled();
if (!hasGPS) {
IsOnTheClock = false;
return Future.error("Location services are disabled");
return await Future.error("Location services are disabled");
}
perm = await Geolocator.checkPermission();
@ -144,13 +181,13 @@ class SessionData {
perm = await Geolocator.requestPermission();
if (perm == LocationPermission.denied) {
IsOnTheClock = false;
return Future.error("Location permissions are denied");
return await Future.error("Location permissions are denied");
}
}
if (perm == LocationPermission.deniedForever) {
IsOnTheClock = false;
return Future.error(
return await Future.error(
"Location permissions are denied permanently. Login cannot proceed.",
);
}
@ -166,6 +203,8 @@ class SessionData {
SessionData.Calls.dispatch();
});
return true;
}
static Future<void> Logout() async {
@ -305,11 +344,14 @@ class SessionData {
/// [b] is the end time
static String GetTotalTimeWorked(DateTime a, DateTime b) {
Duration diff = b.difference(a);
return Duration2Notation(diff);
}
int days = diff.inDays;
int hours = diff.inHours.remainder(24);
int minutes = diff.inMinutes.remainder(60);
int seconds = diff.inSeconds.remainder(60);
static String Duration2Notation(Duration time) {
int days = time.inDays;
int hours = time.inHours.remainder(24);
int minutes = time.inMinutes.remainder(60);
int seconds = time.inSeconds.remainder(60);
List<String> parts = [];
@ -361,6 +403,7 @@ class Trip {
List<Delivery> deliveries = [];
DateTime StartTime = DateTime(0);
DateTime EndTime = DateTime(0);
Trip() {
StartTime = DateTime.now();
@ -374,7 +417,10 @@ class Trip {
}
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 = [];
for (var delivery in deliveries) {
dropOffs.add(delivery.toJsonMap());
@ -388,6 +434,10 @@ class Trip {
static Trip fromJsonMap(Map<String, dynamic> jsx) {
Trip trip = Trip();
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 = [];
List<dynamic> dropOffs = jsx['deliveries'] as List<dynamic>;

View file

@ -151,9 +151,17 @@ class _HomePageState extends State<HomePage> {
Center(
child: ElevatedButton(
onPressed: () async {
setState(() {
SessionData.Login();
});
var result = await 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"),
),

View file

@ -28,6 +28,15 @@ class _WorkData extends State<WorkDataPage> {
super.dispose();
}
Widget GetDurationWidgets() {
return Column(
children: [
Text("Paid Driving Hours: ${SessionData.GetPaidHours()}"),
Text("Unpaid Driving Hours: ${SessionData.GetUnpaidHours()}"),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -77,6 +86,15 @@ class _WorkData extends State<WorkDataPage> {
),
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)
Text(
"Total Pay: \$${SessionData.TotalPay!}",

View file

@ -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
# 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.
version: 1.0.0-beta.12
version: 1.0.0-beta.13
environment:
sdk: ^3.7.2
@ -47,6 +47,7 @@ dependencies:
latlong2: ^0.9.1
flutter_map_tile_caching: ^10.1.1
url_launcher: ^6.3.1
flutter_background: ^1.3.0+1
dev_dependencies:
flutter_test: