Skip to content

Commit 9a12a3e

Browse files
committed
channel_list: Ensure subscription toggle is disabled while loading
1 parent 8f5ee6d commit 9a12a3e

File tree

2 files changed

+56
-6
lines changed

2 files changed

+56
-6
lines changed

lib/widgets/channel_list.dart

+23-6
Original file line numberDiff line numberDiff line change
@@ -105,27 +105,44 @@ class ChannelItem extends StatelessWidget {
105105
overflow: TextOverflow.ellipsis),
106106
])),
107107
const SizedBox(width: 8),
108-
_ChannelItemSubscriptionToggle(stream: stream),
108+
_ChannelItemSubscriptionToggle(stream: stream, channelItemContext: context),
109109
]))));
110110
}
111111
}
112112

113-
class _ChannelItemSubscriptionToggle extends StatelessWidget {
114-
const _ChannelItemSubscriptionToggle({required this.stream});
113+
class _ChannelItemSubscriptionToggle extends StatefulWidget {
114+
const _ChannelItemSubscriptionToggle({required this.stream, required this.channelItemContext});
115115

116116
final ZulipStream stream;
117+
final BuildContext channelItemContext;
118+
119+
@override
120+
State<_ChannelItemSubscriptionToggle> createState() => _ChannelItemSubscriptionToggleState();
121+
}
122+
123+
class _ChannelItemSubscriptionToggleState extends State<_ChannelItemSubscriptionToggle> {
124+
bool _isLoading = false;
125+
126+
void _setIsLoading(bool value) {
127+
if (!mounted) return;
128+
setState(() => _isLoading = value);
129+
}
117130

118131
@override
119132
Widget build(BuildContext context) {
120133
final colorScheme = Theme.of(context).colorScheme;
121-
final (icon, color, onPressed) = stream is Subscription
134+
final (icon, color, onPressed) = widget.stream is Subscription
122135
? (Icons.check, colorScheme.primary, _unsubscribeFromChannel)
123136
: (Icons.add, null, _subscribeToChannel);
124137

125138
return IconButton(
126139
color: color,
127140
icon: Icon(icon),
128-
onPressed: () => onPressed(context, stream));
141+
onPressed: _isLoading ? null : () async {
142+
_setIsLoading(true);
143+
await onPressed(context, widget.stream);
144+
_setIsLoading(false);
145+
});
129146
}
130147

131148
Future<void> _unsubscribeFromChannel(BuildContext context, ZulipStream stream) async {
@@ -175,7 +192,7 @@ class _ChannelItemSubscriptionToggle extends StatelessWidget {
175192
} catch (e) {
176193
if (!context.mounted) return;
177194
final zulipLocalizations = ZulipLocalizations.of(context);
178-
await showErrorDialog(context: context,
195+
showErrorDialog(context: context,
179196
title: zulipLocalizations.errorFailedToSubscribedToChannel(stream.name),
180197
message: e.toString()); // TODO(#741): extract user-facing message better
181198
}

test/widgets/channel_list_test.dart

+33
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,39 @@ void main() {
134134
check(find.byIcon(Icons.check).evaluate()).isEmpty();
135135
}, skip: true);
136136

137+
testWidgets('is disabled while loading', (WidgetTester tester) async {
138+
final stream = eg.stream();
139+
await setupChannelListPage(tester, streams: [stream], subscriptions: []);
140+
connection.prepare(json: SubscribeToChannelsResult(
141+
subscribed: {eg.selfUser.email: [stream.name]},
142+
alreadySubscribed: {}).toJson()); await tapSubscribeButton(tester);
143+
await tester.pump();
144+
145+
check(tester.widget<IconButton>(
146+
find.byType(IconButton)).onPressed).isNull();
147+
148+
await tester.pump(const Duration(seconds: 2));
149+
150+
check(tester.widget<IconButton>(
151+
find.byType(IconButton)).onPressed).isNotNull();
152+
}, skip: true);
153+
154+
testWidgets('is disabled while loading and enabled back when loading fails', (WidgetTester tester) async {
155+
final stream = eg.stream();
156+
await setupChannelListPage(tester, streams: [stream], subscriptions: []);
157+
connection.prepare(exception: http.ClientException('Oops'), delay: const Duration(seconds: 2));
158+
await tapSubscribeButton(tester);
159+
await tester.pump();
160+
161+
check(tester.widget<IconButton>(
162+
find.byType(IconButton)).onPressed).isNull();
163+
164+
await tester.pump(const Duration(seconds: 2));
165+
166+
check(tester.widget<IconButton>(
167+
find.byType(IconButton)).onPressed).isNotNull();
168+
}, skip: true);
169+
137170
group('subscribe', () {
138171
testWidgets('is shown only for streams that user is not subscribed to', (tester) async {
139172
final streams = [eg.stream(), eg.stream(), eg.subscription(eg.stream())];

0 commit comments

Comments
 (0)