TimeTracker/lib/data.dart

385 lines
9.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
import 'dart:math' as math;
import 'dart:ui';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:libac_dart/nbt/Stream.dart';
import 'package:timetrack/consts.dart';
class SessionData {
static DateTime StartTime = DateTime(0);
static bool IsOnTheClock = false;
static List<Trip> Trips = [];
static Delivery? currentDelivery;
static Trip? currentTrip;
static List<Position> positions = [];
static late StreamSubscription<Position> _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;
static double GetTotalBasePay() {
double total = 0;
for (var trip in Trips) {
total += trip.BasePay;
}
return total;
}
static double GetTotalTips() {
double total = 0;
for (var trip in Trips) {
for (var drop in trip.deliveries) {
total += drop.TipAmount;
}
}
return total;
}
static double GetTotalPay() {
return GetTotalBasePay() + GetTotalTips();
}
static double GetTotalMiles() {
double total = 0;
total = _totalMilesTraveled(
positions,
minDistanceMeters: 5,
maxDistanceMeters: 512,
);
return total;
}
static String GetTotalMilesAsString() {
double miles = GetTotalMiles();
if (miles == 0) return "0.0";
List<String> split = miles.toString().split(".");
StringBuilder out = StringBuilder();
out.append(split[0]);
out.append(".");
out.append(split[1].substring(0, 4));
return out.toString();
}
//** Begin AI Generated code */
/// Converts meters → miles.
static const _metersPerMile = 1609.344;
/// Radius of the earth in meters (WGS84 mean radius).
static const _earthRadiusM = 6371000.0;
static double _haversineMeters(
double lat1,
double lon1,
double lat2,
double lon2,
) {
final dLat = _deg2rad(lat2 - lat1);
final dLon = _deg2rad(lon2 - lon1);
final a =
math.sin(dLat / 2) * math.sin(dLat / 2) +
math.cos(_deg2rad(lat1)) *
math.cos(_deg2rad(lat2)) *
math.sin(dLon / 2) *
math.sin(dLon / 2);
return _earthRadiusM * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
}
static double _deg2rad(double deg) => deg * math.pi / 180.0;
/// Returns total miles traveled after basic GPSnoise cleanup.
///
/// * [minDistanceMeters] drop segments shorter than this (jitter).
/// * [maxDistanceMeters] drop segments longer than this (impossible jump).
static double _totalMilesTraveled(
List<Position> positions, {
double minDistanceMeters = 5,
double? maxDistanceMeters,
}) {
if (positions.length < 2) return 0;
var meters = 0.0;
for (var i = 1; i < positions.length; i++) {
final p1 = positions[i - 1];
final p2 = positions[i];
final d = _haversineMeters(
p1.latitude,
p1.longitude,
p2.latitude,
p2.longitude,
);
if (d < minDistanceMeters) continue; // too small → jitter
if (maxDistanceMeters != null && d > maxDistanceMeters)
continue; // glitch
meters += d;
}
return meters / _metersPerMile;
}
//** End AI Generated code */
static Future<void> Login() async {
StartTime = DateTime.now();
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.",
);
}
_listener = Geolocator.getPositionStream(
locationSettings: TTConsts.LOCATION_SETTINGS,
).listen((pos) {
if (!IsOnTheClock) {
_listener.cancel();
return;
}
positions.add(pos);
SessionData.Calls.dispatch();
});
}
static Future<void> Logout() async {
IsOnTheClock = false;
currentDelivery = null;
currentTrip = null;
_listener.cancel();
var saveData = SaveData();
print(saveData);
Trips = [];
positions = [];
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 Map<String, dynamic> SaveData() {
Map<String, dynamic> saveData = {};
List<Map<String, dynamic>> _trips = [];
for (var trip in Trips) {
_trips.add(trip.toJsonMap());
}
List<Map<String, dynamic>> _pos = [];
for (var pos in positions) {
_pos.add(pos.toJson());
}
saveData["trips"] = _trips;
saveData["positions"] = _pos;
return saveData;
}
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);
if (_js.containsKey("error")) {
LastSessionID = "";
return;
}
List<Map<String, dynamic>> _trips =
_js['trips'] as List<Map<String, dynamic>>;
List<Map<String, dynamic>> _pos =
_js['positions'] as List<Map<String, dynamic>>;
for (var trip in _trips) {
Trips.add(Trip.fromJsonMap(trip));
}
for (var position in _pos) {
positions.add(Position.fromMap(position));
}
IsReadOnly = true;
}
static Future<Position> GetNewLocation() async {
Position pos = await Geolocator.getCurrentPosition(
locationSettings: TTConsts.LOCATION_SETTINGS,
);
return pos;
}
static Trip GetNewTrip({required double basePay}) {
currentTrip = Trip(BasePay: basePay);
Trips.add(currentTrip!);
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;
Position? endLocation;
DateTime StartTime = DateTime.now();
Delivery() {
StartTime = DateTime.now();
}
Future<void> MarkEndLocation() async {
var pos = await SessionData.GetNewLocation();
endLocation = pos;
}
Map<String, dynamic> toJsonMap() {
return {
"tip": TipAmount,
"start": StartTime.toString(),
"endPos": endLocation?.toJson() ?? "incomplete",
};
}
static Delivery fromMap(Map<String, dynamic> jsx) {
Delivery delivery = Delivery();
delivery.StartTime = DateTime.parse(jsx['start'] as String);
delivery.TipAmount = jsx['tip'] as double;
if (jsx['endPos'] as String == "incomplete")
delivery.endLocation = null;
else
delivery.endLocation = Position.fromMap(jsx['endPos']);
return delivery;
}
}
class Trip {
List<Delivery> deliveries = [];
DateTime StartTime = DateTime(0);
double BasePay = 0.0;
Trip({required this.BasePay}) {
StartTime = DateTime.now();
}
Delivery startNewDelivery() {
var delivery = Delivery();
deliveries.add(delivery);
return delivery;
}
Map<String, dynamic> toJsonMap() {
Map<String, Object> _trip = {"start": StartTime.toString(), "pay": BasePay};
List<Map<String, dynamic>> _dropOffs = [];
for (var delivery in deliveries) {
_dropOffs.add(delivery.toJsonMap());
}
_trip["deliveries"] = _dropOffs;
return _trip;
}
static Trip fromJsonMap(Map<String, dynamic> jsx) {
Trip trip = Trip(BasePay: 0);
trip.BasePay = jsx['pay'] as double;
trip.StartTime = DateTime.parse(jsx['start'] as String);
trip.deliveries = [];
List<Map<String, dynamic>> _dropOffs =
jsx['deliveries'] as List<Map<String, dynamic>>;
for (var dropOff in _dropOffs) {
trip.deliveries.add(Delivery.fromMap(dropOff));
}
return trip;
}
}
class Callbacks {
VoidCallback? HomeCallback;
VoidCallback? MapCallback;
VoidCallback? UpdateSettingsCallback;
VoidCallback? WorkDataCallback;
void dispatch() {
if (HomeCallback != null) HomeCallback!();
if (MapCallback != null) MapCallback!();
if (UpdateSettingsCallback != null) UpdateSettingsCallback!();
if (WorkDataCallback != null) WorkDataCallback!();
}
}