From 27b06dfb385fc6b79cf1dae92f27c3c258dbf3b3 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 20 Apr 2026 19:01:09 +0000 Subject: [PATCH] ui: channel preferences (#6842) --- .../Views/Chat/Group/GroupChatInfoView.swift | 30 +++--- .../Chat/Group/GroupPreferencesView.swift | 41 ++++---- .../views/chat/group/GroupChatInfoView.kt | 35 +++---- .../views/chat/group/GroupPreferences.kt | 95 ++++++++++++++----- .../commonMain/resources/MR/base/strings.xml | 2 + 5 files changed, 126 insertions(+), 77 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index afc314eb78..9279c53c83 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -157,19 +157,17 @@ struct GroupChatInfoView: View { if groupInfo.groupProfile.description != nil || (groupInfo.isOwner && groupInfo.businessChat == nil) { addOrEditWelcomeMessage() } - if !groupInfo.useRelays { - GroupPreferencesButton(groupInfo: $groupInfo, preferences: groupInfo.fullGroupPreferences, currentPreferences: groupInfo.fullGroupPreferences) - } + GroupPreferencesButton(groupInfo: $groupInfo, preferences: groupInfo.fullGroupPreferences, currentPreferences: groupInfo.fullGroupPreferences) } footer: { - if !groupInfo.useRelays { - let label: LocalizedStringKey = ( - groupInfo.businessChat == nil - ? "Only group owners can change group preferences." - : "Only chat owners can change preferences." - ) - Text(label) - .foregroundColor(theme.colors.secondary) - } + let label: LocalizedStringKey = ( + groupInfo.useRelays + ? "Only channel owners can change channel preferences." + : groupInfo.businessChat == nil + ? "Only group owners can change group preferences." + : "Only chat owners can change preferences." + ) + Text(label) + .foregroundColor(theme.colors.secondary) } Section { @@ -988,7 +986,9 @@ struct GroupPreferencesButton: View { var creatingGroup: Bool = false private var label: LocalizedStringKey { - groupInfo.businessChat == nil ? "Group preferences" : "Chat preferences" + groupInfo.useRelays ? "Channel preferences" + : groupInfo.businessChat == nil ? "Group preferences" + : "Chat preferences" } var body: some View { @@ -1005,7 +1005,9 @@ struct GroupPreferencesButton: View { .navigationBarTitleDisplayMode(.large) .onDisappear { let saveText = NSLocalizedString( - creatingGroup ? "Save" : "Save and notify group members", + creatingGroup ? "Save" + : groupInfo.useRelays ? "Save and notify subscribers" + : "Save and notify group members", comment: "alert button" ) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index 55b1dc6d2e..84e852f5a3 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -27,26 +27,33 @@ struct GroupPreferencesView: View { @State private var showSaveDialogue = false var body: some View { - let saveText: LocalizedStringKey = creatingGroup ? "Save" : "Save and notify group members" + let saveText: LocalizedStringKey = creatingGroup ? "Save" : groupInfo.useRelays ? "Save and notify subscribers" : "Save and notify group members" VStack { List { - Section { - MemberAdmissionButton( - groupInfo: $groupInfo, - admission: groupInfo.groupProfile.memberAdmission_, - currentAdmission: groupInfo.groupProfile.memberAdmission_, - creatingGroup: creatingGroup - ) + if !groupInfo.useRelays { + Section { + MemberAdmissionButton( + groupInfo: $groupInfo, + admission: groupInfo.groupProfile.memberAdmission_, + currentAdmission: groupInfo.groupProfile.memberAdmission_, + creatingGroup: creatingGroup + ) + } + featureSection(.timedMessages, $preferences.timedMessages.enable) + featureSection(.fullDelete, $preferences.fullDelete.enable) + featureSection(.directMessages, $preferences.directMessages.enable, $preferences.directMessages.role) + featureSection(.reactions, $preferences.reactions.enable) + featureSection(.voice, $preferences.voice.enable, $preferences.voice.role) + featureSection(.files, $preferences.files.enable, $preferences.files.role) + featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role) + featureSection(.reports, $preferences.reports.enable) + featureSection(.history, $preferences.history.enable) + } else { + featureSection(.timedMessages, $preferences.timedMessages.enable) + featureSection(.fullDelete, $preferences.fullDelete.enable) + featureSection(.reactions, $preferences.reactions.enable) + featureSection(.history, $preferences.history.enable) } - featureSection(.timedMessages, $preferences.timedMessages.enable) - featureSection(.fullDelete, $preferences.fullDelete.enable) - featureSection(.directMessages, $preferences.directMessages.enable, $preferences.directMessages.role) - featureSection(.reactions, $preferences.reactions.enable) - featureSection(.voice, $preferences.voice.enable, $preferences.voice.role) - featureSection(.files, $preferences.files.enable, $preferences.files.role) - featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role) - featureSection(.reports, $preferences.reports.enable) - featureSection(.history, $preferences.history.enable) if groupInfo.isOwner { Section { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 767eb46923..156ad2cd97 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -599,32 +599,27 @@ fun ModalData.GroupChatInfoLayout( } } } - val showEditSection = (groupInfo.isOwner && groupInfo.businessChat?.chatType == null) - || groupInfo.groupProfile.description != null - || !groupInfo.useRelays if (anyTopSectionRowShow) { SectionDividerSpaced(maxBottomPadding = false) } - if (showEditSection) { - SectionView { - if (groupInfo.isOwner && groupInfo.businessChat?.chatType == null) { - val editProfileTitleId = if (groupInfo.useRelays) MR.strings.button_edit_channel_profile else MR.strings.button_edit_group_profile - EditGroupProfileButton(editProfileTitleId, editGroupProfile) - } - if (groupInfo.groupProfile.description != null || (groupInfo.isOwner && groupInfo.businessChat?.chatType == null)) { - AddOrEditWelcomeMessage(groupInfo.groupProfile.description, addOrEditWelcomeMessage) - } - if (!groupInfo.useRelays) { - val prefsTitleId = if (groupInfo.businessChat == null) MR.strings.group_preferences else MR.strings.chat_preferences - GroupPreferencesButton(prefsTitleId, openPreferences) - } + SectionView { + if (groupInfo.isOwner && groupInfo.businessChat?.chatType == null) { + val editProfileTitleId = if (groupInfo.useRelays) MR.strings.button_edit_channel_profile else MR.strings.button_edit_group_profile + EditGroupProfileButton(editProfileTitleId, editGroupProfile) } - if (!groupInfo.useRelays) { - val footerId = if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs else MR.strings.only_chat_owners_can_change_prefs - SectionTextFooter(stringResource(footerId)) + if (groupInfo.groupProfile.description != null || (groupInfo.isOwner && groupInfo.businessChat?.chatType == null)) { + AddOrEditWelcomeMessage(groupInfo.groupProfile.description, addOrEditWelcomeMessage) } - SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) + val prefsTitleId = if (groupInfo.useRelays) MR.strings.channel_preferences + else if (groupInfo.businessChat == null) MR.strings.group_preferences + else MR.strings.chat_preferences + GroupPreferencesButton(prefsTitleId, openPreferences) } + val footerId = if (groupInfo.useRelays) MR.strings.only_channel_owners_can_change_prefs + else if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs + else MR.strings.only_chat_owners_can_change_prefs + SectionTextFooter(stringResource(footerId)) + SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) SectionView { if (!groupInfo.useRelays) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index ddf0456822..902b4c9828 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -10,6 +10,7 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable +import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -56,10 +57,12 @@ fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () -> afterSave() } } + val saveTextId = if (gInfo.useRelays) MR.strings.save_and_notify_channel_subscribers + else MR.strings.save_and_notify_group_members ModalView( close = { if (preferences == currentPreferences) close() - else showUnsavedChangesAlert({ savePrefs(close) }, close) + else showUnsavedChangesAlert({ savePrefs(close) }, close, saveTextId) }, ) { GroupPreferencesLayout( @@ -97,17 +100,11 @@ private fun GroupPreferencesLayout( savePrefs: () -> Unit, openMemberAdmission: () -> Unit, ) { - ColumnWithScrollBar { - val titleId = if (groupInfo.businessChat == null) MR.strings.group_preferences else MR.strings.chat_preferences - AppBarTitle(stringResource(titleId)) - if (groupInfo.businessChat == null) { - MemberAdmissionButton(openMemberAdmission) - SectionDividerSpaced(maxBottomPadding = false) - } + val onTTLUpdated = { ttl: Int? -> + applyPrefs(preferences.copy(timedMessages = preferences.timedMessages.copy(ttl = ttl))) + } + @Composable fun TimedMessagesPreference() { val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) } - val onTTLUpdated = { ttl: Int? -> - applyPrefs(preferences.copy(timedMessages = preferences.timedMessages.copy(ttl = ttl))) - } FeatureSection(GroupFeature.TimedMessages, timedMessages, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> if (enable == GroupFeatureEnabled.ON) { applyPrefs(preferences.copy(timedMessages = TimedMessagesGroupPreference(enable = enable, ttl = preferences.timedMessages.ttl ?: 86400))) @@ -115,58 +112,104 @@ private fun GroupPreferencesLayout( applyPrefs(preferences.copy(timedMessages = TimedMessagesGroupPreference(enable = enable, ttl = currentPreferences.timedMessages.ttl))) } } - SectionDividerSpaced(true, maxBottomPadding = false) + } + @Composable fun DirectMessagesPreference() { val allowDirectMessages = remember(preferences) { mutableStateOf(preferences.directMessages.enable) } val directMessagesRole = remember(preferences) { mutableStateOf(preferences.directMessages.role) } FeatureSection(GroupFeature.DirectMessages, allowDirectMessages, directMessagesRole, groupInfo, preferences, onTTLUpdated) { enable, role -> applyPrefs(preferences.copy(directMessages = RoleGroupPreference(enable = enable, role))) } - SectionDividerSpaced(true, maxBottomPadding = false) + } + @Composable fun FullDeletePreference() { val allowFullDeletion = remember(preferences) { mutableStateOf(preferences.fullDelete.enable) } FeatureSection(GroupFeature.FullDelete, allowFullDeletion, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> applyPrefs(preferences.copy(fullDelete = GroupPreference(enable = enable))) } - SectionDividerSpaced(true, maxBottomPadding = false) + } + @Composable fun ReactionsPreference() { val allowReactions = remember(preferences) { mutableStateOf(preferences.reactions.enable) } FeatureSection(GroupFeature.Reactions, allowReactions, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> applyPrefs(preferences.copy(reactions = GroupPreference(enable = enable))) } - SectionDividerSpaced(true, maxBottomPadding = false) + } + @Composable fun VoicePreference() { val allowVoice = remember(preferences) { mutableStateOf(preferences.voice.enable) } val voiceRole = remember(preferences) { mutableStateOf(preferences.voice.role) } FeatureSection(GroupFeature.Voice, allowVoice, voiceRole, groupInfo, preferences, onTTLUpdated) { enable, role -> applyPrefs(preferences.copy(voice = RoleGroupPreference(enable = enable, role))) } - SectionDividerSpaced(true, maxBottomPadding = false) + } + @Composable fun FilesPreference() { val allowFiles = remember(preferences) { mutableStateOf(preferences.files.enable) } val filesRole = remember(preferences) { mutableStateOf(preferences.files.role) } FeatureSection(GroupFeature.Files, allowFiles, filesRole, groupInfo, preferences, onTTLUpdated) { enable, role -> applyPrefs(preferences.copy(files = RoleGroupPreference(enable = enable, role))) } - - SectionDividerSpaced(true, maxBottomPadding = false) + } + @Composable fun SimplexLinksPreference() { val allowSimplexLinks = remember(preferences) { mutableStateOf(preferences.simplexLinks.enable) } val simplexLinksRole = remember(preferences) { mutableStateOf(preferences.simplexLinks.role) } FeatureSection(GroupFeature.SimplexLinks, allowSimplexLinks, simplexLinksRole, groupInfo, preferences, onTTLUpdated) { enable, role -> applyPrefs(preferences.copy(simplexLinks = RoleGroupPreference(enable = enable, role))) } - - SectionDividerSpaced(true, maxBottomPadding = false) + } + @Composable fun ReportsPreference() { val enableReports = remember(preferences) { mutableStateOf(preferences.reports.enable) } FeatureSection(GroupFeature.Reports, enableReports, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> applyPrefs(preferences.copy(reports = GroupPreference(enable = enable))) } - SectionDividerSpaced(true, maxBottomPadding = false) + } + @Composable fun HistoryPreference() { val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) } FeatureSection(GroupFeature.History, enableHistory, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> applyPrefs(preferences.copy(history = GroupPreference(enable = enable))) } + } + ColumnWithScrollBar { + val titleId = if (groupInfo.useRelays) MR.strings.channel_preferences + else if (groupInfo.businessChat == null) MR.strings.group_preferences + else MR.strings.chat_preferences + AppBarTitle(stringResource(titleId)) + if (!groupInfo.useRelays) { + if (groupInfo.businessChat == null) { + MemberAdmissionButton(openMemberAdmission) + SectionDividerSpaced(maxBottomPadding = false) + } + TimedMessagesPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + DirectMessagesPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + FullDeletePreference() + SectionDividerSpaced(true, maxBottomPadding = false) + ReactionsPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + VoicePreference() + SectionDividerSpaced(true, maxBottomPadding = false) + FilesPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + SimplexLinksPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + ReportsPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + HistoryPreference() + } else { + TimedMessagesPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + FullDeletePreference() + SectionDividerSpaced(true, maxBottomPadding = false) + ReactionsPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + HistoryPreference() + } if (groupInfo.isOwner) { SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) + val saveTextId = if (groupInfo.useRelays) MR.strings.save_and_notify_channel_subscribers + else MR.strings.save_and_notify_group_members ResetSaveButtons( reset = reset, save = savePrefs, - disabled = preferences == currentPreferences + disabled = preferences == currentPreferences, + saveTextId = saveTextId ) } SectionBottomSpacer() @@ -253,21 +296,21 @@ private fun FeatureSection( } @Composable -private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) { +private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean, saveTextId: StringResource) { SectionView { SectionItemView(reset, disabled = disabled) { Text(stringResource(MR.strings.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } SectionItemView(save, disabled = disabled) { - Text(stringResource(MR.strings.save_and_notify_group_members), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + Text(stringResource(saveTextId), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } } } -private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { +private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit, confirmTextId: StringResource) { AlertManager.shared.showAlertDialogStacked( title = generalGetString(MR.strings.save_preferences_question), - confirmText = generalGetString(MR.strings.save_and_notify_group_members), + confirmText = generalGetString(confirmTextId), dismissText = generalGetString(MR.strings.exit_without_saving), onConfirm = save, onDismiss = revert, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index f1ab9b7c30..9796d276c7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1875,6 +1875,7 @@ Error creating member contact Error sending invitation Only group owners can change group preferences. + Only channel owners can change channel preferences. Only chat owners can change preferences. Address Share address @@ -2217,6 +2218,7 @@ Chat preferences Contact preferences Group preferences + Channel preferences Set group preferences Set member admission Your preferences