Skip to content

Commit

Permalink
lightbox: Use a friendlier format for the date
Browse files Browse the repository at this point in the history
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: zulip#45
  • Loading branch information
lakshya1goel committed Dec 22, 2024
1 parent eb3e2aa commit ec0701f
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 6 deletions.
24 changes: 24 additions & 0 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
}
36 changes: 36 additions & 0 deletions lib/generated/l10n/zulip_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<ZulipLocalizations> {
Expand Down
26 changes: 26 additions & 0 deletions lib/generated/l10n/zulip_localizations_ar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
}
26 changes: 26 additions & 0 deletions lib/generated/l10n/zulip_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
}
26 changes: 26 additions & 0 deletions lib/generated/l10n/zulip_localizations_fr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
}
26 changes: 26 additions & 0 deletions lib/generated/l10n/zulip_localizations_ja.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
}
26 changes: 26 additions & 0 deletions lib/generated/l10n/zulip_localizations_pl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
}
26 changes: 26 additions & 0 deletions lib/generated/l10n/zulip_localizations_ru.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
}
41 changes: 36 additions & 5 deletions lib/widgets/lightbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion test/widgets/lightbox_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
Expand Down

0 comments on commit ec0701f

Please sign in to comment.