MO-Fuel-Tax-Back/lib/services/storage_service.dart

97 lines
3.2 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import '../models/fuel_entry.dart';
import '../models/vehicle.dart';
/// Owns the local, offline staging area: a `data.json` file holding
/// vehicles and fuel entries, plus a `receipts/` subfolder holding receipt
/// photos not yet uploaded to Drive. This is always
/// `ApplicationDocumentsDirectory/FuelTaxTracker` — not user-configurable —
/// since Drive (via [DriveSyncService]) is now the real shared destination
/// and this is just a local cache/holding area used while offline.
class StorageService {
static const _dataFileName = 'fuel_tax_data.json';
static const _receiptsFolderName = 'receipts';
late Directory _saveDirectory;
Directory get saveDirectory => _saveDirectory;
Directory get receiptsDirectory =>
Directory(p.join(_saveDirectory.path, _receiptsFolderName));
File get dataFile => File(p.join(_saveDirectory.path, _dataFileName));
/// Must be called once before any other method.
Future<void> init() async {
final docsDir = await getApplicationDocumentsDirectory();
_saveDirectory = Directory(p.join(docsDir.path, 'FuelTaxTracker'));
await _saveDirectory.create(recursive: true);
await receiptsDirectory.create(recursive: true);
}
Future<({List<Vehicle> vehicles, List<FuelEntry> entries})> loadData() async {
if (!await dataFile.exists()) {
return (vehicles: <Vehicle>[], entries: <FuelEntry>[]);
}
final raw = await dataFile.readAsString();
if (raw.trim().isEmpty) {
return (vehicles: <Vehicle>[], entries: <FuelEntry>[]);
}
return decodeData(raw);
}
({List<Vehicle> vehicles, List<FuelEntry> entries}) decodeData(String raw) {
final json = jsonDecode(raw) as Map<String, dynamic>;
final vehicles = (json['vehicles'] as List<dynamic>? ?? [])
.map((v) => Vehicle.fromJson(v as Map<String, dynamic>))
.toList();
final entries = (json['fuelEntries'] as List<dynamic>? ?? [])
.map((e) => FuelEntry.fromJson(e as Map<String, dynamic>))
.toList();
return (vehicles: vehicles, entries: entries);
}
String encodeData({
required List<Vehicle> vehicles,
required List<FuelEntry> entries,
}) {
final json = {
'vehicles': vehicles.map((v) => v.toJson()).toList(),
'fuelEntries': entries.map((e) => e.toJson()).toList(),
};
return const JsonEncoder.withIndent(' ').convert(json);
}
Future<void> saveData({
required List<Vehicle> vehicles,
required List<FuelEntry> entries,
}) async {
await dataFile.writeAsString(encodeData(vehicles: vehicles, entries: entries));
}
/// Copies a captured receipt image into the local receipts folder and
/// returns the path it was stored at, pending upload to Drive.
Future<String> storeReceiptImage(File sourceImage, String fuelEntryId) async {
final ext = p.extension(sourceImage.path);
final destPath = p.join(receiptsDirectory.path, '$fuelEntryId$ext');
final copied = await sourceImage.copy(destPath);
return copied.path;
}
Future<void> deleteReceiptImage(String? path) async {
if (path == null) return;
final file = File(path);
if (await file.exists()) {
await file.delete();
}
}
}