From 3dc0c09d4953ecb8b840e988550391dcbab63c36 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Thu, 28 May 2026 16:16:44 +0000 Subject: [PATCH] android, desktop, ios: cap memberMentions at MAX so stale cache entries cannot overflow the server limit With the sticky cache, manually typing an @name that happens to match a stale cache entry could push memberMentions above maxSndMentions and have the core reject the send with SEInvalidMention. The getter now walks parsedMessage in text order and stops at MAX_NUMBER_OF_MENTIONS; later @-tokens past the cap become visual-only formatting and the message still goes through. Also hoists the iOS activeMentions computation out of the per-row ForEach so it runs once per picker open rather than once per row. --- .../Chat/ComposeMessage/ComposeView.swift | 15 +++++++++------ .../Views/Chat/Group/GroupMentions.swift | 2 +- .../simplex/common/views/chat/ComposeView.kt | 19 +++++++++---------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index e2b5530f13..720a24bc1a 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -134,12 +134,15 @@ struct ComposeState { } var memberMentions: [String: Int64] { - let usedMentions: Set = Set(parsedMessage.compactMap { ft in - if case let .mention(n) = ft.format { n } else { nil } - }) - return self.mentions - .filter { usedMentions.contains($0.key) } - .compactMapValues { $0.memberRef?.groupMemberId } + var result: [String: Int64] = [:] + for ft in parsedMessage { + if result.count >= MAX_NUMBER_OF_MENTIONS { break } + guard case let .mention(name) = ft.format else { continue } + if result[name] != nil { continue } + guard let id = self.mentions[name]?.memberRef?.groupMemberId else { continue } + result[name] = id + } + return result } var editing: Bool { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift index d0a8cba550..c83e6c6895 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift @@ -43,11 +43,11 @@ struct GroupMentionsView: View { VStack(spacing: 0) { Spacer() Divider() + let activeMentions = composeState.parsedMessage.reduce(0) { n, ft in if case .mention = ft.format { n + 1 } else { n } } let scroll = ScrollView { LazyVStack(spacing: 0) { ForEach(Array(filtered.enumerated()), id: \.element.wrapped.groupMemberId) { index, member in let mentioned = mentionMemberId == member.wrapped.memberId - let activeMentions = composeState.parsedMessage.reduce(0) { n, ft in if case .mention = ft.format { n + 1 } else { n } } let disabled = activeMentions >= MAX_NUMBER_OF_MENTIONS && !mentioned ZStack(alignment: .bottom) { memberRowView(member.wrapped, mentioned) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 93c9594e7a..a259e59abb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -132,16 +132,15 @@ data class ComposeState( val memberMentions: Map get() { - val usedMentions = this.parsedMessage.mapNotNull { (it.format as? Format.Mention)?.memberName }.toSet() - return this.mentions.mapNotNull { - val memberRef = it.value.memberRef - - if (memberRef != null && it.key in usedMentions) { - it.key to memberRef.groupMemberId - } else { - null - } - }.toMap() + val result = mutableMapOf() + for (ft in parsedMessage) { + if (result.size >= MAX_NUMBER_OF_MENTIONS) break + val name = (ft.format as? Format.Mention)?.memberName ?: continue + if (name in result) continue + val id = mentions[name]?.memberRef?.groupMemberId ?: continue + result[name] = id + } + return result } val editing: Boolean