Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion packages/neon_framework/lib/l10n/en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -184,5 +184,54 @@
"accountOptionsAutomatic": "Automatic",
"licenses": "Licenses",
"sourceCode": "Source code",
"issueTracker": "Report a bug or request a feature"
"issueTracker": "Report a bug or request a feature",
"relativeTimeNow": "now",
"relativeTimePast": "{time} ago",
"@relativeTimePast": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"relativeTimeFuture": "in {time}",
"@relativeTimeFuture": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"relativeTimeMinutes": "{time} {time,plural, =1{minute}other{minutes}}",
"@relativeTimeMinutes": {
"placeholders": {
"time": {
"type": "int"
}
}
},
"relativeTimeHours": "{time} {time,plural, =1{hour}other{hours}}",
"@relativeTimeHours": {
"placeholders": {
"time": {
"type": "int"
}
}
},
"relativeTimeDays": "{time} {time,plural, =1{day}other{days}}",
"@relativeTimeDays": {
"placeholders": {
"time": {
"type": "int"
}
}
},
"relativeTimeYears": "{time} {time,plural, =1{year}other{years}}",
"@relativeTimeYears": {
"placeholders": {
"time": {
"type": "int"
}
}
}
}
42 changes: 42 additions & 0 deletions packages/neon_framework/lib/l10n/localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,48 @@ abstract class NeonLocalizations {
/// In en, this message translates to:
/// **'Report a bug or request a feature'**
String get issueTracker;

/// No description provided for @relativeTimeNow.
///
/// In en, this message translates to:
/// **'now'**
String get relativeTimeNow;

/// No description provided for @relativeTimePast.
///
/// In en, this message translates to:
/// **'{time} ago'**
String relativeTimePast(String time);

/// No description provided for @relativeTimeFuture.
///
/// In en, this message translates to:
/// **'in {time}'**
String relativeTimeFuture(String time);

/// No description provided for @relativeTimeMinutes.
///
/// In en, this message translates to:
/// **'{time} {time,plural, =1{minute}other{minutes}}'**
String relativeTimeMinutes(int time);

/// No description provided for @relativeTimeHours.
///
/// In en, this message translates to:
/// **'{time} {time,plural, =1{hour}other{hours}}'**
String relativeTimeHours(int time);

/// No description provided for @relativeTimeDays.
///
/// In en, this message translates to:
/// **'{time} {time,plural, =1{day}other{days}}'**
String relativeTimeDays(int time);

/// No description provided for @relativeTimeYears.
///
/// In en, this message translates to:
/// **'{time} {time,plural, =1{year}other{years}}'**
String relativeTimeYears(int time);
}

