Files
DriverVault/Phone App/lib/format.dart
T
2026-07-06 08:50:52 +02:00

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;
}