Do some refactoring for protocol v2

This commit is contained in:
zontreck 2025-06-15 02:05:52 -07:00
parent d9c79a4ee9
commit 8adaf6169a
5 changed files with 122 additions and 3 deletions

View file

@ -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.31"; static const VERSION = "1.0.0-beta.32";
static bool UPDATE_AVAILABLE = false; static bool UPDATE_AVAILABLE = false;
static UpdateChannel UPDATE_CHANNEL = UpdateChannel.beta; static UpdateChannel UPDATE_CHANNEL = UpdateChannel.beta;

View file

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui'; import 'dart:ui';
@ -45,6 +46,12 @@ class SessionData {
static bool IsSavedData = false; static bool IsSavedData = false;
static String SaveDataType = ""; static String SaveDataType = "";
/// This indicates whether the app is in a live session.
static bool Recording = false;
/// This is the version number of the recording as specified by the server.
static int RecordingVersion = 0;
/// 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;
@ -236,6 +243,8 @@ class SessionData {
); );
} }
await _create();
_listener = Geolocator.getPositionStream( _listener = Geolocator.getPositionStream(
locationSettings: TTConsts.LOCATION_SETTINGS, locationSettings: TTConsts.LOCATION_SETTINGS,
).listen((pos) { ).listen((pos) {
@ -274,6 +283,50 @@ class SessionData {
_upload(nbtData); _upload(nbtData);
} }
/// v2 Create function.
///
/// This function sets the Session ID globally. It will also set the Recording flag to true.
static Future<void> _create() async {
Dio dio = Dio();
Map<String, dynamic> payload = {"cmd": "createv2"};
try {
var reply = await dio.post(
TTConsts.SESSION_SERVER,
data: json.encode(payload),
);
if (reply.statusCode == null) {
throw Exception("Fatal error while creating");
}
if (reply.statusCode! != 200) {
throw Exception("Fatal error while creating");
}
Map<String, dynamic> replyJs = json.decode(reply.data as String);
if (replyJs["status"] == "created") {
print("Successful creation");
LastSessionID = replyJs['session'] as String;
Recording = true;
RecordingVersion = 0;
Calls.dispatch();
}
} catch (E) {
// Retry in 2 seconds
DisplayError =
"Error: Something went wrong during session creation. Retry in 5 seconds...";
Calls.dispatch();
Timer(Duration(seconds: 5), () {
_create();
});
}
}
/// Deprecated: This function uploads using the v1 API, and is intended to be called at the conclusion of data recording.
@Deprecated(
"This function utilizes the Protocol v1 Create method, which has been replaced by createv2 and patch. This function will be removed.",
)
static Future<void> _upload(List<int> nbtData) async { static Future<void> _upload(List<int> nbtData) async {
Dio dio = Dio(); Dio dio = Dio();
@ -325,6 +378,7 @@ class SessionData {
DisplayError = ""; DisplayError = "";
IsReadOnly = false; IsReadOnly = false;
ContainsTripTimes = true; ContainsTripTimes = true;
Recording = false;
if (FlutterBackground.isBackgroundExecutionEnabled) { if (FlutterBackground.isBackgroundExecutionEnabled) {
FlutterBackground.disableBackgroundExecution(); FlutterBackground.disableBackgroundExecution();
} }
@ -367,6 +421,15 @@ class SessionData {
static Future<void> _deserialize(CompoundTag ct) async { static Future<void> _deserialize(CompoundTag ct) async {
IsOnTheClock = NbtUtils.readBoolean(ct, "inprog"); IsOnTheClock = NbtUtils.readBoolean(ct, "inprog");
if (ct.containsKey("record"))
Recording = NbtUtils.readBoolean(ct, "record");
else
Recording = false;
if (Recording && isWeb) {
IsOnTheClock = false;
IsReadOnly = true;
}
if (IsOnTheClock) { if (IsOnTheClock) {
await Login(); await Login();
@ -416,6 +479,7 @@ class SessionData {
CompoundTag ct = CompoundTag(); CompoundTag ct = CompoundTag();
NbtUtils.writeBoolean(ct, "inprog", IsOnTheClock); NbtUtils.writeBoolean(ct, "inprog", IsOnTheClock);
NbtUtils.writeBoolean(ct, "record", Recording);
// No need to write the contains trip times flag, it is set during deserialization. For inprog sessions, it will be set to true by the system. // No need to write the contains trip times flag, it is set during deserialization. For inprog sessions, it will be set to true by the system.
ct.put("start", StringTag.valueOf(StartTime.toIso8601String())); ct.put("start", StringTag.valueOf(StartTime.toIso8601String()));
if (EndTime.year > 2000) { if (EndTime.year > 2000) {
@ -603,6 +667,29 @@ class SessionData {
return tm.toString(); return tm.toString();
} }
static Future<int> FetchVersion() async {
Dio dio = Dio();
var packet = json.encode({"cmd": "get_version", "id": LastSessionID});
var response = await dio.post(TTConsts.SESSION_SERVER, data: packet);
if (response.statusCode == null) {
DisplayError = "Error: Version retrieval failed";
return -1;
}
if (response.statusCode! != 200) {
DisplayError = "Error: Session server HTTP Response code";
return -2;
}
var reply = json.decode(response.data as String);
if (reply["status"] == "version_back") {
return reply["version"] as int;
} else {
DisplayError = "Error: Unknown get_version reply";
return -3;
}
}
} }
class Delivery { class Delivery {

View file

@ -411,7 +411,7 @@ class _HomePageState extends State<HomePage> {
return Column( return Column(
children: [ children: [
Text( Text(
"You are now on the clock\nYour location is being tracked for record keeping purposes.\n\nYou started ${SessionData.GetTotalTimeWorked(SessionData.StartTime, DateTime.now())} ago\n\n", "Your location is being tracked for record keeping purposes.\n\nYou started ${SessionData.GetTotalTimeWorked(SessionData.StartTime, DateTime.now())} ago\n\n",
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
), ),
if (SessionData.currentTrip != null) GetTripWidgets(), if (SessionData.currentTrip != null) GetTripWidgets(),

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:libacflutter/Constants.dart'; import 'package:libacflutter/Constants.dart';
import 'package:timetrack/consts.dart'; import 'package:timetrack/consts.dart';
@ -19,9 +21,31 @@ class _WebMain extends State<WebMain> {
@override @override
void didChangeDependencies() { void didChangeDependencies() {
sessionIDController.text = SessionData.LastSessionID; sessionIDController.text = SessionData.LastSessionID;
// Check if FirstRun
if (SessionData.Calls.HomeCallback == null) {
SessionData.Calls.HomeCallback = _callback;
// After doing this, we also want to schedule the timer
Timer.periodic(Duration(seconds: 5), (timer) async {
if (!SessionData.Recording) {
timer.cancel();
return;
}
// Fetch the latest version number, compare, then redownload the data.
int ver = await SessionData.FetchVersion();
if (ver != SessionData.RecordingVersion) {
await SessionData.DownloadData();
}
});
}
super.didChangeDependencies(); super.didChangeDependencies();
} }
Future<void> _callback() async {
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -132,6 +156,14 @@ class _WebMain extends State<WebMain> {
), ),
tileColor: const Color.fromARGB(255, 7, 123, 255), tileColor: const Color.fromARGB(255, 7, 123, 255),
), ),
if (SessionData.Recording)
ListTile(
title: Text("LIVE SESSION"),
subtitle: Text(
"This session is live! Recording is still in progress. Over time this live view will automatically refresh until the recording is ended.\n\nSession Version: ${SessionData.RecordingVersion}",
),
tileColor: LibACFlutterConstants.TITLEBAR_COLOR,
),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
await showDialog( await showDialog(

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 # 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.31 version: 1.0.0-beta.32
environment: environment:
sdk: ^3.7.2 sdk: ^3.7.2