Remote sync of webdav
This commit is contained in:
parent
f01186df79
commit
483fbeafb6
44 changed files with 4842 additions and 975 deletions
|
|
@ -1,6 +1,10 @@
|
|||
class FuelEntry {
|
||||
final String id;
|
||||
|
||||
/// References [Vehicle.id] (the hidden, immutable identifier) — not the
|
||||
/// VIN, which is user-editable and so unsuitable as a foreign key.
|
||||
final String vehicleId;
|
||||
|
||||
final DateTime date;
|
||||
final double gallons;
|
||||
final double pricePerGallon;
|
||||
|
|
@ -26,16 +30,21 @@ class FuelEntry {
|
|||
this.deletedAt,
|
||||
});
|
||||
|
||||
/// True once the receipt photo has been uploaded to Drive and the local
|
||||
/// copy removed. Viewing it then requires downloading it on demand.
|
||||
bool get isReceiptUploadedToDrive =>
|
||||
receiptImagePath == null && receiptDriveFileId != null;
|
||||
/// True once the receipt photo has been uploaded to Drive, regardless of
|
||||
/// whether a local copy is also being kept (see the "keep photos on this
|
||||
/// phone" setting). Viewing it locally is preferred when a local copy
|
||||
/// exists; otherwise it requires downloading from Drive on demand.
|
||||
bool get isReceiptUploadedToDrive => receiptDriveFileId != null;
|
||||
|
||||
/// True while a receipt photo exists only locally and still needs to be
|
||||
/// uploaded next sync. A row in this state is never pushed to Drive as-is
|
||||
/// (a local file path is meaningless on another device) — sync uploads
|
||||
/// the image first, which clears this.
|
||||
bool get hasPendingLocalReceipt => receiptImagePath != null;
|
||||
/// True while a receipt photo still needs to be uploaded to Drive: a
|
||||
/// local file exists but hasn't made it there yet. Once
|
||||
/// [receiptDriveFileId] is set this is false — even if a local copy is
|
||||
/// also being kept — since that field alone is what marks "no longer
|
||||
/// needs uploading", independent of local retention. A row in this state
|
||||
/// is never pushed to Drive as data (a local file path is meaningless on
|
||||
/// another device) — sync uploads the image first, which sets
|
||||
/// [receiptDriveFileId] and clears this.
|
||||
bool get needsReceiptUpload => receiptImagePath != null && receiptDriveFileId == null;
|
||||
|
||||
FuelEntry copyWith({
|
||||
String? receiptImagePath,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
class Vehicle {
|
||||
/// Hidden, immutable, generated primary key — never shown in the UI and
|
||||
/// never editable. This is what fuel entries actually reference and what
|
||||
/// sync merges on, so that [vin] itself is free to be edited without
|
||||
/// breaking those references or losing sync history.
|
||||
final String id;
|
||||
final String make;
|
||||
final String model;
|
||||
final String color;
|
||||
final String licensePlate;
|
||||
|
||||
/// The vehicle's identification number. Required and must be unique
|
||||
/// among active (non-deleted) vehicles, but — unlike [id] — the user can
|
||||
/// edit it later (e.g. to fix a typo from a misread VIN scan).
|
||||
final String vin;
|
||||
|
||||
/// User-chosen label, e.g. "Mom's Car" or "Red Ford F-150". Optional —
|
||||
/// when absent, screens fall back to other identifying info instead.
|
||||
final String? nickname;
|
||||
|
||||
final DateTime updatedAt;
|
||||
|
||||
/// Soft-delete tombstone: null means active. Deleting sets this instead
|
||||
|
|
@ -15,30 +25,35 @@ class Vehicle {
|
|||
|
||||
Vehicle({
|
||||
required this.id,
|
||||
required this.make,
|
||||
required this.model,
|
||||
required this.color,
|
||||
required this.licensePlate,
|
||||
required this.vin,
|
||||
required this.updatedAt,
|
||||
this.nickname,
|
||||
this.deletedAt,
|
||||
});
|
||||
|
||||
String get displayName => '$color $make $model ($licensePlate)';
|
||||
/// What screens should show as the vehicle's primary label: the nickname
|
||||
/// if one was given, otherwise the VIN itself.
|
||||
String get displayLabel {
|
||||
final trimmedNickname = nickname?.trim();
|
||||
if (trimmedNickname != null && trimmedNickname.isNotEmpty) {
|
||||
return trimmedNickname;
|
||||
}
|
||||
return vin;
|
||||
}
|
||||
|
||||
/// Note: [id] is intentionally not overridable here — it's the hidden
|
||||
/// identifier, not an editable field.
|
||||
Vehicle copyWith({
|
||||
String? make,
|
||||
String? model,
|
||||
String? color,
|
||||
String? licensePlate,
|
||||
String? vin,
|
||||
String? nickname,
|
||||
bool clearNickname = false,
|
||||
DateTime? updatedAt,
|
||||
DateTime? deletedAt,
|
||||
}) {
|
||||
return Vehicle(
|
||||
id: id,
|
||||
make: make ?? this.make,
|
||||
model: model ?? this.model,
|
||||
color: color ?? this.color,
|
||||
licensePlate: licensePlate ?? this.licensePlate,
|
||||
vin: vin ?? this.vin,
|
||||
nickname: clearNickname ? null : (nickname ?? this.nickname),
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
deletedAt: deletedAt ?? this.deletedAt,
|
||||
);
|
||||
|
|
@ -46,20 +61,16 @@ class Vehicle {
|
|||
|
||||
Map<String, Object?> toMap() => {
|
||||
'id': id,
|
||||
'make': make,
|
||||
'model': model,
|
||||
'color': color,
|
||||
'license_plate': licensePlate,
|
||||
'vin': vin,
|
||||
'nickname': nickname,
|
||||
'updated_at': updatedAt.millisecondsSinceEpoch,
|
||||
'deleted_at': deletedAt?.millisecondsSinceEpoch,
|
||||
};
|
||||
|
||||
factory Vehicle.fromMap(Map<String, Object?> map) => Vehicle(
|
||||
id: map['id'] as String,
|
||||
make: map['make'] as String,
|
||||
model: map['model'] as String,
|
||||
color: map['color'] as String,
|
||||
licensePlate: map['license_plate'] as String,
|
||||
vin: map['vin'] as String,
|
||||
nickname: map['nickname'] as String?,
|
||||
updatedAt: DateTime.fromMillisecondsSinceEpoch(map['updated_at'] as int, isUtc: true),
|
||||
deletedAt: map['deleted_at'] != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(map['deleted_at'] as int, isUtc: true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue