Compare commits
29 commits
Author | SHA1 | Date | |
---|---|---|---|
e3712d3f63 | |||
f4f75da847 | |||
2a5f141aa8 | |||
ceb28c1054 | |||
ed4599657c | |||
6b430b163f | |||
15b5efed49 | |||
30159bd217 | |||
c1c4a6e128 | |||
72d94376e0 | |||
3fe877e12c | |||
a30911b56e | |||
b22d907d68 | |||
d2a8e934c0 | |||
96055bb155 | |||
39e32d9f5d | |||
3a0858d3b5 | |||
727f43f943 | |||
0fcce433a5 | |||
98294c7ee3 | |||
04f861a9b7 | |||
87beb6944f | |||
26a768eced | |||
24bb8f49ec | |||
9b50945e3b | |||
0b83eaaf3c | |||
37e5688842 | |||
7c5e3360a5 | |||
762be79df6 |
11 changed files with 234 additions and 16 deletions
28
Jenkinsfile
vendored
28
Jenkinsfile
vendored
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"alpha": "1.0.0-dev.10"
|
"alpha": "1.0.0-dev.10",
|
||||||
|
"beta": "1.0.0-beta.31"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: [
|
||||||
Text(
|
if (!SessionData.IsOnTheClock)
|
||||||
"Hit engage when you are ready to go online and start tracking location data, and trips.",
|
Text(
|
||||||
style: TextStyle(fontSize: 18),
|
"Hit engage when you are ready to go online and start tracking location data, and trips.",
|
||||||
),
|
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(
|
||||||
|
|
|
@ -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
128
lib/pages/WebMainPage.dart
Normal 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)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue