Compare commits

..

29 commits
alpha ... main

Author SHA1 Message Date
e3712d3f63 Update latest-releases.json 2025-06-14 02:08:41 +00:00
f4f75da847 Update latest-releases.json 2025-06-07 22:45:29 +00:00
2a5f141aa8 Update latest-releases.json 2025-06-06 22:03:27 +00:00
ceb28c1054 Update latest-releases.json 2025-05-27 13:58:07 -07:00
ed4599657c Update latest-releases.json 2025-05-25 16:30:07 -07:00
6b430b163f Update latest-releases.json 2025-05-25 16:15:34 -07:00
15b5efed49 Update latest-releases.json 2025-05-25 15:46:54 -07:00
30159bd217 Update latest-releases.json 2025-05-25 15:14:58 -07:00
c1c4a6e128 Update latest-releases.json 2025-05-25 15:01:27 -07:00
72d94376e0 Update latest-releases.json 2025-05-25 13:45:59 -07:00
3fe877e12c Update latest-releases.json 2025-05-25 13:34:12 -07:00
a30911b56e Update latest-releases.json 2025-05-25 12:56:37 -07:00
b22d907d68 Update latest-releases.json 2025-05-25 01:25:20 -07:00
d2a8e934c0 Update latest-releases.json 2025-05-24 19:21:01 -07:00
96055bb155 Update latest-releases.json 2025-05-20 09:23:19 -07:00
39e32d9f5d Update latest-releases.json 2025-05-19 01:55:47 -07:00
3a0858d3b5 Update latest-releases.json 2025-05-18 10:38:29 -07:00
727f43f943 Update latest-releases.json 2025-05-17 12:36:52 -07:00
0fcce433a5 Update latest-releases.json 2025-05-17 12:23:00 -07:00
98294c7ee3 Update latest-releases.json 2025-05-17 02:55:55 -07:00
04f861a9b7 Update latest-releases.json 2025-05-17 02:34:40 -07:00
87beb6944f Update latest-releases.json 2025-05-17 02:12:55 -07:00
26a768eced Update latest-releases.json
Signed-off-by: zontreck <zontreck@noreply>
2025-05-17 00:34:30 -07:00
24bb8f49ec Merge pull request 'Merge current work tree' (#1) from beta into main
Reviewed-on: #1
2025-05-16 13:30:33 -07:00
9b50945e3b ci: Add web build task for web.tgz 2025-05-16 11:46:33 -07:00
0b83eaaf3c Begin to add the web interface page 2025-05-16 10:09:41 -07:00
37e5688842 Add ability to tap to copy the session ID 2025-05-16 02:21:25 -07:00
7c5e3360a5 Make some changes to the PHP URL to make it channel specific 2025-05-16 01:38:40 -07:00
762be79df6 php: Add a disclaimer about ai content 2025-05-15 23:14:41 -07:00
11 changed files with 234 additions and 16 deletions

28
Jenkinsfile vendored
View file

@ -44,5 +44,33 @@ pipeline {
} }
} }
} }
stage("Build Web App") {
agent {
label 'linux'
}
steps {
script {
sh '''
#!/bin/bash
flutter build web
cd build/web
tar -cvf ../../web.tgz .
cd ../..
'''
}
}
post {
always {
archiveArtifacts artifacts: "web.tgz"
cleanWs()
}
}
}
} }
} }

View file

@ -1,3 +1,4 @@
{ {
"alpha": "1.0.0-dev.10" "alpha": "1.0.0-dev.10",
"beta": "1.0.0-beta.31"
} }

View file

