86 lines
3.2 KiB
Dart
86 lines
3.2 KiB
Dart
import "package:flutter/material.dart";
|
|
import "package:intl/intl.dart";
|
|
|
|
import "main.dart";
|
|
import "models.dart";
|
|
import "theme.dart";
|
|
|
|
final _numFmt = NumberFormat.decimalPattern();
|
|
|
|
/// Formats a date per the signed-in user's chosen date format (appSettings),
|
|
/// mirroring the web app's format.js.
|
|
String formatDate(DateTime? d) {
|
|
if (d == null) return "—";
|
|
final pattern = switch (appSettings.dateFormat) {
|
|
"DMY_NUM" => "dd-MM-yyyy",
|
|
"DMY" => "dd MMM yyyy",
|
|
"MDY" => "MMM dd, yyyy",
|
|
_ => "yyyy-MM-dd", // YMD
|
|
};
|
|
return DateFormat(pattern).format(d);
|
|
}
|
|
|
|
String formatKm(int? km) => (km == null || km == 0) ? "—" : "${_numFmt.format(km)} km";
|
|
|
|
const int _kmSoon = 1000;
|
|
|
|
enum StatusKey { unknown, ok, soon, overdue }
|
|
|
|
class Status {
|
|
final StatusKey key;
|
|
final String label;
|
|
const Status(this.key, this.label);
|
|
|
|
/// Soft tint background for the status pill — DriverVault semantic colours,
|
|
/// re-cut for dark surfaces.
|
|
Color bg(bool dark) => switch (key) {
|
|
StatusKey.overdue => dark ? DriverVault.dangerSoftDark : DriverVault.dangerSoft,
|
|
StatusKey.soon => dark ? DriverVault.warningSoftDark : DriverVault.warningSoft,
|
|
StatusKey.ok => dark ? DriverVault.successSoftDark : DriverVault.successSoft,
|
|
StatusKey.unknown => dark ? DriverVault.darkSunken : DriverVault.ink50,
|
|
};
|
|
Color fg(bool dark) => switch (key) {
|
|
StatusKey.overdue => DriverVault.danger,
|
|
StatusKey.soon => DriverVault.warning,
|
|
StatusKey.ok => DriverVault.success,
|
|
StatusKey.unknown => dark ? DriverVault.darkTextMuted : DriverVault.ink500,
|
|
};
|
|
}
|
|
|
|
int _rank(StatusKey k) => switch (k) {
|
|
StatusKey.unknown => 0,
|
|
StatusKey.ok => 1,
|
|
StatusKey.soon => 2,
|
|
StatusKey.overdue => 3,
|
|
};
|
|
|
|
Status _dateSignal(DateTime? nextDate) {
|
|
if (nextDate == null) return const Status(StatusKey.unknown, "No data");
|
|
final today = DateTime.now();
|
|
final days = DateTime(nextDate.year, nextDate.month, nextDate.day)
|
|
.difference(DateTime(today.year, today.month, today.day))
|
|
.inDays;
|
|
if (days < 0) return Status(StatusKey.overdue, "Service Overdue ${days.abs()}d");
|
|
if (days <= 30) return Status(StatusKey.soon, "Due in ${days}d");
|
|
return Status(StatusKey.ok, "OK · ${days}d");
|
|
}
|
|
|
|
Status _kmSignal(int currentKm, int? nextKm) {
|
|
if (currentKm == 0 || nextKm == null) return const Status(StatusKey.unknown, "No km");
|
|
final remaining = nextKm - currentKm;
|
|
if (remaining < 0) return Status(StatusKey.overdue, "Service Overdue ${_numFmt.format(remaining.abs())} km");
|
|
if (remaining <= _kmSoon) return Status(StatusKey.soon, "In ${_numFmt.format(remaining)} km");
|
|
return Status(StatusKey.ok, "${_numFmt.format(remaining)} km left");
|
|
}
|
|
|
|
/// Combines the date- and km-based signals, returning the worse of the two —
|
|
/// the same logic as the web app and the spreadsheet idea.
|
|
Status serviceStatus(ServiceRecord? latest, Car car) {
|
|
final date = _dateSignal(latest?.nextServiceDate);
|
|
final km = _kmSignal(car.currentKm, latest?.nextServiceKm);
|
|
final worse = _rank(km.key) > _rank(date.key) ? km : date;
|
|
if (date.key == StatusKey.unknown && km.key != StatusKey.unknown) return km;
|
|
if (km.key == StatusKey.unknown && date.key != StatusKey.unknown) return date;
|
|
return worse;
|
|
}
|