A whole bunch of changes
This commit is contained in:
parent
901ec678a3
commit
956231048e
122 changed files with 5442 additions and 914 deletions
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' show ThemeMode;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ const _prefsKeyCloudFolderPath = 'cloud_folder_path';
|
|||
const _prefsKeyKeepReceiptPhotosLocally = 'keep_receipt_photos_locally';
|
||||
const _prefsKeyStaleLockMinutes = 'stale_lock_minutes';
|
||||
const _prefsKeyKeepMaxQualityReceiptPhotos = 'keep_max_quality_receipt_photos';
|
||||
const _prefsKeyThemeMode = 'theme_mode';
|
||||
|
||||
const defaultStaleLockMinutes = 10;
|
||||
const minStaleLockMinutes = 1;
|
||||
|
|
@ -85,6 +87,8 @@ class AppState extends ChangeNotifier {
|
|||
/// the original camera/gallery image is kept as-is.
|
||||
bool keepMaxQualityReceiptPhotos = false;
|
||||
|
||||
ThemeMode themeMode = ThemeMode.system;
|
||||
|
||||
/// Set if [init] fails. The UI shows this (with a retry option) instead
|
||||
/// of spinning forever — an unhandled exception here previously left
|
||||
/// `isLoading` stuck at true with no feedback at all.
|
||||
|
|
@ -109,6 +113,10 @@ class AppState extends ChangeNotifier {
|
|||
staleLockMinutes = prefs.getInt(_prefsKeyStaleLockMinutes) ?? defaultStaleLockMinutes;
|
||||
keepMaxQualityReceiptPhotos =
|
||||
prefs.getBool(_prefsKeyKeepMaxQualityReceiptPhotos) ?? false;
|
||||
themeMode = ThemeMode.values.firstWhere(
|
||||
(mode) => mode.name == prefs.getString(_prefsKeyThemeMode),
|
||||
orElse: () => ThemeMode.system,
|
||||
);
|
||||
} catch (e) {
|
||||
initError = e;
|
||||
isLoading = false;
|
||||
|
|
@ -289,6 +297,13 @@ class AppState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> setThemeMode(ThemeMode mode) async {
|
||||
themeMode = mode;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_prefsKeyThemeMode, mode.name);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clamped to [minStaleLockMinutes, maxStaleLockMinutes] — see the
|
||||
/// Settings "Advanced" section, which restricts the picker to that range
|
||||
/// anyway; this is a defensive backstop for any other caller.
|
||||
|
|
@ -373,6 +388,34 @@ class AppState extends ChangeNotifier {
|
|||
return entry;
|
||||
}
|
||||
|
||||
/// Corrects the logged values (date, gallons, price/gal, total cost) for
|
||||
/// an existing fuel entry — the receipt photo itself isn't editable here,
|
||||
/// only the data recorded about it (e.g. fixing a misread OCR value).
|
||||
/// [entryId]'s receipt photo reference (local path and/or cloud file id)
|
||||
/// carries over untouched.
|
||||
Future<void> updateFuelEntry({
|
||||
required String entryId,
|
||||
required DateTime date,
|
||||
required double gallons,
|
||||
required double pricePerGallon,
|
||||
required double totalCost,
|
||||
}) async {
|
||||
final existing = fuelEntries.firstWhere((e) => e.id == entryId);
|
||||
final updated = FuelEntry(
|
||||
id: existing.id,
|
||||
vehicleId: existing.vehicleId,
|
||||
date: date,
|
||||
gallons: gallons,
|
||||
pricePerGallon: pricePerGallon,
|
||||
totalCost: totalCost,
|
||||
receiptImagePath: existing.receiptImagePath,
|
||||
receiptDriveFileId: existing.receiptDriveFileId,
|
||||
updatedAt: DateTime.now().toUtc(),
|
||||
);
|
||||
await database.saveFuelEntry(updated);
|
||||
await _persist();
|
||||
}
|
||||
|
||||
Future<void> deleteFuelEntry(String entryId) async {
|
||||
final entry = fuelEntries.firstWhere((e) => e.id == entryId);
|
||||
await database.deleteReceiptImageFile(entry.receiptImagePath);
|
||||
|
|
@ -380,6 +423,30 @@ class AppState extends ChangeNotifier {
|
|||
await _persist();
|
||||
}
|
||||
|
||||
/// Wipes every vehicle, fuel entry, and local receipt photo — "starting
|
||||
/// completely fresh". Uses the same soft-delete tombstones as any other
|
||||
/// delete rather than a hard SQL `DELETE`, so if a cloud sync is
|
||||
/// connected, the deletion pushes on next sync instead of the old data
|
||||
/// just getting silently re-imported from the remote copy. This is purely
|
||||
/// local + whatever propagates through sync — it never reaches into the
|
||||
/// cloud folder directly to delete anything sitting there.
|
||||
Future<void> purgeAllData() async {
|
||||
await database.purgeAllData(DateTime.now().toUtc());
|
||||
await _persist();
|
||||
}
|
||||
|
||||
/// Wipes every fuel entry (and its local receipt photo) dated within
|
||||
/// [start]–[end] inclusive, across all vehicles. Vehicles themselves are
|
||||
/// left alone. Same soft-delete/sync-propagation reasoning as
|
||||
/// [purgeAllData].
|
||||
Future<void> purgeFuelEntriesInRange(DateTime start, DateTime end) async {
|
||||
final localPaths = await database.purgeFuelEntriesInRange(start, end, DateTime.now().toUtc());
|
||||
for (final path in localPaths) {
|
||||
await database.deleteReceiptImageFile(path);
|
||||
}
|
||||
await _persist();
|
||||
}
|
||||
|
||||
List<FuelEntry> entriesForVehicle(String vehicleId) {
|
||||
final list = fuelEntries.where((e) => e.vehicleId == vehicleId).toList();
|
||||
list.sort((a, b) => b.date.compareTo(a.date));
|
||||
|
|
@ -401,6 +468,14 @@ class AppState extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
FuelEntry? fuelEntryById(String id) {
|
||||
try {
|
||||
return fuelEntries.firstWhere((e) => e.id == id);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Exact match against an active vehicle's VIN — same comparison
|
||||
/// [DatabaseService.vinExists] does, not case-insensitive, since VIN
|
||||
/// isn't normalized to any particular case on manual entry (only OCR
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue