From bc604febbc6bc76a21e895a67b106c74ce12f602 Mon Sep 17 00:00:00 2001
From: Bruno Muniz Azevedo Filho <bruno@elixxir.io>
Date: Mon, 22 Aug 2022 23:58:35 -0300
Subject: [PATCH] Add user defaults flag for reporting enabled and reporting
 optional

---
 .../Controllers/GroupChatController.swift     | 14 ++++---
 .../Controllers/SingleChatController.swift    | 15 +++++--
 .../ViewModels/GroupChatViewModel.swift       |  3 +-
 .../ViewModels/SingleChatViewModel.swift      |  1 +
 .../ViewModel/ChatListViewModel.swift         | 40 +++++++++++++------
 .../ViewModels/ContactListViewModel.swift     | 34 +++++++++++-----
 .../ViewModels/CreateGroupViewModel.swift     |  9 ++++-
 Sources/Defaults/KeyObject.swift              |  3 ++
 Sources/Integration/Session/Session.swift     |  3 +-
 Sources/LaunchFeature/LaunchViewModel.swift   |  8 +++-
 .../ViewModels/MenuViewModel.swift            | 26 ++++++++----
 Sources/PushFeature/PushHandler.swift         |  3 +-
 .../RequestsReceivedViewModel.swift           | 11 ++++-
 .../ViewModels/RequestsSentViewModel.swift    | 15 +++++--
 .../ViewModels/SearchLeftViewModel.swift      | 12 ++++--
 .../ViewModels/SearchRightViewModel.swift     |  7 +++-
 .../SettingsAdvancedController.swift          | 12 ++++++
 .../SettingsAdvancedViewModel.swift           | 12 ++++++
 .../Views/SettingsAdvancedView.swift          | 17 ++++++--
 Sources/Shared/AutoGenerated/Strings.swift    |  6 +++
 .../Resources/en.lproj/Localizable.strings    |  4 ++
 21 files changed, 197 insertions(+), 58 deletions(-)

diff --git a/Sources/ChatFeature/Controllers/GroupChatController.swift b/Sources/ChatFeature/Controllers/GroupChatController.swift
index c6b0d6d2..689bc36c 100644
--- a/Sources/ChatFeature/Controllers/GroupChatController.swift
+++ b/Sources/ChatFeature/Controllers/GroupChatController.swift
@@ -602,17 +602,21 @@ extension GroupChatController: UICollectionViewDelegate {
                 self?.viewModel.retry(item)
             }
 
-            let menu: UIMenu
+            var children = [UIAction]()
 
             if item.status == .sendingFailed {
-                menu = UIMenu(title: "", children: [copy, retry, delete])
+                children = [copy, retry, delete]
             } else if item.status == .sending {
-                menu = UIMenu(title: "", children: [copy])
+                children = [copy]
             } else {
-                menu = UIMenu(title: "", children: [copy, reply, delete, report])
+                children = [copy, reply, delete]
+
+                if self.viewModel.isReportingEnabled {
+                    children.append(report)
+                }
             }
 
-            return menu
+            return UIMenu(title: "", children: children)
         }
     }
 }
diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift
index 03b182e5..08aa3944 100644
--- a/Sources/ChatFeature/Controllers/SingleChatController.swift
+++ b/Sources/ChatFeature/Controllers/SingleChatController.swift
@@ -655,13 +655,20 @@ extension SingleChatController: UICollectionViewDelegate {
             guard let self = self else { return nil }
             let item = self.sections[indexPath.section].elements[indexPath.item]
 
-            return UIMenu(title: "", children: [
+            var children = [
                 ActionFactory.build(from: item, action: .copy, closure: self.viewModel.didRequestCopy(_:)),
                 ActionFactory.build(from: item, action: .retry, closure: self.viewModel.didRequestRetry(_:)),
                 ActionFactory.build(from: item, action: .reply, closure: self.viewModel.didRequestReply(_:)),
-                ActionFactory.build(from: item, action: .delete, closure: self.viewModel.didRequestDeleteSingle(_:)),
-                ActionFactory.build(from: item, action: .report, closure: self.viewModel.didRequestReport(_:))
-            ].compactMap { $0 })
+                ActionFactory.build(from: item, action: .delete, closure: self.viewModel.didRequestDeleteSingle(_:))
+            ]
+
+            if self.viewModel.isReportingEnabled {
+                children.append(
+                    ActionFactory.build(from: item, action: .report, closure: self.viewModel.didRequestReport(_:))
+                )
+            }
+
+            return UIMenu(title: "", children: children.compactMap { $0 })
         }
     }
 
diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
index e9ca9553..42026974 100644
--- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
@@ -23,6 +23,7 @@ final class GroupChatViewModel {
     @Dependency private var toastController: ToastController
 
     @KeyObject(.username, defaultValue: nil) var username: String?
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
 
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
@@ -130,7 +131,7 @@ final class GroupChatViewModel {
 
         var name = (contact.nickname ?? contact.username) ?? "Fetching username..."
 
-        if contact.isBlocked {
+        if contact.isBlocked, isReportingEnabled {
             name = "\(name) (Blocked)"
         }
 
diff --git a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
index 8ba933ad..e08b4df2 100644
--- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
@@ -33,6 +33,7 @@ final class SingleChatViewModel: NSObject {
     @Dependency private var sendReport: SendReport
 
     @KeyObject(.username, defaultValue: nil) var username: String?
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
 
     var contact: Contact { contactSubject.value }
     private var stagedReply: Reply?
diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
index d3918b04..266d095d 100644
--- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
+++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
@@ -24,6 +24,8 @@ typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchIte
 final class ChatListViewModel {
     @Dependency private var session: SessionType
 
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
+
     var isOnline: AnyPublisher<Bool, Never> {
         session.isOnline
     }
@@ -37,7 +39,12 @@ final class ChatListViewModel {
     }
 
     var recentsPublisher: AnyPublisher<RecentsSnapshot, Never> {
-        let query = Contact.Query(isRecent: true, isBlocked: false, isBanned: false)
+        let query = Contact.Query(
+            isRecent: true,
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false : nil
+        )
+
         return session.dbManager.fetchContactsPublisher(query)
             .assertNoFailure()
             .map {
@@ -50,8 +57,13 @@ final class ChatListViewModel {
     }
 
     var searchPublisher: AnyPublisher<SearchSnapshot, Never> {
+        let contactsQuery = Contact.Query(
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false : nil
+        )
+
         let contactsStream = session.dbManager
-            .fetchContactsPublisher(.init(isBlocked: false, isBanned: false))
+            .fetchContactsPublisher(contactsQuery)
             .assertNoFailure()
             .map { $0.filter { $0.id != self.session.myId }}
 
@@ -107,13 +119,17 @@ final class ChatListViewModel {
 
     var badgeCountPublisher: AnyPublisher<Int, Never> {
         let groupQuery = Group.Query(authStatus: [.pending])
-        let contactsQuery = Contact.Query(authStatus: [
-            .verified,
-            .confirming,
-            .confirmationFailed,
-            .verificationFailed,
-            .verificationInProgress
-        ], isBlocked: false, isBanned: false)
+        let contactsQuery = Contact.Query(
+            authStatus: [
+                .verified,
+                .confirming,
+                .confirmationFailed,
+                .verificationFailed,
+                .verificationInProgress
+            ],
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false : nil
+        )
 
         return Publishers.CombineLatest(
             session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
@@ -134,12 +150,12 @@ final class ChatListViewModel {
                 contactChatInfoQuery: .init(
                     userId: session.myId,
                     authStatus: [.friend],
-                    isBlocked: false,
-                    isBanned: false
+                    isBlocked: isReportingEnabled ? false : nil,
+                    isBanned: isReportingEnabled ? false : nil
                 ),
                 groupChatInfoQuery: GroupChatInfo.Query(
                     authStatus: [.participating],
-                    excludeBannedContactsMessages: true
+                    excludeBannedContactsMessages: isReportingEnabled
                 ),
                 groupQuery: Group.Query(
                     withMessages: false,
diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
index d0915822..35711caf 100644
--- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
@@ -1,14 +1,21 @@
 import Models
 import Combine
 import XXModels
+import Defaults
 import Integration
 import DependencyInjection
 
 final class ContactListViewModel {
     @Dependency private var session: SessionType
 
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
+
     var contacts: AnyPublisher<[Contact], Never> {
-        let query = Contact.Query(authStatus: [.friend], isBlocked: false, isBanned: false)
+        let query = Contact.Query(
+            authStatus: [.friend],
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false: nil
+        )
 
         return session.dbManager.fetchContactsPublisher(query)
             .assertNoFailure()
@@ -17,14 +24,23 @@ final class ContactListViewModel {
     }
 
     var requestCount: AnyPublisher<Int, Never> {
-        let groupQuery = Group.Query(authStatus: [.pending], isLeaderBlocked: false, isLeaderBanned: false)
-        let contactsQuery = Contact.Query(authStatus: [
-            .verified,
-            .confirming,
-            .confirmationFailed,
-            .verificationFailed,
-            .verificationInProgress
-        ], isBlocked: false, isBanned: false)
+        let groupQuery = Group.Query(
+            authStatus: [.pending],
+            isLeaderBlocked: isReportingEnabled ? false : nil,
+            isLeaderBanned: isReportingEnabled ? false : nil
+        )
+
+        let contactsQuery = Contact.Query(
+            authStatus: [
+                .verified,
+                .confirming,
+                .confirmationFailed,
+                .verificationFailed,
+                .verificationInProgress
+            ],
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false : nil
+        )
 
         return Publishers.CombineLatest(
             session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
index 668b396b..19cadc6e 100644
--- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
@@ -9,6 +9,7 @@ import DependencyInjection
 
 final class CreateGroupViewModel {
     @KeyObject(.username, defaultValue: "") var username: String
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
 
     // MARK: Injected
 
@@ -42,7 +43,13 @@ final class CreateGroupViewModel {
     // MARK: Lifecycle
 
     init() {
-        session.dbManager.fetchContactsPublisher(.init(authStatus: [.friend], isBlocked: false, isBanned: false))
+        let query = Contact.Query(
+            authStatus: [.friend],
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false : nil
+        )
+
+        session.dbManager.fetchContactsPublisher(query)
             .assertNoFailure()
             .map { $0.filter { $0.id != self.session.myId }}
             .map { $0.sorted(by: { $0.username! < $1.username! })}
diff --git a/Sources/Defaults/KeyObject.swift b/Sources/Defaults/KeyObject.swift
index 0ade4e83..ddf8aa3a 100644
--- a/Sources/Defaults/KeyObject.swift
+++ b/Sources/Defaults/KeyObject.swift
@@ -41,6 +41,9 @@ public enum Key: String {
 
     case dummyTrafficOn
     case askedDummyTrafficOnce
+
+    case isReportingEnabled
+    case isReportingOptional
 }
 
 public struct KeyObjectStore {
diff --git a/Sources/Integration/Session/Session.swift b/Sources/Integration/Session/Session.swift
index 317a4bae..02836d48 100644
--- a/Sources/Integration/Session/Session.swift
+++ b/Sources/Integration/Session/Session.swift
@@ -47,6 +47,7 @@ public final class Session: SessionType {
     @KeyObject(.icognitoKeyboard, defaultValue: false) var icognitoKeyboard: Bool
     @KeyObject(.pushNotifications, defaultValue: false) var pushNotifications: Bool
     @KeyObject(.inappnotifications, defaultValue: true) var inappnotifications: Bool
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
 
     @Dependency var backupService: BackupService
     @Dependency var toastController: ToastController
@@ -461,7 +462,7 @@ public final class Session: SessionType {
                 }
 
                 if let contact = try! dbManager.fetchContacts(.init(id: [request.0.leaderId])).first {
-                    if contact.isBanned || contact.isBlocked {
+                    if isReportingEnabled, (contact.isBlocked || contact.isBanned) {
                         return
                     }
                 }
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index 399a87a8..81b240eb 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -42,6 +42,7 @@ final class LaunchViewModel {
 
     @KeyObject(.username, defaultValue: nil) var username: String?
     @KeyObject(.biometrics, defaultValue: false) var isBiometricsOn: Bool
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
 
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
@@ -138,7 +139,12 @@ final class LaunchViewModel {
     }
 
     func getContactWith(userId: Data) -> Contact? {
-        let query = Contact.Query(id: [userId], isBlocked: false, isBanned: false)
+        let query = Contact.Query(
+            id: [userId],
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false : nil
+        )
+
         return try! session.dbManager.fetchContacts(query).first
     }
 
diff --git a/Sources/MenuFeature/ViewModels/MenuViewModel.swift b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
index 35dfd9ec..62be1c13 100644
--- a/Sources/MenuFeature/ViewModels/MenuViewModel.swift
+++ b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
@@ -10,16 +10,26 @@ final class MenuViewModel {
 
     @KeyObject(.avatar, defaultValue: nil) var avatar: Data?
     @KeyObject(.username, defaultValue: "") var username: String
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
 
     var requestCount: AnyPublisher<Int, Never> {
-        let groupQuery = Group.Query(authStatus: [.pending], isLeaderBlocked: false, isLeaderBanned: false)
-        let contactsQuery = Contact.Query(authStatus: [
-            .verified,
-            .confirming,
-            .confirmationFailed,
-            .verificationFailed,
-            .verificationInProgress
-        ], isBlocked: false, isBanned: false)
+        let groupQuery = Group.Query(
+            authStatus: [.pending],
+            isLeaderBlocked: isReportingEnabled ? false : nil,
+            isLeaderBanned: isReportingEnabled ? false : nil
+        )
+
+        let contactsQuery = Contact.Query(
+            authStatus: [
+                .verified,
+                .confirming,
+                .confirmationFailed,
+                .verificationFailed,
+                .verificationInProgress
+            ],
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false : nil
+        )
 
         return Publishers.CombineLatest(
             session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
diff --git a/Sources/PushFeature/PushHandler.swift b/Sources/PushFeature/PushHandler.swift
index 9afec926..8f29b148 100644
--- a/Sources/PushFeature/PushHandler.swift
+++ b/Sources/PushFeature/PushHandler.swift
@@ -12,6 +12,7 @@ public final class PushHandler: PushHandling {
     }
 
     @KeyObject(.pushNotifications, defaultValue: false) var isPushEnabled: Bool
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
 
     let requestAuth: RequestAuth
     public static let defaultRequestAuth = UNUserNotificationCenter.current().requestAuthorization
@@ -108,7 +109,7 @@ public final class PushHandler: PushHandling {
                 return ($0.type.unknownSenderContent!, $0)
             }
 
-            if contact.isBlocked || contact.isBanned {
+            if isReportingEnabled, (contact.isBlocked || contact.isBanned) {
                 return nil
             }
 
diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
index 5da72d12..650777d1 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
@@ -19,6 +19,7 @@ struct RequestReceived: Hashable, Equatable {
 final class RequestsReceivedViewModel {
     @Dependency private var session: SessionType
 
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
     @KeyObject(.isShowingHiddenRequests, defaultValue: false) var isShowingHiddenRequests: Bool
 
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
@@ -56,7 +57,10 @@ final class RequestsReceivedViewModel {
             authStatus: [
                 .hidden,
                 .pending
-            ], isLeaderBlocked: false, isLeaderBanned: false)
+            ],
+            isLeaderBlocked: isReportingEnabled ? false : nil,
+            isLeaderBanned: isReportingEnabled ? false : nil
+        )
 
         let contactsQuery = Contact.Query(
             authStatus: [
@@ -65,7 +69,10 @@ final class RequestsReceivedViewModel {
                 .verified,
                 .verificationFailed,
                 .verificationInProgress
-            ], isBlocked: false, isBanned: false)
+            ],
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false : nil
+        )
 
         let groupStream = session.dbManager.fetchGroupsPublisher(groupsQuery).assertNoFailure()
         let contactsStream = session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure()
diff --git a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
index 26a6f485..fa902162 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
@@ -3,6 +3,7 @@ import UIKit
 import Models
 import Shared
 import Combine
+import Defaults
 import XXModels
 import Integration
 import ToastFeature
@@ -18,6 +19,8 @@ final class RequestsSentViewModel {
     @Dependency private var session: SessionType
     @Dependency private var toastController: ToastController
 
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
+
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
     }
@@ -33,10 +36,14 @@ final class RequestsSentViewModel {
     var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
 
     init() {
-        let query = Contact.Query(authStatus: [
-            .requested,
-            .requesting
-        ], isBlocked: false, isBanned: false)
+        let query = Contact.Query(
+            authStatus: [
+                .requested,
+                .requesting
+            ],
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false : nil
+        )
 
         session.dbManager.fetchContactsPublisher(query)
             .assertNoFailure()
diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
index d5ea157d..f7dfe937 100644
--- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
@@ -3,6 +3,7 @@ import UIKit
 import Shared
 import Combine
 import XXModels
+import Defaults
 import Countries
 import Integration
 import NetworkMonitor
@@ -21,6 +22,8 @@ final class SearchLeftViewModel {
     @Dependency var session: SessionType
     @Dependency var networkMonitor: NetworkMonitoring
 
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
+
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
     }
@@ -150,7 +153,10 @@ final class SearchLeftViewModel {
                 user.authStatus = contact.authStatus
             }
 
-            if user.authStatus != .friend, !user.isBanned, !user.isBlocked {
+            if user.authStatus != .friend, !isReportingEnabled {
+                snapshot.appendSections([.stranger])
+                snapshot.appendItems([.stranger(user)], toSection: .stranger)
+            } else if user.authStatus != .friend, isReportingEnabled, !user.isBanned, !user.isBlocked {
                 snapshot.appendSections([.stranger])
                 snapshot.appendItems([.stranger(user)], toSection: .stranger)
             }
@@ -159,8 +165,8 @@ final class SearchLeftViewModel {
         let localsQuery = Contact.Query(
             text: stateSubject.value.input,
             authStatus: [.friend],
-            isBlocked: false,
-            isBanned: false
+            isBlocked: isReportingEnabled ? false : nil,
+            isBanned: isReportingEnabled ? false : nil
         )
 
         if let locals = try? session.dbManager.fetchContacts(localsQuery),
diff --git a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
index 148db4ca..e70b2d36 100644
--- a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
@@ -1,6 +1,7 @@
 import Shared
 import Combine
 import XXModels
+import Defaults
 import Foundation
 import Permissions
 import Integration
@@ -24,6 +25,8 @@ final class SearchRightViewModel {
     @Dependency var session: SessionType
     @Dependency var permissions: PermissionHandling
 
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
+
     var foundPublisher: AnyPublisher<Contact, Never> {
         foundSubject.eraseToAnyPublisher()
     }
@@ -78,12 +81,12 @@ final class SearchRightViewModel {
         /// that we already have
         ///
         if let alreadyContact = try? session.dbManager.fetchContacts(.init(id: [userId])).first {
-            if alreadyContact.isBlocked {
+            if alreadyContact.isBlocked, isReportingEnabled {
                 statusSubject.send(.failed(.unknown("You previously blocked this user.")))
                 return
             }
 
-            if alreadyContact.isBanned {
+            if alreadyContact.isBanned, isReportingEnabled {
                 statusSubject.send(.failed(.unknown("This user was banned.")))
                 return
             }
diff --git a/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift b/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift
index a7d92763..16d474d8 100644
--- a/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift
+++ b/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift
@@ -61,17 +61,29 @@ public final class SettingsAdvancedController: UIViewController {
             .sink { [weak viewModel] in viewModel?.didToggleCrashReporting() }
             .store(in: &cancellables)
 
+        screenView.reportingSwitcher.switcherView
+            .publisher(for: .valueChanged)
+            .sink { [weak viewModel] in viewModel?.didToggleReporting() }
+            .store(in: &cancellables)
+
         viewModel.sharePublisher
             .receive(on: DispatchQueue.main)
             .sink { [unowned self] in coordinator.toActivityController(with: [$0], from: self) }
             .store(in: &cancellables)
 
+        viewModel.state
+            .removeDuplicates()
+            .map(\.isReportingOptional)
+            .sink { [unowned self] in screenView.reportingSwitcher.isHidden = !$0 }
+            .store(in: &cancellables)
+
         viewModel.state
             .removeDuplicates()
             .sink { [unowned self] state in
                 screenView.logRecordingSwitcher.switcherView.setOn(state.isRecordingLogs, animated: true)
                 screenView.crashReportingSwitcher.switcherView.setOn(state.isCrashReporting, animated: true)
                 screenView.showUsernamesSwitcher.switcherView.setOn(state.isShowingUsernames, animated: true)
+                screenView.reportingSwitcher.switcherView.setOn(state.isReportingEnabled, animated: true)
             }.store(in: &cancellables)
     }
 }
diff --git a/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift b/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift
index 87170244..60e7f5e7 100644
--- a/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift
+++ b/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift
@@ -9,12 +9,17 @@ struct AdvancedViewState: Equatable {
     var isRecordingLogs = false
     var isCrashReporting = false
     var isShowingUsernames = false
+    var isReportingEnabled = false
+    var isReportingOptional = false
 }
 
 final class SettingsAdvancedViewModel {
     @KeyObject(.recordingLogs, defaultValue: true) var isRecordingLogs: Bool
     @KeyObject(.crashReporting, defaultValue: true) var isCrashReporting: Bool
 
+    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
+    @KeyObject(.isReportingOptional, defaultValue: true) var isReportingOptional: Bool
+
     private let isShowingUsernamesKey = "isShowingUsernames"
 
     @Dependency private var logger: XXLogger
@@ -29,6 +34,8 @@ final class SettingsAdvancedViewModel {
     func loadCachedSettings() {
         stateRelay.value.isRecordingLogs = isRecordingLogs
         stateRelay.value.isCrashReporting = isCrashReporting
+        stateRelay.value.isReportingEnabled = isReportingEnabled
+        stateRelay.value.isReportingOptional = isReportingOptional
 
         guard let defaults = UserDefaults(suiteName: "group.elixxir.messenger") else {
             print("^^^ Couldn't access user defaults in the app group container \(#file):\(#line)")
@@ -75,4 +82,9 @@ final class SettingsAdvancedViewModel {
         stateRelay.value.isCrashReporting.toggle()
         crashReporter.setEnabled(isCrashReporting)
     }
+
+    func didToggleReporting() {
+        isReportingEnabled.toggle()
+        stateRelay.value.isReportingEnabled.toggle()
+    }
 }
diff --git a/Sources/SettingsFeature/Views/SettingsAdvancedView.swift b/Sources/SettingsFeature/Views/SettingsAdvancedView.swift
index fb3df7c7..0e5e5d89 100644
--- a/Sources/SettingsFeature/Views/SettingsAdvancedView.swift
+++ b/Sources/SettingsFeature/Views/SettingsAdvancedView.swift
@@ -7,6 +7,7 @@ final class SettingsAdvancedView: UIView {
     let logRecordingSwitcher = SettingsSwitcher()
     let crashReportingSwitcher = SettingsSwitcher()
     let showUsernamesSwitcher = SettingsSwitcher()
+    let reportingSwitcher = SettingsSwitcher()
 
     init() {
         super.init(frame: .zero)
@@ -33,21 +34,29 @@ final class SettingsAdvancedView: UIView {
             icon: Asset.settingsCrash.image
         )
 
+        reportingSwitcher.set(
+            title: Localized.Settings.Advanced.Reporting.title,
+            text: Localized.Settings.Advanced.Reporting.description,
+            icon: Asset.settingsCrash.image
+        )
+
         stackView.axis = .vertical
         stackView.addArrangedSubview(logRecordingSwitcher)
         stackView.addArrangedSubview(crashReportingSwitcher)
         stackView.addArrangedSubview(showUsernamesSwitcher)
+        stackView.addArrangedSubview(reportingSwitcher)
 
         stackView.setCustomSpacing(20, after: logRecordingSwitcher)
         stackView.setCustomSpacing(10, after: crashReportingSwitcher)
         stackView.setCustomSpacing(10, after: showUsernamesSwitcher)
+        stackView.setCustomSpacing(10, after: reportingSwitcher)
 
         addSubview(stackView)
 
-        stackView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(24)
-            make.left.equalToSuperview().offset(16)
-            make.right.equalToSuperview().offset(-16)
+        stackView.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(24)
+            $0.left.equalToSuperview().offset(16)
+            $0.right.equalToSuperview().offset(-16)
         }
     }
 
diff --git a/Sources/Shared/AutoGenerated/Strings.swift b/Sources/Shared/AutoGenerated/Strings.swift
index afcdff24..975cacad 100644
--- a/Sources/Shared/AutoGenerated/Strings.swift
+++ b/Sources/Shared/AutoGenerated/Strings.swift
@@ -1110,6 +1110,12 @@ public enum Localized {
         /// Record logs
         public static let title = Localized.tr("Localizable", "settings.advanced.logs.title")
       }
+      public enum Reporting {
+        /// Allows you to report users sending innapropriate content
+        public static let description = Localized.tr("Localizable", "settings.advanced.reporting.description")
+        /// Enable user reporting feature
+        public static let title = Localized.tr("Localizable", "settings.advanced.reporting.title")
+      }
       public enum ShowUsername {
         /// Allow us to show a more detailed push notification
         public static let description = Localized.tr("Localizable", "settings.advanced.showUsername.description")
diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/Shared/Resources/en.lproj/Localizable.strings
index 6b61f437..24d260d8 100644
--- a/Sources/Shared/Resources/en.lproj/Localizable.strings
+++ b/Sources/Shared/Resources/en.lproj/Localizable.strings
@@ -630,6 +630,10 @@
 = "Enable crash reporting";
 "settings.advanced.crashes.description"
 = "Automatically sends anonymous reports containing crash data";
+"settings.advanced.reporting.title"
+= "Enable user reporting feature";
+"settings.advanced.reporting.description"
+= "Allows you to report users sending innapropriate content";
 "settings.advanced.accountBackup.title"
 = "Account Backup";
 
-- 
GitLab