mirror of
https://github.com/krille-chan/fluffychat
synced 2024-10-05 15:52:44 +00:00
design: New date separators in timeline
This commit is contained in:
parent
7183158cf9
commit
f15b81c5a6
6 changed files with 134 additions and 79 deletions
|
@ -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"
|
||||
}
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
42
lib/pages/chat/events/date_separator.dart
Normal file
42
lib/pages/chat/events/date_separator.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue