@@ -438,30 +438,40 @@ class ScrollToBottomButton extends StatelessWidget {
438
438
}
439
439
}
440
440
441
- class MarkAsReadWidget extends StatelessWidget {
441
+ class MarkAsReadWidget extends StatefulWidget {
442
442
const MarkAsReadWidget ({super .key, required this .narrow});
443
443
444
444
final Narrow narrow;
445
445
446
+ @override
447
+ State <MarkAsReadWidget > createState () => MarkAsReadWidgetState ();
448
+ }
449
+
450
+ class MarkAsReadWidgetState extends State <MarkAsReadWidget > {
451
+ bool isLoading = false ;
452
+
446
453
void _handlePress (BuildContext context) async {
447
454
if (! context.mounted) return ;
448
455
449
456
final store = PerAccountStoreWidget .of (context);
450
457
final connection = store.connection;
451
458
final useLegacy = connection.zulipFeatureLevel! < 155 ;
459
+ setState (() => isLoading = true );
452
460
453
461
try {
454
- await markNarrowAsRead (context, narrow, useLegacy);
462
+ await markNarrowAsRead (context, widget. narrow, useLegacy);
455
463
} catch (e) {
456
464
if (! context.mounted) return ;
457
465
final zulipLocalizations = ZulipLocalizations .of (context);
458
466
await showErrorDialog (context: context,
459
467
title: zulipLocalizations.errorMarkAsReadFailedTitle,
460
468
message: e.toString ()); // TODO(#741): extract user-facing message better
461
469
return ;
470
+ } finally {
471
+ setState (() => isLoading = false );
462
472
}
463
473
if (! context.mounted) return ;
464
- if (narrow is CombinedFeedNarrow && ! useLegacy) {
474
+ if (widget. narrow is CombinedFeedNarrow && ! useLegacy) {
465
475
PerAccountStoreWidget .of (context).unreads.handleAllMessagesReadSuccess ();
466
476
}
467
477
}
@@ -470,15 +480,14 @@ class MarkAsReadWidget extends StatelessWidget {
470
480
Widget build (BuildContext context) {
471
481
final zulipLocalizations = ZulipLocalizations .of (context);
472
482
final store = PerAccountStoreWidget .of (context);
473
- final unreadCount = store.unreads.countInNarrow (narrow);
483
+ final unreadCount = store.unreads.countInNarrow (widget. narrow);
474
484
final areMessagesRead = unreadCount == 0 ;
475
485
476
486
return IgnorePointer (
477
487
ignoring: areMessagesRead,
478
- child: AnimatedOpacity (
479
- opacity: areMessagesRead ? 0 : 1 ,
480
- duration: Duration (milliseconds: areMessagesRead ? 2000 : 300 ),
481
- curve: Curves .easeOut,
488
+ child: MarkAsReadAnimation (
489
+ visible: ! areMessagesRead,
490
+ loading: isLoading,
482
491
child: SizedBox (width: double .infinity,
483
492
// Design referenced from:
484
493
// https://www.figma.com/file/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?type=design&node-id=132-9684&mode=design&t=jJwHzloKJ0TMOG4M-0
@@ -505,8 +514,7 @@ class MarkAsReadWidget extends StatelessWidget {
505
514
),
506
515
onPressed: () => _handlePress (context),
507
516
icon: const Icon (Icons .playlist_add_check),
508
- label: Text (zulipLocalizations.markAllAsReadLabel))))),
509
- );
517
+ label: Text (zulipLocalizations.markAllAsReadLabel))))));
510
518
}
511
519
}
512
520
@@ -643,6 +651,35 @@ class _UnreadMarker extends StatelessWidget {
643
651
}
644
652
}
645
653
654
+ class MarkAsReadAnimation extends StatelessWidget {
655
+ const MarkAsReadAnimation ({
656
+ super .key,
657
+ required this .visible,
658
+ required this .loading,
659
+ required this .child});
660
+
661
+ final bool visible;
662
+ final bool loading;
663
+ final Widget child;
664
+
665
+ @override
666
+ Widget build (BuildContext context) {
667
+ final opacity = loading ? 0.5 : visible ? 1.0 : 0.0 ;
668
+ final scale = loading ? 0.95 : 1.0 ;
669
+ const duration = Duration (milliseconds: 300 );
670
+ const curve = Curves .easeOut;
671
+ return AnimatedScale (
672
+ scale: scale,
673
+ duration: duration,
674
+ curve: curve,
675
+ child: AnimatedOpacity (
676
+ opacity: opacity,
677
+ duration: duration,
678
+ curve: curve,
679
+ child: child));
680
+ }
681
+ }
682
+
646
683
class StreamMessageRecipientHeader extends StatelessWidget {
647
684
const StreamMessageRecipientHeader ({
648
685
super .key,
0 commit comments