From 8d1c145b0b6367fffa50f108184a32d23b25d067 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 13 Oct 2025 00:16:50 +0800 Subject: [PATCH] :dizzy: Message list fully animated --- lib/screens/chat/room.dart | 172 +++++++++++++++++++++++-------------- 1 file changed, 107 insertions(+), 65 deletions(-) diff --git a/lib/screens/chat/room.dart b/lib/screens/chat/room.dart index 7a96a305..d5ee8b6c 100644 --- a/lib/screens/chat/room.dart +++ b/lib/screens/chat/room.dart @@ -561,59 +561,77 @@ class ChatRoomScreen extends HookConsumerWidget { .abs() > 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( - skipError: true, - data: - (identity) => MessageItem( - key: key, - message: message, - isCurrentUser: identity?.id == message.senderId, - onAction: (action) { - switch (action) { - case MessageItemAction.delete: - messagesNotifier.deleteMessage(message.id); - case MessageItemAction.edit: - messageEditingTo.value = message.toRemoteMessage(); - messageController.text = - messageEditingTo.value?.content ?? ''; - attachments.value = - messageEditingTo.value!.attachments - .map((e) => UniversalFile.fromAttachment(e)) - .toList(); - case MessageItemAction.forward: - messageForwardingTo.value = message.toRemoteMessage(); - case MessageItemAction.reply: - messageReplyingTo.value = message.toRemoteMessage(); - case MessageItemAction.resend: - messagesNotifier.retryMessage(message.id); - } - }, - onJump: (messageId) { - scrollToMessage( - messageId: messageId, - messageList: messageList, - messagesNotifier: messagesNotifier, - listController: listController, - scrollController: scrollController, - ref: ref, - ); - }, - progress: attachmentProgress.value[message.id], - showAvatar: isLastInGroup, - ), - loading: - () => MessageItem( - key: key, - message: message, - isCurrentUser: false, - onAction: null, - progress: null, - showAvatar: false, - onJump: (_) {}, - ), - error: (_, _) => SizedBox.shrink(key: key), + return TweenAnimationBuilder( + key: animationKey, + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration( + milliseconds: 400 + (index % 5) * 50, + ), // Staggered delay + curve: Curves.easeOutCubic, + builder: (context, animationValue, child) { + return Transform.translate( + offset: Offset( + 0, + 20 * (1 - animationValue), + ), // Slide up from bottom + child: Opacity(opacity: animationValue, child: child), + ); + }, + child: chatIdentity.when( + skipError: true, + data: + (identity) => MessageItem( + message: message, + isCurrentUser: identity?.id == message.senderId, + onAction: (action) { + switch (action) { + case MessageItemAction.delete: + messagesNotifier.deleteMessage(message.id); + case MessageItemAction.edit: + messageEditingTo.value = message.toRemoteMessage(); + messageController.text = + messageEditingTo.value?.content ?? ''; + attachments.value = + messageEditingTo.value!.attachments + .map((e) => UniversalFile.fromAttachment(e)) + .toList(); + case MessageItemAction.forward: + messageForwardingTo.value = message.toRemoteMessage(); + case MessageItemAction.reply: + messageReplyingTo.value = message.toRemoteMessage(); + case MessageItemAction.resend: + messagesNotifier.retryMessage(message.id); + } + }, + onJump: (messageId) { + scrollToMessage( + messageId: messageId, + messageList: messageList, + messagesNotifier: messagesNotifier, + listController: listController, + scrollController: scrollController, + ref: ref, + ); + }, + 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( children: [ Expanded( - child: messages.when( - data: - (messageList) => - messageList.isEmpty - ? Center(child: Text('No messages yet'.tr())) - : chatMessageListWidget(messageList), - loading: - () => const Center(child: CircularProgressIndicator()), - error: - (error, _) => ResponseErrorWidget( - error: error, - onRetry: () => messagesNotifier.loadInitial(), - ), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + switchInCurve: Curves.easeOutCubic, + switchOutCurve: Curves.easeInCubic, + transitionBuilder: ( + Widget child, + Animation animation, + ) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 0.05), + 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(