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