128 lines
4.4 KiB
Dart
128 lines
4.4 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../models/fuel_entry.dart';
|
|
import '../models/vehicle.dart';
|
|
|
|
/// Owns the on-disk layout for the app: a `data.json` file holding vehicles
|
|
/// and fuel entries, plus a `receipts/` subfolder holding receipt photos.
|
|
/// The parent directory containing both is user-configurable (see
|
|
/// [setSaveDirectory]) and persisted across launches via SharedPreferences.
|
|
class StorageService {
|
|
static const _prefsKey = 'save_directory_path';
|
|
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. Loads the previously
|
|
/// chosen save directory, falling back to the app's own documents
|
|
/// directory the first time the app runs.
|
|
Future<void> init() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final storedPath = prefs.getString(_prefsKey);
|
|
|
|
if (storedPath != null && await Directory(storedPath).exists()) {
|
|
_saveDirectory = Directory(storedPath);
|
|
} else {
|
|
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>[]);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
Future<void> saveData({
|
|
required List<Vehicle> vehicles,
|
|
required List<FuelEntry> entries,
|
|
}) async {
|
|
final json = {
|
|
'vehicles': vehicles.map((v) => v.toJson()).toList(),
|
|
'fuelEntries': entries.map((e) => e.toJson()).toList(),
|
|
};
|
|
await _dataFile.writeAsString(const JsonEncoder.withIndent(' ').convert(json));
|
|
}
|
|
|
|
/// Copies a captured receipt image into the receipts folder and returns
|
|
/// the path it was stored at.
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// Changes where the data file and receipt images live, moving any
|
|
/// existing data/images from the old location into the new one.
|
|
Future<void> setSaveDirectory(String newPath) async {
|
|
final newDir = Directory(newPath);
|
|
await newDir.create(recursive: true);
|
|
final newReceiptsDir = Directory(p.join(newPath, _receiptsFolderName));
|
|
await newReceiptsDir.create(recursive: true);
|
|
|
|
final oldDataFile = _dataFile;
|
|
final oldReceiptsDir = receiptsDirectory;
|
|
|
|
if (await oldDataFile.exists() &&
|
|
p.equals(oldDataFile.path, p.join(newPath, _dataFileName)) == false) {
|
|
await oldDataFile.copy(p.join(newPath, _dataFileName));
|
|
}
|
|
|
|
if (await oldReceiptsDir.exists()) {
|
|
await for (final entity in oldReceiptsDir.list()) {
|
|
if (entity is File) {
|
|
final destPath = p.join(newReceiptsDir.path, p.basename(entity.path));
|
|
if (!p.equals(entity.path, destPath)) {
|
|
await entity.copy(destPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_saveDirectory = newDir;
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_prefsKey, newPath);
|
|
}
|
|
}
|