@@ -459,30 +459,41 @@ class ScrollToBottomButton extends StatelessWidget {
459459 }
460460}
461461
462- class MarkAsReadWidget extends StatelessWidget {
462+ class MarkAsReadWidget extends StatefulWidget {
463463 const MarkAsReadWidget ({super .key, required this .narrow});
464464
465465 final Narrow narrow;
466466
467+ @override
468+ State <MarkAsReadWidget > createState () => MarkAsReadWidgetState ();
469+ }
470+
471+ class MarkAsReadWidgetState extends State <MarkAsReadWidget > {
472+ @visibleForTesting
473+ bool loading = false ;
474+
467475 void _handlePress (BuildContext context) async {
468476 if (! context.mounted) return ;
469477
470478 final store = PerAccountStoreWidget .of (context);
471479 final connection = store.connection;
472480 final useLegacy = connection.zulipFeatureLevel! < 155 ;
481+ setState (() => loading = true );
473482
474483 try {
475- await markNarrowAsRead (context, narrow, useLegacy);
484+ await markNarrowAsRead (context, widget. narrow, useLegacy);
476485 } catch (e) {
477486 if (! context.mounted) return ;
478487 final zulipLocalizations = ZulipLocalizations .of (context);
479- await showErrorDialog (context: context,
488+ showErrorDialog (context: context,
480489 title: zulipLocalizations.errorMarkAsReadFailedTitle,
481490 message: e.toString ()); // TODO(#741): extract user-facing message better
482491 return ;
492+ } finally {
493+ setState (() => loading = false );
483494 }
484495 if (! context.mounted) return ;
485- if (narrow is CombinedFeedNarrow && ! useLegacy) {
496+ if (widget. narrow is CombinedFeedNarrow && ! useLegacy) {
486497 PerAccountStoreWidget .of (context).unreads.handleAllMessagesReadSuccess ();
487498 }
488499 }
@@ -491,13 +502,13 @@ class MarkAsReadWidget extends StatelessWidget {
491502 Widget build (BuildContext context) {
492503 final zulipLocalizations = ZulipLocalizations .of (context);
493504 final store = PerAccountStoreWidget .of (context);
494- final unreadCount = store.unreads.countInNarrow (narrow);
505+ final unreadCount = store.unreads.countInNarrow (widget. narrow);
495506 final areMessagesRead = unreadCount == 0 ;
496507
497508 return IgnorePointer (
498509 ignoring: areMessagesRead,
499510 child: AnimatedOpacity (
500- opacity: areMessagesRead ? 0 : 1 ,
511+ opacity: areMessagesRead ? 0 : loading ? 0.5 : 1 ,
501512 duration: Duration (milliseconds: areMessagesRead ? 2000 : 300 ),
502513 curve: Curves .easeOut,
503514 child: SizedBox (width: double .infinity,
@@ -508,8 +519,6 @@ class MarkAsReadWidget extends StatelessWidget {
508519 padding: const EdgeInsets .symmetric (horizontal: 10 , vertical: 10 - ((48 - 38 ) / 2 )),
509520 child: FilledButton .icon (
510521 style: FilledButton .styleFrom (
511- // TODO(#95) need dark-theme colors (foreground and background)
512- backgroundColor: _UnreadMarker .color,
513522 minimumSize: const Size .fromHeight (38 ),
514523 textStyle:
515524 // Restate [FilledButton]'s default, which inherits from
@@ -523,8 +532,13 @@ class MarkAsReadWidget extends StatelessWidget {
523532 height: (23 / 18 ))
524533 .merge (weightVariableTextStyle (context, wght: 400 ))),
525534 shape: RoundedRectangleBorder (borderRadius: BorderRadius .circular (7 )),
535+ ).copyWith (
536+ // clobber `FilledButton`'s default disabled state
537+ // TODO(#95) need dark-theme colors (foreground and background)
538+ foregroundColor: WidgetStateColor .resolveWith ((_) => Colors .white),
539+ backgroundColor: WidgetStateColor .resolveWith ((_) => _UnreadMarker .color),
526540 ),
527- onPressed: () => _handlePress (context),
541+ onPressed: loading ? null : () => _handlePress (context),
528542 icon: const Icon (Icons .playlist_add_check),
529543 label: Text (zulipLocalizations.markAllAsReadLabel))))));
530544 }
0 commit comments