From 7bf79b345c62f339585443694a3996aab62c8363 Mon Sep 17 00:00:00 2001 From: Vasu Nageshri Date: Thu, 18 Jun 2026 13:36:15 +0530 Subject: [PATCH] fix: Implement "Seen" receipt reveal animation with fade, slide, and expand effects --- CHANGELOG.md | 1 + lib/src/utils/constants/constants.dart | 7 +++ lib/src/widgets/message_view.dart | 59 ++++++++++++++++++-------- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91a6a660..39b2b8d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * **Feat**: [457](https://github.com/SimformSolutionsPvtLtd/chatview/pull/457) :sparkles: Add errorWidget support to ImageMessageConfiguration * **Fix**: [458](https://github.com/SimformSolutionsPvtLtd/chatview/pull/458) Enhance text field action animations for improved UX * **Refactor**: [455](https://github.com/SimformSolutionsPvtLtd/chatview/pull/455) Rework sending receipt animation with different transition +* **Fix**: [463](https://github.com/SimformSolutionsPvtLtd/chatview/pull/463) Implement "Seen" receipt reveal animation with fade, slide, and expand effects ## 3.1.0 diff --git a/lib/src/utils/constants/constants.dart b/lib/src/utils/constants/constants.dart index e459f391..50ec4cb6 100644 --- a/lib/src/utils/constants/constants.dart +++ b/lib/src/utils/constants/constants.dart @@ -91,6 +91,13 @@ const double sendingReceiptInBubbleReservedHeight = 18; /// message is pending. const String sendingLabelText = 'Sending...'; +/// Duration of the "Seen" receipt reveal (fade + slide + expand) shown below +/// the last outgoing bubble once it is read. +const Duration seenAppearanceAnimationDuration = Duration(milliseconds: 300); + +/// Curve of the "Seen" receipt reveal animation. +const Curve seenAppearanceAnimationCurve = Curves.easeOutCubic; + String applicationDateFormatter(DateTime inputTime) { if (DateTime.now().difference(inputTime).inDays <= 3) { return timeago.format(inputTime); diff --git a/lib/src/widgets/message_view.dart b/lib/src/widgets/message_view.dart index 66e961fe..0a36fd78 100644 --- a/lib/src/widgets/message_view.dart +++ b/lib/src/widgets/message_view.dart @@ -284,26 +284,49 @@ class _MessageViewState extends State ValueListenableBuilder( valueListenable: widget.message.statusNotifier, builder: (context, value, child) { - if (widget.isMessageBySender && + final showSeen = widget.isMessageBySender && widget.controller?.initialMessageList.last.id == widget.message.id && - widget.message.status == MessageStatus.read) { - if (ChatViewInheritedWidget.of(context) - ?.featureActiveConfig - .lastSeenAgoBuilderVisibility ?? - true) { - return widget.outgoingChatBubbleConfig?.receiptsWidgetConfig - ?.lastSeenAgoBuilder - ?.call( - widget.message, - applicationDateFormatter( - widget.message.createdAt)) ?? - lastSeenAgoBuilder(widget.message, - applicationDateFormatter(widget.message.createdAt)); - } - return const SizedBox(); - } - return const SizedBox(); + widget.message.status == MessageStatus.read && + (ChatViewInheritedWidget.of(context) + ?.featureActiveConfig + .lastSeenAgoBuilderVisibility ?? + true); + return AnimatedSwitcher( + duration: seenAppearanceAnimationDuration, + switchInCurve: seenAppearanceAnimationCurve, + switchOutCurve: seenAppearanceAnimationCurve, + transitionBuilder: (child, animation) => SizeTransition( + alignment: Alignment.topCenter, + sizeFactor: animation, + child: FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + begin: const Offset(0, 0.35), + end: Offset.zero, + ).animate(animation), + child: child, + ), + ), + ), + child: showSeen + ? Align( + key: const ValueKey('seen'), + alignment: Alignment.centerRight, + child: widget.outgoingChatBubbleConfig + ?.receiptsWidgetConfig?.lastSeenAgoBuilder + ?.call( + widget.message, + applicationDateFormatter( + widget.message.createdAt)) ?? + lastSeenAgoBuilder( + widget.message, + applicationDateFormatter( + widget.message.createdAt)), + ) + : const SizedBox(key: ValueKey('seen-empty')), + ); }, ) ],