From 93518f3d8a88e4813c90228cacaaf0f0530f1aac Mon Sep 17 00:00:00 2001
From: Bruno Muniz Azevedo Filho <bruno@elixxir.io>
Date: Tue, 23 Aug 2022 17:37:27 -0300
Subject: [PATCH] Using ReportStatus functors

---
 Sources/App/AppDelegate.swift                 | 14 -------
 Sources/App/DependencyRegistrator.swift       |  1 +
 .../Controllers/GroupChatController.swift     |  3 +-
 .../Controllers/SingleChatController.swift    |  3 +-
 .../ViewModels/GroupChatViewModel.swift       |  4 +-
 .../ViewModels/SingleChatViewModel.swift      |  1 -
 .../ViewModel/ChatListViewModel.swift         | 22 +++++------
 .../ViewModels/ContactListViewModel.swift     | 16 ++++----
 .../ViewModels/CreateGroupViewModel.swift     |  7 ++--
 Sources/Defaults/KeyObject.swift              |  3 --
 Sources/Integration/Session/Session.swift     |  5 ++-
 Sources/LaunchFeature/LaunchViewModel.swift   |  6 +--
 .../ViewModels/MenuViewModel.swift            | 11 +++---
 Sources/PushFeature/PushHandler.swift         |  6 ++-
 .../ReportingFeature/ReportingStatus.swift    | 38 +++++++++++++++++++
 .../ReportingStatusIsEnabled.swift            | 38 +++++++++++++++++++
 .../ReportingStatusIsOptional.swift           | 24 ++++++++++++
 .../RequestsReceivedViewModel.swift           | 11 +++---
 .../ViewModels/RequestsSentViewModel.swift    |  8 ++--
 .../ViewModels/SearchLeftViewModel.swift      | 12 +++---
 .../ViewModels/SearchRightViewModel.swift     |  8 ++--
 .../SettingsAdvancedViewModel.swift           | 11 +++---
 22 files changed, 171 insertions(+), 81 deletions(-)
 create mode 100644 Sources/ReportingFeature/ReportingStatus.swift
 create mode 100644 Sources/ReportingFeature/ReportingStatusIsEnabled.swift
 create mode 100644 Sources/ReportingFeature/ReportingStatusIsOptional.swift

diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift
index e9c2b744..4143a5f2 100644
--- a/Sources/App/AppDelegate.swift
+++ b/Sources/App/AppDelegate.swift
@@ -23,7 +23,6 @@ public class AppDelegate: UIResponder, UIApplicationDelegate {
     @KeyObject(.hideAppList, defaultValue: false) var hideAppList: Bool
     @KeyObject(.recordingLogs, defaultValue: true) var recordingLogs: Bool
     @KeyObject(.crashReporting, defaultValue: true) var isCrashReportingEnabled: Bool
-    @KeyObject(.isReportingOptional, defaultValue: false) var isReportingOptional: Bool
 
     var calledStopNetwork = false
     var forceFailedPendingMessages = false
@@ -49,8 +48,6 @@ public class AppDelegate: UIResponder, UIApplicationDelegate {
         crashReporter.configure()
         crashReporter.setEnabled(isCrashReportingEnabled)
 
-        isReportingOptional = isReportingFeatureOptionalOnInfoPlist()
-
         UNUserNotificationCenter.current().delegate = self
 
         let window = Window()
@@ -178,17 +175,6 @@ func getUsernameFromInvitationDeepLink(_ url: URL) -> String? {
     return nil
 }
 
-func isReportingFeatureOptionalOnInfoPlist() -> Bool {
-    struct Root : Decodable {
-        let isReportingOptional : Bool
-    }
-
-    let url = Bundle.main.url(forResource: "Info", withExtension: "plist")!
-    let data = try! Data(contentsOf: url)
-    let result = try! PropertyListDecoder().decode(Root.self, from: data)
-    return result.isReportingOptional
-}
-
 // MARK: Notifications
 
 extension AppDelegate: UNUserNotificationCenterDelegate {
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
index 1071b031..c5d0b830 100644
--- a/Sources/App/DependencyRegistrator.swift
+++ b/Sources/App/DependencyRegistrator.swift
@@ -81,6 +81,7 @@ struct DependencyRegistrator {
         container.register(XXLogger.live())
         container.register(CrashReporter.live)
         container.register(VersionChecker.live())
+        container.register(ReportingStatus.live())
 
         container.register(XXNetwork<BindingsClient>() as XXNetworking)
         container.register(NetworkMonitor() as NetworkMonitoring)
diff --git a/Sources/ChatFeature/Controllers/GroupChatController.swift b/Sources/ChatFeature/Controllers/GroupChatController.swift
index 689bc36c..5cae4a06 100644
--- a/Sources/ChatFeature/Controllers/GroupChatController.swift
+++ b/Sources/ChatFeature/Controllers/GroupChatController.swift
@@ -25,6 +25,7 @@ public final class GroupChatController: UIViewController {
     @Dependency private var hud: HUD
     @Dependency private var session: SessionType
     @Dependency private var coordinator: ChatCoordinating
+    @Dependency private var reportingStatus: ReportingStatus
     @Dependency private var makeReportDrawer: MakeReportDrawer
     @Dependency private var makeAppScreenshot: MakeAppScreenshot
     @Dependency private var statusBarController: StatusBarStyleControlling
@@ -611,7 +612,7 @@ extension GroupChatController: UICollectionViewDelegate {
             } else {
                 children = [copy, reply, delete]
 
-                if self.viewModel.isReportingEnabled {
+                if self.reportingStatus.isEnabled() {
                     children.append(report)
                 }
             }
diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift
index 08aa3944..91aa66ec 100644
--- a/Sources/ChatFeature/Controllers/SingleChatController.swift
+++ b/Sources/ChatFeature/Controllers/SingleChatController.swift
@@ -29,6 +29,7 @@ public final class SingleChatController: UIViewController {
     @Dependency private var logger: XXLogger
     @Dependency private var voxophone: Voxophone
     @Dependency private var coordinator: ChatCoordinating
+    @Dependency private var reportingStatus: ReportingStatus
     @Dependency private var makeReportDrawer: MakeReportDrawer
     @Dependency private var makeAppScreenshot: MakeAppScreenshot
     @Dependency private var statusBarController: StatusBarStyleControlling
@@ -662,7 +663,7 @@ extension SingleChatController: UICollectionViewDelegate {
                 ActionFactory.build(from: item, action: .delete, closure: self.viewModel.didRequestDeleteSingle(_:))
             ]
 
-            if self.viewModel.isReportingEnabled {
+            if self.reportingStatus.isEnabled() {
                 children.append(
                     ActionFactory.build(from: item, action: .report, closure: self.viewModel.didRequestReport(_:))
                 )
diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
index 42026974..1dbce8a8 100644
--- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
@@ -20,10 +20,10 @@ enum GroupChatNavigationRoutes: Equatable {
 final class GroupChatViewModel {
     @Dependency private var session: SessionType
     @Dependency private var sendReport: SendReport
+    @Dependency private var reportingStatus: ReportingStatus
     @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()
@@ -131,7 +131,7 @@ final class GroupChatViewModel {
 
         var name = (contact.nickname ?? contact.username) ?? "Fetching username..."
 
-        if contact.isBlocked, isReportingEnabled {
+        if contact.isBlocked, reportingStatus.isEnabled() {
             name = "\(name) (Blocked)"
         }
 
diff --git a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
index e08b4df2..8ba933ad 100644
--- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
@@ -33,7 +33,6 @@ 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 266d095d..af0a8401 100644
--- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
+++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
@@ -6,6 +6,7 @@ import Combine
 import XXModels
 import Defaults
 import Integration
+import ReportingFeature
 import DependencyInjection
 
 enum SearchSection {
@@ -23,8 +24,7 @@ typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchIte
 
 final class ChatListViewModel {
     @Dependency private var session: SessionType
-
-    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
+    @Dependency private var reportingStatus: ReportingStatus
 
     var isOnline: AnyPublisher<Bool, Never> {
         session.isOnline
@@ -41,8 +41,8 @@ final class ChatListViewModel {
     var recentsPublisher: AnyPublisher<RecentsSnapshot, Never> {
         let query = Contact.Query(
             isRecent: true,
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false : nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         return session.dbManager.fetchContactsPublisher(query)
@@ -58,8 +58,8 @@ final class ChatListViewModel {
 
     var searchPublisher: AnyPublisher<SearchSnapshot, Never> {
         let contactsQuery = Contact.Query(
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false : nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         let contactsStream = session.dbManager
@@ -127,8 +127,8 @@ final class ChatListViewModel {
                 .verificationFailed,
                 .verificationInProgress
             ],
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false : nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         return Publishers.CombineLatest(
@@ -150,12 +150,12 @@ final class ChatListViewModel {
                 contactChatInfoQuery: .init(
                     userId: session.myId,
                     authStatus: [.friend],
-                    isBlocked: isReportingEnabled ? false : nil,
-                    isBanned: isReportingEnabled ? false : nil
+                    isBlocked: reportingStatus.isEnabled() ? false : nil,
+                    isBanned: reportingStatus.isEnabled() ? false : nil
                 ),
                 groupChatInfoQuery: GroupChatInfo.Query(
                     authStatus: [.participating],
-                    excludeBannedContactsMessages: isReportingEnabled
+                    excludeBannedContactsMessages: reportingStatus.isEnabled()
                 ),
                 groupQuery: Group.Query(
                     withMessages: false,
diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
index 35711caf..830172f3 100644
--- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
@@ -3,18 +3,18 @@ import Combine
 import XXModels
 import Defaults
 import Integration
+import ReportingFeature
 import DependencyInjection
 
 final class ContactListViewModel {
     @Dependency private var session: SessionType
-
-    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
+    @Dependency private var reportingStatus: ReportingStatus
 
     var contacts: AnyPublisher<[Contact], Never> {
         let query = Contact.Query(
             authStatus: [.friend],
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false: nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? false: nil
         )
 
         return session.dbManager.fetchContactsPublisher(query)
@@ -26,8 +26,8 @@ final class ContactListViewModel {
     var requestCount: AnyPublisher<Int, Never> {
         let groupQuery = Group.Query(
             authStatus: [.pending],
-            isLeaderBlocked: isReportingEnabled ? false : nil,
-            isLeaderBanned: isReportingEnabled ? false : nil
+            isLeaderBlocked: reportingStatus.isEnabled() ? false : nil,
+            isLeaderBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         let contactsQuery = Contact.Query(
@@ -38,8 +38,8 @@ final class ContactListViewModel {
                 .verificationFailed,
                 .verificationInProgress
             ],
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false : nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         return Publishers.CombineLatest(
diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
index 19cadc6e..55540753 100644
--- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
@@ -5,15 +5,16 @@ import Combine
 import XXModels
 import Defaults
 import Integration
+import ReportingFeature
 import DependencyInjection
 
 final class CreateGroupViewModel {
     @KeyObject(.username, defaultValue: "") var username: String
-    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
 
     // MARK: Injected
 
     @Dependency private var session: SessionType
+    @Dependency private var reportingStatus: ReportingStatus
 
     // MARK: Properties
 
@@ -45,8 +46,8 @@ final class CreateGroupViewModel {
     init() {
         let query = Contact.Query(
             authStatus: [.friend],
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false : nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         session.dbManager.fetchContactsPublisher(query)
diff --git a/Sources/Defaults/KeyObject.swift b/Sources/Defaults/KeyObject.swift
index ddf8aa3a..0ade4e83 100644
--- a/Sources/Defaults/KeyObject.swift
+++ b/Sources/Defaults/KeyObject.swift
@@ -41,9 +41,6 @@ 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 02836d48..49e1c60e 100644
--- a/Sources/Integration/Session/Session.swift
+++ b/Sources/Integration/Session/Session.swift
@@ -10,6 +10,7 @@ import Foundation
 import ToastFeature
 import BackupFeature
 import NetworkMonitor
+import ReportingFeature
 import DependencyInjection
 import XXLegacyDatabaseMigrator
 
@@ -47,10 +48,10 @@ 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
+    @Dependency var reportingStatus: ReportingStatus
     @Dependency var networkMonitor: NetworkMonitoring
 
     public let client: Client
@@ -462,7 +463,7 @@ public final class Session: SessionType {
                 }
 
                 if let contact = try! dbManager.fetchContacts(.init(id: [request.0.leaderId])).first {
-                    if isReportingEnabled, (contact.isBlocked || contact.isBanned) {
+                    if reportingStatus.isEnabled(), (contact.isBlocked || contact.isBanned) {
                         return
                     }
                 }
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index 81b240eb..73fa6722 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -36,13 +36,13 @@ final class LaunchViewModel {
     @Dependency private var keychainHandler: KeychainHandling
     @Dependency private var permissionHandler: PermissionHandling
     @Dependency private var fetchBannedList: FetchBannedList
+    @Dependency private var reportingStatus: ReportingStatus
     @Dependency private var processBannedList: ProcessBannedList
     @Dependency private var toastController: ToastController
     @Dependency private var session: SessionType
 
     @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()
@@ -141,8 +141,8 @@ final class LaunchViewModel {
     func getContactWith(userId: Data) -> Contact? {
         let query = Contact.Query(
             id: [userId],
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false : nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         return try! session.dbManager.fetchContacts(query).first
diff --git a/Sources/MenuFeature/ViewModels/MenuViewModel.swift b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
index 62be1c13..72fbe071 100644
--- a/Sources/MenuFeature/ViewModels/MenuViewModel.swift
+++ b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
@@ -3,20 +3,21 @@ import XXModels
 import Defaults
 import Foundation
 import Integration
+import ReportingFeature
 import DependencyInjection
 
 final class MenuViewModel {
     @Dependency private var session: SessionType
+    @Dependency private var reportingStatus: ReportingStatus
 
     @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: isReportingEnabled ? false : nil,
-            isLeaderBanned: isReportingEnabled ? false : nil
+            isLeaderBlocked: reportingStatus.isEnabled() ? false : nil,
+            isLeaderBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         let contactsQuery = Contact.Query(
@@ -27,8 +28,8 @@ final class MenuViewModel {
                 .verificationFailed,
                 .verificationInProgress
             ],
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false : nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         return Publishers.CombineLatest(
diff --git a/Sources/PushFeature/PushHandler.swift b/Sources/PushFeature/PushHandler.swift
index 8f29b148..b090664f 100644
--- a/Sources/PushFeature/PushHandler.swift
+++ b/Sources/PushFeature/PushHandler.swift
@@ -3,6 +3,7 @@ import Models
 import Defaults
 import XXModels
 import Integration
+import ReportingFeature
 import DependencyInjection
 
 public final class PushHandler: PushHandling {
@@ -11,8 +12,9 @@ public final class PushHandler: PushHandling {
         static let usernamesSetting = "isShowingUsernames"
     }
 
+    @Dependency var reportingStatus: ReportingStatus
+
     @KeyObject(.pushNotifications, defaultValue: false) var isPushEnabled: Bool
-    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
 
     let requestAuth: RequestAuth
     public static let defaultRequestAuth = UNUserNotificationCenter.current().requestAuthorization
@@ -109,7 +111,7 @@ public final class PushHandler: PushHandling {
                 return ($0.type.unknownSenderContent!, $0)
             }
 
-            if isReportingEnabled, (contact.isBlocked || contact.isBanned) {
+            if reportingStatus.isEnabled(), (contact.isBlocked || contact.isBanned) {
                 return nil
             }
 
diff --git a/Sources/ReportingFeature/ReportingStatus.swift b/Sources/ReportingFeature/ReportingStatus.swift
new file mode 100644
index 00000000..2ed407a8
--- /dev/null
+++ b/Sources/ReportingFeature/ReportingStatus.swift
@@ -0,0 +1,38 @@
+import Combine
+
+public struct ReportingStatus {
+    public var isOptional: () -> Bool
+    public var isEnabled: () -> Bool
+    public var isEnabledPublisher: () -> AnyPublisher<Bool, Never>
+    public var enable: (Bool) -> Void
+}
+
+extension ReportingStatus {
+    public static func live(
+        isOptional: ReportingStatusIsOptional = .live(),
+        isEnabled: ReportingStatusIsEnabled = .live()
+    ) -> ReportingStatus {
+        ReportingStatus(
+            isOptional: {
+                isOptional.get()
+            },
+            isEnabled: {
+                if isOptional.get() == false {
+                    return true
+                }
+
+                return isEnabled.get()
+            },
+            isEnabledPublisher: {
+                if isOptional.get() == false {
+                    return Just(true).eraseToAnyPublisher()
+                }
+
+                return isEnabled.publisher()
+            },
+            enable: { enabled in
+                isEnabled.set(enabled)
+            }
+        )
+    }
+}
diff --git a/Sources/ReportingFeature/ReportingStatusIsEnabled.swift b/Sources/ReportingFeature/ReportingStatusIsEnabled.swift
new file mode 100644
index 00000000..32f23fcb
--- /dev/null
+++ b/Sources/ReportingFeature/ReportingStatusIsEnabled.swift
@@ -0,0 +1,38 @@
+import Combine
+import Foundation
+
+public struct ReportingStatusIsEnabled {
+    public var get: () -> Bool
+    public var set: (Bool) -> Void
+    public var publisher: () -> AnyPublisher<Bool, Never>
+}
+
+extension ReportingStatusIsEnabled {
+    public static func live(
+        userDefaults: UserDefaults = .standard
+    ) -> ReportingStatusIsEnabled {
+        ReportingStatusIsEnabled(
+            get: {
+                userDefaults.isReportingEnabled
+            },
+            set: { enabled in
+                userDefaults.isReportingEnabled = enabled
+            },
+            publisher: {
+                userDefaults.publisher(for: \.isReportingEnabled).eraseToAnyPublisher()
+            }
+        )
+    }
+}
+
+private extension UserDefaults {
+    static let isReportingEnabledKey = "isReportingEnabled"
+
+    @objc var isReportingEnabled: Bool {
+        get {
+            bool(forKey: Self.isReportingEnabledKey)
+        } set {
+            set(newValue, forKey: Self.isReportingEnabledKey)
+        }
+    }
+}
diff --git a/Sources/ReportingFeature/ReportingStatusIsOptional.swift b/Sources/ReportingFeature/ReportingStatusIsOptional.swift
new file mode 100644
index 00000000..e0cc6591
--- /dev/null
+++ b/Sources/ReportingFeature/ReportingStatusIsOptional.swift
@@ -0,0 +1,24 @@
+import Foundation
+
+public struct ReportingStatusIsOptional {
+    public var get: () -> Bool
+}
+
+extension ReportingStatusIsOptional {
+    public static func live(
+        plist url: URL = Bundle.main.url(forResource: "Info", withExtension: "plist")!
+    ) -> ReportingStatusIsOptional {
+        ReportingStatusIsOptional {
+            struct Plist: Decodable {
+                let isReportingOptional: Bool
+            }
+
+            guard let data = try? Data(contentsOf: url),
+                  let infoPlist = try? PropertyListDecoder().decode(Plist.self, from: data) else {
+                return true
+            }
+
+            return infoPlist.isReportingOptional
+        }
+    }
+}
diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
index 650777d1..3f4be9cb 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
@@ -7,6 +7,7 @@ import Defaults
 import XXModels
 import Integration
 import DrawerFeature
+import ReportingFeature
 import CombineSchedulers
 import DependencyInjection
 
@@ -18,8 +19,8 @@ struct RequestReceived: Hashable, Equatable {
 
 final class RequestsReceivedViewModel {
     @Dependency private var session: SessionType
+    @Dependency private var reportingStatus: ReportingStatus
 
-    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
     @KeyObject(.isShowingHiddenRequests, defaultValue: false) var isShowingHiddenRequests: Bool
 
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
@@ -58,8 +59,8 @@ final class RequestsReceivedViewModel {
                 .hidden,
                 .pending
             ],
-            isLeaderBlocked: isReportingEnabled ? false : nil,
-            isLeaderBanned: isReportingEnabled ? false : nil
+            isLeaderBlocked: reportingStatus.isEnabled() ? false : nil,
+            isLeaderBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         let contactsQuery = Contact.Query(
@@ -70,8 +71,8 @@ final class RequestsReceivedViewModel {
                 .verificationFailed,
                 .verificationInProgress
             ],
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false : nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         let groupStream = session.dbManager.fetchGroupsPublisher(groupsQuery).assertNoFailure()
diff --git a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
index fa902162..40964837 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
@@ -7,6 +7,7 @@ import Defaults
 import XXModels
 import Integration
 import ToastFeature
+import ReportingFeature
 import CombineSchedulers
 import DependencyInjection
 
@@ -17,10 +18,9 @@ struct RequestSent: Hashable, Equatable {
 
 final class RequestsSentViewModel {
     @Dependency private var session: SessionType
+    @Dependency private var reportingStatus: ReportingStatus
     @Dependency private var toastController: ToastController
 
-    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
-
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
     }
@@ -41,8 +41,8 @@ final class RequestsSentViewModel {
                 .requested,
                 .requesting
             ],
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false : nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? false : nil
         )
 
         session.dbManager.fetchContactsPublisher(query)
diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
index f7dfe937..6f708401 100644
--- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
@@ -7,6 +7,7 @@ import Defaults
 import Countries
 import Integration
 import NetworkMonitor
+import ReportingFeature
 import DependencyInjection
 
 typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>
@@ -20,10 +21,9 @@ struct SearchLeftViewState {
 
 final class SearchLeftViewModel {
     @Dependency var session: SessionType
+    @Dependency var reportingStatus: ReportingStatus
     @Dependency var networkMonitor: NetworkMonitoring
 
-    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
-
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
     }
@@ -153,10 +153,10 @@ final class SearchLeftViewModel {
                 user.authStatus = contact.authStatus
             }
 
-            if user.authStatus != .friend, !isReportingEnabled {
+            if user.authStatus != .friend, !reportingStatus.isEnabled() {
                 snapshot.appendSections([.stranger])
                 snapshot.appendItems([.stranger(user)], toSection: .stranger)
-            } else if user.authStatus != .friend, isReportingEnabled, !user.isBanned, !user.isBlocked {
+            } else if user.authStatus != .friend, reportingStatus.isEnabled(), !user.isBanned, !user.isBlocked {
                 snapshot.appendSections([.stranger])
                 snapshot.appendItems([.stranger(user)], toSection: .stranger)
             }
@@ -165,8 +165,8 @@ final class SearchLeftViewModel {
         let localsQuery = Contact.Query(
             text: stateSubject.value.input,
             authStatus: [.friend],
-            isBlocked: isReportingEnabled ? false : nil,
-            isBanned: isReportingEnabled ? false : nil
+            isBlocked: reportingStatus.isEnabled() ? false : nil,
+            isBanned: reportingStatus.isEnabled() ? 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 e70b2d36..db977528 100644
--- a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
@@ -5,6 +5,7 @@ import Defaults
 import Foundation
 import Permissions
 import Integration
+import ReportingFeature
 import DependencyInjection
 
 enum ScanningStatus: Equatable {
@@ -24,8 +25,7 @@ enum ScanningError: Equatable {
 final class SearchRightViewModel {
     @Dependency var session: SessionType
     @Dependency var permissions: PermissionHandling
-
-    @KeyObject(.isReportingEnabled, defaultValue: true) var isReportingEnabled: Bool
+    @Dependency var reportingStatus: ReportingStatus
 
     var foundPublisher: AnyPublisher<Contact, Never> {
         foundSubject.eraseToAnyPublisher()
@@ -81,12 +81,12 @@ final class SearchRightViewModel {
         /// that we already have
         ///
         if let alreadyContact = try? session.dbManager.fetchContacts(.init(id: [userId])).first {
-            if alreadyContact.isBlocked, isReportingEnabled {
+            if alreadyContact.isBlocked, reportingStatus.isEnabled() {
                 statusSubject.send(.failed(.unknown("You previously blocked this user.")))
                 return
             }
 
-            if alreadyContact.isBanned, isReportingEnabled {
+            if alreadyContact.isBanned, reportingStatus.isEnabled() {
                 statusSubject.send(.failed(.unknown("This user was banned.")))
                 return
             }
diff --git a/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift b/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift
index c88dba70..a0f199a9 100644
--- a/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift
+++ b/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift
@@ -3,6 +3,7 @@ import XXLogger
 import Defaults
 import Foundation
 import CrashReporting
+import ReportingFeature
 import DependencyInjection
 
 struct AdvancedViewState: Equatable {
@@ -17,13 +18,11 @@ 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: false) var isReportingOptional: Bool
-
     private let isShowingUsernamesKey = "isShowingUsernames"
 
     @Dependency private var logger: XXLogger
     @Dependency private var crashReporter: CrashReporter
+    @Dependency private var reportingStatus: ReportingStatus
 
     var sharePublisher: AnyPublisher<URL, Never> { shareRelay.eraseToAnyPublisher() }
     private let shareRelay = PassthroughSubject<URL, Never>()
@@ -34,8 +33,8 @@ final class SettingsAdvancedViewModel {
     func loadCachedSettings() {
         stateRelay.value.isRecordingLogs = isRecordingLogs
         stateRelay.value.isCrashReporting = isCrashReporting
-        stateRelay.value.isReportingEnabled = isReportingEnabled
-        stateRelay.value.isReportingOptional = isReportingOptional
+        stateRelay.value.isReportingEnabled = reportingStatus.isEnabled()
+        stateRelay.value.isReportingOptional = reportingStatus.isOptional()
 
         guard let defaults = UserDefaults(suiteName: "group.elixxir.messenger") else {
             print("^^^ Couldn't access user defaults in the app group container \(#file):\(#line)")
@@ -84,7 +83,7 @@ final class SettingsAdvancedViewModel {
     }
 
     func didToggleReporting() {
-        isReportingEnabled.toggle()
+        reportingStatus.enable(reportingStatus.isEnabled())
         stateRelay.value.isReportingEnabled.toggle()
     }
 }
-- 
GitLab