@@ -8,6 +8,7 @@ import 'package:share_plus/share_plus.dart';
88
99import '../api/exception.dart' ;
1010import '../api/model/model.dart' ;
11+ import '../api/route/channels.dart' ;
1112import '../api/route/messages.dart' ;
1213import '../model/internal_link.dart' ;
1314import '../model/narrow.dart' ;
@@ -140,6 +141,199 @@ class ActionSheetCancelButton extends StatelessWidget {
140141 }
141142}
142143
144+ /// Show a sheet of actions you can take on a topic.
145+ void showTopicActionSheet (BuildContext context, {
146+ required int channelId,
147+ required String topic,
148+ }) {
149+ final narrow = TopicNarrow (channelId, topic);
150+ UserTopicUpdateButton button ({
151+ UserTopicVisibilityPolicy ? from,
152+ required UserTopicVisibilityPolicy to,
153+ }) {
154+ return UserTopicUpdateButton (
155+ currentVisibilityPolicy: from,
156+ newVisibilityPolicy: to,
157+ narrow: narrow,
158+ pageContext: context);
159+ }
160+
161+ final mute = button (to: UserTopicVisibilityPolicy .muted);
162+ final unmute = button (from: UserTopicVisibilityPolicy .muted,
163+ to: UserTopicVisibilityPolicy .none);
164+ final unmuteInMutedChannel = button (to: UserTopicVisibilityPolicy .unmuted);
165+ final follow = button (to: UserTopicVisibilityPolicy .followed);
166+ final unfollow = button (from: UserTopicVisibilityPolicy .followed,
167+ to: UserTopicVisibilityPolicy .none);
168+
169+ final store = PerAccountStoreWidget .of (context);
170+ final channelMuted = store.subscriptions[channelId]? .isMuted;
171+ final visibilityPolicy = store.topicVisibilityPolicy (channelId, topic);
172+
173+ // TODO(server-7): simplify this condition away
174+ final supportsUnmutingTopics = store.connection.zulipFeatureLevel! >= 170 ;
175+ // TODO(server-8): simplify this condition away
176+ final supportsFollowingTopics = store.connection.zulipFeatureLevel! >= 219 ;
177+
178+ final optionButtons = < Widget > [];
179+ if (channelMuted != null && ! channelMuted) {
180+ switch (visibilityPolicy) {
181+ case UserTopicVisibilityPolicy .muted:
182+ optionButtons.add (unmute);
183+ if (supportsFollowingTopics) {
184+ optionButtons.add (follow);
185+ }
186+ case UserTopicVisibilityPolicy .none:
187+ case UserTopicVisibilityPolicy .unmuted:
188+ optionButtons.add (mute);
189+ if (supportsFollowingTopics) {
190+ optionButtons.add (follow);
191+ }
192+ case UserTopicVisibilityPolicy .followed:
193+ optionButtons.add (mute);
194+ if (supportsFollowingTopics) {
195+ optionButtons.add (unfollow);
196+ }
197+ case UserTopicVisibilityPolicy .unknown:
198+ assert (false );
199+ return ;
200+ }
201+ } else if (channelMuted != null && channelMuted) {
202+ if (supportsUnmutingTopics) {
203+ switch (visibilityPolicy) {
204+ case UserTopicVisibilityPolicy .none:
205+ case UserTopicVisibilityPolicy .muted:
206+ optionButtons.add (unmuteInMutedChannel);
207+ if (supportsFollowingTopics) {
208+ optionButtons.add (follow);
209+ }
210+ case UserTopicVisibilityPolicy .unmuted:
211+ optionButtons.add (mute);
212+ if (supportsFollowingTopics) {
213+ optionButtons.add (follow);
214+ }
215+ case UserTopicVisibilityPolicy .followed:
216+ optionButtons.add (mute);
217+ if (supportsFollowingTopics) {
218+ optionButtons.add (unfollow);
219+ }
220+ case UserTopicVisibilityPolicy .unknown:
221+ assert (false );
222+ return ;
223+ }
224+ }
225+ } else {
226+ // Not subscribed to the channel; there is no user topic change to be made.
227+ return ;
228+ }
229+
230+ if (optionButtons.isEmpty) {
231+ assert (! supportsUnmutingTopics);
232+ return ;
233+ }
234+
235+ _showActionSheet (context, optionButtons: optionButtons);
236+ }
237+
238+ class UserTopicUpdateButton extends ActionSheetMenuItemButton {
239+ const UserTopicUpdateButton ({
240+ super .key,
241+ this .currentVisibilityPolicy,
242+ required this .newVisibilityPolicy,
243+ required this .narrow,
244+ required super .pageContext,
245+ });
246+
247+ final UserTopicVisibilityPolicy ? currentVisibilityPolicy;
248+ final UserTopicVisibilityPolicy newVisibilityPolicy;
249+ final TopicNarrow narrow;
250+
251+ @override IconData get icon {
252+ switch (newVisibilityPolicy) {
253+ case UserTopicVisibilityPolicy .none:
254+ return ZulipIcons .inherit;
255+ case UserTopicVisibilityPolicy .muted:
256+ return ZulipIcons .mute;
257+ case UserTopicVisibilityPolicy .unmuted:
258+ return ZulipIcons .unmute;
259+ case UserTopicVisibilityPolicy .followed:
260+ return ZulipIcons .follow;
261+ case UserTopicVisibilityPolicy .unknown:
262+ assert (false );
263+ return ZulipIcons .inherit;
264+ }
265+ }
266+
267+ @override
268+ String label (ZulipLocalizations zulipLocalizations) {
269+ switch ((currentVisibilityPolicy, newVisibilityPolicy)) {
270+ case (UserTopicVisibilityPolicy .muted, UserTopicVisibilityPolicy .none):
271+ return zulipLocalizations.actionSheetOptionUnmuteTopic;
272+ case (UserTopicVisibilityPolicy .followed, UserTopicVisibilityPolicy .none):
273+ return zulipLocalizations.actionSheetOptionUnfollowTopic;
274+
275+ case (_, UserTopicVisibilityPolicy .muted):
276+ return zulipLocalizations.actionSheetOptionMuteTopic;
277+ case (_, UserTopicVisibilityPolicy .unmuted):
278+ return zulipLocalizations.actionSheetOptionUnmuteTopic;
279+ case (_, UserTopicVisibilityPolicy .followed):
280+ return zulipLocalizations.actionSheetOptionFollowTopic;
281+
282+ case (_, UserTopicVisibilityPolicy .none):
283+ case (_, UserTopicVisibilityPolicy .unknown):
284+ assert (false );
285+ return '' ;
286+ }
287+ }
288+
289+ String _errorTitle (ZulipLocalizations zulipLocalizations) {
290+ switch ((currentVisibilityPolicy, newVisibilityPolicy)) {
291+ case (UserTopicVisibilityPolicy .muted, UserTopicVisibilityPolicy .none):
292+ return zulipLocalizations.errorUnmuteTopicFailed;
293+ case (UserTopicVisibilityPolicy .followed, UserTopicVisibilityPolicy .none):
294+ return zulipLocalizations.errorUnfollowTopicFailed;
295+
296+ case (_, UserTopicVisibilityPolicy .muted):
297+ return zulipLocalizations.errorMuteTopicFailed;
298+ case (_, UserTopicVisibilityPolicy .unmuted):
299+ return zulipLocalizations.errorUnmuteTopicFailed;
300+ case (_, UserTopicVisibilityPolicy .followed):
301+ return zulipLocalizations.errorFollowTopicFailed;
302+
303+ case (_, UserTopicVisibilityPolicy .none):
304+ case (_, UserTopicVisibilityPolicy .unknown):
305+ assert (false );
306+ return '' ;
307+ }
308+ }
309+
310+ @override void onPressed () async {
311+ try {
312+ await updateUserTopic (
313+ PerAccountStoreWidget .of (pageContext).connection,
314+ streamId: narrow.streamId,
315+ topic: narrow.topic,
316+ visibilityPolicy: newVisibilityPolicy);
317+ } catch (e) {
318+ if (! pageContext.mounted) return ;
319+
320+ String ? errorMessage;
321+
322+ switch (e) {
323+ case ZulipApiException ():
324+ errorMessage = e.message;
325+ // TODO(#741) specific messages for common errors, like network errors
326+ // (support with reusable code)
327+ default :
328+ }
329+
330+ final zulipLocalizations = ZulipLocalizations .of (pageContext);
331+ showErrorDialog (context: pageContext,
332+ title: _errorTitle (zulipLocalizations), message: errorMessage);
333+ }
334+ }
335+ }
336+
143337/// Show a sheet of actions you can take on a message in the message list.
144338///
145339/// Must have a [MessageListPage] ancestor.
0 commit comments