import 'dart:math' as math; import 'package:intl/intl.dart'; import '../../shared/l10n/l10n.dart'; import 'channel.dart'; class EphemeralChannelDisplay { final String? detailLabel; final String tooltipLabel; const EphemeralChannelDisplay({ required this.detailLabel, required this.tooltipLabel, }); } bool isEphemeralChannel(Channel channel) => channel.ttlSeconds != null || channel.ttlDeadline != null; EphemeralChannelDisplay? ephemeralChannelDisplay( Channel channel, { DateTime? now, AppLocalizations? l10n, }) { if (!isEphemeralChannel(channel)) return null; final deadline = channel.ttlDeadline; final remainingSeconds = deadline == null ? null : ((deadline.millisecondsSinceEpoch - (now ?? DateTime.now()).millisecondsSinceEpoch) / 1000) .ceil(); if (remainingSeconds == null) { final ttlSeconds = channel.ttlSeconds; return EphemeralChannelDisplay( detailLabel: ttlSeconds == null ? null : formatCompactTtl(ttlSeconds, l10n: l10n), tooltipLabel: ttlSeconds == null ? l10n?.ephemeralAutoCleanup ?? 'Ephemeral channel. Cleans up automatically after inactivity.' : l10n?.ephemeralCleanupAfter( formatVerboseTtl(ttlSeconds, l10n: l10n), ) ?? 'Ephemeral channel. Cleans up after ${formatVerboseTtl(ttlSeconds)} of inactivity.', ); } final compactRemaining = formatCompactRemaining( remainingSeconds, l10n: l10n, ); final verboseRemaining = formatVerboseRemaining( remainingSeconds, l10n: l10n, ); final absoluteDeadlineLabel = formatAbsoluteDeadline(deadline!, l10n: l10n); return EphemeralChannelDisplay( detailLabel: compactRemaining, tooltipLabel: remainingSeconds <= 0 ? l10n?.ephemeralCleanupDueTooltip ?? 'Ephemeral channel. Cleanup is due now.' : l10n?.ephemeralScheduled(verboseRemaining, absoluteDeadlineLabel) ?? 'Ephemeral channel. Cleans up $verboseRemaining. Scheduled for $absoluteDeadlineLabel.', ); } String formatCompactRemaining( int remainingSeconds, { AppLocalizations? l10n, }) { if (remainingSeconds <= 0) return l10n?.ephemeralCleanupDue ?? 'Cleanup due'; if (remainingSeconds <= 60) return l10n?.ephemeralOneMinuteLeft ?? '1m left'; if (remainingSeconds < 60 * 60) { final minutes = math.max(1, (remainingSeconds / 60).ceil()); return l10n?.ephemeralMinutesLeft(minutes) ?? '${minutes}m left'; } if (remainingSeconds < 60 * 60 * 24) { final hours = math.max(1, (remainingSeconds / (60 * 60)).ceil()); return l10n?.ephemeralHoursLeft(hours) ?? '${hours}h left'; } final days = math.max(1, (remainingSeconds / (60 * 60 * 24)).ceil()); return l10n?.ephemeralDaysLeft(days) ?? '${days}d left'; } String formatVerboseRemaining( int remainingSeconds, { AppLocalizations? l10n, }) { if (remainingSeconds <= 0) return l10n?.timeNow ?? 'now'; if (remainingSeconds <= 60) { return l10n?.ephemeralInOneMinute ?? 'in 1 minute'; } if (remainingSeconds < 60 * 60) { final minutes = math.max(1, (remainingSeconds / 60).ceil()); return l10n?.ephemeralInMinutes(minutes) ?? 'in $minutes minute${minutes == 1 ? '' : 's'}'; } if (remainingSeconds < 60 * 60 * 24) { final hours = math.max(1, (remainingSeconds / (60 * 60)).ceil()); return l10n?.ephemeralInHours(hours) ?? 'in $hours hour${hours == 1 ? '' : 's'}'; } final days = math.max(1, (remainingSeconds / (60 * 60 * 24)).ceil()); return l10n?.ephemeralInDays(days) ?? 'in $days day${days == 1 ? '' : 's'}'; } String formatCompactTtl(int ttlSeconds, {AppLocalizations? l10n}) { if (ttlSeconds < 60) { final seconds = math.max(1, ttlSeconds); return l10n?.ephemeralSeconds(seconds) ?? '${seconds}s TTL'; } if (ttlSeconds < 60 * 60) { final minutes = math.max(1, (ttlSeconds / 60).ceil()); return l10n?.ephemeralMinutes(minutes) ?? '${minutes}m TTL'; } if (ttlSeconds < 60 * 60 * 24) { final hours = math.max(1, (ttlSeconds / (60 * 60)).ceil()); return l10n?.ephemeralHours(hours) ?? '${hours}h TTL'; } final days = math.max(1, (ttlSeconds / (60 * 60 * 24)).ceil()); return l10n?.ephemeralDays(days) ?? '${days}d TTL'; } String formatVerboseTtl(int ttlSeconds, {AppLocalizations? l10n}) { if (ttlSeconds < 60) { final seconds = math.max(1, ttlSeconds); return l10n?.ephemeralSeconds(seconds) ?? '$seconds second${seconds == 1 ? '' : 's'}'; } if (ttlSeconds < 60 * 60) { final minutes = math.max(1, (ttlSeconds / 60).ceil()); return l10n?.ephemeralMinutes(minutes) ?? '$minutes minute${minutes == 1 ? '' : 's'}'; } if (ttlSeconds < 60 * 60 * 24) { final hours = math.max(1, (ttlSeconds / (60 * 60)).ceil()); return l10n?.ephemeralHours(hours) ?? '$hours hour${hours == 1 ? '' : 's'}'; } final days = math.max(1, (ttlSeconds / (60 * 60 * 24)).ceil()); return l10n?.ephemeralDays(days) ?? '$days day${days == 1 ? '' : 's'}'; } String formatAbsoluteDeadline(DateTime deadline, {AppLocalizations? l10n}) { if (l10n != null) { return DateFormat.yMMMd(l10n.localeName).add_jm().format(deadline.toLocal()); } const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', ]; final local = deadline.toLocal(); final hour12 = local.hour % 12 == 0 ? 12 : local.hour % 12; final minute = local.minute.toString().padLeft(2, '0'); final period = local.hour < 12 ? 'AM' : 'PM'; return '${months[local.month - 1]} ${local.day}, $hour12:$minute $period'; }