From 88f47a455ea14c72c8507bdb6a2e8d2d976a5eea Mon Sep 17 00:00:00 2001 From: Bruno Muniz Azevedo Filho <bruno@elixxir.io> Date: Tue, 16 Aug 2022 19:04:13 -0300 Subject: [PATCH] Finished adding report to group --- .../Controllers/GroupChatController.swift | 40 ++++++++- .../ViewModels/GroupChatViewModel.swift | 80 +++++++++++++++++- .../ViewModel/ChatListViewModel.swift | 6 +- .../Integration/Session/Session+Group.swift | 2 +- Sources/ReportingFeature/Report.swift | 11 ++- .../Resources/report_cert.der | Bin 40 -> 1508 bytes 6 files changed, 128 insertions(+), 11 deletions(-) diff --git a/Sources/ChatFeature/Controllers/GroupChatController.swift b/Sources/ChatFeature/Controllers/GroupChatController.swift index ae732094..9d5e9a67 100644 --- a/Sources/ChatFeature/Controllers/GroupChatController.swift +++ b/Sources/ChatFeature/Controllers/GroupChatController.swift @@ -1,3 +1,4 @@ +import HUD import UIKit import Theme import Models @@ -6,8 +7,10 @@ import Combine import XXModels import Voxophone import ChatLayout +import Integration import DrawerFeature import DifferenceKit +import ReportingFeature import ChatInputFeature import DependencyInjection @@ -19,7 +22,11 @@ typealias OutgoingFailedGroupTextCell = CollectionCell<FlexibleSpace, StackMessa typealias OutgoingFailedGroupReplyCell = CollectionCell<FlexibleSpace, ReplyStackMessageView> public final class GroupChatController: UIViewController { + @Dependency private var hud: HUD + @Dependency private var session: SessionType @Dependency private var coordinator: ChatCoordinating + @Dependency private var makeReportDrawer: MakeReportDrawer + @Dependency private var makeAppScreenshot: MakeAppScreenshot @Dependency private var statusBarController: StatusBarStyleControlling private let members: MembersController @@ -176,6 +183,17 @@ public final class GroupChatController: UIViewController { } }.store(in: &cancellables) + viewModel.hudPublisher + .receive(on: DispatchQueue.main) + .sink { [hud] in hud.update(with: $0) } + .store(in: &cancellables) + + viewModel.reportPopupPublisher + .receive(on: DispatchQueue.main) + .sink { [unowned self] contact in + presentReportDrawer(contact) + }.store(in: &cancellables) + viewModel.messages .receive(on: DispatchQueue.main) .sink { [unowned self] sections in @@ -229,6 +247,19 @@ public final class GroupChatController: UIViewController { coordinator.toMembersList(members, from: self) } + private func presentReportDrawer(_ contact: Contact) { + var config = MakeReportDrawer.Config() + config.onReport = { [weak self] in + guard let self = self else { return } + let screenshot = try! self.makeAppScreenshot() + self.viewModel.report(contact: contact, screenshot: screenshot) { + self.collectionView.reloadData() + } + } + let drawer = makeReportDrawer(config) + coordinator.toDrawer(drawer, from: self) + } + private func makeWaitingRoundDrawer() -> UIViewController { let text = DrawerText( font: Fonts.Mulish.semiBold.font(size: 14.0), @@ -332,8 +363,7 @@ extension GroupChatController: UICollectionViewDataSource { var isSenderBanned = false - if let database = try? DependencyInjection.Container.shared.resolve() as Database, - let sender = try? database.fetchContacts(.init(id: [item.senderId])).first { + if let sender = try? session.dbManager.fetchContacts(.init(id: [item.senderId])).first { isSenderBanned = sender.isBanned } @@ -568,6 +598,10 @@ extension GroupChatController: UICollectionViewDelegate { self?.viewModel.didRequestDelete([item]) } + let report = UIAction(title: Localized.Chat.BubbleMenu.report, state: .off) { [weak self] _ in + self?.viewModel.didRequestReport(item) + } + let retry = UIAction(title: Localized.Chat.BubbleMenu.retry, state: .off) { [weak self] _ in self?.viewModel.retry(item) } @@ -579,7 +613,7 @@ extension GroupChatController: UICollectionViewDelegate { } else if item.status == .sending { menu = UIMenu(title: "", children: [copy]) } else { - menu = UIMenu(title: "", children: [copy, reply, delete]) + menu = UIMenu(title: "", children: [copy, reply, delete, report]) } return menu diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift index ece776ae..e9ca9553 100644 --- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift +++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift @@ -1,10 +1,15 @@ +import HUD import UIKit import Models +import Shared import Combine import XXModels +import Defaults import Foundation import Integration +import ToastFeature import DifferenceKit +import ReportingFeature import DependencyInjection enum GroupChatNavigationRoutes: Equatable { @@ -14,6 +19,18 @@ enum GroupChatNavigationRoutes: Equatable { final class GroupChatViewModel { @Dependency private var session: SessionType + @Dependency private var sendReport: SendReport + @Dependency private var toastController: ToastController + + @KeyObject(.username, defaultValue: nil) var username: String? + + var hudPublisher: AnyPublisher<HUDStatus, Never> { + hudSubject.eraseToAnyPublisher() + } + + var reportPopupPublisher: AnyPublisher<Contact, Never> { + reportPopupSubject.eraseToAnyPublisher() + } var replyPublisher: AnyPublisher<(String, String), Never> { replySubject.eraseToAnyPublisher() @@ -26,11 +43,13 @@ final class GroupChatViewModel { let info: GroupInfo private var stagedReply: Reply? private var cancellables = Set<AnyCancellable>() + private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none) + private let reportPopupSubject = PassthroughSubject<Contact, Never>() private let replySubject = PassthroughSubject<(String, String), Never>() private let routesSubject = PassthroughSubject<GroupChatNavigationRoutes, Never>() var messages: AnyPublisher<[ArraySection<ChatSection, Message>], Never> { - session.dbManager.fetchMessagesPublisher(.init(chat: .group(info.group.id), isSenderBanned: false)) + session.dbManager.fetchMessagesPublisher(.init(chat: .group(info.group.id))) .assertNoFailure() .map { messages -> [ArraySection<ChatSection, Message>] in let groupedByDate = Dictionary(grouping: messages) { domainModel -> Date in @@ -63,6 +82,12 @@ final class GroupChatViewModel { _ = try? session.dbManager.deleteMessages(.init(id: Set(messages.map(\.id)))) } + func didRequestReport(_ message: Message) { + if let contact = try? session.dbManager.fetchContacts(.init(id: [message.senderId])).first { + reportPopupSubject.send(contact) + } + } + func send(_ text: String) { session.send(.init( text: text.trimmingCharacters(in: .whitespacesAndNewlines), @@ -117,4 +142,57 @@ final class GroupChatViewModel { stagedReply = Reply(messageId: networkId, senderId: message.senderId) replySubject.send(getReplyContent(for: networkId)) } + + func report(contact: Contact, screenshot: UIImage, completion: @escaping () -> Void) { + let report = Report( + sender: .init( + userId: contact.id.base64EncodedString(), + username: contact.username! + ), + recipient: .init( + userId: session.myId.base64EncodedString(), + username: username! + ), + type: .group, + screenshot: screenshot.pngData()!, + partyName: info.group.name, + partyBlob: info.group.id.base64EncodedString(), + partyMembers: info.members.map { Report.ReportUser( + userId: $0.id.base64EncodedString(), + username: $0.username ?? "") + } + ) + + hudSubject.send(.on) + sendReport(report) { result in + switch result { + case .failure(let error): + DispatchQueue.main.async { + self.hudSubject.send(.error(.init(with: error))) + } + + case .success(_): + self.blockContact(contact) + DispatchQueue.main.async { + self.hudSubject.send(.none) + self.presentReportConfirmation(contact: contact) + completion() + } + } + } + } + + private func blockContact(_ contact: Contact) { + var contact = contact + contact.isBlocked = true + _ = try? session.dbManager.saveContact(contact) + } + + private func presentReportConfirmation(contact: Contact) { + let name = (contact.nickname ?? contact.username) ?? "the contact" + toastController.enqueueToast(model: .init( + title: "Your report has been sent and \(name) is now blocked.", + leftImage: Asset.requestSentToaster.image + )) + } } diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift index 0e9b4c5e..d3918b04 100644 --- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift +++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift @@ -139,15 +139,11 @@ final class ChatListViewModel { ), groupChatInfoQuery: GroupChatInfo.Query( authStatus: [.participating], - isLeaderBlocked: false, - isLeaderBanned: false, excludeBannedContactsMessages: true ), groupQuery: Group.Query( withMessages: false, - authStatus: [.participating], - isLeaderBlocked: false, - isLeaderBanned: false + authStatus: [.participating] ) )) .assertNoFailure() diff --git a/Sources/Integration/Session/Session+Group.swift b/Sources/Integration/Session/Session+Group.swift index a3cf6896..47652ee9 100644 --- a/Sources/Integration/Session/Session+Group.swift +++ b/Sources/Integration/Session/Session+Group.swift @@ -53,7 +53,7 @@ extension Session { recipientId: nil, groupId: group.id, date: group.createdAt, - status: .received, + status: .sent, isUnread: false, text: welcome, replyMessageId: nil, diff --git a/Sources/ReportingFeature/Report.swift b/Sources/ReportingFeature/Report.swift index 61607f7b..c2032b6a 100644 --- a/Sources/ReportingFeature/Report.swift +++ b/Sources/ReportingFeature/Report.swift @@ -5,18 +5,27 @@ public struct Report: Encodable { sender: ReportUser, recipient: ReportUser, type: ReportType, - screenshot: Data + screenshot: Data, + partyName: String? = nil, + partyBlob: String? = nil, + partyMembers: [ReportUser]? = nil ) { self.sender = sender self.recipient = recipient self.type = type self.screenshot = screenshot + self.partyName = partyName + self.partyBlob = partyBlob + self.partyMembers = partyMembers } public var sender: ReportUser public var recipient: ReportUser public var type: ReportType public var screenshot: Data + public var partyName: String? + public var partyBlob: String? + public var partyMembers: [ReportUser]? } extension Report { diff --git a/Sources/ReportingFeature/Resources/report_cert.der b/Sources/ReportingFeature/Resources/report_cert.der index 978f65098ea8f361f1f369194e24d68d258f4dc2..b040579312ef63ed54d63419ad2955e6160b9666 100644 GIT binary patch literal 1508 zcmXqLVtruH#C&1_GZP~dlSn*g<*UVP%XYo#o1~GO%lm23ew{l8ylk9WZ60mkc^MhG zSs4r(dknb^IN6v(S=fY`LW5x(4jv|FM?)b40gwzk52tfZVo_>teqM>8fB_#!jEjfe zH7B#8BD2U)#6Son!p+0&l3JFUlV6aV3REX-AP5p==HW_(sME{LH<UM!g<H?bC?=4Y zlAD?5fKXr{C(dhRWME-vXlQ0)VQdg3&T9<h0tF!4!RFj1CMDzmWn^VwZerqRFlb`p zVrpVyWSBd#$MIC?jgvQDRK5Lv{hH*=LyQ-*yuBBcoawt#v~gPWYw?10Ub|9u9Iwz? zZh5Lb+{5@+a@pV3lMV{2w>?=_$>nm|Q~b+}7kP{321k4C*>YlGmB;ga?pHVX-Re=3 za=&`zcE^6JnN9rd+`cz|i&{mV3O&5K@P|RtJQ=q8#tpsi19Lx?acKNCSE*GH4A|dv z@KA^Gsq=r1yj9iPIH7{!+}zT&92W1h=eV{9mK6(aQI;~i`d(FHqf%C~Ms&!eTRSUv zew?P`@1phWrv5>$)iGPMITkgiPfBx++-|KT<?6c6ed49v3vTgVm^)kQ_MR{M&b`l< ze{x{O0=eefw{thHF8X}#yyx|d5c7qnC4|3+L~Qk(8S(Dw(Yx8RZY}ua9ewl1mZ`5A z&+7i{vn^?yTA<(@^6!nYgy2Pu-plj#j!*HjnyI^r!+B4ksJf=M`OBR3N{)ix7T$GA zJ<|AC_K?i8Mdz39yAo_zSd<ks{ZEOd?BS4|(lLAPA1gbe(c19E_0#(ZzrZaImeler z>ChLQTlf4|@2B>Y8@p;HCqA)xbgClx&9u1LIy#ygLVo{JUcUdzBT?CX$6523dUky{ zXVv<y%vk*lr<42h-ur&%)^8B}_k3mQg^C;ek?zbsN$%ITPkq&~bJEi%SLIIz3GPe& zGNFE6Mb)fTm-jx}xhAfEO{Uzh*}v_3udLO2&BV;ez_{4Lz|24tm>FaRS@;ZinvgOC zFgpN+`B=nQL`))%Z@jx*)HvQ*s)G4Q?ULu3CQZm`3z%7fX^W8|v-k7`-s18X4k4d- zzAf+cGMdYgoP70|LjA!k)znu)oVh1Cqw3af<k~0aAe=SDN9T<;!@3pg9USWp#I>zh zz^8HhN1t**bE2hqpN@99CdUsG`IYiEXM-YZGv}za-~N_(vOH|&wdKEpnuNO^zK#7l zk58JBp;}*Y{teDW#@eAbUjL|A&#wE8Y1*pCj5ep{UAB7cmu8f;CG6C<sND~*zg*1j zx8>bg>q3$J^X@h8v0l2YUSiGWsW&;yvjz4Gnfff_o56T#>z*Im?>J2Pw@%b~-qk1h z_tvp55ud;M)6|8pABw&Ib!WpGwu2r%P5Zpxe%Y}0Mc}QeK1Yo6Wundgs($#@-yWQL z@4~FPTRsVyE}bg0{Qcz8x+nQhnX~6#EIPzI&+pdpsMrs4=Im~k%&qvoH=R%B?KQ1+ z&$k;t?@K*4!-4P8&9GSJUHOF>GFzG(&Fb`jBqZ^?yMI9P#j0Ho&${fF(~`=5_C4@e z=*@bmOXrupled}au|xdymv23Nd)w?i&pZ~0>gNbrq%}wT!_+-@(}E5yZJd(-$|Ixi zagqDHvWV{Pl9CCgd@}Zii_XecRe!p&@y3@!yhn5vx9i33&Sfn&Ry$(+cV6j!C%>i= zbIINF&p4=@j(NLaX=PhFuhkI~mFV!iV6C_2(>Copq!jz%V{7=<sE0RA=T3X&ualp( N^3sRs{w3Gzqyb}ehFAaq literal 40 vcmazJat-itbaqt;@$d{*aP#zWRS5SC@lbGa4N`D+4GQse^K^C$ag7B4-nt6s -- GitLab