Skip to content

Commit 594039f

Browse files
committed
msglist: Handle loading state in MarkAsReadWidget
MarkAsReadWidget needs to be disabled during loading state to prevent multiple requests to the server. Additionally it is needed for styling/animation convenience.
1 parent 9525c4f commit 594039f

File tree

2 files changed

+73
-9
lines changed

2 files changed

+73
-9
lines changed

lib/widgets/message_list.dart

+23-9
Original file line numberDiff line numberDiff line change
@@ -438,30 +438,41 @@ class ScrollToBottomButton extends StatelessWidget {
438438
}
439439
}
440440

441-
class MarkAsReadWidget extends StatelessWidget {
441+
class MarkAsReadWidget extends StatefulWidget {
442442
const MarkAsReadWidget({super.key, required this.narrow});
443443

444444
final Narrow narrow;
445445

446+
@override
447+
State<MarkAsReadWidget> createState() => MarkAsReadWidgetState();
448+
}
449+
450+
class MarkAsReadWidgetState extends State<MarkAsReadWidget> {
451+
@visibleForTesting
452+
bool loading = false;
453+
446454
void _handlePress(BuildContext context) async {
447455
if (!context.mounted) return;
448456

449457
final store = PerAccountStoreWidget.of(context);
450458
final connection = store.connection;
451459
final useLegacy = connection.zulipFeatureLevel! < 155;
460+
setState(() => loading = true);
452461

453462
try {
454-
await markNarrowAsRead(context, narrow, useLegacy);
463+
await markNarrowAsRead(context, widget.narrow, useLegacy);
455464
} catch (e) {
456465
if (!context.mounted) return;
457466
final zulipLocalizations = ZulipLocalizations.of(context);
458-
await showErrorDialog(context: context,
467+
showErrorDialog(context: context,
459468
title: zulipLocalizations.errorMarkAsReadFailedTitle,
460469
message: e.toString()); // TODO(#741): extract user-facing message better
461470
return;
471+
} finally {
472+
setState(() => loading = false);
462473
}
463474
if (!context.mounted) return;
464-
if (narrow is CombinedFeedNarrow && !useLegacy) {
475+
if (widget.narrow is CombinedFeedNarrow && !useLegacy) {
465476
PerAccountStoreWidget.of(context).unreads.handleAllMessagesReadSuccess();
466477
}
467478
}
@@ -470,13 +481,13 @@ class MarkAsReadWidget extends StatelessWidget {
470481
Widget build(BuildContext context) {
471482
final zulipLocalizations = ZulipLocalizations.of(context);
472483
final store = PerAccountStoreWidget.of(context);
473-
final unreadCount = store.unreads.countInNarrow(narrow);
484+
final unreadCount = store.unreads.countInNarrow(widget.narrow);
474485
final areMessagesRead = unreadCount == 0;
475486

476487
return IgnorePointer(
477488
ignoring: areMessagesRead,
478489
child: AnimatedOpacity(
479-
opacity: areMessagesRead ? 0 : 1,
490+
opacity: areMessagesRead ? 0 : loading ? 0.5 : 1,
480491
duration: Duration(milliseconds: areMessagesRead ? 2000 : 300),
481492
curve: Curves.easeOut,
482493
child: SizedBox(width: double.infinity,
@@ -487,8 +498,6 @@ class MarkAsReadWidget extends StatelessWidget {
487498
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10 - ((48 - 38) / 2)),
488499
child: FilledButton.icon(
489500
style: FilledButton.styleFrom(
490-
// TODO(#95) need dark-theme colors (foreground and background)
491-
backgroundColor: _UnreadMarker.color,
492501
minimumSize: const Size.fromHeight(38),
493502
textStyle:
494503
// Restate [FilledButton]'s default, which inherits from
@@ -502,8 +511,13 @@ class MarkAsReadWidget extends StatelessWidget {
502511
height: (23 / 18))
503512
.merge(weightVariableTextStyle(context, wght: 400))),
504513
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(7)),
514+
).copyWith(
515+
// clobber `FilledButton`'s default disabled state
516+
// TODO(#95) need dark-theme colors (foreground and background)
517+
foregroundColor: WidgetStateColor.resolveWith((_) => Colors.white),
518+
backgroundColor: WidgetStateColor.resolveWith((_) => _UnreadMarker.color),
505519
),
506-
onPressed: () => _handlePress(context),
520+
onPressed: loading ? null : () => _handlePress(context),
507521
icon: const Icon(Icons.playlist_add_check),
508522
label: Text(zulipLocalizations.markAllAsReadLabel))))));
509523
}

test/widgets/message_list_test.dart

+50
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,56 @@ void main() {
753753
unreadMessageIds: [message.id]),
754754
]);
755755

756+
group('MarkAsReadAnimation', () {
757+
testWidgets('loading is changed correctly', (WidgetTester tester) async {
758+
final narrow = TopicNarrow.ofMessage(message);
759+
await setupMessageListPage(tester,
760+
narrow: narrow, messages: [message], unreadMsgs: unreadMsgs);
761+
check(isMarkAsReadButtonVisible(tester)).isTrue();
762+
763+
connection.prepare(json: UpdateMessageFlagsForNarrowResult(
764+
processedCount: 11, updatedCount: 3,
765+
firstProcessedId: null, lastProcessedId: null,
766+
foundOldest: true, foundNewest: true).toJson());
767+
768+
final state = tester.state<MarkAsReadWidgetState>(find.byType(MarkAsReadWidget));
769+
check(state.loading).isFalse();
770+
771+
await tester.tap(find.byType(MarkAsReadWidget));
772+
await tester.pump();
773+
check(state.loading).isTrue();
774+
775+
await tester.idle();
776+
await tester.pump();
777+
check(state.loading).isFalse();
778+
});
779+
780+
testWidgets('loading is changed correctly if request fails', (WidgetTester tester) async {
781+
final narrow = TopicNarrow.ofMessage(message);
782+
await setupMessageListPage(tester,
783+
narrow: narrow, messages: [message], unreadMsgs: unreadMsgs);
784+
check(isMarkAsReadButtonVisible(tester)).isTrue();
785+
786+
connection.prepare(httpStatus: 400, json: {
787+
'code': 'BAD_REQUEST',
788+
'msg': 'Invalid message(s)',
789+
'result': 'error',
790+
});
791+
792+
final state = tester.state<MarkAsReadWidgetState>(find.byType(MarkAsReadWidget));
793+
check(state.loading).isFalse();
794+
795+
await tester.tap(find.byType(MarkAsReadWidget));
796+
await tester.pump();
797+
check(state.loading).isTrue();
798+
799+
await tester.idle();
800+
await tester.pump();
801+
802+
check(state.loading).isFalse();
803+
});
804+
});
805+
756806
testWidgets('smoke test on modern server', (WidgetTester tester) async {
757807
final narrow = TopicNarrow.ofMessage(message);
758808
await setupMessageListPage(tester,

0 commit comments

Comments
 (0)