diff --git a/latest-releases.json b/latest-releases.json index 59e46f8..5922860 100644 --- a/latest-releases.json +++ b/latest-releases.json @@ -1,4 +1,4 @@ { "alpha": "1.0.0-dev.10", - "beta": "1.0.0-beta.2" + "beta": "1.0.0-beta.3" } diff --git a/lib/consts.dart b/lib/consts.dart index 51df1d6..85c2227 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -9,10 +9,10 @@ class TTConsts { static get SESSION_SERVER => "https://api.zontreck.com/timetrack/${UPDATE_CHANNEL}/timetrack.php"; - static const VERSION = "1.0.0-beta.2"; + static const VERSION = "1.0.0-beta.3"; static bool UPDATE_AVAILABLE = false; - static UpdateChannel UPDATE_CHANNEL = UpdateChannel.alpha; + static UpdateChannel UPDATE_CHANNEL = UpdateChannel.beta; static final LocationSettings LOCATION_SETTINGS = LocationSettings( accuracy: LocationAccuracy.bestForNavigation, distanceFilter: 15, diff --git a/lib/data.dart b/lib/data.dart index 4bf36ff..460585f 100644 --- a/lib/data.dart +++ b/lib/data.dart @@ -22,6 +22,7 @@ class SessionData { static late StreamSubscription _listener; static Callbacks Calls = Callbacks(); static String LastSessionID = ""; + static String DisplayError = ""; /// This flag is usually set when data is loaded from a saved state. Or when accessed using the Web version of the app. static bool IsReadOnly = false; @@ -222,8 +223,25 @@ class SessionData { return saveData; } - void LoadData(String js) { + static Future DownloadData() async { + Dio dio = Dio(); + Map payload = {"cmd": "get", "id": LastSessionID}; + + // Send the data, and get the response + var reply = await dio.post( + TTConsts.SESSION_SERVER, + data: json.encode(payload), + ); + + LoadData(reply.data as String); + } + + static void LoadData(String js) { Map _js = json.decode(js); + if (_js.containsKey("error")) { + LastSessionID = ""; + return; + } List> _trips = _js['trips'] as List>; List> _pos = diff --git a/lib/main.dart b/lib/main.dart index 3c1c510..fef79bc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,19 @@ import 'package:flutter/material.dart'; import 'package:timetrack/consts.dart'; +import 'package:timetrack/data.dart'; import 'package:timetrack/pages/MainApp.dart'; Future main() async { await TTConsts.checkUpdate(); + var sess = Uri.base.queryParameters["code"] ?? ""; + SessionData.LastSessionID = sess; + if (SessionData.LastSessionID.isNotEmpty) { + await SessionData.DownloadData(); + if (SessionData.LastSessionID.isEmpty) { + // Invalid session token + SessionData.DisplayError = "The URL and or session token is invalid"; + } + } runApp(MainApp()); } diff --git a/lib/pages/MainApp.dart b/lib/pages/MainApp.dart index 4a01ae0..087fd6f 100644 --- a/lib/pages/MainApp.dart +++ b/lib/pages/MainApp.dart @@ -1,7 +1,10 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:timetrack/pages/HomePage.dart'; import 'package:timetrack/pages/MapPage.dart'; import 'package:timetrack/pages/UpdateSettings.dart'; +import 'package:timetrack/pages/WebMainPage.dart'; import 'package:timetrack/pages/WorkData.dart'; class MainApp extends StatefulWidget { @@ -24,7 +27,7 @@ class MainAppState extends State { return MaterialApp( title: "Time Tracker", routes: { - "/": (ctx) => HomePage(), + "/": (ctx) => Platform.isAndroid ? HomePage() : WebMain(), "/upd": (ctx) => UpdateSettingsPage(), "/map": (ctx) => MapPage(), "/work": (ctx) => WorkDataPage(), diff --git a/lib/pages/WebMainPage.dart b/lib/pages/WebMainPage.dart new file mode 100644 index 0000000..41db49f --- /dev/null +++ b/lib/pages/WebMainPage.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:libacflutter/Constants.dart'; +import 'package:timetrack/consts.dart'; +import 'package:timetrack/data.dart'; + +class WebMain extends StatefulWidget { + @override + State createState() { + return _WebMain(); + } +} + +class _WebMain extends State { + TextEditingController sessionIDController = TextEditingController(); + + @override + void didChangeDependencies() { + sessionIDController.text = SessionData.LastSessionID; + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Time Tracker"), + backgroundColor: LibACFlutterConstants.TITLEBAR_COLOR, + ), + drawer: Drawer( + elevation: 8, + child: SingleChildScrollView( + child: Column( + children: [ + DrawerHeader( + child: Column( + children: [ + Text("Time Tracker"), + Text("Created by Tara Piccari"), + Text("Copyright 2025 - Present"), + Text("Version: ${TTConsts.VERSION}"), + ], + ), + ), + if (SessionData.IsReadOnly) + ListTile( + title: Text("Trip Map"), + leading: Icon(Icons.map), + subtitle: Text( + "View a map of the route\n(NOTE: This is not live, and reflects the current state as of the time the map is opened.)", + ), + onTap: () async { + await Navigator.pushNamed(context, "/map"); + }, + ), + if (SessionData.IsReadOnly) + ListTile( + title: Text("Work Data"), + subtitle: Text("View work data"), + leading: Icon(Icons.work_history), + onTap: () async { + // Open up the work data viewer and editor. + // Edit will be disabled for web or read only mode. + await Navigator.pushNamed(context, "/work"); + setState(() {}); + }, + ), + ], + ), + ), + ), + body: Padding( + padding: EdgeInsets.all(8), + child: SingleChildScrollView( + child: Column( + children: [ + // Start doing magic! + if (SessionData.DisplayError.isNotEmpty) + Text(SessionData.DisplayError, style: TextStyle(fontSize: 18)), + // Check what widgets need to be displayed. + if (SessionData.IsReadOnly) GetReadOnlyWidgets(), + if (!SessionData.IsReadOnly) GetLoadWidgets(), + ], + ), + ), + ), + ); + } + + Widget GetReadOnlyWidgets() { + return Column( + children: [ + Text( + "Use the top left menu to show the various pages for the data viewer.", + ), + ElevatedButton( + onPressed: () async { + SessionData.IsReadOnly = false; + SessionData.Trips = []; + SessionData.positions = []; + SessionData.DisplayError = ""; + }, + child: Text("Close Session"), + ), + ], + ); + } + + Widget GetLoadWidgets() { + return Column( + children: [ + // Present a text box for the session ID, and a button for loading. + ListTile(title: Text("Session ID")), + TextField( + controller: sessionIDController, + decoration: InputDecoration(border: OutlineInputBorder()), + ), + ElevatedButton( + onPressed: () async { + await SessionData.DownloadData(); + setState(() {}); + }, + child: Text("Load Session", style: TextStyle(fontSize: 18)), + ), + ], + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 0781771..dd244a5 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-beta.2 +version: 1.0.0-beta.3 environment: sdk: ^3.7.2