diff --git a/Session/Media Viewing & Editing/SendMediaNavigationController.swift b/Session/Media Viewing & Editing/SendMediaNavigationController.swift index 594cc1fa7..4b5efa4ca 100644 --- a/Session/Media Viewing & Editing/SendMediaNavigationController.swift +++ b/Session/Media Viewing & Editing/SendMediaNavigationController.swift @@ -147,7 +147,8 @@ class SendMediaNavigationController: UINavigationController { } private func didTapCameraModeButton() { - Permissions.requestCameraPermissionIfNeeded(using: dependencies) { [weak self] in + Permissions.requestCameraPermissionIfNeeded(using: dependencies) { [weak self] granted in + guard granted else { return } DispatchQueue.main.async { self?.fadeTo(viewControllers: ((self?.captureViewController).map { [$0] } ?? [])) } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 49e4f9224..eed100801 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -133,6 +133,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } ) + /// Do a little migration on an educated guess of if the user has been asked for local network permission based on calls permission + dependencies[defaults: .standard, key: .hasRequestedLocalNetworkPermission] = dependencies[singleton: .storage, key: .areCallsEnabled] + /// Now that the theme settings have been applied we can complete the migrations self?.completePostMigrationSetup(calledFrom: .finishLaunching) }, @@ -291,7 +294,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // On every activation, clear old temp directories. dependencies[singleton: .fileManager].clearOldTemporaryDirectories() - if dependencies[singleton: .storage, key: .areCallsEnabled] { + if dependencies[singleton: .storage, key: .areCallsEnabled] && dependencies[defaults: .standard, key: .hasRequestedLocalNetworkPermission] { Permissions.checkLocalNetworkPermission(using: dependencies) } } diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 34a5ab270..00ef3b118 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -613,7 +613,8 @@ private final class ScanQRCodePlaceholderVC: UIViewController { } @objc private func requestCameraAccess() { - Permissions.requestCameraPermissionIfNeeded(using: dependencies) { [weak self] in + Permissions.requestCameraPermissionIfNeeded(using: dependencies) { [weak self] granted in + guard granted else { return } self?.joinOpenGroupVC?.handleCameraAccessGranted() } } diff --git a/Session/Settings/PrivacySettingsViewModel.swift b/Session/Settings/PrivacySettingsViewModel.swift index 2b71ca0a3..18169e45f 100644 --- a/Session/Settings/PrivacySettingsViewModel.swift +++ b/Session/Settings/PrivacySettingsViewModel.swift @@ -90,6 +90,7 @@ class PrivacySettingsViewModel: SessionTableViewModel, NavigationItemSource, Nav let typingIndicatorsEnabled: Bool let areLinkPreviewsEnabled: Bool let areCallsEnabled: Bool + let localNetworkPermission: Bool } let title: String = "sessionPrivacy".localized() @@ -102,7 +103,8 @@ class PrivacySettingsViewModel: SessionTableViewModel, NavigationItemSource, Nav areReadReceiptsEnabled: db[.areReadReceiptsEnabled], typingIndicatorsEnabled: db[.typingIndicatorsEnabled], areLinkPreviewsEnabled: db[.areLinkPreviewsEnabled], - areCallsEnabled: db[.areCallsEnabled] + areCallsEnabled: db[.areCallsEnabled], + localNetworkPermission: db[.lastSeenHasLocalNetworkPermission] ) } .mapWithPrevious { [dependencies] previous, current -> [SectionModel] in @@ -132,9 +134,7 @@ class PrivacySettingsViewModel: SessionTableViewModel, NavigationItemSource, Nav confirmStyle: .danger, cancelStyle: .alert_text, onConfirm: { _ in - Permissions.requestMicrophonePermissionIfNeeded(using: dependencies) - Permissions.requestCameraPermissionIfNeeded(using: dependencies) - Permissions.requestLocalNetworkPermissionIfNeeded(using: dependencies) + Permissions.requestPermissionsForCalls(using: dependencies) } ), onTap: { [weak self] in @@ -192,8 +192,8 @@ class PrivacySettingsViewModel: SessionTableViewModel, NavigationItemSource, Nav title: "Local Network", subtitle: "Allow access to local network to facilitate voice and video calls", trailingAccessory: .toggle( - Permissions.localNetwork(using: dependencies) == .granted, - oldValue: Permissions.localNetwork(using: dependencies) == .granted, + current.localNetworkPermission, + oldValue: (previous ?? current).localNetworkPermission, accessibility: Accessibility( identifier: "Local Network Permission - Switch" ) @@ -406,9 +406,7 @@ class PrivacySettingsViewModel: SessionTableViewModel, NavigationItemSource, Nav confirmStyle: .danger, cancelStyle: .alert_text, onConfirm: { [dependencies] _ in - Permissions.requestMicrophonePermissionIfNeeded(using: dependencies) - Permissions.requestCameraPermissionIfNeeded(using: dependencies) - Permissions.requestLocalNetworkPermissionIfNeeded(using: dependencies) + Permissions.requestPermissionsForCalls(using: dependencies) dependencies[singleton: .storage].write { db in try db.setAndUpdateConfig( .areCallsEnabled, diff --git a/Session/Shared/ScanQRCodeScreen.swift b/Session/Shared/ScanQRCodeScreen.swift index 52d533600..18a084a82 100644 --- a/Session/Shared/ScanQRCodeScreen.swift +++ b/Session/Shared/ScanQRCodeScreen.swift @@ -74,7 +74,8 @@ struct ScanQRCodeScreen: View { } private func requestCameraAccess() { - Permissions.requestCameraPermissionIfNeeded(using: dependencies) { + Permissions.requestCameraPermissionIfNeeded(using: dependencies) { granted in + guard granted else { return } hasCameraAccess.toggle() } } diff --git a/Session/Utilities/Permissions.swift b/Session/Utilities/Permissions.swift index 2e28c2582..09f16e8f8 100644 --- a/Session/Utilities/Permissions.swift +++ b/Session/Utilities/Permissions.swift @@ -13,11 +13,11 @@ extension Permissions { @discardableResult public static func requestCameraPermissionIfNeeded( presentingViewController: UIViewController? = nil, using dependencies: Dependencies, - onAuthorized: (() -> Void)? = nil + onAuthorized: ((Bool) -> Void)? = nil ) -> Bool { switch AVCaptureDevice.authorizationStatus(for: .video) { case .authorized: - onAuthorized?() + onAuthorized?(true) return true case .denied, .restricted: @@ -45,8 +45,8 @@ extension Permissions { return false case .notDetermined: - AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in - onAuthorized?() + AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in + onAuthorized?(granted) }) return false @@ -57,6 +57,7 @@ extension Permissions { public static func requestMicrophonePermissionIfNeeded( presentingViewController: UIViewController? = nil, using dependencies: Dependencies, + onAuthorized: ((Bool) -> Void)? = nil, onNotGranted: (() -> Void)? = nil ) { let handlePermissionDenied: () -> Void = { @@ -94,6 +95,7 @@ extension Permissions { onNotGranted?() AVAudioApplication.requestRecordPermission { granted in dependencies[defaults: .appGroup, key: .lastSeenHasMicrophonePermission] = granted + onAuthorized?(granted) } default: break } @@ -105,6 +107,7 @@ extension Permissions { onNotGranted?() AVAudioSession.sharedInstance().requestRecordPermission { granted in dependencies[defaults: .appGroup, key: .lastSeenHasMicrophonePermission] = granted + onAuthorized?(granted) } default: break } @@ -167,7 +170,15 @@ extension Permissions { } } + // MARK: - Local Network Premission + + public static func localNetwork(using dependencies: Dependencies) -> Status { + let status: Bool = dependencies[singleton: .storage, key: .lastSeenHasLocalNetworkPermission] + return status ? .granted : .denied + } + public static func requestLocalNetworkPermissionIfNeeded(using dependencies: Dependencies) { + dependencies[defaults: .standard, key: .hasRequestedLocalNetworkPermission] = true checkLocalNetworkPermission(using: dependencies) } @@ -176,10 +187,14 @@ extension Permissions { do { if try await checkLocalNetworkPermissionWithBonjour() { // Permission is granted, continue to next onboarding step - dependencies[defaults: .appGroup, key: .lastSeenHasLocalNetworkPermission] = true + dependencies[singleton: .storage].writeAsync { db in + db[.lastSeenHasLocalNetworkPermission] = true + } } else { // Permission denied, explain why we need it and show button to open Settings - dependencies[defaults: .appGroup, key: .lastSeenHasLocalNetworkPermission] = false + dependencies[singleton: .storage].writeAsync { db in + db[.lastSeenHasLocalNetworkPermission] = false + } } } catch { // Networking failure, handle error @@ -301,5 +316,30 @@ extension Permissions { browser.cancel() } } + + public static func requestPermissionsForCalls( + presentingViewController: UIViewController? = nil, + using dependencies: Dependencies + ) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + requestMicrophonePermissionIfNeeded( + presentingViewController: presentingViewController, + using: dependencies, + onAuthorized: { _ in + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + requestCameraPermissionIfNeeded( + presentingViewController: presentingViewController, + using: dependencies, + onAuthorized: { _ in + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + requestLocalNetworkPermissionIfNeeded(using: dependencies) + } + } + ) + } + } + ) + } + } } diff --git a/SessionMessagingKit/Utilities/Preferences.swift b/SessionMessagingKit/Utilities/Preferences.swift index b1779a051..99b55ed52 100644 --- a/SessionMessagingKit/Utilities/Preferences.swift +++ b/SessionMessagingKit/Utilities/Preferences.swift @@ -76,6 +76,10 @@ public extension Setting.BoolKey { /// Controls whether developer mode is enabled (this displays a section within the Settings screen which allows manual control of feature flags /// and system settings for better debugging) static let developerModeEnabled: Setting.BoolKey = "developerModeEnabled" + + /// There is no native api to get local network permission, so we need to modify the state and store in database to update UI accordingly. + /// Remove this in the future if Apple provides native api + static let lastSeenHasLocalNetworkPermission: Setting.BoolKey = "lastSeenHasLocalNetworkPermission" } // stringlint:ignore_contents diff --git a/SessionUtilitiesKit/Types/UserDefaultsType.swift b/SessionUtilitiesKit/Types/UserDefaultsType.swift index fec3de4ba..4918be39c 100644 --- a/SessionUtilitiesKit/Types/UserDefaultsType.swift +++ b/SessionUtilitiesKit/Types/UserDefaultsType.swift @@ -110,8 +110,8 @@ public extension UserDefaults.BoolKey { /// Indicates whether we had the microphone permission the last time the app when to the background static let lastSeenHasMicrophonePermission: UserDefaults.BoolKey = "lastSeenHasMicrophonePermission" - /// Indicates whether we had the local network permission - static let lastSeenHasLocalNetworkPermission: UserDefaults.BoolKey = "lastSeenHasLocalNetworkPermission" + /// Indicates whether we had asked for the local network permission + static let hasRequestedLocalNetworkPermission: UserDefaults.BoolKey = "hasRequestedLocalNetworkPermission" } public extension UserDefaults.DateKey { diff --git a/SessionUtilitiesKit/Utilities/Permissions.swift b/SessionUtilitiesKit/Utilities/Permissions.swift index d8f47f763..87e927c99 100644 --- a/SessionUtilitiesKit/Utilities/Permissions.swift +++ b/SessionUtilitiesKit/Utilities/Permissions.swift @@ -50,9 +50,4 @@ public enum Permissions { return .unknown } } - - public static func localNetwork(using dependencies: Dependencies) -> Status { - let status: Bool = dependencies[defaults:.appGroup, key: .lastSeenHasLocalNetworkPermission] - return status ? .granted : .denied - } }