💫 Message list fully animated

This commit is contained in:
2025-10-13 00:16:50 +08:00
parent 51b4754182
commit 8d1c145b0b

View File

@@ -561,59 +561,77 @@ class ChatRoomScreen extends HookConsumerWidget {
.abs() > .abs() >
3; 3;
final key = ValueKey('$messageKeyPrefix${message.id}'); // Use a stable animation key that doesn't change during message lifecycle
final animationKey = ValueKey(
'message-anim-${message.nonce ?? message.id}',
);
return chatIdentity.when( return TweenAnimationBuilder<double>(
skipError: true, key: animationKey,
data: tween: Tween<double>(begin: 0.0, end: 1.0),
(identity) => MessageItem( duration: Duration(
key: key, milliseconds: 400 + (index % 5) * 50,
message: message, ), // Staggered delay
isCurrentUser: identity?.id == message.senderId, curve: Curves.easeOutCubic,
onAction: (action) { builder: (context, animationValue, child) {
switch (action) { return Transform.translate(
case MessageItemAction.delete: offset: Offset(
messagesNotifier.deleteMessage(message.id); 0,
case MessageItemAction.edit: 20 * (1 - animationValue),
messageEditingTo.value = message.toRemoteMessage(); ), // Slide up from bottom
messageController.text = child: Opacity(opacity: animationValue, child: child),
messageEditingTo.value?.content ?? ''; );
attachments.value = },
messageEditingTo.value!.attachments child: chatIdentity.when(
.map((e) => UniversalFile.fromAttachment(e)) skipError: true,
.toList(); data:
case MessageItemAction.forward: (identity) => MessageItem(
messageForwardingTo.value = message.toRemoteMessage(); message: message,
case MessageItemAction.reply: isCurrentUser: identity?.id == message.senderId,
messageReplyingTo.value = message.toRemoteMessage(); onAction: (action) {
case MessageItemAction.resend: switch (action) {
messagesNotifier.retryMessage(message.id); case MessageItemAction.delete:
} messagesNotifier.deleteMessage(message.id);
}, case MessageItemAction.edit:
onJump: (messageId) { messageEditingTo.value = message.toRemoteMessage();
scrollToMessage( messageController.text =
messageId: messageId, messageEditingTo.value?.content ?? '';
messageList: messageList, attachments.value =
messagesNotifier: messagesNotifier, messageEditingTo.value!.attachments
listController: listController, .map((e) => UniversalFile.fromAttachment(e))
scrollController: scrollController, .toList();
ref: ref, case MessageItemAction.forward:
); messageForwardingTo.value = message.toRemoteMessage();
}, case MessageItemAction.reply:
progress: attachmentProgress.value[message.id], messageReplyingTo.value = message.toRemoteMessage();
showAvatar: isLastInGroup, case MessageItemAction.resend:
), messagesNotifier.retryMessage(message.id);
loading: }
() => MessageItem( },
key: key, onJump: (messageId) {
message: message, scrollToMessage(
isCurrentUser: false, messageId: messageId,
onAction: null, messageList: messageList,
progress: null, messagesNotifier: messagesNotifier,
showAvatar: false, listController: listController,
onJump: (_) {}, scrollController: scrollController,
), ref: ref,
error: (_, _) => SizedBox.shrink(key: key), );
},
progress: attachmentProgress.value[message.id],
showAvatar: isLastInGroup,
),
loading:
() => MessageItem(
message: message,
isCurrentUser: false,
onAction: null,
progress: null,
showAvatar: false,
onJump: (_) {},
),
error: (_, _) => SizedBox.shrink(),
),
); );
}, },
); );
@@ -692,19 +710,43 @@ class ChatRoomScreen extends HookConsumerWidget {
child: Column( child: Column(
children: [ children: [
Expanded( Expanded(
child: messages.when( child: AnimatedSwitcher(
data: duration: const Duration(milliseconds: 300),
(messageList) => switchInCurve: Curves.easeOutCubic,
messageList.isEmpty switchOutCurve: Curves.easeInCubic,
? Center(child: Text('No messages yet'.tr())) transitionBuilder: (
: chatMessageListWidget(messageList), Widget child,
loading: Animation<double> animation,
() => const Center(child: CircularProgressIndicator()), ) {
error: return SlideTransition(
(error, _) => ResponseErrorWidget( position: Tween<Offset>(
error: error, begin: const Offset(0, 0.05),
onRetry: () => messagesNotifier.loadInitial(), end: Offset.zero,
), ).animate(animation),
child: FadeTransition(opacity: animation, child: child),
);
},
child: messages.when(
data:
(messageList) =>
messageList.isEmpty
? Center(
key: const ValueKey('empty-messages'),
child: Text('No messages yet'.tr()),
)
: chatMessageListWidget(messageList),
loading:
() => const Center(
key: ValueKey('loading-messages'),
child: CircularProgressIndicator(),
),
error:
(error, _) => ResponseErrorWidget(
key: const ValueKey('error-messages'),
error: error,
onRetry: () => messagesNotifier.loadInitial(),
),
),
), ),
), ),
chatRoom.when( chatRoom.when(