From ec0701f4513fb34b42147ec51b1f85ec383a8731 Mon Sep 17 00:00:00 2001 From: lakshya1goel Date: Sun, 22 Dec 2024 08:08:03 +0530 Subject: [PATCH] lightbox: Use a friendlier format for the date Changed the timestamp format from Mar 31, 2023 15:09:51 to a more readable Mar 31, 2023 at 3:09 PM. Added proper localization support to adapt timestamps to the user's language and region preferences. Optimized the timestamp logic to ensure accurate categorization into Today, Yesterday, or specific dates. Fixes: #45 --- assets/l10n/app_en.arb | 24 +++++++++++ lib/generated/l10n/zulip_localizations.dart | 36 ++++++++++++++++ .../l10n/zulip_localizations_ar.dart | 26 ++++++++++++ .../l10n/zulip_localizations_en.dart | 26 ++++++++++++ .../l10n/zulip_localizations_fr.dart | 26 ++++++++++++ .../l10n/zulip_localizations_ja.dart | 26 ++++++++++++ .../l10n/zulip_localizations_pl.dart | 26 ++++++++++++ .../l10n/zulip_localizations_ru.dart | 26 ++++++++++++ lib/widgets/lightbox.dart | 41 ++++++++++++++++--- test/widgets/lightbox_test.dart | 2 +- 10 files changed, 253 insertions(+), 6 deletions(-) diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 58822303fd..bdfb221992 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -680,5 +680,29 @@ "emojiPickerSearchEmoji": "Search emoji", "@emojiPickerSearchEmoji": { "description": "Hint text for the emoji picker search text field." + }, + "aFewSecondsAgo": "A few seconds ago", + "@aFewSecondsAgo": { + "description": "Message displayed when the difference between the current time and the given time is less than 60 seconds." + }, + "oneMinuteAgo": "1 minute ago", + "@oneMinuteAgo": { + "description": "Message displayed when the difference between the current time and the given time is exactly 1 minute." + }, + "minutesAgo": "{minutes} minutes ago", + "@minutesAgo": { + "description": "Message displayed when the difference between the current time and the given time is in minutes. The placeholder {minutes} will be replaced with the actual number of minutes." + }, + "todayAt": "Today at {time}", + "@todayAt": { + "description": "Message displayed when the given time is from today. The placeholder {time} will be replaced with the formatted time." + }, + "yesterdayAt": "Yesterday at {time}", + "@yesterdayAt": { + "description": "Message displayed when the given time is from yesterday. The placeholder {time} will be replaced with the formatted time." + }, + "dateAtTime": "{date} at {time}", + "@dateAtTime": { + "description": "Message displayed when the given time is older than yesterday. The placeholder {date} will be replaced with the formatted date and {time} with the formatted time." } } diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index 00d7cfde72..77b2c8c35d 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -1014,6 +1014,42 @@ abstract class ZulipLocalizations { /// In en, this message translates to: /// **'Search emoji'** String get emojiPickerSearchEmoji; + + /// Message displayed when the difference between the current time and the given time is less than 60 seconds. + /// + /// In en, this message translates to: + /// **'A few seconds ago'** + String get aFewSecondsAgo; + + /// Message displayed when the difference between the current time and the given time is exactly 1 minute. + /// + /// In en, this message translates to: + /// **'1 minute ago'** + String get oneMinuteAgo; + + /// Message displayed when the difference between the current time and the given time is in minutes. The placeholder {minutes} will be replaced with the actual number of minutes. + /// + /// In en, this message translates to: + /// **'{minutes} minutes ago'** + String minutesAgo(Object minutes); + + /// Message displayed when the given time is from today. The placeholder {time} will be replaced with the formatted time. + /// + /// In en, this message translates to: + /// **'Today at {time}'** + String todayAt(Object time); + + /// Message displayed when the given time is from yesterday. The placeholder {time} will be replaced with the formatted time. + /// + /// In en, this message translates to: + /// **'Yesterday at {time}'** + String yesterdayAt(Object time); + + /// Message displayed when the given time is older than yesterday. The placeholder {date} will be replaced with the formatted date and {time} with the formatted time. + /// + /// In en, this message translates to: + /// **'{date} at {time}'** + String dateAtTime(Object date, Object time); } class _ZulipLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart index 542b85031b..d81953ac7d 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -537,4 +537,30 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Search emoji'; + + @override + String get aFewSecondsAgo => 'A few seconds ago'; + + @override + String get oneMinuteAgo => '1 minute ago'; + + @override + String minutesAgo(Object minutes) { + return '$minutes minutes ago'; + } + + @override + String todayAt(Object time) { + return 'Today at $time'; + } + + @override + String yesterdayAt(Object time) { + return 'Yesterday at $time'; + } + + @override + String dateAtTime(Object date, Object time) { + return '$date at $time'; + } } diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index b6bc9f72e7..e8f5c71848 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -537,4 +537,30 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Search emoji'; + + @override + String get aFewSecondsAgo => 'A few seconds ago'; + + @override + String get oneMinuteAgo => '1 minute ago'; + + @override + String minutesAgo(Object minutes) { + return '$minutes minutes ago'; + } + + @override + String todayAt(Object time) { + return 'Today at $time'; + } + + @override + String yesterdayAt(Object time) { + return 'Yesterday at $time'; + } + + @override + String dateAtTime(Object date, Object time) { + return '$date at $time'; + } } diff --git a/lib/generated/l10n/zulip_localizations_fr.dart b/lib/generated/l10n/zulip_localizations_fr.dart index c857da2c82..57a6d7723e 100644 --- a/lib/generated/l10n/zulip_localizations_fr.dart +++ b/lib/generated/l10n/zulip_localizations_fr.dart @@ -537,4 +537,30 @@ class ZulipLocalizationsFr extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Search emoji'; + + @override + String get aFewSecondsAgo => 'A few seconds ago'; + + @override + String get oneMinuteAgo => '1 minute ago'; + + @override + String minutesAgo(Object minutes) { + return '$minutes minutes ago'; + } + + @override + String todayAt(Object time) { + return 'Today at $time'; + } + + @override + String yesterdayAt(Object time) { + return 'Yesterday at $time'; + } + + @override + String dateAtTime(Object date, Object time) { + return '$date at $time'; + } } diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index 7adbc9ae8a..138763d3eb 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -537,4 +537,30 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Search emoji'; + + @override + String get aFewSecondsAgo => 'A few seconds ago'; + + @override + String get oneMinuteAgo => '1 minute ago'; + + @override + String minutesAgo(Object minutes) { + return '$minutes minutes ago'; + } + + @override + String todayAt(Object time) { + return 'Today at $time'; + } + + @override + String yesterdayAt(Object time) { + return 'Yesterday at $time'; + } + + @override + String dateAtTime(Object date, Object time) { + return '$date at $time'; + } } diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index 07746b3f27..3227357f4f 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -537,4 +537,30 @@ class ZulipLocalizationsPl extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Search emoji'; + + @override + String get aFewSecondsAgo => 'A few seconds ago'; + + @override + String get oneMinuteAgo => '1 minute ago'; + + @override + String minutesAgo(Object minutes) { + return '$minutes minutes ago'; + } + + @override + String todayAt(Object time) { + return 'Today at $time'; + } + + @override + String yesterdayAt(Object time) { + return 'Yesterday at $time'; + } + + @override + String dateAtTime(Object date, Object time) { + return '$date at $time'; + } } diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index 9c2065376b..30df91b057 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -537,4 +537,30 @@ class ZulipLocalizationsRu extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Search emoji'; + + @override + String get aFewSecondsAgo => 'A few seconds ago'; + + @override + String get oneMinuteAgo => '1 minute ago'; + + @override + String minutesAgo(Object minutes) { + return '$minutes minutes ago'; + } + + @override + String todayAt(Object time) { + return 'Today at $time'; + } + + @override + String yesterdayAt(Object time) { + return 'Yesterday at $time'; + } + + @override + String dateAtTime(Object date, Object time) { + return '$date at $time'; + } } diff --git a/lib/widgets/lightbox.dart b/lib/widgets/lightbox.dart index 7e4141db63..a4f80b8832 100644 --- a/lib/widgets/lightbox.dart +++ b/lib/widgets/lightbox.dart @@ -152,11 +152,42 @@ class _LightboxPageLayoutState extends State<_LightboxPageLayout> { PreferredSizeWidget? appBar; if (_headerFooterVisible) { - // TODO(#45): Format with e.g. "Yesterday at 4:47 PM" - final timestampText = DateFormat - .yMMMd(/* TODO(#278): Pass selected language here, I think? */) - .add_Hms() - .format(DateTime.fromMillisecondsSinceEpoch(widget.message.timestamp * 1000)); + final zulipLocalizations = ZulipLocalizations.of(context); + + String formatLocalizedTimestamp(DateTime date) { + final now = DateTime.now(); + final nowDateOnly = DateTime(now.year, now.month, now.day); + final messageDateOnly = DateTime(date.year, date.month, date.day); + + final differenceInSeconds = now.difference(date).inSeconds; + final differenceInMinutes = now.difference(date).inMinutes; + final differenceInDays = nowDateOnly.difference(messageDateOnly).inDays; + + if (differenceInSeconds < 60) { + return zulipLocalizations.aFewSecondsAgo; + } else if (differenceInMinutes < 60) { + return Intl.plural( + differenceInMinutes, + one: zulipLocalizations.oneMinuteAgo, + other: zulipLocalizations.minutesAgo(differenceInMinutes), + locale: zulipLocalizations.localeName, + ); + } else if (differenceInDays == 0) { + final time = DateFormat.jm(zulipLocalizations.localeName).format(date); + return zulipLocalizations.todayAt(time); + } else if (differenceInDays == 1) { + final time = DateFormat.jm(zulipLocalizations.localeName).format(date); + return zulipLocalizations.yesterdayAt(time); + } else { + final dateStr = DateFormat('MMM d, yyyy', zulipLocalizations.localeName).format(date); + final timeStr = DateFormat('hh:mm a', zulipLocalizations.localeName).format(date); + return zulipLocalizations.dateAtTime(dateStr, timeStr); + } + } + + final timestampText = formatLocalizedTimestamp( + DateTime.fromMillisecondsSinceEpoch(widget.message.timestamp * 1000), + ); // We use plain [AppBar] instead of [ZulipAppBar], even though this page // has a [PerAccountStore], because: diff --git a/test/widgets/lightbox_test.dart b/test/widgets/lightbox_test.dart index 4a84f79b27..826a986e49 100644 --- a/test/widgets/lightbox_test.dart +++ b/test/widgets/lightbox_test.dart @@ -249,7 +249,7 @@ void main() { matching: find.textContaining(findRichText: true, eg.otherUser.fullName))); check(labelTextWidget.text.toPlainText()) - .contains('Jul 23, 2024 23:12:24'); + .contains('Jul 23, 2024 at 11:12 PM'); debugNetworkImageHttpClientProvider = null; });