diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift
index e066e1fba0..9a82c912dd 100644
--- a/apps/ios/SimpleXChat/ChatTypes.swift
+++ b/apps/ios/SimpleXChat/ChatTypes.swift
@@ -15,6 +15,9 @@ public let CREATE_MEMBER_CONTACT_VERSION = 2
// version to receive reports (MCReport)
public let REPORTS_VERSION = 12
+// support group knocking (MsgScope)
+public let GROUP_KNOCKING_VERSION = 15
+
public let contentModerationPostLink = URL(string: "https://simplex.chat/blog/20250114-simplex-network-large-groups-privacy-preserving-content-moderation.html#preventing-server-abuse-without-compromising-e2e-encryption")!
public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable {
@@ -1346,11 +1349,19 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
return nil
case let .group(groupInfo, groupChatScope):
if groupInfo.membership.memberActive {
- if groupChatScope == nil {
+ switch(groupChatScope) {
+ case .none:
if groupInfo.membership.memberPending { return ("reviewed by admins", "Please contact group admin.") }
if groupInfo.membership.memberRole == .observer { return ("you are observer", "Please contact group admin.") }
+ return nil
+ case let .some(.memberSupport(groupMember_: .some(supportMember))):
+ if supportMember.versionRange.maxVersion < GROUP_KNOCKING_VERSION && !supportMember.memberPending {
+ return ("member has old version", nil)
+ }
+ return nil
+ case .some(.memberSupport(groupMember_: .none)):
+ return nil
}
- return nil
} else {
switch groupInfo.membership.memberStatus {
case .memRejected: return ("request to join rejected", nil)
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 7bd9a1844a..270b3a73b2 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
@@ -1500,15 +1500,29 @@ sealed class ChatInfo: SomeChat, NamedChat {
}
is Group -> {
if (groupInfo.membership.memberActive) {
- if (groupChatScope == null) {
- if (groupInfo.membership.memberPending) {
- return generalGetString(MR.strings.reviewed_by_admins) to generalGetString(MR.strings.observer_cant_send_message_desc)
- }
- if (groupInfo.membership.memberRole == GroupMemberRole.Observer) {
- return generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc)
+ when (groupChatScope) {
+ null -> {
+ if (groupInfo.membership.memberPending) {
+ return generalGetString(MR.strings.reviewed_by_admins) to generalGetString(MR.strings.observer_cant_send_message_desc)
+ }
+ if (groupInfo.membership.memberRole == GroupMemberRole.Observer) {
+ return generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc)
+ }
+ return null
}
+ is GroupChatScopeInfo.MemberSupport ->
+ if (groupChatScope.groupMember_ != null) {
+ if (
+ groupChatScope.groupMember_.versionRange.maxVersion < GROUP_KNOCKING_VERSION
+ && !groupChatScope.groupMember_.memberPending
+ ) {
+ return generalGetString(MR.strings.cant_send_message_member_has_old_version) to null
+ }
+ return null
+ } else {
+ return null
+ }
}
- return null
} else {
return when (groupInfo.membership.memberStatus) {
GroupMemberStatus.MemRejected -> generalGetString(MR.strings.cant_send_message_rejected) to null
@@ -2005,7 +2019,8 @@ data class GroupMember (
val memberContactId: Long? = null,
val memberContactProfileId: Long,
var activeConn: Connection? = null,
- val supportChat: GroupSupportChat? = null
+ val supportChat: GroupSupportChat? = null,
+ val memberChatVRange: VersionRange
): NamedChat {
val id: String get() = "#$groupId @$groupMemberId"
val ready get() = activeConn?.connStatus == ConnStatus.Ready
@@ -2119,6 +2134,8 @@ data class GroupMember (
&& userRole >= GroupMemberRole.Moderator && userRole >= memberRole && groupInfo.membership.memberActive
}
+ val versionRange: VersionRange = activeConn?.peerChatVRange ?: memberChatVRange
+
val memberIncognito = memberProfile.profileId != memberContactProfileId
companion object {
@@ -2136,7 +2153,8 @@ data class GroupMember (
memberProfile = LocalProfile.sampleData,
memberContactId = 1,
memberContactProfileId = 1L,
- activeConn = Connection.sampleData
+ activeConn = Connection.sampleData,
+ memberChatVRange = VersionRange(minVersion = 1, maxVersion = 15)
)
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
index 12c93888ee..8378b6fa3b 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
@@ -58,6 +58,9 @@ typealias ChatCtrl = Long
// version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core)
val CREATE_MEMBER_CONTACT_VERSION = 2
+// support group knocking (MsgScope)
+val GROUP_KNOCKING_VERSION = 15
+
enum class CallOnLockScreen {
DISABLE,
SHOW,
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 6198207991..b130970467 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -526,6 +526,7 @@
can\'t send messages
you are observer
reviewed by admins
+ member has old version
Image