ui: fix channel contributor being able to "send message" without relays (#7139)

This commit is contained in:
spaced4ndy
2026-06-27 12:12:47 +01:00
committed by GitHub
parent e979b7efdc
commit 894e44b8ed
4 changed files with 68 additions and 36 deletions
@@ -392,38 +392,31 @@ struct ComposeView: View {
}
let ownerState = ownerRelayState
let subscriberState = subscriberRelayState
if let gInfo = chat.chatInfo.groupInfo, gInfo.useRelays,
![.memRejected, .memLeft, .memRemoved, .memGroupDeleted].contains(gInfo.membership.memberStatus) {
if gInfo.membership.memberRole == .owner {
if let s = ownerState, s.relays.isEmpty || s.activeCount < s.relays.count {
ownerChannelRelayBar(relays: s.relays, activeCount: s.activeCount, failedCount: s.failedCount, removedCount: s.removedCount)
}
} else {
let hostnames = (chatModel.channelRelayHostnames[gInfo.groupId] ?? []).sorted()
let relayMembers = chatModel.groupMembers
.filter { $0.wrapped.memberRole == .relay && ![.memRemoved, .memGroupDeleted].contains($0.wrapped.memberStatus) }
.sorted { hostFromRelayLink($0.wrapped.relayLink ?? "") < hostFromRelayLink($1.wrapped.relayLink ?? "") }
} else if let s = subscriberState {
let showProgress = !gInfo.nextConnectPrepared || composeState.inProgress
let removedCount = relayMembers.filter { relayMemberRemoved($0.wrapped.memberStatus) }.count
let connectedCount = relayMembers.filter { !relayMemberRemoved($0.wrapped.memberStatus) && $0.wrapped.activeConn?.connStatus == .ready && $0.wrapped.activeConn?.connFailedErr == nil }.count
let failedCount = relayMembers.filter { !relayMemberRemoved($0.wrapped.memberStatus) && $0.wrapped.activeConn?.connFailedErr != nil }.count
let resolvedCount = connectedCount + removedCount + failedCount
let total = relayMembers.count > 0 ? relayMembers.count : hostnames.count
if total == 0 || removedCount + failedCount > 0 || resolvedCount < total {
let resolvedCount = s.connectedCount + s.removedCount + s.failedCount
if s.total == 0 || s.removedCount + s.failedCount > 0 || resolvedCount < s.total {
subscriberChannelRelayBar(
hostnames: hostnames,
relayMembers: relayMembers,
connectedCount: connectedCount,
removedCount: removedCount,
failedCount: failedCount,
total: total,
hostnames: s.hostnames,
relayMembers: s.relayMembers,
connectedCount: s.connectedCount,
removedCount: s.removedCount,
failedCount: s.failedCount,
total: s.total,
showProgress: showProgress
)
}
}
}
let userCantSendReason = chat.chatInfo.userCantSendReason(allRelaysBroken: ownerState?.noActiveRelays ?? false)
let userCantSendReason = chat.chatInfo.userCantSendReason(allRelaysBroken: (ownerState?.noActiveRelays ?? subscriberState?.noActiveRelays) ?? false)
let composeEnabled = (
userCantSendReason == nil ||
(chat.chatInfo.groupInfo?.nextConnectPrepared ?? false) ||
@@ -748,8 +741,25 @@ struct ComposeView: View {
return (relays, activeCount, failedCount, removedCount, noActiveRelays)
}
private var subscriberRelayState: (hostnames: [String], relayMembers: [GMember], connectedCount: Int, removedCount: Int, failedCount: Int, total: Int, noActiveRelays: Bool)? {
guard let gInfo = chat.chatInfo.groupInfo, gInfo.useRelays,
gInfo.membership.memberRole != .owner,
![.memRejected, .memLeft, .memRemoved, .memGroupDeleted].contains(gInfo.membership.memberStatus)
else { return nil }
let hostnames = (chatModel.channelRelayHostnames[gInfo.groupId] ?? []).sorted()
let relayMembers = chatModel.groupMembers
.filter { $0.wrapped.memberRole == .relay && ![.memRemoved, .memGroupDeleted].contains($0.wrapped.memberStatus) }
.sorted { hostFromRelayLink($0.wrapped.relayLink ?? "") < hostFromRelayLink($1.wrapped.relayLink ?? "") }
let removedCount = relayMembers.filter { relayMemberRemoved($0.wrapped.memberStatus) }.count
let connectedCount = relayMembers.filter { !relayMemberRemoved($0.wrapped.memberStatus) && $0.wrapped.activeConn?.connStatus == .ready && $0.wrapped.activeConn?.connFailedErr == nil }.count
let failedCount = relayMembers.filter { !relayMemberRemoved($0.wrapped.memberStatus) && $0.wrapped.activeConn?.connFailedErr != nil }.count
let total = relayMembers.count > 0 ? relayMembers.count : hostnames.count
let noActiveRelays = connectedCount == 0 && (removedCount + failedCount) == total
return (hostnames, relayMembers, connectedCount, removedCount, failedCount, total, noActiveRelays)
}
private var disabledText: LocalizedStringKey? {
chat.chatInfo.userCantSendReason(allRelaysBroken: ownerRelayState?.noActiveRelays ?? false)?.composeLabel
chat.chatInfo.userCantSendReason(allRelaysBroken: (ownerRelayState?.noActiveRelays ?? subscriberRelayState?.noActiveRelays) ?? false)?.composeLabel
}
@ViewBuilder private func ownerChannelRelayBar(relays: [GroupRelay], activeCount: Int, failedCount: Int, removedCount: Int) -> some View {
+1 -1
View File
@@ -1720,11 +1720,11 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
if groupInfo.membership.memberActive {
switch(groupChatScope) {
case .none:
if allRelaysBroken && groupInfo.useRelays { return ("can't broadcast", nil) }
if groupInfo.membership.memberPending { return ("reviewed by admins", "Please contact group admin.") }
if groupInfo.membership.memberRole == .observer {
return groupInfo.useRelays ? ("you are subscriber", nil) : ("you are observer", "Please contact group admin.")
}
if allRelaysBroken && groupInfo.useRelays { return ("can't broadcast", nil) }
return nil
case let .some(.memberSupport(groupMember_: .some(supportMember))):
if supportMember.versionRange.maxVersion < GROUP_KNOCKING_VERSION && !supportMember.memberPending {
@@ -1660,9 +1660,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
if (groupInfo.membership.memberActive) {
when (groupChatScope) {
null -> {
if (allRelaysBroken && groupInfo.useRelays) {
return generalGetString(MR.strings.cant_broadcast_message) to null
}
if (groupInfo.membership.memberPending) {
return generalGetString(MR.strings.reviewed_by_admins) to generalGetString(MR.strings.observer_cant_send_message_desc)
}
@@ -1673,6 +1670,9 @@ sealed class ChatInfo: SomeChat, NamedChat {
generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc)
}
}
if (allRelaysBroken && groupInfo.useRelays) {
return generalGetString(MR.strings.cant_broadcast_message) to null
}
return null
}
is GroupChatScopeInfo.MemberSupport ->
@@ -1203,8 +1203,9 @@ fun ComposeView(
}
val ownerRelayState = ownerRelayState(chat, chatModel)
val subscriberRelayState = subscriberRelayState(chat, chatModel)
val userCantSendReason = rememberUpdatedState(chat.chatInfo.userCantSendReason(ownerRelayState?.noActiveRelays == true))
val userCantSendReason = rememberUpdatedState(chat.chatInfo.userCantSendReason((ownerRelayState?.noActiveRelays ?: subscriberRelayState?.noActiveRelays) == true))
val sendMsgEnabled = rememberUpdatedState(userCantSendReason.value == null)
val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv)
@@ -1575,18 +1576,12 @@ fun ComposeView(
}
}
} else {
val hostnames = (chatModel.channelRelayHostnames[gInfo.groupId] ?: emptyList()).sorted()
val relayMembers = chatModel.groupMembers.value
.filter { it.memberRole == GroupMemberRole.Relay && it.memberStatus !in listOf(GroupMemberStatus.MemRemoved, GroupMemberStatus.MemGroupDeleted) }
.sortedBy { hostFromRelayLink(it.relayLink ?: "") }
val showProgress = !gInfo.nextConnectPrepared || composeState.value.inProgress
val removedCount = relayMembers.count { relayMemberRemoved(it.memberStatus) }
val connectedCount = relayMembers.count { !relayMemberRemoved(it.memberStatus) && it.activeConn?.connStatus == ConnStatus.Ready && it.activeConn?.connFailedErr == null }
val failedCount = relayMembers.count { !relayMemberRemoved(it.memberStatus) && it.activeConn?.connFailedErr != null }
val resolvedCount = connectedCount + removedCount + failedCount
val total = if (relayMembers.isNotEmpty()) relayMembers.size else hostnames.size
if (total == 0 || removedCount + failedCount > 0 || resolvedCount < total) {
SubscriberChannelRelayBar(hostnames, relayMembers, connectedCount, removedCount, failedCount, total, showProgress, relayListExpanded)
subscriberRelayState?.let { s ->
val showProgress = !gInfo.nextConnectPrepared || composeState.value.inProgress
val resolvedCount = s.connectedCount + s.removedCount + s.failedCount
if (s.total == 0 || s.removedCount + s.failedCount > 0 || resolvedCount < s.total) {
SubscriberChannelRelayBar(s.hostnames, s.relayMembers, s.connectedCount, s.removedCount, s.failedCount, s.total, showProgress, relayListExpanded)
}
}
}
}
@@ -2052,6 +2047,33 @@ private data class OwnerRelayState(
val noActiveRelays: Boolean
)
private fun subscriberRelayState(chat: Chat, chatModel: ChatModel): SubscriberRelayState? {
val gInfo = (chat.chatInfo as? ChatInfo.Group)?.groupInfo ?: return null
if (!gInfo.useRelays || gInfo.membership.memberRole == GroupMemberRole.Owner ||
gInfo.membership.memberStatus in listOf(GroupMemberStatus.MemRejected, GroupMemberStatus.MemLeft, GroupMemberStatus.MemRemoved, GroupMemberStatus.MemGroupDeleted)
) return null
val hostnames = (chatModel.channelRelayHostnames[gInfo.groupId] ?: emptyList()).sorted()
val relayMembers = chatModel.groupMembers.value
.filter { it.memberRole == GroupMemberRole.Relay && it.memberStatus !in listOf(GroupMemberStatus.MemRemoved, GroupMemberStatus.MemGroupDeleted) }
.sortedBy { hostFromRelayLink(it.relayLink ?: "") }
val removedCount = relayMembers.count { relayMemberRemoved(it.memberStatus) }
val connectedCount = relayMembers.count { !relayMemberRemoved(it.memberStatus) && it.activeConn?.connStatus == ConnStatus.Ready && it.activeConn?.connFailedErr == null }
val failedCount = relayMembers.count { !relayMemberRemoved(it.memberStatus) && it.activeConn?.connFailedErr != null }
val total = if (relayMembers.isNotEmpty()) relayMembers.size else hostnames.size
val noActiveRelays = connectedCount == 0 && (removedCount + failedCount) == total
return SubscriberRelayState(hostnames, relayMembers, connectedCount, removedCount, failedCount, total, noActiveRelays)
}
private data class SubscriberRelayState(
val hostnames: List<String>,
val relayMembers: List<GroupMember>,
val connectedCount: Int,
val removedCount: Int,
val failedCount: Int,
val total: Int,
val noActiveRelays: Boolean
)
private fun relayMemberRemoved(status: GroupMemberStatus?): Boolean =
status in listOf(GroupMemberStatus.MemLeft, GroupMemberStatus.MemRemoved, GroupMemberStatus.MemGroupDeleted)