design: New date separators in timeline

This commit is contained in:
Krille 2024-03-27 08:44:02 +01:00
parent 7183158cf9
commit f15b81c5a6
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
6 changed files with 134 additions and 79 deletions

View file

@ -2485,5 +2485,6 @@
"stickers": "Stickers", "stickers": "Stickers",
"discover": "Discover", "discover": "Discover",
"commandHint_ignore": "Ignore the given matrix ID", "commandHint_ignore": "Ignore the given matrix ID",
"commandHint_unignore": "Unignore the given matrix ID" "commandHint_unignore": "Unignore the given matrix ID",
"today": "Today"
} }

View file

@ -5,12 +5,14 @@ import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.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/events/message.dart';
import 'package:fluffychat/pages/chat/seen_by_row.dart'; import 'package:fluffychat/pages/chat/seen_by_row.dart';
import 'package:fluffychat/pages/chat/typing_indicators.dart'; import 'package:fluffychat/pages/chat/typing_indicators.dart';
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/utils/account_config.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.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/matrix_sdk_extensions/filtered_timeline_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
@ -106,6 +108,8 @@ class ChatEventList extends StatelessWidget {
// The message at this index: // The message at this index:
final event = events[i]; 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 && final animateIn = animateInEventIndex != null &&
controller.timeline!.events.length > animateInEventIndex && controller.timeline!.events.length > animateInEventIndex &&
event == controller.timeline!.events[animateInEventIndex]; event == controller.timeline!.events[animateInEventIndex];
@ -114,39 +118,48 @@ class ChatEventList extends StatelessWidget {
key: ValueKey(event.eventId), key: ValueKey(event.eventId),
index: i, index: i,
controller: controller.scrollController, controller: controller.scrollController,
child: Message( child: Column(
event, mainAxisSize: MainAxisSize.min,
animateIn: animateIn, children: [
resetAnimateIn: () { if (nextEvent?.originServerTs
controller.animateInEventIndex = null; .isSameDate(event.originServerTs) !=
}, true)
onSwipe: () => controller.replyAction(replyTo: event), DateSeparator(date: event.originServerTs),
onInfoTab: controller.showEventInfo, Message(
onAvatarTab: (Event event) => showAdaptiveBottomSheet( event,
context: context, animateIn: animateIn,
builder: (c) => UserBottomSheet( resetAnimateIn: () {
user: event.senderFromMemoryOrFallback, controller.animateInEventIndex = null;
outerContext: context, },
onMention: () => controller.sendController.text += onSwipe: () => controller.replyAction(replyTo: event),
'${event.senderFromMemoryOrFallback.mention} ', 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,
), ),
); );
}, },

View file

@ -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,
),
),
],
),
);
}
}

View file

@ -203,27 +203,53 @@ class Message extends StatelessWidget {
if (!nextEventSameSender) if (!nextEventSameSender)
Padding( Padding(
padding: const EdgeInsets.only(left: 8.0, bottom: 4), padding: const EdgeInsets.only(left: 8.0, bottom: 4),
child: ownMessage || event.room.isDirectChat child: Row(
? const SizedBox(height: 12) mainAxisAlignment: ownMessage
: FutureBuilder<User?>( ? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
if (ownMessage || event.room.isDirectChat)
const SizedBox(height: 12)
else
FutureBuilder<User?>(
future: event.fetchSenderUser(), future: event.fetchSenderUser(),
builder: (context, snapshot) { builder: (context, snapshot) {
final displayname = final displayname =
snapshot.data?.calcDisplayname() ?? snapshot.data?.calcDisplayname() ??
event.senderFromMemoryOrFallback event.senderFromMemoryOrFallback
.calcDisplayname(); .calcDisplayname();
return Text( return ConstrainedBox(
displayname, constraints: const BoxConstraints(
style: TextStyle( maxWidth: FluffyThemes.columnWidth / 2,
fontSize: 12, ),
color: (Theme.of(context).brightness == child: Text(
Brightness.light displayname,
? displayname.color maxLines: 1,
: displayname.lightColorText), 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( Container(
alignment: alignment, alignment: alignment,
@ -363,38 +389,12 @@ class Message extends StatelessWidget {
Widget container; Widget container;
final showReceiptsRow = final showReceiptsRow =
event.hasAggregatedEvents(timeline, RelationshipTypes.reaction); event.hasAggregatedEvents(timeline, RelationshipTypes.reaction);
if (showReceiptsRow || displayTime || selected || displayReadMarker) { if (showReceiptsRow || selected || displayReadMarker) {
container = Column( container = Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: crossAxisAlignment:
ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
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, row,
AnimatedSize( AnimatedSize(
duration: FluffyThemes.animationDuration, duration: FluffyThemes.animationDuration,

View file

@ -187,12 +187,7 @@ class ChatListView extends StatelessWidget {
? NavigationBar( ? NavigationBar(
elevation: 4, elevation: 4,
labelBehavior: labelBehavior:
NavigationDestinationLabelBehavior.alwaysHide, NavigationDestinationLabelBehavior.alwaysShow,
height: 64,
shadowColor:
Theme.of(context).colorScheme.onBackground,
surfaceTintColor:
Theme.of(context).colorScheme.background,
selectedIndex: controller.selectedIndex, selectedIndex: controller.selectedIndex,
onDestinationSelected: onDestinationSelected:
controller.onDestinationSelected, controller.onDestinationSelected,

View file

@ -45,7 +45,7 @@ extension DateTimeExtension on DateTime {
/// Returns [localizedTimeOfDay()] if the ChatTime is today, the name of the week /// Returns [localizedTimeOfDay()] if the ChatTime is today, the name of the week
/// day if the ChatTime is this week and a date string else. /// 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 now = DateTime.now();
final sameYear = now.year == year; final sameYear = now.year == year;
@ -58,7 +58,7 @@ extension DateTimeExtension on DateTime {
1000 * 60 * 60 * 24 * 7; 1000 * 60 * 60 * 24 * 7;
if (sameDay) { if (sameDay) {
return localizedTimeOfDay(context); return dateOnly ? L10n.of(context)!.today : localizedTimeOfDay(context);
} else if (sameWeek) { } else if (sameWeek) {
return DateFormat.EEEE(Localizations.localeOf(context).languageCode) return DateFormat.EEEE(Localizations.localeOf(context).languageCode)
.format(this); .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(); static String _z(int i) => i < 10 ? '0${i.toString()}' : i.toString();
} }