/// 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(); }