@ -1,5 +1,4 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:ui';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
@ -7,10 +6,13 @@ import 'package:geolocator/geolocator.dart';
class TTConsts { class TTConsts {
static get UPDATE_URL => static get UPDATE_URL =>
"https://git.zontreck.com/AriasCreations/TimeTracker/raw/branch/main/latest-releases.json"; "https://git.zontreck.com/AriasCreations/TimeTracker/raw/branch/main/latest-releases.json";
static const VERSION = "1.0.0-dev.10"; static get SESSION_SERVER =>
"https://api.zontreck.com/timetrack/${UPDATE_CHANNEL}/timetrack.php";
static const VERSION = "1.0.0-beta.3";
static bool UPDATE_AVAILABLE = false; static bool UPDATE_AVAILABLE = false;
static UpdateChannel UPDATE_CHANNEL = UpdateChannel.alpha; static UpdateChannel UPDATE_CHANNEL = UpdateChannel.beta;
static final LocationSettings LOCATION_SETTINGS = LocationSettings( static final LocationSettings LOCATION_SETTINGS = LocationSettings(
accuracy: LocationAccuracy.bestForNavigation, accuracy: LocationAccuracy.bestForNavigation,
distanceFilter: 15, distanceFilter: 15,

View file

@ -3,6 +3,8 @@ import 'dart:convert';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui'; import 'dart:ui';
import 'package:dio/dio.dart';
import 'package:flutter/material.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';
@ -19,6 +21,8 @@ class SessionData {
static List<Position> positions = []; static List<Position> positions = [];
static late StreamSubscription<Position> _listener; static late StreamSubscription<Position> _listener;
static Callbacks Calls = Callbacks(); 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. /// 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; static bool IsReadOnly = false;
@ -185,10 +189,22 @@ class SessionData {
Trips = []; Trips = [];
positions = []; positions = [];
// TODO: Upload to the server. Dio dio = Dio();
Map<String, dynamic> payload = {"cmd": "create", "data": saveData};
var reply = await dio.post(
TTConsts.SESSION_SERVER,
data: json.encode(payload),
);
Map<String, dynamic> replyJs = json.decode(reply.data as String);
if (replyJs["status"] == "ok") {
print("Successful upload");
LastSessionID = replyJs['session'] as String;
Calls.dispatch();
}
} }
static String SaveData() { static Map<String, dynamic> SaveData() {
Map<String, dynamic> saveData = {}; Map<String, dynamic> saveData = {};
List<Map<String, dynamic>> _trips = []; List<Map<String, dynamic>> _trips = [];
@ -204,11 +220,28 @@ class SessionData {
saveData["trips"] = _trips; saveData["trips"] = _trips;
saveData["positions"] = _pos; saveData["positions"] = _pos;
return json.encode(saveData); return saveData;
} }
void LoadData(String js) { static Future<void> DownloadData() async {
Dio dio = Dio();
Map<String, dynamic> 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<String, dynamic> _js = json.decode(js); Map<String, dynamic> _js = json.decode(js);
if (_js.containsKey("error")) {
LastSessionID = "";
return;
}
List<Map<String, dynamic>> _trips = List<Map<String, dynamic>> _trips =
_js['trips'] as List<Map<String, dynamic>>; _js['trips'] as List<Map<String, dynamic>>;
List<Map<String, dynamic>> _pos = List<Map<String, dynamic>> _pos =

View file

@ -1,9 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:timetrack/consts.dart'; import 'package:timetrack/consts.dart';
import 'package:timetrack/data.dart';
import 'package:timetrack/pages/MainApp.dart'; import 'package:timetrack/pages/MainApp.dart';
Future<void> main() async { Future<void> main() async {
await TTConsts.checkUpdate(); 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()); runApp(MainApp());
} }

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:libacflutter/Constants.dart'; import 'package:libacflutter/Constants.dart';
import 'package:libacflutter/Prompt.dart'; import 'package:libacflutter/Prompt.dart';
import 'package:timetrack/consts.dart'; import 'package:timetrack/consts.dart';
@ -112,10 +113,22 @@ class _HomePageState extends State<HomePage> {
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
if (!SessionData.IsOnTheClock)
Text( Text(
"Hit engage when you are ready to go online and start tracking location data, and trips.", "Hit engage when you are ready to go online and start tracking location data, and trips.",
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
), ),
if (SessionData.LastSessionID.isNotEmpty)
ListTile(
title: Text("Session ID"),
subtitle: Text("${SessionData.LastSessionID} - Tap to copy"),
onTap: () {
Clipboard.setData(
ClipboardData(text: SessionData.LastSessionID),
);
},
),
if (!SessionData.IsOnTheClock) if (!SessionData.IsOnTheClock)
Center( Center(
child: ElevatedButton( child: ElevatedButton(

View file

@ -1,7 +1,10 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:timetrack/pages/HomePage.dart'; import 'package:timetrack/pages/HomePage.dart';
import 'package:timetrack/pages/MapPage.dart'; import 'package:timetrack/pages/MapPage.dart';
import 'package:timetrack/pages/UpdateSettings.dart'; import 'package:timetrack/pages/UpdateSettings.dart';
import 'package:timetrack/pages/WebMainPage.dart';
import 'package:timetrack/pages/WorkData.dart'; import 'package:timetrack/pages/WorkData.dart';
class MainApp extends StatefulWidget { class MainApp extends StatefulWidget {
@ -24,7 +27,7 @@ class MainAppState extends State<MainApp> {
return MaterialApp( return MaterialApp(
title: "Time Tracker", title: "Time Tracker",
routes: { routes: {
"/": (ctx) => HomePage(), "/": (ctx) => Platform.isAndroid ? HomePage() : WebMain(),
"/upd": (ctx) => UpdateSettingsPage(), "/upd": (ctx) => UpdateSettingsPage(),
"/map": (ctx) => MapPage(), "/map": (ctx) => MapPage(),
"/work": (ctx) => WorkDataPage(), "/work": (ctx) => WorkDataPage(),

128
lib/pages/WebMainPage.dart Normal file
View file

@ -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<StatefulWidget> createState() {
return _WebMain();
}
}
class _WebMain extends State<WebMain> {
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)),
),
],
);
}
}

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:libacflutter/Constants.dart'; import 'package:libacflutter/Constants.dart';
import 'package:timetrack/consts.dart';
import 'package:timetrack/data.dart'; import 'package:timetrack/data.dart';
class WorkDataPage extends StatefulWidget { class WorkDataPage extends StatefulWidget {
@ -70,7 +69,7 @@ class _WorkData extends State<WorkDataPage> {
), ),
SizedBox(height: 20), SizedBox(height: 20),
Text( Text(
"Total Miles: ${SessionData.GetTotalMilesAsString()}", "Total Estimated Miles: ${SessionData.GetTotalMilesAsString()}\n(Note: The miles displayed above may not be 100% accurate)",
style: TextStyle(fontSize: 24), style: TextStyle(fontSize: 24),
), ),
], ],

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-dev.10 version: 1.0.0-beta.3
environment: environment:
sdk: ^3.7.2 sdk: ^3.7.2

View file

@ -10,6 +10,7 @@ $DB = get_DB("timetrack");
$jsx = json_decode(file_get_contents("php://input"), true); $jsx = json_decode(file_get_contents("php://input"), true);
// Get operation information // Get operation information
// DISCLAIMER: All php code below this point is AI Generated
switch($jsx['cmd']) { switch($jsx['cmd']) {
case "create": { case "create": {
// Get UUID from MySQL and insert into sessions table // Get UUID from MySQL and insert into sessions table