From 9705ae15eaf069e35adf0aa2789b43a7431aaf4c Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 5 Sep 2025 11:38:26 +0000 Subject: [PATCH] ui: fix report archiving (#6239) * kotlin: fix report archivation * ios wip * surrogate reports scope * refactor --- apps/ios/Shared/Model/AppAPITypes.swift | 2 ++ apps/ios/Shared/Model/ChatModel.swift | 28 +++++++++++-------- apps/ios/Shared/Views/Chat/ChatView.swift | 17 ++++++----- .../Views/Chat/Group/GroupChatInfoView.swift | 14 ++++++---- .../Views/Chat/Group/GroupMentions.swift | 2 ++ apps/ios/SimpleX SE/ShareAPI.swift | 2 ++ apps/ios/SimpleXChat/ChatTypes.swift | 21 ++++++++++---- .../simplex/common/views/chat/ChatView.kt | 6 ---- 8 files changed, 57 insertions(+), 35 deletions(-) diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index 2055a0ab99..d37d64646d 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -574,6 +574,8 @@ enum ChatCommand: ChatCmdProtocol { } else { "(_support)" } + case .reports: + "(reports, prohibited)" // can't use surrogate Reports scope } } diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 33b3fd8009..a08f70fd53 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -667,20 +667,24 @@ final class ChatModel: ObservableObject { func getCIItemsModel(_ cInfo: ChatInfo, _ ci: ChatItem) -> ItemsModel? { let cInfoScope = cInfo.groupChatScope() - if let cInfoScope = cInfoScope { - switch cInfoScope { - case .memberSupport: - switch secondaryIM?.secondaryIMFilter { - case .none: - return nil - case let .groupChatScopeContext(groupScopeInfo): - return (cInfo.id == chatId && sameChatScope(cInfoScope, groupScopeInfo.toChatScope())) ? secondaryIM : nil - case let .msgContentTagContext(contentTag): - return (cInfo.id == chatId && ci.isReport && contentTag == .report) ? secondaryIM : nil - } + return if let cInfoScope = cInfoScope { + switch (cInfoScope, secondaryIM?.secondaryIMFilter) { + case let (.memberSupport, .some(.groupChatScopeContext(groupScopeInfo))): + // Chat with member or Chat with admins opened (secondaryIM has .groupChatScopeContext filter), cInfo has matching scope + (cInfo.id == chatId && sameChatScope(cInfoScope, groupScopeInfo.toChatScope())) ? secondaryIM : nil + + case let (.memberSupport, .some(.msgContentTagContext(contentTag))): + // Reports view opened (secondaryIM has .msgContentTagContext(.report) filter), we process event (cInfo has proper .memberSupport scope) + (cInfo.id == chatId && ci.isReport && contentTag == .report) ? secondaryIM : nil + + case let (.reports, .some(.msgContentTagContext(contentTag))): + // Reports view opened (secondaryIM has .msgContentTagContext(.report) filter), we process user action (cInfo has surrogate .reports scope) + (cInfo.id == chatId && ci.isReport && contentTag == .report) ? secondaryIM : nil + default: + nil } } else { - return cInfo.id == chatId ? im : nil + cInfo.id == chatId ? im : nil } } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 83382cfe4f..f5348c9baf 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -234,13 +234,13 @@ struct ChatView: View { .confirmationDialog(selectedChatItems?.count == 1 ? "Archive report?" : "Archive \((selectedChatItems?.count ?? 0)) reports?", isPresented: $showArchiveSelectedReports, titleVisibility: .visible) { Button("For me", role: .destructive) { if let selected = selectedChatItems { - archiveReports(chat.chatInfo, selected.sorted(), false, deletedSelectedMessages) + archiveReports(chat, selected.sorted(), false, deletedSelectedMessages) } } if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, groupInfo.membership.memberActive { Button("For all moderators", role: .destructive) { if let selected = selectedChatItems { - archiveReports(chat.chatInfo, selected.sorted(), true, deletedSelectedMessages) + archiveReports(chat, selected.sorted(), true, deletedSelectedMessages) } } } @@ -598,6 +598,8 @@ struct ChatView: View { } else { textChatToolbar("Chat with admins") } + case .reports: + textChatToolbar("Member reports") } case let .msgContentTagContext(contentTag): switch contentTag { @@ -1913,14 +1915,14 @@ struct ChatView: View { .confirmationDialog(archivingReports?.count == 1 ? "Archive report?" : "Archive \(archivingReports?.count ?? 0) reports?", isPresented: $showArchivingReports, titleVisibility: .visible) { Button("For me", role: .destructive) { if let reports = self.archivingReports { - archiveReports(chat.chatInfo, reports.sorted(), false) + archiveReports(chat, reports.sorted(), false) self.archivingReports = [] } } if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, groupInfo.membership.memberActive { Button("For all moderators", role: .destructive) { if let reports = self.archivingReports { - archiveReports(chat.chatInfo, reports.sorted(), true) + archiveReports(chat, reports.sorted(), true) self.archivingReports = [] } } @@ -2712,13 +2714,13 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe await MainActor.run { for di in deletedItems { if let toItem = di.toChatItem { - _ = ChatModel.shared.upsertChatItem(chat.chatInfo, toItem.chatItem) + _ = ChatModel.shared.upsertChatItem(chatInfo, toItem.chatItem) } else { ChatModel.shared.removeChatItem(chatInfo, di.deletedChatItem.chatItem) } let deletedItem = di.deletedChatItem.chatItem if deletedItem.isActiveReport { - ChatModel.shared.decreaseGroupReportsCounter(chat.chatInfo.id) + ChatModel.shared.decreaseGroupReportsCounter(chatInfo.id) } } if let updatedChatInfo = deletedItems.last?.deletedChatItem.chatInfo { @@ -2733,8 +2735,9 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe } } -func archiveReports(_ chatInfo: ChatInfo, _ itemIds: [Int64], _ forAll: Bool, _ onSuccess: @escaping () async -> Void = {}) { +func archiveReports(_ chat: Chat, _ itemIds: [Int64], _ forAll: Bool, _ onSuccess: @escaping () async -> Void = {}) { if itemIds.count > 0 { + let chatInfo = chat.chatInfo Task { do { let deleted = try await apiDeleteReceivedReports( diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 872e65c7a3..d8929caa3e 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -98,7 +98,7 @@ struct GroupChatInfoView: View { memberSupportButton() } if groupInfo.canModerate { - GroupReportsChatNavLink(chat: chat, scrollToItemId: $scrollToItemId) + GroupReportsChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId) } if groupInfo.membership.memberActive && (groupInfo.membership.memberRole < .moderator || groupInfo.membership.supportChat != nil) { @@ -612,15 +612,19 @@ struct GroupChatInfoView: View { } struct GroupReportsChatNavLink: View { - @EnvironmentObject var chatModel: ChatModel - @EnvironmentObject var theme: AppTheme - @State private var navLinkActive = false @ObservedObject var chat: Chat + @EnvironmentObject var theme: AppTheme + var groupInfo: GroupInfo + @EnvironmentObject var chatModel: ChatModel @Binding var scrollToItemId: ChatItem.ID? + @State private var navLinkActive = false var body: some View { NavigationLink(isActive: $navLinkActive) { - SecondaryChatView(chat: chat, scrollToItemId: $scrollToItemId) + SecondaryChatView( + chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: .reports), chatItems: [], chatStats: ChatStats()), + scrollToItemId: $scrollToItemId + ) } label: { HStack { Label { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift index 440ed5227d..cdbed7fe30 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift @@ -107,6 +107,8 @@ struct GroupMentionsView: View { } else { return member.memberRole >= .moderator } + case .reports: + return false } case .msgContentTagContext: return false diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift index 2b3e8068ae..4dad9d5d15 100644 --- a/apps/ios/SimpleX SE/ShareAPI.swift +++ b/apps/ios/SimpleX SE/ShareAPI.swift @@ -160,6 +160,8 @@ enum SEChatCommand: ChatCmdProtocol { } else { "(_support)" } + case .reports: + "(reports, prohibited)" // can't use surrogate Reports scope } } } diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index febe12de02..2e2f41ec99 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1584,6 +1584,8 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { return nil case .some(.memberSupport(groupMember_: .none)): return nil + case .some(.reports): + return ("can't send messages", nil) } } else if groupInfo.nextConnectPrepared { return nil @@ -1895,26 +1897,35 @@ public struct ChatStats: Decodable, Hashable { public enum GroupChatScope: Decodable { case memberSupport(groupMemberId_: Int64?) + case reports // surrogate scope used for matching new items to opened Reports "chat scope" in UI, this type is not present in backend } public func sameChatScope(_ scope1: GroupChatScope, _ scope2: GroupChatScope) -> Bool { - switch (scope1, scope2) { + return switch (scope1, scope2) { case let (.memberSupport(groupMemberId1_), .memberSupport(groupMemberId2_)): - return groupMemberId1_ == groupMemberId2_ + groupMemberId1_ == groupMemberId2_ + case (.reports, .reports): + true + case (.reports, .memberSupport): + false + case (.memberSupport(groupMemberId_: let groupMemberId_), .reports): + false } } public enum GroupChatScopeInfo: Decodable, Hashable { case memberSupport(groupMember_: GroupMember?) + case reports // surrogate scope used for matching new items to opened Reports "chat scope" in UI, this type is not present in backend public func toChatScope() -> GroupChatScope { - switch self { + return switch self { case let .memberSupport(groupMember_): if let groupMember = groupMember_ { - return .memberSupport(groupMemberId_: groupMember.groupMemberId) + .memberSupport(groupMemberId_: groupMember.groupMemberId) } else { - return .memberSupport(groupMemberId_: nil) + .memberSupport(groupMemberId_: nil) } + case .reports: .reports } } } 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 eb858d8d5f..44a625fd9d 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 @@ -2943,12 +2943,6 @@ private fun archiveReports(chatRh: Long?, chatInfo: ChatInfo, itemIds: List