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