From aa9b147aa8ed1bd3cd34fe04bf5a46b35b7cf8cf Mon Sep 17 00:00:00 2001
From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Date: Thu, 25 Jun 2026 08:28:54 +0000
Subject: [PATCH] ui: show subsriber roles in list; contributor list for
subscribers (#7126)
---
.../Shared/Views/Chat/ChatInfoToolbar.swift | 9 ++++++++
.../Views/Chat/Group/ChannelMembersView.swift | 19 ++++++++++-----
.../Views/Chat/Group/GroupChatInfoView.swift | 2 +-
.../simplex/common/views/chat/ChatView.kt | 5 ++++
.../views/chat/group/ChannelMembersView.kt | 23 ++++++++++++++-----
.../commonMain/resources/MR/base/strings.xml | 5 +++-
6 files changed, 49 insertions(+), 14 deletions(-)
diff --git a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift
index 00c8d7070b..f825dbeca7 100644
--- a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift
+++ b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift
@@ -131,6 +131,15 @@ public func subscriberCountStr(_ count: Int64) -> String {
: String.localizedStringWithFormat(NSLocalizedString("%d subscribers", comment: "channel subscriber count"), count)
}
+public func ownersContributorsCountStr(_ count: Int, withContributors: Bool) -> String {
+ if withContributors {
+ return String.localizedStringWithFormat(NSLocalizedString("%d owners & contributors", comment: "channel members count"), count)
+ }
+ return count == 1
+ ? String.localizedStringWithFormat(NSLocalizedString("%d owner", comment: "channel owners count"), count)
+ : String.localizedStringWithFormat(NSLocalizedString("%d owners", comment: "channel owners count"), count)
+}
+
struct ChatInfoToolbar_Previews: PreviewProvider {
static var previews: some View {
ChatInfoToolbar(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []))
diff --git a/apps/ios/Shared/Views/Chat/Group/ChannelMembersView.swift b/apps/ios/Shared/Views/Chat/Group/ChannelMembersView.swift
index 44fc302aff..231054fd78 100644
--- a/apps/ios/Shared/Views/Chat/Group/ChannelMembersView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/ChannelMembersView.swift
@@ -21,22 +21,29 @@ struct ChannelMembersView: View {
let s = m.wrapped.memberStatus
return s != .memLeft && s != .memRemoved && m.wrapped.memberRole != .relay
}
+ .sorted { $0.wrapped.memberRole > $1.wrapped.memberRole }
+ let subscriberCount = groupInfo.groupSummary.publicMemberCount ?? Int64(members.count + 1)
if groupInfo.isOwner {
- let subscriberCount = groupInfo.groupSummary.publicMemberCount ?? Int64(members.count + 1)
List {
Section(header: Text(subscriberCountStr(subscriberCount)).foregroundColor(theme.colors.secondary)) {
memberRow(GMember(groupInfo.membership), user: true, showRole: true)
ForEach(members) { member in
- memberRow(member, user: false, showRole: member.wrapped.memberRole >= .owner)
+ memberRow(member, user: false, showRole: member.wrapped.memberRole >= .member)
}
}
}
} else {
- let owners = members.filter { $0.wrapped.memberRole >= .owner }
+ let contributors = members.filter { $0.wrapped.memberRole >= .member && $0.wrapped.memberStatus != .memUnknown }
+ let contributorCount = contributors.count + (groupInfo.membership.memberRole >= .member ? 1 : 0)
+ let withContributors = contributors.contains { $0.wrapped.memberRole < .owner }
+ || groupInfo.membership.memberRole >= .member
List {
- Section(header: Text("Owners").foregroundColor(theme.colors.secondary)) {
- ForEach(owners) { member in
- memberRow(member, user: false, showRole: false)
+ Section(header: Text(ownersContributorsCountStr(contributorCount, withContributors: withContributors)).foregroundColor(theme.colors.secondary)) {
+ if groupInfo.membership.memberRole >= .member {
+ memberRow(GMember(groupInfo.membership), user: true, showRole: true)
+ }
+ ForEach(contributors) { member in
+ memberRow(member, user: false, showRole: member.wrapped.memberRole >= .moderator)
}
}
}
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
index 5563d79e61..41e24a6ced 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
@@ -691,7 +691,7 @@ struct GroupChatInfoView: View {
}
private func channelMembersButton() -> some View {
- let label: LocalizedStringKey = groupInfo.isOwner ? "Subscribers" : "Owners"
+ let label: LocalizedStringKey = groupInfo.isOwner ? "Subscribers" : "Owners & contributors"
return NavigationLink {
ChannelMembersView(chat: chat, groupInfo: groupInfo)
.navigationTitle(label)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt
index 6f7c746691..cc9e71354c 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt
@@ -1547,6 +1547,11 @@ fun subscriberCountStr(count: Long): String =
if (count == 1L) String.format(generalGetString(MR.strings.channel_subscriber_count_singular), count)
else String.format(generalGetString(MR.strings.channel_subscriber_count_plural), count)
+fun ownersContributorsCountStr(count: Int, withContributors: Boolean): String =
+ if (withContributors) String.format(generalGetString(MR.strings.channel_owners_contributors_count), count)
+ else if (count == 1) String.format(generalGetString(MR.strings.channel_owner_count_singular), count)
+ else String.format(generalGetString(MR.strings.channel_owner_count_plural), count)
+
@Composable
fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)) {
Row(
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 9f13cf2b19..ef3d8805f4 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
@@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp
import chat.simplex.common.model.*
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
+import chat.simplex.common.views.chat.ownersContributorsCountStr
import chat.simplex.common.views.chat.subscriberCountStr
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
@@ -33,6 +34,7 @@ fun ChannelMembersView(
&& m.memberStatus != GroupMemberStatus.MemRemoved
&& m.memberRole != GroupMemberRole.Relay
}
+ .sortedByDescending { it.memberRole }
ColumnWithScrollBar {
val title = if (groupInfo.isOwner) {
@@ -42,8 +44,8 @@ fun ChannelMembersView(
}
AppBarTitle(title)
+ val subscriberCount = groupInfo.groupSummary.publicMemberCount ?: (members.size + 1).toLong()
if (groupInfo.isOwner) {
- val subscriberCount = groupInfo.groupSummary.publicMemberCount ?: (members.size + 1).toLong()
SectionView(title = subscriberCountStr(subscriberCount)) {
SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
ChannelMemberRow(groupInfo.membership, user = true, showRole = true, isChannel = groupInfo.isChannel)
@@ -55,14 +57,23 @@ fun ChannelMembersView(
minHeight = 54.dp,
padding = PaddingValues(horizontal = DEFAULT_PADDING)
) {
- ChannelMemberRow(member, user = false, showRole = member.memberRole >= GroupMemberRole.Owner, isChannel = groupInfo.isChannel)
+ ChannelMemberRow(member, user = false, showRole = member.memberRole >= GroupMemberRole.Member, isChannel = groupInfo.isChannel)
}
}
}
} else {
- val owners = members.filter { it.memberRole >= GroupMemberRole.Owner }
- SectionView(title = generalGetString(MR.strings.channel_members_section_owners)) {
- owners.forEachIndexed { index, member ->
+ val contributors = members.filter { it.memberRole >= GroupMemberRole.Member && it.memberStatus != GroupMemberStatus.MemUnknown }
+ val contributorCount = contributors.size + if (groupInfo.membership.memberRole >= GroupMemberRole.Member) 1 else 0
+ val withContributors = contributors.any { it.memberRole < GroupMemberRole.Owner } ||
+ groupInfo.membership.memberRole >= GroupMemberRole.Member
+ SectionView(title = ownersContributorsCountStr(contributorCount, withContributors)) {
+ if (groupInfo.membership.memberRole >= GroupMemberRole.Member) {
+ SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
+ ChannelMemberRow(groupInfo.membership, user = true, showRole = true, isChannel = groupInfo.isChannel)
+ }
+ Divider()
+ }
+ contributors.forEachIndexed { index, member ->
if (index > 0) {
Divider()
}
@@ -71,7 +82,7 @@ fun ChannelMembersView(
minHeight = 54.dp,
padding = PaddingValues(horizontal = DEFAULT_PADDING)
) {
- ChannelMemberRow(member, user = false, showRole = false, isChannel = groupInfo.isChannel)
+ ChannelMemberRow(member, user = false, showRole = member.memberRole >= GroupMemberRole.Moderator, isChannel = groupInfo.isChannel)
}
}
}
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 71c0bf98a4..9262bd9dac 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -2976,9 +2976,12 @@
Subscribers
- Owners
+ Owners & contributors
%1$d subscriber
%1$d subscribers
+ %1$d owner
+ %1$d owners
+ %1$d owners & contributors
you