class _NeonLocalizationsDelegate extends LocalizationsDelegate<NeonLocalizations> {
Expand Down
57 changes: 57 additions & 0 deletions packages/neon_framework/lib/l10n/localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,61 @@ class NeonLocalizationsEn extends NeonLocalizations {

@override
String get issueTracker => 'Report a bug or request a feature';

@override
String get relativeTimeNow => 'now';

@override
String relativeTimePast(String time) {
return '$time ago';
}

@override
String relativeTimeFuture(String time) {
return 'in $time';
}

@override
String relativeTimeMinutes(int time) {
String _temp0 = intl.Intl.pluralLogic(
time,
locale: localeName,
other: 'minutes',
one: 'minute',
);
return '$time $_temp0';
}

@override
String relativeTimeHours(int time) {
String _temp0 = intl.Intl.pluralLogic(
time,
locale: localeName,
other: 'hours',
one: 'hour',
);
return '$time $_temp0';
}

@override
String relativeTimeDays(int time) {
String _temp0 = intl.Intl.pluralLogic(
time,
locale: localeName,
other: 'days',
one: 'day',
);
return '$time $_temp0';
}

@override
String relativeTimeYears(int time) {
String _temp0 = intl.Intl.pluralLogic(
time,
locale: localeName,
other: 'years',
one: 'year',
);
return '$time $_temp0';
}
}
47 changes: 25 additions & 22 deletions packages/neon_framework/lib/src/utils/relative_time.dart
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
import 'package:meta/meta.dart';
import 'package:neon_framework/l10n/localizations.dart';

/// Extension for formatting the difference between two [DateTime]s.
@internal
extension RelativeTimeFormatDateTime on DateTime {
/// Format the relative time between this and [to].
///
/// If unspecified [DateTime.now] will be used.
String formatRelative([DateTime? to]) => (to ?? DateTime.now()).difference(toLocal()).formatRelative();
/// If [to] is unspecified [DateTime.now] will be used.
String formatRelative(
NeonLocalizations localizations, [
DateTime? to,
]) =>
toLocal().difference(to ?? DateTime.now()).formatRelative(localizations);
}

/// Extension for formatting difference of a [Duration].
@internal
extension RelativeTimeFormatDuration on Duration {
/// Format the relative time.
String formatRelative() {
var diff = this;
final text = StringBuffer();

if (diff.isNegative) {
// Only add minus sign when not showing 'now'
if (diff.inMinutes <= -1) {
text.write('-');
}
diff = Duration(microseconds: -diff.inMicroseconds);
///
String formatRelative(NeonLocalizations localizations) {
final normalizedDuration = isNegative ? Duration(microseconds: -inMicroseconds) : this;
if (normalizedDuration.inMinutes < 1) {
return localizations.relativeTimeNow;
}

if (diff.inMinutes < 1) {
text.write('now');
} else if (diff.inHours < 1) {
text.write('${diff.inMinutes}m');
} else if (diff.inDays < 1) {
text.write('${diff.inHours}h');
} else if (diff.inDays < 365) {
text.write('${diff.inDays}d');
String time;
if (normalizedDuration.inHours < 1) {
time = localizations.relativeTimeMinutes(normalizedDuration.inMinutes);
} else if (normalizedDuration.inDays < 1) {
time = localizations.relativeTimeHours(normalizedDuration.inHours);
} else if (normalizedDuration.inDays < 365) {
time = localizations.relativeTimeDays(normalizedDuration.inDays);
} else {
text.write('${diff.inDays ~/ 365}y');
time = localizations.relativeTimeYears(normalizedDuration.inDays ~/ 365);
}

return text.toString();
if (isNegative) {
return localizations.relativeTimePast(time);
} else {
return localizations.relativeTimeFuture(time);
}
}
}
3 changes: 2 additions & 1 deletion packages/neon_framework/lib/src/widgets/relative_time.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:neon_framework/l10n/localizations.dart';
import 'package:neon_framework/src/utils/relative_time.dart';

/// Shows the time elapsed since a [DateTime] and periodically updates itself.
Expand Down Expand Up @@ -46,7 +47,7 @@ class _RelativeTimeState extends State<RelativeTime> {

@override
Widget build(BuildContext context) => Text(
widget.date.formatRelative(),
widget.date.formatRelative(NeonLocalizations.of(context)),
style: widget.style,
);
}
51 changes: 27 additions & 24 deletions packages/neon_framework/test/relative_time_test.dart
Original file line number Diff line number Diff line change
@@ -1,45 +1,48 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:neon_framework/l10n/localizations_en.dart';
import 'package:neon_framework/src/utils/relative_time.dart';

void main() {
final localizations = NeonLocalizationsEn();

final durations = <Duration, String>{
const Duration(days: -730): '-2y',
const Duration(days: -729): '-1y',
const Duration(days: -365): '-1y',
const Duration(days: -364): '-364d',
const Duration(days: -1): '-1d',
const Duration(hours: -23): '-23h',
const Duration(hours: -1): '-1h',
const Duration(minutes: -59): '-59m',
const Duration(minutes: -1): '-1m',
const Duration(seconds: -59): 'now',
const Duration(seconds: -1): 'now',
const Duration(seconds: 1): 'now',
const Duration(days: 730): 'in 2 years',
const Duration(days: 729): 'in 1 year',
const Duration(days: 365): 'in 1 year',
const Duration(days: 364): 'in 364 days',
const Duration(days: 1): 'in 1 day',
const Duration(hours: 23): 'in 23 hours',
const Duration(hours: 1): 'in 1 hour',
const Duration(minutes: 59): 'in 59 minutes',
const Duration(minutes: 1): 'in 1 minute',
const Duration(seconds: 59): 'now',
const Duration(minutes: 1): '1m',
const Duration(minutes: 59): '59m',
const Duration(hours: 1): '1h',
const Duration(hours: 23): '23h',
const Duration(days: 1): '1d',
const Duration(days: 364): '364d',
const Duration(days: 365): '1y',
const Duration(days: 729): '1y',
const Duration(days: 730): '2y',
const Duration(seconds: 1): 'now',
const Duration(seconds: -1): 'now',
const Duration(seconds: -59): 'now',
const Duration(minutes: -1): '1 minute ago',
const Duration(minutes: -59): '59 minutes ago',
const Duration(hours: -1): '1 hour ago',
const Duration(hours: -23): '23 hours ago',
const Duration(days: -1): '1 day ago',
const Duration(days: -364): '364 days ago',
const Duration(days: -365): '1 year ago',
const Duration(days: -729): '1 year ago',
const Duration(days: -730): '2 years ago',
};

test('Duration', () {
for (final entry in durations.entries) {
expect(entry.key.formatRelative(), entry.value);
expect(entry.key.formatRelative(localizations), entry.value);
}
});

test('DateTime', () {
final base = DateTime.now();

expect(base.formatRelative(), 'now');
expect(base.formatRelative(localizations), 'now');

for (final entry in durations.entries) {
expect(base.formatRelative(base.add(entry.key)), entry.value);
expect(base.add(entry.key).formatRelative(localizations, base), entry.value);
}
});
}