From f15b81c5a66f971c59eb9db71f5c47d2d0a7c747 Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 27 Mar 2024 08:44:02 +0100 Subject: [PATCH] design: New date separators in timeline --- assets/l10n/intl_en.arb | 3 +- lib/pages/chat/chat_event_list.dart | 77 +++++++++++++---------- lib/pages/chat/events/date_separator.dart | 42 +++++++++++++ lib/pages/chat/events/message.dart | 76 +++++++++++----------- lib/pages/chat_list/chat_list_view.dart | 7 +-- lib/utils/date_time_extension.dart | 8 ++- 6 files changed, 134 insertions(+), 79 deletions(-) create mode 100644 lib/pages/chat/events/date_separator.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 9424b01ae..4bde0f879 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2485,5 +2485,6 @@ "stickers": "Stickers", "discover": "Discover", "commandHint_ignore": "Ignore the given matrix ID", - "commandHint_unignore": "Unignore the given matrix ID" + "commandHint_unignore": "Unignore the given matrix ID", + "today": "Today" } \ No newline at end of file diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index a35acb234..87b79ccdd 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -5,12 +5,14 @@ import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pages/chat/events/date_separator.dart'; import 'package:fluffychat/pages/chat/events/message.dart'; import 'package:fluffychat/pages/chat/seen_by_row.dart'; import 'package:fluffychat/pages/chat/typing_indicators.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; +import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; @@ -106,6 +108,8 @@ class ChatEventList extends StatelessWidget { // The message at this index: final event = events[i]; + final nextEvent = i + 1 < events.length ? events[i + 1] : null; + final previousEvent = i > 0 ? events[i - 1] : null; final animateIn = animateInEventIndex != null && controller.timeline!.events.length > animateInEventIndex && event == controller.timeline!.events[animateInEventIndex]; @@ -114,39 +118,48 @@ class ChatEventList extends StatelessWidget { key: ValueKey(event.eventId), index: i, controller: controller.scrollController, - child: Message( - event, - animateIn: animateIn, - resetAnimateIn: () { - controller.animateInEventIndex = null; - }, - onSwipe: () => controller.replyAction(replyTo: event), - onInfoTab: controller.showEventInfo, - onAvatarTab: (Event event) => showAdaptiveBottomSheet( - context: context, - builder: (c) => UserBottomSheet( - user: event.senderFromMemoryOrFallback, - outerContext: context, - onMention: () => controller.sendController.text += - '${event.senderFromMemoryOrFallback.mention} ', + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (nextEvent?.originServerTs + .isSameDate(event.originServerTs) != + true) + DateSeparator(date: event.originServerTs), + Message( + event, + animateIn: animateIn, + resetAnimateIn: () { + controller.animateInEventIndex = null; + }, + onSwipe: () => controller.replyAction(replyTo: event), + onInfoTab: controller.showEventInfo, + onAvatarTab: (Event event) => showAdaptiveBottomSheet( + context: context, + builder: (c) => UserBottomSheet( + user: event.senderFromMemoryOrFallback, + outerContext: context, + onMention: () => controller.sendController.text += + '${event.senderFromMemoryOrFallback.mention} ', + ), + ), + highlightMarker: + controller.scrollToEventIdMarker == event.eventId, + onSelect: controller.onSelectMessage, + scrollToEventId: (String eventId) => + controller.scrollToEventId(eventId), + longPressSelect: controller.selectedEvents.isNotEmpty, + selected: controller.selectedEvents + .any((e) => e.eventId == event.eventId), + timeline: controller.timeline!, + displayReadMarker: + controller.readMarkerEventId == event.eventId && + controller.timeline?.allowNewEvent == false, + nextEvent: nextEvent, + previousEvent: previousEvent, + avatarPresenceBackgroundColor: + hasWallpaper ? Colors.transparent : null, ), - ), - highlightMarker: - controller.scrollToEventIdMarker == event.eventId, - onSelect: controller.onSelectMessage, - scrollToEventId: (String eventId) => - controller.scrollToEventId(eventId), - longPressSelect: controller.selectedEvents.isNotEmpty, - selected: controller.selectedEvents - .any((e) => e.eventId == event.eventId), - timeline: controller.timeline!, - displayReadMarker: - controller.readMarkerEventId == event.eventId && - controller.timeline?.allowNewEvent == false, - nextEvent: i + 1 < events.length ? events[i + 1] : null, - previousEvent: i > 0 ? events[i - 1] : null, - avatarPresenceBackgroundColor: - hasWallpaper ? Colors.transparent : null, + ], ), ); }, diff --git a/lib/pages/chat/events/date_separator.dart b/lib/pages/chat/events/date_separator.dart new file mode 100644 index 000000000..f8d9ef06f --- /dev/null +++ b/lib/pages/chat/events/date_separator.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/date_time_extension.dart'; + +class DateSeparator extends StatelessWidget { + final DateTime date; + const DateSeparator({required this.date, super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + children: [ + Expanded( + child: Container( + height: 1, + color: Theme.of(context).dividerColor, + ), + ), + Padding( + padding: const EdgeInsets.all(12.0), + child: Text( + date.localizedTimeShort(context, dateOnly: true), + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontSize: 13 * AppConfig.fontSizeFactor, + ), + ), + ), + Expanded( + child: Container( + height: 1, + color: Theme.of(context).dividerColor, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index fc711c861..eb34058e7 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -203,27 +203,53 @@ class Message extends StatelessWidget { if (!nextEventSameSender) Padding( padding: const EdgeInsets.only(left: 8.0, bottom: 4), - child: ownMessage || event.room.isDirectChat - ? const SizedBox(height: 12) - : FutureBuilder( + child: Row( + mainAxisAlignment: ownMessage + ? MainAxisAlignment.end + : MainAxisAlignment.start, + children: [ + if (ownMessage || event.room.isDirectChat) + const SizedBox(height: 12) + else + FutureBuilder( future: event.fetchSenderUser(), builder: (context, snapshot) { final displayname = snapshot.data?.calcDisplayname() ?? event.senderFromMemoryOrFallback .calcDisplayname(); - return Text( - displayname, - style: TextStyle( - fontSize: 12, - color: (Theme.of(context).brightness == - Brightness.light - ? displayname.color - : displayname.lightColorText), + return ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth / 2, + ), + child: Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: (Theme.of(context).brightness == + Brightness.light + ? displayname.color + : displayname.lightColorText), + ), ), ); }, ), + Text( + (ownMessage || event.room.isDirectChat + ? '' + : ' | ') + + event.originServerTs + .localizedTimeOfDay(context), + style: TextStyle( + fontSize: 12 * AppConfig.fontSizeFactor, + color: Theme.of(context).colorScheme.secondary, + ), + ), + ], + ), ), Container( alignment: alignment, @@ -363,38 +389,12 @@ class Message extends StatelessWidget { Widget container; final showReceiptsRow = event.hasAggregatedEvents(timeline, RelationshipTypes.reaction); - if (showReceiptsRow || displayTime || selected || displayReadMarker) { + if (showReceiptsRow || selected || displayReadMarker) { container = Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - if (displayTime || selected) - Padding( - padding: displayTime - ? const EdgeInsets.symmetric(vertical: 8.0) - : EdgeInsets.zero, - child: Center( - child: Material( - color: displayTime - ? Theme.of(context).colorScheme.background - : Theme.of(context) - .colorScheme - .background - .withOpacity(0.33), - borderRadius: - BorderRadius.circular(AppConfig.borderRadius / 2), - clipBehavior: Clip.antiAlias, - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Text( - event.originServerTs.localizedTime(context), - style: TextStyle(fontSize: 13 * AppConfig.fontSizeFactor), - ), - ), - ), - ), - ), row, AnimatedSize( duration: FluffyThemes.animationDuration, diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 96e2a4675..f9ffd9de2 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -187,12 +187,7 @@ class ChatListView extends StatelessWidget { ? NavigationBar( elevation: 4, labelBehavior: - NavigationDestinationLabelBehavior.alwaysHide, - height: 64, - shadowColor: - Theme.of(context).colorScheme.onBackground, - surfaceTintColor: - Theme.of(context).colorScheme.background, + NavigationDestinationLabelBehavior.alwaysShow, selectedIndex: controller.selectedIndex, onDestinationSelected: controller.onDestinationSelected, diff --git a/lib/utils/date_time_extension.dart b/lib/utils/date_time_extension.dart index 121eaf72e..72452b108 100644 --- a/lib/utils/date_time_extension.dart +++ b/lib/utils/date_time_extension.dart @@ -45,7 +45,7 @@ extension DateTimeExtension on DateTime { /// Returns [localizedTimeOfDay()] if the ChatTime is today, the name of the week /// day if the ChatTime is this week and a date string else. - String localizedTimeShort(BuildContext context) { + String localizedTimeShort(BuildContext context, {bool dateOnly = false}) { final now = DateTime.now(); final sameYear = now.year == year; @@ -58,7 +58,7 @@ extension DateTimeExtension on DateTime { 1000 * 60 * 60 * 24 * 7; if (sameDay) { - return localizedTimeOfDay(context); + return dateOnly ? L10n.of(context)!.today : localizedTimeOfDay(context); } else if (sameWeek) { return DateFormat.EEEE(Localizations.localeOf(context).languageCode) .format(this); @@ -92,5 +92,9 @@ extension DateTimeExtension on DateTime { ); } + bool isSameDate(DateTime other) { + return year == other.year && month == other.month && day == other.day; + } + static String _z(int i) => i < 10 ? '0${i.toString()}' : i.toString(); }