Skip to content

Commit 7665f52

Browse files
committed
msg_list: Use scale down with opacity animation for mark-as-read
Fixes: #613
1 parent 657a22f commit 7665f52

File tree

2 files changed

+73
-4
lines changed

2 files changed

+73
-4
lines changed

lib/widgets/message_list.dart

+33-4
Original file line numberDiff line numberDiff line change
@@ -486,10 +486,9 @@ class MarkAsReadWidgetState extends State<MarkAsReadWidget> {
486486

487487
return IgnorePointer(
488488
ignoring: areMessagesRead,
489-
child: AnimatedOpacity(
490-
opacity: areMessagesRead ? 0 : 1,
491-
duration: Duration(milliseconds: areMessagesRead ? 2000 : 300),
492-
curve: Curves.easeOut,
489+
child: MarkAsReadAnimation(
490+
visible: !areMessagesRead,
491+
loading: isLoading,
493492
child: SizedBox(width: double.infinity,
494493
// Design referenced from:
495494
// https://www.figma.com/file/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?type=design&node-id=132-9684&mode=design&t=jJwHzloKJ0TMOG4M-0
@@ -653,6 +652,36 @@ class _UnreadMarker extends StatelessWidget {
653652
}
654653
}
655654

655+
class MarkAsReadAnimation extends StatelessWidget {
656+
const MarkAsReadAnimation({
657+
super.key,
658+
required this.visible,
659+
required this.loading,
660+
required this.child});
661+
662+
final bool visible;
663+
final bool loading;
664+
final Widget child;
665+
666+
@override
667+
Widget build(BuildContext context) {
668+
final (opacity, scale) = loading
669+
? (0.5, 0.95)
670+
: visible ? (1.0 , 1.0) : (0.0, 0.0);
671+
const duration = Duration(milliseconds: 500);
672+
const curve = Curves.easeOut;
673+
return AnimatedScale(
674+
scale: scale,
675+
duration: duration,
676+
curve: curve,
677+
child: AnimatedOpacity(
678+
opacity: opacity,
679+
duration: duration,
680+
curve: curve,
681+
child: child));
682+
}
683+
}
684+
656685
class StreamMessageRecipientHeader extends StatelessWidget {
657686
const StreamMessageRecipientHeader({
658687
super.key,

test/widgets/message_list_test.dart

+40
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,46 @@ void main() {
779779
await tester.pump();
780780
check(state.isLoading).isFalse();
781781
});
782+
783+
testWidgets('displays correctly based on visible and loading', (WidgetTester tester) async {
784+
final child = Container();
785+
786+
// Test loading state
787+
await tester.pumpWidget(MaterialApp(
788+
home: MarkAsReadAnimation(
789+
visible: false,
790+
loading: true,
791+
child: child,
792+
),
793+
));
794+
check(find.byWidget(child).evaluate()).length.equals(1);
795+
check(tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity)).opacity).equals(0.5);
796+
check(tester.widget<AnimatedScale>(find.byType(AnimatedScale)).scale).equals(0.95);
797+
798+
// Test visible state
799+
await tester.pumpWidget(MaterialApp(
800+
home: MarkAsReadAnimation(
801+
visible: true,
802+
loading: false,
803+
child: child,
804+
),
805+
));
806+
check(find.byWidget(child).evaluate()).length.equals(1);
807+
check(tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity)).opacity).equals(1);
808+
check(tester.widget<AnimatedScale>(find.byType(AnimatedScale)).scale).equals(1);
809+
810+
// Test default state
811+
await tester.pumpWidget(MaterialApp(
812+
home: MarkAsReadAnimation(
813+
visible: false,
814+
loading: false,
815+
child: child,
816+
),
817+
));
818+
check(find.byWidget(child).evaluate()).length.equals(1);
819+
check(tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity)).opacity).equals(0);
820+
check(tester.widget<AnimatedScale>(find.byType(AnimatedScale)).scale).equals(0);
821+
});
782822
});
783823

784824
testWidgets('smoke test on modern server', (WidgetTester tester) async {

0 commit comments

Comments
 (0)