Added Google Drive storage location
This commit is contained in:
parent
e630f2a3ec
commit
73a7c93e64
22 changed files with 1690 additions and 205 deletions
|
|
@ -1,24 +1,46 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../models/fuel_entry.dart';
|
||||
import '../models/vehicle.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 persistence. Screens read from
|
||||
/// this via Provider and call its mutating methods, which take care of
|
||||
/// writing through to disk and notifying listeners.
|
||||
/// entries), backed by [StorageService] for local 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.
|
||||
class AppState extends ChangeNotifier {
|
||||
final StorageService storage = StorageService();
|
||||
final DriveAuthService driveAuth = DriveAuthService();
|
||||
late final DriveSyncService driveSync =
|
||||
DriveSyncService(authService: driveAuth, storageService: storage);
|
||||
final _uuid = const Uuid();
|
||||
|
||||
List<Vehicle> vehicles = [];
|
||||
List<FuelEntry> fuelEntries = [];
|
||||
bool isLoading = true;
|
||||
|
||||
bool isDriveConnected = false;
|
||||
String? driveAccountEmail;
|
||||
String? driveFolderPath;
|
||||
bool isSyncing = false;
|
||||
DateTime? lastSyncedAt;
|
||||
Object? lastSyncError;
|
||||
|
||||
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
|
||||
|
||||
Future<void> init() async {
|
||||
await storage.init();
|
||||
final data = await storage.loadData();
|
||||
|
|
@ -26,6 +48,100 @@ class AppState extends ChangeNotifier {
|
|||
fuelEntries = data.entries;
|
||||
isLoading = false;
|
||||
notifyListeners();
|
||||
|
||||
_connectivitySubscription = Connectivity().onConnectivityChanged.listen((results) {
|
||||
if (results.any((r) => r != ConnectivityResult.none)) {
|
||||
unawaited(syncNow());
|
||||
}
|
||||
});
|
||||
|
||||
unawaited(_restoreDriveConnection());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_connectivitySubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _restoreDriveConnection() async {
|
||||
final signedIn = await driveAuth.attemptSilentSignIn();
|
||||
if (!signedIn) return;
|
||||
|
||||
isDriveConnected = true;
|
||||
driveAccountEmail = driveAuth.currentAccountEmail;
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final folderId = prefs.getString(_prefsKeyDriveFolderId);
|
||||
driveFolderPath = prefs.getString(_prefsKeyDriveFolderPath);
|
||||
if (folderId != null) {
|
||||
driveSync.configure(folderId);
|
||||
}
|
||||
notifyListeners();
|
||||
|
||||
if (folderId != null) {
|
||||
unawaited(syncNow());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> connectDrive() async {
|
||||
final email = await driveAuth.signIn();
|
||||
isDriveConnected = true;
|
||||
driveAccountEmail = email;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> disconnectDrive() async {
|
||||
await driveAuth.signOut();
|
||||
driveSync.clearConfiguration();
|
||||
isDriveConnected = false;
|
||||
driveAccountEmail = null;
|
||||
driveFolderPath = null;
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_prefsKeyDriveFolderId);
|
||||
await prefs.remove(_prefsKeyDriveFolderPath);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> chooseDriveFolder({
|
||||
required String parentId,
|
||||
required String breadcrumbPath,
|
||||
}) async {
|
||||
final folderId = await driveSync.selectAppFolder(parentId);
|
||||
driveFolderPath = breadcrumbPath;
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_prefsKeyDriveFolderId, folderId);
|
||||
await prefs.setString(_prefsKeyDriveFolderPath, breadcrumbPath);
|
||||
notifyListeners();
|
||||
|
||||
unawaited(syncNow());
|
||||
}
|
||||
|
||||
Future<void> syncNow() async {
|
||||
if (isSyncing || !driveSync.isConfigured) return;
|
||||
|
||||
isSyncing = true;
|
||||
notifyListeners();
|
||||
|
||||
final result = await driveSync.syncNow(
|
||||
localVehicles: vehicles,
|
||||
localFuelEntries: fuelEntries,
|
||||
);
|
||||
|
||||
if (result.ranSync) {
|
||||
vehicles = result.vehicles!;
|
||||
fuelEntries = result.fuelEntries!;
|
||||
await storage.saveData(vehicles: vehicles, entries: fuelEntries);
|
||||
lastSyncedAt = DateTime.now();
|
||||
lastSyncError = null;
|
||||
} else if (result.error != null) {
|
||||
lastSyncError = result.error;
|
||||
}
|
||||
|
||||
isSyncing = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> addVehicle({
|
||||
|
|
@ -40,6 +156,7 @@ class AppState extends ChangeNotifier {
|
|||
model: model,
|
||||
color: color,
|
||||
licensePlate: licensePlate,
|
||||
updatedAt: DateTime.now().toUtc(),
|
||||
));
|
||||
await _persist();
|
||||
}
|
||||
|
|
@ -47,7 +164,7 @@ class AppState extends ChangeNotifier {
|
|||
Future<void> updateVehicle(Vehicle updated) async {
|
||||
final index = vehicles.indexWhere((v) => v.id == updated.id);
|
||||
if (index != -1) {
|
||||
vehicles[index] = updated;
|
||||
vehicles[index] = updated.copyWith(updatedAt: DateTime.now().toUtc());
|
||||
await _persist();
|
||||
}
|
||||
}
|
||||
|
|
@ -83,6 +200,7 @@ class AppState extends ChangeNotifier {
|
|||
pricePerGallon: pricePerGallon,
|
||||
totalCost: totalCost,
|
||||
receiptImagePath: storedImagePath,
|
||||
updatedAt: DateTime.now().toUtc(),
|
||||
);
|
||||
fuelEntries.add(entry);
|
||||
await _persist();
|
||||
|
|
@ -117,13 +235,9 @@ class AppState extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> changeSaveDirectory(String newPath) async {
|
||||
await storage.setSaveDirectory(newPath);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _persist() async {
|
||||
await storage.saveData(vehicles: vehicles, entries: fuelEntries);
|
||||
notifyListeners();
|
||||
unawaited(syncNow());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue