From 35bbaef930eeb2c3c161afb6f11e02d960f015f7 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Tue, 30 Sep 2025 17:35:53 -0700 Subject: [PATCH 1/2] action_sheet test: Check "Subscribe" button absent when no content access This tests the change in cf83c9ec6 / #1822. --- test/model/test_store.dart | 13 +++++++++++++ test/widgets/action_sheet_test.dart | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/test/model/test_store.dart b/test/model/test_store.dart index e706f3139f..faf62412e4 100644 --- a/test/model/test_store.dart +++ b/test/model/test_store.dart @@ -319,6 +319,19 @@ extension PerAccountStoreTestExtension on PerAccountStore { await handleEvent(ChannelCreateEvent(id: 1, streams: streams)); } + Future updateChannel( + int channelId, + ChannelPropertyName property, + Object? value, + ) async { + await handleEvent(ChannelUpdateEvent( + id: 1, + streamId: channelId, + name: 'some channel name', // (not true, of course, but that's fine) + property: property, + value: value)); + } + Future addSubscription(Subscription subscription) async { await addSubscriptions([subscription]); } diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index 9866b9dc10..cb3626896e 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -341,14 +341,30 @@ void main() { await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e } - testWidgets('channel not subscribed', (tester) async { + testWidgets('channel not subscribed, with content access', (tester) async { await prepare(); final narrow = ChannelNarrow(someChannel.streamId); await store.removeSubscription(narrow.streamId); + check(store.selfHasContentAccess(someChannel)).isTrue(); await showFromMsglistAppBar(tester, narrow: narrow); checkButton('Subscribe'); }); + testWidgets('channel not subscribed, without content access', (tester) async { + final privateChannel = eg.stream(inviteOnly: true); + await prepare(); + await store.addStream(privateChannel); + await store.updateChannel(privateChannel.streamId, + ChannelPropertyName.canSubscribeGroup, eg.groupSetting(members: [])); + await store.updateChannel(privateChannel.streamId, + ChannelPropertyName.canAddSubscribersGroup, eg.groupSetting(members: [])); + final narrow = ChannelNarrow(privateChannel.streamId); + check(store.selfHasContentAccess(privateChannel)).isFalse(); + await showFromMsglistAppBar(tester, + channel: privateChannel, narrow: narrow); + checkNoButton('Subscribe'); + }); + testWidgets('channel subscribed', (tester) async { await prepare(); final narrow = ChannelNarrow(someChannel.streamId); From 67eea614c655b09785b8fd7c44aab87a7f30f987 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Tue, 30 Sep 2025 17:32:46 -0700 Subject: [PATCH 2/2] action_sheet: Check can_subscribe_group for "Subscribe" confirmation dialog Fixes #1786. --- assets/l10n/app_en.arb | 6 +++--- lib/generated/l10n/zulip_localizations.dart | 6 +++--- lib/generated/l10n/zulip_localizations_ar.dart | 4 ++-- lib/generated/l10n/zulip_localizations_de.dart | 4 ++-- lib/generated/l10n/zulip_localizations_en.dart | 4 ++-- lib/generated/l10n/zulip_localizations_fr.dart | 4 ++-- lib/generated/l10n/zulip_localizations_it.dart | 4 ++-- lib/generated/l10n/zulip_localizations_ja.dart | 4 ++-- lib/generated/l10n/zulip_localizations_nb.dart | 4 ++-- lib/generated/l10n/zulip_localizations_pl.dart | 4 ++-- lib/generated/l10n/zulip_localizations_ru.dart | 4 ++-- lib/generated/l10n/zulip_localizations_sk.dart | 4 ++-- lib/generated/l10n/zulip_localizations_sl.dart | 4 ++-- lib/generated/l10n/zulip_localizations_uk.dart | 4 ++-- lib/generated/l10n/zulip_localizations_zh.dart | 12 ++---------- lib/widgets/action_sheet.dart | 12 +++++++----- test/widgets/action_sheet_test.dart | 2 +- 17 files changed, 40 insertions(+), 46 deletions(-) diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 73bcb373b0..89160f5bad 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -143,9 +143,9 @@ "channelName": {"type": "String", "example": "mobile"} } }, - "unsubscribeConfirmationDialogMessageMaybeCannotResubscribe": "Once you leave this channel, you might not be able to rejoin.", - "@unsubscribeConfirmationDialogMessageMaybeCannotResubscribe": { - "description": "Message for a confirmation dialog for unsubscribing from a channel when you might not have permission to resubscribe." + "unsubscribeConfirmationDialogMessageCannotResubscribe": "Once you leave this channel, you will not be able to rejoin.", + "@unsubscribeConfirmationDialogMessageCannotResubscribe": { + "description": "Message for a confirmation dialog for unsubscribing from a channel when you will not have permission to resubscribe." }, "unsubscribeConfirmationDialogConfirmButton": "Unsubscribe", "@unsubscribeConfirmationDialogConfirmButton": { diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index 44fadaa2ec..a3bf9c2cc7 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -341,11 +341,11 @@ abstract class ZulipLocalizations { /// **'Unsubscribe from {channelName}?'** String unsubscribeConfirmationDialogTitle(String channelName); - /// Message for a confirmation dialog for unsubscribing from a channel when you might not have permission to resubscribe. + /// Message for a confirmation dialog for unsubscribing from a channel when you will not have permission to resubscribe. /// /// In en, this message translates to: - /// **'Once you leave this channel, you might not be able to rejoin.'** - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe; + /// **'Once you leave this channel, you will not be able to rejoin.'** + String get unsubscribeConfirmationDialogMessageCannotResubscribe; /// Label for the 'Unsubscribe' button on a confirmation dialog for unsubscribing from a channel. /// diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart index 6c9ef2fcc5..9c67f11fc6 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -124,8 +124,8 @@ class ZulipLocalizationsAr extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Once you leave this channel, you might not be able to rejoin.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_de.dart b/lib/generated/l10n/zulip_localizations_de.dart index 9768e579e5..d693740f43 100644 --- a/lib/generated/l10n/zulip_localizations_de.dart +++ b/lib/generated/l10n/zulip_localizations_de.dart @@ -126,8 +126,8 @@ class ZulipLocalizationsDe extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Wenn du diesen Kanal verlässt, kannst du sich vielleicht nicht wieder beitreten.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Deabonnieren'; diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index 81ceadcb9c..18e76a9331 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -124,8 +124,8 @@ class ZulipLocalizationsEn extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Once you leave this channel, you might not be able to rejoin.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_fr.dart b/lib/generated/l10n/zulip_localizations_fr.dart index 94c4848a9f..2268056038 100644 --- a/lib/generated/l10n/zulip_localizations_fr.dart +++ b/lib/generated/l10n/zulip_localizations_fr.dart @@ -126,8 +126,8 @@ class ZulipLocalizationsFr extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Once you leave this channel, you might not be able to rejoin.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Se désinscrire'; diff --git a/lib/generated/l10n/zulip_localizations_it.dart b/lib/generated/l10n/zulip_localizations_it.dart index a7b2cdd663..cccd1dc8b1 100644 --- a/lib/generated/l10n/zulip_localizations_it.dart +++ b/lib/generated/l10n/zulip_localizations_it.dart @@ -125,8 +125,8 @@ class ZulipLocalizationsIt extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Once you leave this channel, you might not be able to rejoin.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index 09a9b822b4..5f6fa404d4 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -123,8 +123,8 @@ class ZulipLocalizationsJa extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'このチャンネルを退出すると、再び参加できない可能性があります。'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'チャンネルから退出'; diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart index 8ee2e9c4da..8653de5c7b 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -124,8 +124,8 @@ class ZulipLocalizationsNb extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Once you leave this channel, you might not be able to rejoin.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index fcc527a284..98d6b1de53 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -126,8 +126,8 @@ class ZulipLocalizationsPl extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Po opuszczeniu kanału możesz utracić możliwość powrotu.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Odsubskrybuj'; diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index fedbacf98f..7effd9fa73 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -126,8 +126,8 @@ class ZulipLocalizationsRu extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Покинув этот канал, возможно, вы не сможете присоединиться вновь.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Отписаться'; diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart index 4201d37be2..14310986f6 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -124,8 +124,8 @@ class ZulipLocalizationsSk extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Once you leave this channel, you might not be able to rejoin.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_sl.dart b/lib/generated/l10n/zulip_localizations_sl.dart index ac70ca61da..8bfc8810cb 100644 --- a/lib/generated/l10n/zulip_localizations_sl.dart +++ b/lib/generated/l10n/zulip_localizations_sl.dart @@ -124,8 +124,8 @@ class ZulipLocalizationsSl extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Ko zapustite ta kanal, se morda ne boste mogli znova pridružiti.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Prekliči naročnino'; diff --git a/lib/generated/l10n/zulip_localizations_uk.dart b/lib/generated/l10n/zulip_localizations_uk.dart index ddfb79ac57..f5b955fb55 100644 --- a/lib/generated/l10n/zulip_localizations_uk.dart +++ b/lib/generated/l10n/zulip_localizations_uk.dart @@ -127,8 +127,8 @@ class ZulipLocalizationsUk extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Після того, як ви залишите цей канал, ви, можливо, не зможете приєднатися знову.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Скасувати підписку'; diff --git a/lib/generated/l10n/zulip_localizations_zh.dart b/lib/generated/l10n/zulip_localizations_zh.dart index f974ce7573..e3f483e560 100644 --- a/lib/generated/l10n/zulip_localizations_zh.dart +++ b/lib/generated/l10n/zulip_localizations_zh.dart @@ -124,8 +124,8 @@ class ZulipLocalizationsZh extends ZulipLocalizations { } @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - 'Once you leave this channel, you might not be able to rejoin.'; + String get unsubscribeConfirmationDialogMessageCannotResubscribe => + 'Once you leave this channel, you will not be able to rejoin.'; @override String get unsubscribeConfirmationDialogConfirmButton => 'Unsubscribe'; @@ -1254,10 +1254,6 @@ class ZulipLocalizationsZhHansCn extends ZulipLocalizationsZh { return '确定取消订阅$channelName么?'; } - @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - '一旦退出该频道,您可能无法重新加入。'; - @override String get unsubscribeConfirmationDialogConfirmButton => '取消订阅'; @@ -2342,10 +2338,6 @@ class ZulipLocalizationsZhHantTw extends ZulipLocalizationsZh { return '確定要取消訂閱 $channelName 嗎?'; } - @override - String get unsubscribeConfirmationDialogMessageMaybeCannotResubscribe => - '一旦您離開此頻道,可能無法重新加入。'; - @override String get unsubscribeConfirmationDialogConfirmButton => '取消訂閱'; diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart index 8191f58069..5d233a527f 100644 --- a/lib/widgets/action_sheet.dart +++ b/lib/widgets/action_sheet.dart @@ -17,6 +17,7 @@ import '../model/content.dart'; import '../model/emoji.dart'; import '../model/internal_link.dart'; import '../model/narrow.dart'; +import '../model/realm.dart'; import 'actions.dart'; import 'button.dart'; import 'color.dart'; @@ -633,20 +634,21 @@ class UnsubscribeButton extends ActionSheetMenuItemButton { @override void onPressed() async { - final subscription = PerAccountStoreWidget.of(pageContext).subscriptions[channelId]; + final store = PerAccountStoreWidget.of(pageContext); + final subscription = store.subscriptions[channelId]; if (subscription == null) return; // TODO could give feedback - // TODO(#1786) check group-based permission to subscribe, then replace - // error message with a new one saying "will not" instead of "might not" // TODO(future) check if the self-user is a guest and the channel is not web-public - final couldResubscribe = !subscription.inviteOnly; + final couldResubscribe = !subscription.inviteOnly + || store.selfHasPermissionForGroupSetting(subscription.canSubscribeGroup, + GroupSettingType.stream, 'can_subscribe_group'); if (!couldResubscribe) { // TODO(#1788) warn if org would lose content access (nobody can subscribe) final zulipLocalizations = ZulipLocalizations.of(pageContext); final dialog = showSuggestedActionDialog(context: pageContext, title: zulipLocalizations.unsubscribeConfirmationDialogTitle(subscription.name), - message: zulipLocalizations.unsubscribeConfirmationDialogMessageMaybeCannotResubscribe, + message: zulipLocalizations.unsubscribeConfirmationDialogMessageCannotResubscribe, destructiveActionButton: true, actionButtonText: zulipLocalizations.unsubscribeConfirmationDialogConfirmButton); if (await dialog.result != true) return; diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index cb3626896e..e03e102cf5 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -600,7 +600,7 @@ void main() { final (unsubscribeButton, cancelButton) = checkSuggestedActionDialog(tester, expectedTitle: 'Unsubscribe from ${channel.name}?', - expectedMessage: 'Once you leave this channel, you might not be able to rejoin.', + expectedMessage: 'Once you leave this channel, you will not be able to rejoin.', expectDestructiveActionButton: true, expectedActionButtonText: 'Unsubscribe'); await tester.tap(find.byWidget(unsubscribeButton));