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",
"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"
}

View file

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

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)
Padding(
padding: const EdgeInsets.only(left: 8.0, bottom: 4),
child: ownMessage || event.room.isDirectChat
? const SizedBox(height: 12)
: FutureBuilder<User?>(
child: Row(
mainAxisAlignment: ownMessage
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
if (ownMessage || event.room.isDirectChat)
const SizedBox(height: 12)
else
FutureBuilder<User?>(
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: <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,
AnimatedSize(
duration: FluffyThemes.animationDuration,

View file

@ -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,

View file

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