Added sqlite

This commit is contained in:
Courtney Arnold 2026-08-08 16:55:40 -05:00
parent 73a7c93e64
commit f01186df79
14 changed files with 784 additions and 382 deletions

View file

@ -8,24 +8,29 @@ import 'package:uuid/uuid.dart';
import '../models/fuel_entry.dart';
import '../models/vehicle.dart';
import 'database_service.dart';
import 'drive_auth_service.dart';
import 'drive_sync_service.dart';
import 'storage_service.dart';
const _prefsKeyDriveFolderId = 'drive_app_folder_id';
const _prefsKeyDriveFolderPath = 'drive_app_folder_path';
/// Single source of truth for the app's in-memory data (vehicles + fuel
/// entries), backed by [StorageService] for local persistence and
/// entries), backed by [DatabaseService] (local SQLite) for persistence and
/// [DriveSyncService] for pushing/pulling the shared Drive copy. Screens
/// read from this via Provider and call its mutating methods, which write
/// through to local disk immediately and kick off a best-effort background
/// sync to Drive.
/// through to the local database immediately and kick off a best-effort
/// background sync to Drive.
///
/// The `vehicles`/`fuelEntries` lists are an in-memory read cache of the
/// database, refreshed after every mutation and after every sync (since
/// sync's merge happens as SQL directly against the database, not by
/// handing back updated Dart objects).
class AppState extends ChangeNotifier {
final StorageService storage = StorageService();
final DatabaseService database = DatabaseService();
final DriveAuthService driveAuth = DriveAuthService();
late final DriveSyncService driveSync =
DriveSyncService(authService: driveAuth, storageService: storage);
DriveSyncService(authService: driveAuth, databaseService: database);
final _uuid = const Uuid();
List<Vehicle> vehicles = [];
@ -42,10 +47,8 @@ class AppState extends ChangeNotifier {
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
Future<void> init() async {
await storage.init();
final data = await storage.loadData();
vehicles = data.vehicles;
fuelEntries = data.entries;
await database.init();
await _refreshFromDatabase();
isLoading = false;
notifyListeners();
@ -64,6 +67,11 @@ class AppState extends ChangeNotifier {
super.dispose();
}
Future<void> _refreshFromDatabase() async {
vehicles = await database.getVehicles();
fuelEntries = await database.getFuelEntries();
}
Future<void> _restoreDriveConnection() async {
final signedIn = await driveAuth.attemptSilentSignIn();
if (!signedIn) return;
@ -125,15 +133,10 @@ class AppState extends ChangeNotifier {
isSyncing = true;
notifyListeners();
final result = await driveSync.syncNow(
localVehicles: vehicles,
localFuelEntries: fuelEntries,
);
final result = await driveSync.syncNow();
if (result.ranSync) {
vehicles = result.vehicles!;
fuelEntries = result.fuelEntries!;
await storage.saveData(vehicles: vehicles, entries: fuelEntries);
await _refreshFromDatabase();
lastSyncedAt = DateTime.now();
lastSyncError = null;
} else if (result.error != null) {
@ -150,31 +153,30 @@ class AppState extends ChangeNotifier {
required String color,
required String licensePlate,
}) async {
vehicles.add(Vehicle(
final vehicle = Vehicle(
id: _uuid.v4(),
make: make,
model: model,
color: color,
licensePlate: licensePlate,
updatedAt: DateTime.now().toUtc(),
));
);
await database.saveVehicle(vehicle);
await _persist();
}
Future<void> updateVehicle(Vehicle updated) async {
final index = vehicles.indexWhere((v) => v.id == updated.id);
if (index != -1) {
vehicles[index] = updated.copyWith(updatedAt: DateTime.now().toUtc());
await _persist();
}
await database.saveVehicle(updated.copyWith(updatedAt: DateTime.now().toUtc()));
await _persist();
}
Future<void> deleteVehicle(String vehicleId) async {
for (final entry in fuelEntries.where((e) => e.vehicleId == vehicleId)) {
await storage.deleteReceiptImage(entry.receiptImagePath);
final now = DateTime.now().toUtc();
final orphanedLocalPaths = await database.softDeleteFuelEntriesForVehicle(vehicleId, now);
for (final path in orphanedLocalPaths) {
await database.deleteReceiptImageFile(path);
}
fuelEntries.removeWhere((e) => e.vehicleId == vehicleId);
vehicles.removeWhere((v) => v.id == vehicleId);
await database.softDeleteVehicle(vehicleId, now);
await _persist();
}
@ -189,7 +191,7 @@ class AppState extends ChangeNotifier {
final id = _uuid.v4();
String? storedImagePath;
if (receiptImage != null) {
storedImagePath = await storage.storeReceiptImage(receiptImage, id);
storedImagePath = await database.storeReceiptImage(receiptImage, id);
}
final entry = FuelEntry(
@ -202,15 +204,15 @@ class AppState extends ChangeNotifier {
receiptImagePath: storedImagePath,
updatedAt: DateTime.now().toUtc(),
);
fuelEntries.add(entry);
await database.saveFuelEntry(entry);
await _persist();
return entry;
}
Future<void> deleteFuelEntry(String entryId) async {
final entry = fuelEntries.firstWhere((e) => e.id == entryId);
await storage.deleteReceiptImage(entry.receiptImagePath);
fuelEntries.removeWhere((e) => e.id == entryId);
await database.deleteReceiptImageFile(entry.receiptImagePath);
await database.softDeleteFuelEntry(entryId, DateTime.now().toUtc());
await _persist();
}
@ -236,7 +238,7 @@ class AppState extends ChangeNotifier {
}
Future<void> _persist() async {
await storage.saveData(vehicles: vehicles, entries: fuelEntries);
await _refreshFromDatabase();
notifyListeners();
unawaited(syncNow());
}