chore: Revert "refactor: Make ChatListItem cache lasteventbody for better performance"

This reverts commit 42438052b1.
This commit is contained in:
Christian Kußowski 2025-09-30 11:28:56 +02:00
commit 0be267e8a4
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652

View file

@ -15,7 +15,7 @@ import '../../widgets/avatar.dart';
enum ArchivedRoomAction { delete, rejoin } enum ArchivedRoomAction { delete, rejoin }
class ChatListItem extends StatefulWidget { class ChatListItem extends StatelessWidget {
final Room room; final Room room;
final Room? space; final Room? space;
final bool activeChat; final bool activeChat;
@ -35,87 +35,37 @@ class ChatListItem extends StatefulWidget {
super.key, super.key,
}); });
@override
State<ChatListItem> createState() => _ChatListItemState();
}
class _ChatListItemState extends State<ChatListItem> {
String? lastEventKey;
String? lastEventBody;
late final bool isDirectChat;
late final String? directChatMatrixId;
String _calcLastEventKey() =>
'${widget.room.lastEvent?.eventId}_${widget.room.lastEvent?.type}_${widget.room.lastEvent?.redacted}';
@override
void initState() {
isDirectChat = widget.room.isDirectChat;
directChatMatrixId = widget.room.directChatMatrixID;
super.initState();
lastEventKey = _calcLastEventKey();
lastEventBody = widget.room.lastEvent?.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)),
hideReply: true,
hideEdit: true,
plaintextBody: true,
removeMarkdown: true,
withSenderNamePrefix: (!isDirectChat ||
directChatMatrixId != widget.room.lastEvent?.senderId),
);
if (!widget.room.participantListComplete) {
widget.room.loadHeroUsers().then((_) {
setState(() {});
});
}
}
void _maybeUpdateLastEventBody() async {
final newLastEventKey = _calcLastEventKey();
if (newLastEventKey == lastEventKey) return;
final newLastEventBody = await widget.room.lastEvent?.calcLocalizedBody(
MatrixLocals(L10n.of(context)),
hideReply: true,
hideEdit: true,
plaintextBody: true,
removeMarkdown: true,
withSenderNamePrefix: (!isDirectChat ||
directChatMatrixId != widget.room.lastEvent?.senderId),
);
if (lastEventBody != newLastEventBody) {
setState(() {
lastEventBody = newLastEventBody;
});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_maybeUpdateLastEventBody();
final theme = Theme.of(context); final theme = Theme.of(context);
final isMuted = widget.room.pushRuleState != PushRuleState.notify; final isMuted = room.pushRuleState != PushRuleState.notify;
final typingText = widget.room.getLocalizedTypingText(context); final typingText = room.getLocalizedTypingText(context);
final lastEvent = widget.room.lastEvent; final lastEvent = room.lastEvent;
final ownMessage = lastEvent?.senderId == widget.room.client.userID; final ownMessage = lastEvent?.senderId == room.client.userID;
final unread = widget.room.isUnread; final unread = room.isUnread;
final unreadBubbleSize = unread || widget.room.hasNewMessages final directChatMatrixId = room.directChatMatrixID;
? widget.room.notificationCount > 0 final isDirectChat = directChatMatrixId != null;
final unreadBubbleSize = unread || room.hasNewMessages
? room.notificationCount > 0
? 20.0 ? 20.0
: 14.0 : 14.0
: 0.0; : 0.0;
final hasNotifications = widget.room.notificationCount > 0; final hasNotifications = room.notificationCount > 0;
final backgroundColor = final backgroundColor =
widget.activeChat ? theme.colorScheme.secondaryContainer : null; activeChat ? theme.colorScheme.secondaryContainer : null;
final displayname = widget.room.getLocalizedDisplayname( final displayname = room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)), MatrixLocals(L10n.of(context)),
); );
final filter = widget.filter; final filter = this.filter;
if (filter != null && !displayname.toLowerCase().contains(filter)) { if (filter != null && !displayname.toLowerCase().contains(filter)) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
final space = widget.space;
final needLastEventSender = lastEvent == null
? false
: room.getState(EventTypes.RoomMember, lastEvent.senderId) == null;
final space = this.space;
return Padding( return Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@ -126,288 +76,314 @@ class _ChatListItemState extends State<ChatListItem> {
borderRadius: BorderRadius.circular(AppConfig.borderRadius), borderRadius: BorderRadius.circular(AppConfig.borderRadius),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
color: backgroundColor, color: backgroundColor,
child: HoverBuilder( child: FutureBuilder(
builder: (context, listTileHovered) => ListTile( future: room.loadHeroUsers(),
visualDensity: const VisualDensity(vertical: -0.5), builder: (context, snapshot) => HoverBuilder(
contentPadding: const EdgeInsets.symmetric(horizontal: 8), builder: (context, listTileHovered) => ListTile(
onLongPress: () => widget.onLongPress?.call(context), visualDensity: const VisualDensity(vertical: -0.5),
leading: HoverBuilder( contentPadding: const EdgeInsets.symmetric(horizontal: 8),
builder: (context, hovered) => AnimatedScale( onLongPress: () => onLongPress?.call(context),
duration: FluffyThemes.animationDuration, leading: HoverBuilder(
curve: FluffyThemes.animationCurve, builder: (context, hovered) => AnimatedScale(
scale: hovered ? 1.1 : 1.0, duration: FluffyThemes.animationDuration,
child: SizedBox( curve: FluffyThemes.animationCurve,
width: Avatar.defaultSize, scale: hovered ? 1.1 : 1.0,
height: Avatar.defaultSize, child: SizedBox(
child: Stack( width: Avatar.defaultSize,
children: [ height: Avatar.defaultSize,
if (space != null) child: Stack(
children: [
if (space != null)
Positioned(
top: 0,
left: 0,
child: Avatar(
border: BorderSide(
width: 2,
color: backgroundColor ??
theme.colorScheme.surface,
),
borderRadius: BorderRadius.circular(
AppConfig.borderRadius / 4,
),
mxContent: space.avatar,
size: Avatar.defaultSize * 0.75,
name: space.getLocalizedDisplayname(),
onTap: () => onLongPress?.call(context),
),
),
Positioned( Positioned(
top: 0, bottom: 0,
left: 0, right: 0,
child: Avatar( child: Avatar(
border: BorderSide( border: space == null
width: 2, ? room.isSpace
color: ? BorderSide(
backgroundColor ?? theme.colorScheme.surface, width: 1,
), color: theme.dividerColor,
borderRadius: BorderRadius.circular( )
AppConfig.borderRadius / 4, : null
), : BorderSide(
mxContent: space.avatar, width: 2,
size: Avatar.defaultSize * 0.75, color: backgroundColor ??
name: space.getLocalizedDisplayname(), theme.colorScheme.surface,
onTap: () => widget.onLongPress?.call(context), ),
borderRadius: room.isSpace
? BorderRadius.circular(
AppConfig.borderRadius / 4,
)
: null,
mxContent: room.avatar,
size: space != null
? Avatar.defaultSize * 0.75
: Avatar.defaultSize,
name: displayname,
presenceUserId: directChatMatrixId,
presenceBackgroundColor: backgroundColor,
onTap: () => onLongPress?.call(context),
), ),
), ),
Positioned( Positioned(
bottom: 0, top: 0,
right: 0, right: 0,
child: Avatar( child: GestureDetector(
border: space == null onTap: () => onLongPress?.call(context),
? widget.room.isSpace child: AnimatedScale(
? BorderSide( duration: FluffyThemes.animationDuration,
width: 1, curve: FluffyThemes.animationCurve,
color: theme.dividerColor, scale: listTileHovered ? 1.0 : 0.0,
) child: Material(
: null color: backgroundColor,
: BorderSide( borderRadius: BorderRadius.circular(16),
width: 2, child: const Icon(
color: backgroundColor ?? Icons.arrow_drop_down_circle_outlined,
theme.colorScheme.surface, size: 18,
), ),
borderRadius: widget.room.isSpace
? BorderRadius.circular(
AppConfig.borderRadius / 4,
)
: null,
mxContent: widget.room.avatar,
size: space != null
? Avatar.defaultSize * 0.75
: Avatar.defaultSize,
name: displayname,
presenceUserId: directChatMatrixId,
presenceBackgroundColor: backgroundColor,
onTap: () => widget.onLongPress?.call(context),
),
),
Positioned(
top: 0,
right: 0,
child: GestureDetector(
onTap: () => widget.onLongPress?.call(context),
child: AnimatedScale(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
scale: listTileHovered ? 1.0 : 0.0,
child: Material(
color: backgroundColor,
borderRadius: BorderRadius.circular(16),
child: const Icon(
Icons.arrow_drop_down_circle_outlined,
size: 18,
), ),
), ),
), ),
), ),
), ],
], ),
), ),
), ),
), ),
), title: Row(
title: Row( children: <Widget>[
children: <Widget>[ Expanded(
Expanded(
child: Text(
displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: TextStyle(
fontWeight: unread || widget.room.hasNewMessages
? FontWeight.w500
: null,
),
),
),
if (isMuted)
const Padding(
padding: EdgeInsets.only(left: 4.0),
child: Icon(
Icons.notifications_off_outlined,
size: 16,
),
),
if (widget.room.isFavourite)
Padding(
padding: EdgeInsets.only(
right: hasNotifications ? 4.0 : 0.0,
),
child: Icon(
Icons.push_pin,
size: 16,
color: theme.colorScheme.primary,
),
),
if (!widget.room.isSpace &&
widget.room.membership != Membership.invite)
Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Text( child: Text(
widget.room.latestEventReceivedTime displayname,
.localizedTimeShort(context), maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: TextStyle( style: TextStyle(
fontSize: 12, fontWeight: unread || room.hasNewMessages
color: theme.colorScheme.outline, ? FontWeight.w500
: null,
), ),
), ),
), ),
], if (isMuted)
), const Padding(
subtitle: Row( padding: EdgeInsets.only(left: 4.0),
crossAxisAlignment: CrossAxisAlignment.start, child: Icon(
mainAxisAlignment: MainAxisAlignment.center, Icons.notifications_off_outlined,
children: <Widget>[ size: 16,
if (typingText.isEmpty && ),
ownMessage && ),
widget.room.lastEvent!.status.isSending) ...[ if (room.isFavourite)
const SizedBox( Padding(
width: 16, padding: EdgeInsets.only(
height: 16, right: hasNotifications ? 4.0 : 0.0,
child: CircularProgressIndicator.adaptive(strokeWidth: 2), ),
), child: Icon(
const SizedBox(width: 4), Icons.push_pin,
size: 16,
color: theme.colorScheme.primary,
),
),
if (!room.isSpace && room.membership != Membership.invite)
Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Text(
room.latestEventReceivedTime
.localizedTimeShort(context),
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
),
),
], ],
AnimatedContainer( ),
width: typingText.isEmpty ? 0 : 18, subtitle: Row(
clipBehavior: Clip.hardEdge, crossAxisAlignment: CrossAxisAlignment.start,
decoration: const BoxDecoration(), mainAxisAlignment: MainAxisAlignment.center,
duration: FluffyThemes.animationDuration, children: <Widget>[
curve: FluffyThemes.animationCurve, if (typingText.isEmpty &&
padding: const EdgeInsets.only(right: 4), ownMessage &&
child: Icon( room.lastEvent!.status.isSending) ...[
Icons.edit_outlined, const SizedBox(
color: theme.colorScheme.secondary, width: 16,
size: 14, height: 16,
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
),
const SizedBox(width: 4),
],
AnimatedContainer(
width: typingText.isEmpty ? 0 : 18,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
padding: const EdgeInsets.only(right: 4),
child: Icon(
Icons.edit_outlined,
color: theme.colorScheme.secondary,
size: 14,
),
), ),
), Expanded(
Expanded( child: room.isSpace && room.membership == Membership.join
child: widget.room.isSpace && ? Text(
widget.room.membership == Membership.join L10n.of(context).countChatsAndCountParticipants(
? Text( room.spaceChildren.length,
L10n.of(context).countChatsAndCountParticipants( (room.summary.mJoinedMemberCount ?? 1),
widget.room.spaceChildren.length, ),
(widget.room.summary.mJoinedMemberCount ?? 1), style: TextStyle(color: theme.colorScheme.outline),
), )
style: TextStyle(color: theme.colorScheme.outline), : typingText.isNotEmpty
) ? Text(
: typingText.isNotEmpty typingText,
? Text( style: TextStyle(
typingText, color: theme.colorScheme.primary,
style: TextStyle( ),
color: theme.colorScheme.primary, maxLines: 1,
), softWrap: false,
maxLines: 1, )
softWrap: false, : FutureBuilder(
) key: ValueKey(
: Text( '${lastEvent?.eventId}_${lastEvent?.type}_${lastEvent?.redacted}',
widget.room.membership == Membership.invite ),
? widget.room future: needLastEventSender
.getState( ? lastEvent.calcLocalizedBody(
EventTypes.RoomMember, MatrixLocals(L10n.of(context)),
widget.room.client.userID!, hideReply: true,
) hideEdit: true,
?.content plaintextBody: true,
.tryGet<String>('reason') ?? removeMarkdown: true,
(isDirectChat withSenderNamePrefix: (!isDirectChat ||
? L10n.of(context).newChatRequest directChatMatrixId !=
: L10n.of(context).inviteGroupChat) room.lastEvent?.senderId),
: lastEventBody ?? )
L10n.of(context).noMessagesYet, : null,
softWrap: false, initialData:
maxLines: lastEvent?.calcLocalizedBodyFallback(
widget.room.notificationCount >= 1 ? 2 : 1, MatrixLocals(L10n.of(context)),
overflow: TextOverflow.ellipsis, hideReply: true,
style: TextStyle( hideEdit: true,
color: unread || widget.room.hasNewMessages plaintextBody: true,
? theme.colorScheme.onSurface removeMarkdown: true,
: theme.colorScheme.outline, withSenderNamePrefix: (!isDirectChat ||
decoration: directChatMatrixId !=
widget.room.lastEvent?.redacted == true room.lastEvent?.senderId),
),
builder: (context, snapshot) => Text(
room.membership == Membership.invite
? room
.getState(
EventTypes.RoomMember,
room.client.userID!,
)
?.content
.tryGet<String>('reason') ??
(isDirectChat
? L10n.of(context).newChatRequest
: L10n.of(context)
.inviteGroupChat)
: snapshot.data ??
L10n.of(context).noMessagesYet,
softWrap: false,
maxLines: room.notificationCount >= 1 ? 2 : 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: unread || room.hasNewMessages
? theme.colorScheme.onSurface
: theme.colorScheme.outline,
decoration: room.lastEvent?.redacted == true
? TextDecoration.lineThrough ? TextDecoration.lineThrough
: null, : null,
),
),
), ),
),
const SizedBox(width: 8),
AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 7),
height: unreadBubbleSize,
width: !hasNotifications && !unread && !room.hasNewMessages
? 0
: (unreadBubbleSize - 9) *
room.notificationCount.toString().length +
9,
decoration: BoxDecoration(
color: room.highlightCount > 0
? theme.colorScheme.error
: hasNotifications || room.markedUnread
? theme.colorScheme.primary
: theme.colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(7),
),
child: hasNotifications
? Text(
room.notificationCount.toString(),
style: TextStyle(
color: room.highlightCount > 0
? theme.colorScheme.onError
: hasNotifications
? theme.colorScheme.onPrimary
: theme.colorScheme.onPrimaryContainer,
fontSize: 13,
fontWeight: FontWeight.w500,
), ),
), textAlign: TextAlign.center,
const SizedBox(width: 8), )
AnimatedContainer( : const SizedBox.shrink(),
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 7),
height: unreadBubbleSize,
width: !hasNotifications &&
!unread &&
!widget.room.hasNewMessages
? 0
: (unreadBubbleSize - 9) *
widget.room.notificationCount.toString().length +
9,
decoration: BoxDecoration(
color: widget.room.highlightCount > 0
? theme.colorScheme.error
: hasNotifications || widget.room.markedUnread
? theme.colorScheme.primary
: theme.colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(7),
), ),
child: hasNotifications ],
? Text( ),
widget.room.notificationCount.toString(), onTap: onTap,
style: TextStyle( trailing: onForget == null
color: widget.room.highlightCount > 0 ? room.membership == Membership.invite
? theme.colorScheme.onError ? IconButton(
: hasNotifications tooltip: L10n.of(context).declineInvitation,
? theme.colorScheme.onPrimary icon: const Icon(Icons.delete_forever_outlined),
: theme.colorScheme.onPrimaryContainer, color: theme.colorScheme.error,
fontSize: 13, onPressed: () async {
fontWeight: FontWeight.w500, final consent = await showOkCancelAlertDialog(
), context: context,
textAlign: TextAlign.center, title: L10n.of(context).declineInvitation,
message: L10n.of(context).areYouSure,
okLabel: L10n.of(context).yes,
isDestructive: true,
);
if (consent != OkCancelResult.ok) return;
if (!context.mounted) return;
await showFutureLoadingDialog(
context: context,
future: room.leave,
);
},
) )
: const SizedBox.shrink(), : null
), : IconButton(
], icon: const Icon(Icons.delete_outlined),
onPressed: onForget,
),
), ),
onTap: widget.onTap,
trailing: widget.onForget == null
? widget.room.membership == Membership.invite
? IconButton(
tooltip: L10n.of(context).declineInvitation,
icon: const Icon(Icons.delete_forever_outlined),
color: theme.colorScheme.error,
onPressed: () async {
final consent = await showOkCancelAlertDialog(
context: context,
title: L10n.of(context).declineInvitation,
message: L10n.of(context).areYouSure,
okLabel: L10n.of(context).yes,
isDestructive: true,
);
if (consent != OkCancelResult.ok) return;
if (!context.mounted) return;
await showFutureLoadingDialog(
context: context,
future: widget.room.leave,
);
},
)
: null
: IconButton(
icon: const Icon(Icons.delete_outlined),
onPressed: widget.onForget,
),
), ),
), ),
), ),