Skip to content

Commit 4e32381

Browse files
committed
msg_list: 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 c16219d commit 4e32381

File tree

2 files changed

+67
-6
lines changed

2 files changed

+67
-6
lines changed

lib/widgets/message_list.dart

+17-6
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,7 +481,7 @@ 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(
@@ -503,7 +514,7 @@ class MarkAsReadWidget extends StatelessWidget {
503514
.merge(weightVariableTextStyle(context, wght: 400))),
504515
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(7)),
505516
),
506-
onPressed: () => _handlePress(context),
517+
onPressed: () => loading ? null : _handlePress(context),
507518
icon: const Icon(Icons.playlist_add_check),
508519
label: Text(zulipLocalizations.markAllAsReadLabel))))));
509520
}

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)