Files
doormile_customer_app/lib/ui/format.dart
Thiru-tenext 0d66627c3c Replace the customer app with Doormile CX
Book a pickup, track it to delivery — the rebuilt customer app.

- Design language from doormile-screens.html: brand #8F0F06, Manrope +
  Geist Mono (variable fonts), bordered cards instead of shadows, crimson
  brand headers, sliding tab indicator, mono for anything read digit by digit.
- lib/data (one live API implementation, plus a debug-only offline fake),
  lib/state, lib/ui (tokens, widgets, screens).
- 84 tests, plus a design snapshot harness that renders every screen with the
  real fonts: flutter test test/design_snapshot_test.dart --run-skipped
  --update-goldens

This replaces the previous app (pubspec 'doormile', app id
com.doormile.customer). That tree remains in history at 6c7d656; note its
android/app/google-services.json is not carried over, and the application id
here is in.doormile.customer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 16:07:33 +05:30

81 lines
2.0 KiB
Dart

/// Small formatting helpers shared across screens.
library;
String greetingForNow([DateTime? now]) {
final h = (now ?? DateTime.now()).hour;
if (h < 12) return 'Good morning';
if (h < 17) return 'Good afternoon';
return 'Good evening';
}
/// "Just now" / "18 min ago" / "3 hr ago" / "5 d ago"
String relativeTime(DateTime? at, [DateTime? now]) {
if (at == null) return '';
final mins = (now ?? DateTime.now()).difference(at).inMinutes;
if (mins < 1) return 'Just now';
if (mins < 60) return '$mins min ago';
final hrs = (mins / 60).round();
if (hrs < 24) return '$hrs hr ago';
return '${(hrs / 24).round()} d ago';
}
const _months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
/// "Today" / "Yesterday" / "29 Aug"
String dateLabel(DateTime date, [DateTime? now]) {
final today = now ?? DateTime.now();
bool sameDay(DateTime a, DateTime b) =>
a.year == b.year && a.month == b.month && a.day == b.day;
if (sameDay(date, today)) return 'Today';
final yesterday = today.subtract(const Duration(days: 1));
if (sameDay(date, yesterday)) return 'Yesterday';
return '${date.day} ${_months[date.month - 1]}';
}
/// "SEPTEMBER 2026" — the eyebrow over a tab root's title. Spelled in full
/// rather than abbreviated: it is read once, at the top of the screen, and
/// "SEP 2026" saves four characters nobody needed saved.
String monthLabel(DateTime date) => '${_monthsFull[date.month - 1]} ${date.year}';
const _monthsFull = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
/// Initials for an avatar, e.g. "Joe Oommen" -> "JO".
String initialsOf(String? name) {
final n = (name ?? '').trim();
if (n.isEmpty) return 'DM';
final letters = n
.split(RegExp(r'\s+'))
.map((p) => p.isEmpty ? '' : p[0])
.join();
return letters
.substring(0, letters.length < 2 ? letters.length : 2)
.toUpperCase();
}