mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-25 18:32:17 +00:00
Merge branch 'master' into master-android
This commit is contained in:
@@ -27,79 +27,76 @@ struct GroupMentionsView: View {
|
||||
@State private var mentionName: String = ""
|
||||
@State private var mentionRange: NSRange?
|
||||
@State private var mentionMemberId: String?
|
||||
@State private var sortedMembers: [GMember] = []
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if isVisible {
|
||||
Color.white.opacity(0.01)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.onTapGesture {
|
||||
isVisible = false
|
||||
}
|
||||
}
|
||||
VStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Spacer()
|
||||
let filtered = filteredMembers()
|
||||
if filtered.count > 0 {
|
||||
Color.white.opacity(0.01)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.onTapGesture {
|
||||
isVisible = false
|
||||
}
|
||||
VStack {
|
||||
Divider()
|
||||
let list = List {
|
||||
ForEach(filteredMembers, id: \.wrapped.groupMemberId) { member in
|
||||
let mentioned = mentionMemberId == member.wrapped.memberId
|
||||
let disabled = composeState.mentions.count >= MAX_NUMBER_OF_MENTIONS && !mentioned
|
||||
memberRowView(member.wrapped, mentioned)
|
||||
.contentShape(Rectangle())
|
||||
.disabled(disabled)
|
||||
.opacity(disabled ? 0.6 : 1)
|
||||
.onTapGesture {
|
||||
memberSelected(member)
|
||||
Spacer()
|
||||
VStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Divider()
|
||||
let list = List {
|
||||
ForEach(filtered, id: \.wrapped.groupMemberId) { member in
|
||||
let mentioned = mentionMemberId == member.wrapped.memberId
|
||||
let disabled = composeState.mentions.count >= MAX_NUMBER_OF_MENTIONS && !mentioned
|
||||
memberRowView(member.wrapped, mentioned)
|
||||
.contentShape(Rectangle())
|
||||
.disabled(disabled)
|
||||
.opacity(disabled ? 0.6 : 1)
|
||||
.onTapGesture {
|
||||
memberSelected(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
.frame(maxHeight: MEMBER_ROW_SIZE * min(MAX_VISIBLE_MEMBER_ROWS, CGFloat(filtered.count)))
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
list.scrollDismissesKeyboard(.never)
|
||||
} else {
|
||||
list
|
||||
}
|
||||
}
|
||||
.background(Color(UIColor.systemBackground))
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
.frame(height: MEMBER_ROW_SIZE * min(MAX_VISIBLE_MEMBER_ROWS, CGFloat(filteredMembers.count)))
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
list.scrollDismissesKeyboard(.never)
|
||||
} else {
|
||||
list
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: MEMBER_ROW_SIZE * MAX_VISIBLE_MEMBER_ROWS)
|
||||
}
|
||||
.background(Color(UIColor.systemBackground))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: MEMBER_ROW_SIZE * MAX_VISIBLE_MEMBER_ROWS)
|
||||
}
|
||||
.offset(y: isVisible ? 0 : 300)
|
||||
.animation(.spring(), value: isVisible)
|
||||
.onChange(of: composeState.parsedMessage) { parsedMsg in
|
||||
currentMessage = composeState.message
|
||||
messageChanged(currentMessage, parsedMsg, selectedRange)
|
||||
}
|
||||
.onChange(of: selectedRange) { r in
|
||||
// This condition is needed to prevent messageChanged called twice,
|
||||
// because composeState.formattedText triggers later when message changes.
|
||||
// The condition is only true if position changed without text change
|
||||
if currentMessage == composeState.message {
|
||||
messageChanged(currentMessage, composeState.parsedMessage, r)
|
||||
.animation(.spring(), value: isVisible)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
currentMessage = composeState.message
|
||||
}
|
||||
.onChange(of: composeState.parsedMessage) { parsedMsg in
|
||||
currentMessage = composeState.message
|
||||
messageChanged(currentMessage, parsedMsg, selectedRange)
|
||||
}
|
||||
.onChange(of: selectedRange) { r in
|
||||
// This condition is needed to prevent messageChanged called twice,
|
||||
// because composeState.formattedText triggers later when message changes.
|
||||
// The condition is only true if position changed without text change
|
||||
if currentMessage == composeState.message {
|
||||
messageChanged(currentMessage, composeState.parsedMessage, r)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
currentMessage = composeState.message
|
||||
}
|
||||
}
|
||||
|
||||
private var filteredMembers: [GMember] {
|
||||
let members = m.groupMembers
|
||||
.filter { m in
|
||||
let status = m.wrapped.memberStatus
|
||||
return status != .memLeft && status != .memRemoved && status != .memInvited
|
||||
}
|
||||
.sorted { $0.wrapped.memberRole > $1.wrapped.memberRole }
|
||||
private func filteredMembers() -> [GMember] {
|
||||
let s = mentionName.lowercased()
|
||||
return s.isEmpty
|
||||
? members
|
||||
: members.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
|
||||
? sortedMembers
|
||||
: sortedMembers.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
|
||||
}
|
||||
|
||||
private func messageChanged(_ msg: String, _ parsedMsg: [FormattedText], _ range: NSRange) {
|
||||
@@ -112,7 +109,10 @@ struct GroupMentionsView: View {
|
||||
mentionRange = r
|
||||
mentionMemberId = composeState.mentions[name]?.memberId
|
||||
if !m.membersLoaded {
|
||||
Task { await m.loadGroupMembers(groupInfo) }
|
||||
Task {
|
||||
await m.loadGroupMembers(groupInfo)
|
||||
sortMembers()
|
||||
}
|
||||
}
|
||||
return
|
||||
case .none: () //
|
||||
@@ -124,7 +124,10 @@ struct GroupMentionsView: View {
|
||||
mentionName = ""
|
||||
mentionRange = atRange
|
||||
mentionMemberId = nil
|
||||
Task { await m.loadGroupMembers(groupInfo) }
|
||||
Task {
|
||||
await m.loadGroupMembers(groupInfo)
|
||||
sortMembers()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -134,6 +137,14 @@ struct GroupMentionsView: View {
|
||||
closeMemberList()
|
||||
}
|
||||
|
||||
private func sortMembers() {
|
||||
sortedMembers = m.groupMembers.filter({ m in
|
||||
let status = m.wrapped.memberStatus
|
||||
return status != .memLeft && status != .memRemoved && status != .memInvited
|
||||
})
|
||||
.sorted { $0.wrapped.memberRole > $1.wrapped.memberRole }
|
||||
}
|
||||
|
||||
private func removeUnusedMentions(_ parsedMsg: [FormattedText]) {
|
||||
let usedMentions: Set<String> = Set(parsedMsg.compactMap { ft in
|
||||
if case let .mention(name) = ft.format { name } else { nil }
|
||||
|
||||
@@ -172,9 +172,9 @@
|
||||
648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; };
|
||||
648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; };
|
||||
649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D82CFE07CF00536B68 /* libffi.a */; };
|
||||
649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE-ghc9.6.3.a */; };
|
||||
649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH-ghc9.6.3.a */; };
|
||||
649B28DF2CFE07CF00536B68 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DA2CFE07CF00536B68 /* libgmpxx.a */; };
|
||||
649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE.a */; };
|
||||
649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH.a */; };
|
||||
649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DC2CFE07CF00536B68 /* libgmp.a */; };
|
||||
649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; };
|
||||
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; };
|
||||
@@ -530,9 +530,9 @@
|
||||
648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemForwardingView.swift; sourceTree = "<group>"; };
|
||||
6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
649B28D82CFE07CF00536B68 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
649B28DA2CFE07CF00536B68 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE.a"; sourceTree = "<group>"; };
|
||||
649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH.a"; sourceTree = "<group>"; };
|
||||
649B28DC2CFE07CF00536B68 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = "<group>"; };
|
||||
649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = "<group>"; };
|
||||
@@ -689,9 +689,9 @@
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE.a in Frameworks */,
|
||||
649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE-ghc9.6.3.a in Frameworks */,
|
||||
649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH-ghc9.6.3.a in Frameworks */,
|
||||
649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -772,8 +772,8 @@
|
||||
649B28D82CFE07CF00536B68 /* libffi.a */,
|
||||
649B28DC2CFE07CF00536B68 /* libgmp.a */,
|
||||
649B28DA2CFE07CF00536B68 /* libgmpxx.a */,
|
||||
649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE-ghc9.6.3.a */,
|
||||
649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.1-HduHbmAnH3q9HOCIBFU5AE.a */,
|
||||
649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH-ghc9.6.3.a */,
|
||||
649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.3.0.2-Dof9ekfAaNQ8X3suD73WcH.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -1957,7 +1957,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -2006,7 +2006,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -2047,7 +2047,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
@@ -2067,7 +2067,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
@@ -2092,7 +2092,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
@@ -2129,7 +2129,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
@@ -2166,7 +2166,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2217,7 +2217,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2268,7 +2268,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -2302,7 +2302,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
|
||||
@@ -24,11 +24,11 @@ android.nonTransitiveRClass=true
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
kotlin.jvm.target=11
|
||||
|
||||
android.version_name=6.3-beta.1
|
||||
android.version_code=270
|
||||
android.version_name=6.3-beta.2
|
||||
android.version_code=271
|
||||
|
||||
desktop.version_name=6.3-beta.1
|
||||
desktop.version_code=88
|
||||
desktop.version_name=6.3-beta.2
|
||||
desktop.version_code=89
|
||||
|
||||
kotlin.version=1.9.23
|
||||
gradle.plugin.version=8.2.0
|
||||
|
||||
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 6.3.0.2
|
||||
version: 6.3.0.3
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
|
||||
@@ -390,19 +390,34 @@ forwardedGroupMsg msg@ChatMessage {chatMsgEvent} = case encoding @e of
|
||||
_ -> Nothing
|
||||
|
||||
-- applied after checking forwardedGroupMsg and building list of group members to forward to, see Chat;
|
||||
--
|
||||
-- this filters out members if any of forwarded events in batch is an XGrpMemRestrict event referring to them,
|
||||
-- but practically XGrpMemRestrict is not batched with other events so it wouldn't prevent forwarding of other events
|
||||
-- to these members
|
||||
-- to these members;
|
||||
--
|
||||
-- same for reports (MCReport) - they are not batched with other events, so we can safely filter out
|
||||
-- members with role less than moderator when forwarding
|
||||
forwardedToGroupMembers :: forall e. MsgEncodingI e => [GroupMember] -> NonEmpty (ChatMessage e) -> [GroupMember]
|
||||
forwardedToGroupMembers ms forwardedMsgs =
|
||||
filter (\GroupMember {memberId} -> memberId `notElem` restrictMemberIds) ms
|
||||
filter forwardToMember ms
|
||||
where
|
||||
forwardToMember GroupMember {memberId, memberRole} =
|
||||
(memberId `notElem` restrictMemberIds)
|
||||
&& (not hasReport || memberRole >= GRModerator)
|
||||
restrictMemberIds = mapMaybe restrictMemberId $ L.toList forwardedMsgs
|
||||
restrictMemberId ChatMessage {chatMsgEvent} = case encoding @e of
|
||||
SJson -> case chatMsgEvent of
|
||||
XGrpMemRestrict mId _ -> Just mId
|
||||
_ -> Nothing
|
||||
_ -> Nothing
|
||||
hasReport = any isReport forwardedMsgs
|
||||
isReport ChatMessage {chatMsgEvent} = case encoding @e of
|
||||
SJson -> case chatMsgEvent of
|
||||
XMsgNew mc -> case mcExtMsgContent mc of
|
||||
ExtMsgContent {content = MCReport {}} -> True
|
||||
_ -> False
|
||||
_ -> False
|
||||
_ -> False
|
||||
|
||||
data MsgReaction = MREmoji {emoji :: MREmojiChar} | MRUnknown {tag :: Text, json :: J.Object}
|
||||
deriving (Eq, Show)
|
||||
|
||||
@@ -891,7 +891,7 @@ getGroupModerators db vr user@User {userId, userContactId} GroupInfo {groupId} =
|
||||
map (toContactMember vr user)
|
||||
<$> DB.query
|
||||
db
|
||||
(groupMemberQuery <> " WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?) AND member_role IN (?,?,?)")
|
||||
(groupMemberQuery <> " WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?) AND m.member_role IN (?,?,?)")
|
||||
(userId, userId, groupId, userContactId, GRModerator, GRAdmin, GROwner)
|
||||
|
||||
getGroupMembersForExpiration :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember]
|
||||
|
||||
@@ -4577,7 +4577,7 @@ Query:
|
||||
FROM connections cc
|
||||
WHERE cc.user_id = ? AND cc.group_member_id = m.group_member_id
|
||||
)
|
||||
WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?) AND member_role IN (?,?,?)
|
||||
WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?) AND m.member_role IN (?,?,?)
|
||||
Plan:
|
||||
SEARCH m USING INDEX idx_group_members_group_id (user_id=? AND group_id=?)
|
||||
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
@@ -133,6 +133,7 @@ chatGroupTests = do
|
||||
it "re-create member contact after deletion, many groups" testRecreateMemberContactManyGroups
|
||||
describe "group message forwarding" $ do
|
||||
it "forward messages between invitee and introduced (x.msg.new)" testGroupMsgForward
|
||||
it "forward reports to moderators, don't forward to members (x.msg.new, MCReport)" testGroupMsgForwardReport
|
||||
it "deduplicate forwarded messages" testGroupMsgForwardDeduplicate
|
||||
it "forward message edit (x.msg.update)" testGroupMsgForwardEdit
|
||||
it "forward message reaction (x.msg.react)" testGroupMsgForwardReaction
|
||||
@@ -3980,6 +3981,58 @@ testGroupMsgForward =
|
||||
cath <# "#team bob> hi there [>>]"
|
||||
cath <# "#team hey team"
|
||||
|
||||
testGroupMsgForwardReport :: HasCallStack => TestParams -> IO ()
|
||||
testGroupMsgForwardReport =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> do
|
||||
setupGroupForwarding3 "team" alice bob cath
|
||||
|
||||
bob #> "#team hi there"
|
||||
alice <# "#team bob> hi there"
|
||||
cath <# "#team bob> hi there [>>]"
|
||||
|
||||
alice ##> "/mr team bob moderator"
|
||||
concurrentlyN_
|
||||
[ alice <## "#team: you changed the role of bob from admin to moderator",
|
||||
bob <## "#team: alice changed your role from admin to moderator",
|
||||
cath <## "#team: alice changed the role of bob from admin to moderator"
|
||||
]
|
||||
|
||||
cath ##> "/report #team content hi there"
|
||||
cath <# "#team > bob hi there"
|
||||
cath <## " report content"
|
||||
concurrentlyN_
|
||||
[ do
|
||||
alice <# "#team cath> > bob hi there"
|
||||
alice <## " report content",
|
||||
do
|
||||
bob <# "#team cath!> > bob hi there [>>]"
|
||||
bob <## " report content [>>]"
|
||||
]
|
||||
|
||||
alice ##> "/mr team bob member"
|
||||
concurrentlyN_
|
||||
[ alice <## "#team: you changed the role of bob from moderator to member",
|
||||
bob <## "#team: alice changed your role from moderator to member",
|
||||
cath <## "#team: alice changed the role of bob from moderator to member"
|
||||
]
|
||||
|
||||
cath ##> "/report #team content hi there"
|
||||
cath <# "#team > bob hi there"
|
||||
cath <## " report content"
|
||||
concurrentlyN_
|
||||
[ do
|
||||
alice <# "#team cath> > bob hi there"
|
||||
alice <## " report content",
|
||||
(bob </)
|
||||
]
|
||||
|
||||
-- regular messages are still forwarded
|
||||
|
||||
cath #> "#team hey team"
|
||||
alice <# "#team cath> hey team"
|
||||
bob <# "#team cath> hey team [>>]"
|
||||
|
||||
setupGroupForwarding3 :: String -> TestCC -> TestCC -> TestCC -> IO ()
|
||||
setupGroupForwarding3 gName alice bob cath = do
|
||||
createGroup3 gName alice bob cath
|
||||
|
||||
Reference in New Issue
Block a user