From 770c1e7c74e17afeb5c112b64a20b900e4243d45 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 15 May 2025 00:33:42 -0700 Subject: [PATCH] Begin implementing GPS related functions, and initial UI for trip and delivery --- .vscode/settings.json | 3 + latest-releases.json | 2 +- lib/consts.dart | 6 +- lib/data.dart | 121 ++++++++++++++++++++++++++++++++++ lib/pages/HomePage.dart | 143 ++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 4 +- 6 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 lib/data.dart diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5f3f6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/latest-releases.json b/latest-releases.json index e8f4557..34609f7 100644 --- a/latest-releases.json +++ b/latest-releases.json @@ -1,3 +1,3 @@ { - "alpha": "1.0.0-dev.5" + "alpha": "1.0.0-dev.6" } diff --git a/lib/consts.dart b/lib/consts.dart index a0d3c5a..636cd5a 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -1,13 +1,17 @@ import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:geolocator/geolocator.dart'; class TTConsts { static get UPDATE_URL => "https://git.zontreck.com/AriasCreations/TimeTracker/raw/branch/main/latest-releases.json"; - static const VERSION = "1.0.0-dev.5"; + static const VERSION = "1.0.0-dev.6"; static bool UPDATE_AVAILABLE = false; static UpdateChannel UPDATE_CHANNEL = UpdateChannel.alpha; + static final LocationSettings LOCATION_SETTINGS = LocationSettings( + accuracy: LocationAccuracy.bestForNavigation, + ); static Future checkUpdate() async { Dio dio = Dio(); diff --git a/lib/data.dart b/lib/data.dart new file mode 100644 index 0000000..3f1f317 --- /dev/null +++ b/lib/data.dart @@ -0,0 +1,121 @@ +import 'package:geolocator/geolocator.dart'; +import 'package:libac_dart/utils/TimeUtils.dart'; +import 'package:timetrack/consts.dart'; + +class SessionData { + static int StartTime = 0; + + static get StartTimeAsDateTime => + DateTime.fromMillisecondsSinceEpoch(StartTime * 1000); + + static bool IsOnTheClock = false; + + static List Trips = []; + + static Delivery? currentDelivery; + static Trip? currentTrip; + List positions = []; + + static Future Login() async { + StartTime = TimeUtils.getUnixTimestamp(); + IsOnTheClock = true; + + bool hasGPS; + + LocationPermission perm; + hasGPS = await Geolocator.isLocationServiceEnabled(); + if (!hasGPS) { + IsOnTheClock = false; + return Future.error("Location services are disabled"); + } + + perm = await Geolocator.checkPermission(); + if (perm == LocationPermission.denied) { + perm = await Geolocator.requestPermission(); + if (perm == LocationPermission.denied) { + IsOnTheClock = false; + return Future.error("Location permissions are denied"); + } + } + + if (perm == LocationPermission.deniedForever) { + IsOnTheClock = false; + return Future.error( + "Location permissions are denied permanently. Login cannot proceed.", + ); + } + } + + static Future Logout() async { + IsOnTheClock = false; + + // TODO: Do other tasks to finalize the saved work data. + } + + static Future GetNewLocation() async { + Position pos = await Geolocator.getCurrentPosition( + locationSettings: TTConsts.LOCATION_SETTINGS, + ); + + return pos; + } + + static Trip GetNewTrip({required double basePay}) { + currentTrip = Trip(BasePay: basePay); + return currentTrip!; + } + + static Delivery GetNewDelivery() { + if (currentTrip != null) { + var dropOff = currentTrip!.startNewDelivery(); + ; + currentDelivery = dropOff; + return dropOff; + } else { + throw Exception("A delivery cannot exist without a trip"); + } + } + + static void EndTrip() { + currentDelivery = null; + currentTrip = null; + } +} + +class Delivery { + double TipAmount = 0; + late Position endLocation; + int StartTime = 0; + DateTime get StartTimeAsDateTime => + DateTime.fromMillisecondsSinceEpoch(StartTime * 1000); + + Delivery() { + StartTime = TimeUtils.getUnixTimestamp(); + } + + Future MarkEndLocation() async { + var pos = await SessionData.GetNewLocation(); + endLocation = pos; + } +} + +class Trip { + List deliveries = []; + + int StartTime = 0; + DateTime get StartTimeAsDateTime => + DateTime.fromMillisecondsSinceEpoch(StartTime * 1000); + + double BasePay = 0.0; + + Trip({required this.BasePay}) { + StartTime = TimeUtils.getUnixTimestamp(); + } + + Delivery startNewDelivery() { + var delivery = Delivery(); + deliveries.add(delivery); + + return delivery; + } +} diff --git a/lib/pages/HomePage.dart b/lib/pages/HomePage.dart index 70455ad..9c7706f 100644 --- a/lib/pages/HomePage.dart +++ b/lib/pages/HomePage.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:libac_dart/utils/TimeUtils.dart'; import 'package:libacflutter/Constants.dart'; +import 'package:libacflutter/Prompt.dart'; import 'package:timetrack/consts.dart'; +import 'package:timetrack/data.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -51,10 +54,150 @@ class _HomePageState extends State { }, leading: Icon(Icons.update), ), + ListTile( + title: Text("RESET APP SESSION"), + onTap: () async { + setState(() { + SessionData.IsOnTheClock = false; + SessionData.StartTime = 0; + SessionData.Trips = []; + SessionData.currentDelivery = null; + SessionData.currentTrip = null; + }); + }, + ), ], ), ), ), + body: SingleChildScrollView( + child: Column( + children: [ + if (!SessionData.IsOnTheClock) + Center( + child: ElevatedButton( + onPressed: () async { + setState(() { + SessionData.Login(); + }); + }, + child: Text("ENGAGE"), + ), + ), + + if (SessionData.IsOnTheClock) GetLoggedInWidgets(), + ], + ), + ), + ); + } + + Widget GetTripWidgets() { + return Column( + children: [ + Text( + "Trip started; Base Pay: \$${SessionData.currentTrip!.BasePay}", + style: TextStyle(fontSize: 18), + ), + Text( + "To end both your current delivery, and the trip, tap on END TRIP", + style: TextStyle(fontSize: 18), + ), + Text( + "You are currently on Delivery #${SessionData.currentTrip!.deliveries.length}", + ), + ElevatedButton( + onPressed: () async { + var reply = await showDialog( + context: context, + builder: (bld) { + return InputPrompt( + title: "What was the tip?", + prompt: "If there was no tip, enter a 0, or just hit submit.", + type: InputPromptType.Number, + ); + }, + ); + + if (reply == null || reply == "") reply = "0"; + double tip = double.parse(reply as String); + SessionData.currentDelivery!.TipAmount = tip; + SessionData.currentDelivery!.MarkEndLocation(); + + SessionData.EndTrip(); + + setState(() {}); + }, + child: Text("END TRIP"), + ), + ], + ); + } + + Widget GetDeliveryWidgets() { + return Column( + children: [ + ElevatedButton( + onPressed: () async { + var reply = await showDialog( + context: context, + builder: (bld) { + return InputPrompt( + title: "What was the tip?", + prompt: + "If there was no tip, enter a 0, or hit submit and leave blank", + type: InputPromptType.Number, + ); + }, + ); + + if (reply == null || reply == "") reply = "0"; + double tip = double.parse(reply as String); + SessionData.currentDelivery!.TipAmount = tip; + SessionData.currentDelivery!.MarkEndLocation(); + SessionData.GetNewDelivery(); + + setState(() {}); + }, + child: Text("Start new delivery"), + ), + ], + ); + } + + Widget GetLoggedInWidgets() { + return Column( + children: [ + Text( + "You are now on the clock\nYour location is being tracked for record keeping purposes.\n\nYou started at ${SessionData.StartTimeAsDateTime}\n\n", + style: TextStyle(fontSize: 18), + ), + if (SessionData.currentTrip != null) GetTripWidgets(), + if (SessionData.currentDelivery != null) GetDeliveryWidgets(), + if (SessionData.currentTrip == null) + ElevatedButton( + onPressed: () async { + var reply = await showDialog( + context: context, + builder: (builder) { + return InputPrompt( + title: "What is the base pay?", + prompt: "Enter the base pay amount below.", + type: InputPromptType.Number, + ); + }, + ); + if (reply == null || reply == "") reply = "0"; + + double basePay = double.parse(reply as String); + SessionData.GetNewTrip(basePay: basePay); + SessionData.GetNewDelivery(); + + setState(() {}); + }, + child: Text("Start New Trip"), + ), + ], ); } } diff --git a/pubspec.yaml b/pubspec.yaml index ddf5916..ce24803 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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-dev.5 +version: 1.0.0-dev.6 environment: sdk: ^3.7.2 @@ -42,6 +42,8 @@ dependencies: version: 1.0.31525+0222 dio: ^5.8.0+1 ota_update: ^7.0.1 + geolocator: ^14.0.0 + free_map: ^2.0.3 dev_dependencies: flutter_test: