Skip to content

Commit f841ec0

Browse files
committed
msg_list: Use scale down with opacity animation for mark-as-read
Fixes: zulip#613
1 parent 4e32381 commit f841ec0

File tree

2 files changed

+86
-4
lines changed

2 files changed

+86
-4
lines changed

lib/widgets/message_list.dart

+46-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+
loading: loading,
491+
hidden: areMessagesRead,
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
@@ -520,6 +519,49 @@ class MarkAsReadWidgetState extends State<MarkAsReadWidget> {
520519
}
521520
}
522521

522+
class MarkAsReadAnimation extends StatefulWidget {
523+
const MarkAsReadAnimation({
524+
super.key,
525+
required this.loading,
526+
required this.hidden,
527+
required this.child
528+
});
529+
530+
final bool loading;
531+
final bool hidden;
532+
final Widget child;
533+
534+
@override
535+
State<MarkAsReadAnimation> createState() => _MarkAsReadAnimationState();
536+
}
537+
538+
class _MarkAsReadAnimationState extends State<MarkAsReadAnimation> {
539+
bool _isPressed = false;
540+
541+
void _setScale(bool isPressed) {
542+
setState(() {
543+
_isPressed = isPressed;
544+
});
545+
}
546+
547+
@override
548+
Widget build(BuildContext context) {
549+
return GestureDetector(
550+
onTapDown: (_) => _setScale(true),
551+
onTapUp: (_) => _setScale(false),
552+
onTapCancel: () => _setScale(false),
553+
child: AnimatedScale(
554+
scale: _isPressed ? 0.95 : 1,
555+
duration: const Duration(milliseconds: 100),
556+
curve: Curves.easeOut,
557+
child: AnimatedOpacity(
558+
opacity: widget.hidden ? 0 : widget.loading ? 0.55 : 1,
559+
duration: const Duration(milliseconds: 500),
560+
curve: Curves.easeOut,
561+
child: widget.child)));
562+
}
563+
}
564+
523565
class RecipientHeader extends StatelessWidget {
524566
const RecipientHeader({super.key, required this.message, required this.narrow});
525567

test/widgets/message_list_test.dart

+40
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,46 @@ void main() {
801801

802802
check(state.loading).isFalse();
803803
});
804+
805+
testWidgets('in idle state', (WidgetTester tester) async {
806+
final child = Container();
807+
808+
await tester.pumpWidget(MaterialApp(
809+
home: MarkAsReadAnimation(hidden: false, loading: false, child: child)));
810+
811+
check(find.byWidget(child).evaluate()).length.equals(1);
812+
check(tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity)).opacity).equals(1);
813+
});
814+
815+
testWidgets('in loading state', (WidgetTester tester) async {
816+
final child = Container();
817+
818+
await tester.pumpWidget(MaterialApp(
819+
home: MarkAsReadAnimation(hidden: false, loading: true, child: child)));
820+
821+
check(find.byWidget(child).evaluate()).length.equals(1);
822+
check(tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity)).opacity).equals(0.55);
823+
});
824+
825+
testWidgets('in hidden state', (WidgetTester tester) async {
826+
final child = Container();
827+
828+
await tester.pumpWidget(MaterialApp(
829+
home: MarkAsReadAnimation(hidden: true, loading: false, child: child)));
830+
831+
check(find.byWidget(child).evaluate()).length.equals(1);
832+
check(tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity)).opacity).equals(0);
833+
});
834+
835+
testWidgets('in hidden state but loading is true', (WidgetTester tester) async {
836+
final child = Container();
837+
838+
await tester.pumpWidget(MaterialApp(
839+
home: MarkAsReadAnimation(hidden: true, loading: true, child: child)));
840+
841+
check(find.byWidget(child).evaluate()).length.equals(1);
842+
check(tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity)).opacity).equals(0);
843+
});
804844
});
805845

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

0 commit comments

Comments
 (0)