Files
buzz/mobile/lib/features/channels/ephemeral_channel_display.dart
T
cls 9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
feat: import Chinese-localized Buzz source snapshot
Signed-off-by: cls_宁波本机 <908705107@qq.com>
2026-08-13 18:34:25 +08:00

175 lines
5.6 KiB
Dart

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