diff --git a/apps/ios/Shared/Views/Chat/Group/ChannelMembersView.swift b/apps/ios/Shared/Views/Chat/Group/ChannelMembersView.swift
index 6b7f5b65fc..e4d788b47c 100644
--- a/apps/ios/Shared/Views/Chat/Group/ChannelMembersView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/ChannelMembersView.swift
@@ -26,7 +26,7 @@ struct ChannelMembersView: View {
.sorted { $0.wrapped.memberRole > $1.wrapped.memberRole }
let subscriberCount = groupInfo.groupSummary.publicMemberCount ?? Int64(members.count + 1)
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
- if groupInfo.isOwner {
+ if groupInfo.canModerate {
List {
Section(header: Text(subscriberCountStr(subscriberCount)).foregroundColor(theme.colors.secondary)) {
searchFieldView(text: $searchText, focussed: $searchFocussed, theme.colors.onBackground, theme.colors.secondary)
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
index 1be8070259..d2644e600f 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
@@ -125,7 +125,7 @@ struct GroupChatInfoView: View {
Label("Share via chat", systemImage: "arrowshape.turn.up.forward")
}
}
- if groupInfo.isOwner || members.contains(where: { $0.wrapped.memberRole >= .owner }) {
+ if groupInfo.canModerate || members.contains(where: { $0.wrapped.memberRole >= .owner }) {
channelMembersButton()
}
if groupInfo.membership.memberRole >= .moderator {
@@ -734,7 +734,7 @@ struct GroupChatInfoView: View {
}
private func channelMembersButton() -> some View {
- let label: LocalizedStringKey = groupInfo.isOwner ? "Subscribers" : "Owners & contributors"
+ let label: LocalizedStringKey = groupInfo.canModerate ? "Subscribers" : "Owners & contributors"
return NavigationLink {
ChannelMembersView(chat: chat, groupInfo: groupInfo)
.navigationTitle(label)
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
index ebbdc6b6a3..d529bc4e76 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
@@ -38,6 +38,8 @@ struct GroupMemberInfoView: View {
switch groupMember.wrapped.memberRole {
case .relay: "Relay"
case .owner: "Owner"
+ case .moderator: "Moderator"
+ case .member: "Contributor"
default: "Subscriber"
}
} else {
diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift
index 68300193d5..60d8ca1da8 100644
--- a/apps/ios/SimpleXChat/ChatTypes.swift
+++ b/apps/ios/SimpleXChat/ChatTypes.swift
@@ -3414,10 +3414,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
if groupInfo.useRelays && !groupInfo.isOwner { return nil }
let userRole = groupInfo.membership.memberRole
if groupInfo.useRelays {
- // TODO [relays]: for now owners can only set observer/member in channels.
- // Restore the full Owner-excluded picker when moderator/admin promotion is supported:
- // return GroupMemberRole.supportedRoles.filter { $0 <= userRole && $0 != .owner }
- return [.observer, .member]
+ return [.observer, .member, .moderator]
} else {
return GroupMemberRole.supportedRoles.filter { $0 <= userRole }
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
index 094615eee1..8a0f18d5de 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
@@ -2987,10 +2987,7 @@ data class GroupMember (
else if (groupInfo.useRelays && !groupInfo.isOwner) null
else groupInfo.membership.memberRole.let { userRole ->
if (groupInfo.useRelays)
- // TODO [relays]: for now owners can only set observer/member in channels.
- // Restore the full Owner-excluded picker when moderator/admin promotion is supported:
- // GroupMemberRole.selectableRoles.filter { it <= userRole && it != GroupMemberRole.Owner }
- listOf(GroupMemberRole.Observer, GroupMemberRole.Member)
+ listOf(GroupMemberRole.Observer, GroupMemberRole.Member, GroupMemberRole.Moderator)
else
GroupMemberRole.selectableRoles.filter { it <= userRole }
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/ChannelMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/ChannelMembersView.kt
index ea841acc86..3ad2906759 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/ChannelMembersView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/ChannelMembersView.kt
@@ -44,7 +44,7 @@ fun ModalData.ChannelMembersView(
val s = searchText.value.text.trim().lowercase()
val subscriberCount = groupInfo.groupSummary.publicMemberCount ?: (members.size + 1).toLong()
val oneHandUI = remember { ChatController.appPrefs.oneHandUI.state }
- val title = if (groupInfo.isOwner) {
+ val title = if (groupInfo.canModerate) {
generalGetString(MR.strings.channel_members_title_subscribers)
} else {
generalGetString(MR.strings.channel_members_section_owners)
@@ -57,7 +57,7 @@ fun ModalData.ChannelMembersView(
item {
AppBarTitle(title)
}
- if (groupInfo.isOwner) {
+ if (groupInfo.canModerate) {
val showSearch = members.size > 8
item {
SectionView(title = subscriberCountStr(subscriberCount)) {
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 973b684d38..8cbbeb18c7 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
@@ -651,7 +651,7 @@ fun ModalData.GroupChatInfoLayout(
ModalManager.closeAllModalsEverywhere()
}
}
- if (groupInfo.isOwner || activeSortedMembers.any { it.memberRole >= GroupMemberRole.Owner }) {
+ if (groupInfo.canModerate || activeSortedMembers.any { it.memberRole >= GroupMemberRole.Owner }) {
anyTopSectionRowShow = true
ChannelMembersButton(chat.remoteHostId, groupInfo, showMemberInfo)
}
@@ -1295,7 +1295,7 @@ private fun ShareViaChatButton(onClick: () -> Unit) {
@Composable
private fun ChannelMembersButton(rhId: Long?, groupInfo: GroupInfo, showMemberInfo: (GroupMember, GroupRelay?) -> Unit) {
- val title = if (groupInfo.isOwner) {
+ val title = if (groupInfo.canModerate) {
stringResource(MR.strings.channel_members_title_subscribers)
} else {
stringResource(MR.strings.channel_members_section_owners)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt
index ac1c6915b4..dc06d15ea6 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt
@@ -594,6 +594,8 @@ fun GroupMemberInfoLayout(
when (member.memberRole) {
GroupMemberRole.Relay -> stringResource(MR.strings.member_info_section_title_relay)
GroupMemberRole.Owner -> stringResource(MR.strings.member_info_section_title_owner)
+ GroupMemberRole.Moderator -> stringResource(MR.strings.member_info_section_title_moderator)
+ GroupMemberRole.Member -> stringResource(MR.strings.member_info_section_title_contributor)
else -> stringResource(MR.strings.member_info_section_title_subscriber)
}
} else {
diff --git a/apps/multiplatform/common/src/commonMain/moko-resources/base/strings.xml b/apps/multiplatform/common/src/commonMain/moko-resources/base/strings.xml
index 793d72244b..b4d12e357f 100644
--- a/apps/multiplatform/common/src/commonMain/moko-resources/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/moko-resources/base/strings.xml
@@ -3090,6 +3090,8 @@
Relay
Owner
+ Moderator
+ Contributor
Subscriber
Channel
Relay link