Commit b08fb641 authored by Amirjanyan's avatar Amirjanyan

Merge branch 'settings-permissions' into 'main'

#75 Implement Actions for requesting access to photos, contacts, camera, and microphone

See merge request !30
parents 59438b4e 3b576980
Pipeline #2760 failed with stage
......@@ -39,6 +39,7 @@
54A61E3E265F9EEE005438D0 /* ContextualBottomBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A61E3D265F9EEE005438D0 /* ContextualBottomBar.swift */; };
54D36B92266E6BB6003788F5 /* PubSubController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D36B91266E6BB6003788F5 /* PubSubController.swift */; };
54E1CE492656CFB2009C7029 /* EdgeRecord+Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E1CE482656CFB2009C7029 /* EdgeRecord+Sync.swift */; };
A261AF3E267C9FD000D3B25F /* PermissionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A261AF3D267C9FD000D3B25F /* PermissionsController.swift */; };
C45DBCE31E0F7B5923E3619D /* Pods_Memri.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5485A6E19DEFD43D9AE69C85 /* Pods_Memri.framework */; };
C8A19D83F56B9F41F550915F /* Pods_MemriPodAPITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92BAC70949EBE62C3C1877BD /* Pods_MemriPodAPITests.framework */; };
D714B156257DB23C0027ECF3 /* DatabaseQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714B155257DB23C0027ECF3 /* DatabaseQuery.swift */; };
......@@ -275,6 +276,7 @@
54D36B91266E6BB6003788F5 /* PubSubController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubSubController.swift; sourceTree = "<group>"; };
54E1CE482656CFB2009C7029 /* EdgeRecord+Sync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EdgeRecord+Sync.swift"; sourceTree = "<group>"; };
92BAC70949EBE62C3C1877BD /* Pods_MemriPodAPITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MemriPodAPITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A261AF3D267C9FD000D3B25F /* PermissionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsController.swift; sourceTree = "<group>"; };
A988F1EE8986768124914644 /* Pods-MemriAppTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MemriAppTests.debug.xcconfig"; path = "Target Support Files/Pods-MemriAppTests/Pods-MemriAppTests.debug.xcconfig"; sourceTree = "<group>"; };
CFB6596CA20EDFC2E83E6EAE /* Pods-Memri.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Memri.debug.xcconfig"; path = "Target Support Files/Pods-Memri/Pods-Memri.debug.xcconfig"; sourceTree = "<group>"; };
D714B155257DB23C0027ECF3 /* DatabaseQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseQuery.swift; sourceTree = "<group>"; };
......@@ -1060,6 +1062,7 @@
D7EBBC2A2586D1D800A343E6 /* SceneController.swift */,
D7E89C6725A729D9003AEFD8 /* FileStorageController.swift */,
54254FC426452A890044D689 /* AddressBookController.swift */,
A261AF3D267C9FD000D3B25F /* PermissionsController.swift */,
D7A135B7257B55EC00EBEEF7 /* Database */,
D7A135BB257B55FE00EBEEF7 /* Syncing */,
54D36B90266E6B97003788F5 /* PubSub */,
......@@ -1450,6 +1453,7 @@
D7D9C88425B1B71F009C0E5B /* KeyboardModifiers.swift in Sources */,
D744F72F25BACA960002C8B7 /* MemriTextEditor_UIKit.swift in Sources */,
545A24B62639EE2F00E0CC12 /* Data.swift in Sources */,
A261AF3E267C9FD000D3B25F /* PermissionsController.swift in Sources */,
D7E89C9325A72C52003AEFD8 /* CVU_Shape.swift in Sources */,
D714B156257DB23C0027ECF3 /* DatabaseQuery.swift in Sources */,
545FD29726205F1200F23F46 /* Dictionary.swift in Sources */,
......
......@@ -16,8 +16,10 @@ struct AddressBookController {
static var syncing = false
static func start() {
appStateObservation = AppController.shared.statePublisher.sink {_ in
if AppController.shared.state == .authenticated {
appStateObservation = AppController.shared.permissionController.contactAuthorization
.filter { $0 }
.sink { state in
if AppController.shared.state == .authenticated && state {
sync {
DispatchQueue.main.async {
syncing = false
......
......@@ -18,6 +18,7 @@ class AppController: ObservableObject {
let syncController: SyncController
let cvuController: CVUController
let pubsubController: PubSubController
let permissionController: PermissionsController
private init() {
let databaseController = DatabaseController()
......@@ -25,6 +26,7 @@ class AppController: ObservableObject {
self.syncController = SyncController(databaseController: databaseController)
self.pubsubController = PubSubController()
self.cvuController = CVUController()
self.permissionController = PermissionsController()
}
enum AppState {
......
//
// PermissionsController.swift
// Memri
import AVFoundation
import Contacts
import Combine
import CoreLocation
import Foundation
import Photos
import SwiftUI
class PermissionsController: NSObject, ObservableObject {
lazy var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
locationManager.desiredAccuracy = .greatestFiniteMagnitude
locationManager.delegate = self
return locationManager
}()
static let shared = PermissionsController()
let contactAuthorization: PassthroughSubject<Bool, Never> = PassthroughSubject()
var authorizationCompletionBlock:((Bool)->())? = {_ in}
var locationPermission: Bool = CLLocationManager().authorizationStatus == .authorizedAlways || CLLocationManager().authorizationStatus == .authorizedWhenInUse
var libraryPermission: Bool = PHPhotoLibrary.authorizationStatus() == .authorized
var cameraPermission: Bool = AVCaptureDevice.authorizationStatus(for: AVMediaType.video) == AVAuthorizationStatus.authorized
var microPhonePermission: Bool = AVAudioSession.sharedInstance().recordPermission == .granted
var contactsPermission: Bool = CNContactStore.authorizationStatus(for: .contacts) == .authorized || CNContactStore.authorizationStatus(for: .contacts) == .restricted
override init() {
super.init()
contactAuthorization.send(CNContactStore.authorizationStatus(for: .contacts) == .authorized || CNContactStore.authorizationStatus(for: .contacts) == .restricted )
}
func requestLocation(completion: ((Bool) -> ())? = nil) {
if self.locationManager.authorizationStatus == .notDetermined {
self.locationManager.requestWhenInUseAuthorization()
authorizationCompletionBlock = { isGranted in
completion?(isGranted)
}
} else if self.locationManager.authorizationStatus != .authorizedAlways, self.locationManager.authorizationStatus != .authorizedWhenInUse {
self.navigateToSystemSettings()
}
}
func requestPhotos(_ completion: ((Bool) -> ())? = nil) {
if PHPhotoLibrary.authorizationStatus() == .notDetermined {
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
completion?(status == .authorized)
}
} else if PHPhotoLibrary.authorizationStatus() != .authorized,
PHPhotoLibrary.authorizationStatus() != .limited
{
self.navigateToSystemSettings()
}
}
func requestCamera(_ completion: ((Bool) -> ())? = nil) {
if AVCaptureDevice.authorizationStatus(for: AVMediaType.video) == AVAuthorizationStatus.notDetermined {
AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { status in
completion?(status)
})
} else if AVCaptureDevice.authorizationStatus(for: AVMediaType.video) != AVAuthorizationStatus.authorized {
self.navigateToSystemSettings()
}
}
func requestMicrophone(_ completion: ((Bool) -> ())? = nil) {
if AVAudioSession.sharedInstance().recordPermission == .undetermined {
AVAudioSession.sharedInstance().requestRecordPermission { status in
completion?(status)
}
} else if AVAudioSession.sharedInstance().recordPermission != .granted {
self.navigateToSystemSettings()
}
}
func requestContacts(_ completion: ((Bool) -> ())? = nil) {
if CNContactStore.authorizationStatus(for: .contacts) == .notDetermined {
CNContactStore().requestAccess(for: .contacts) { state, error in
guard error == nil else {
self.contactAuthorization.send(false)
completion?(false)
return
}
self.contactAuthorization.send(state)
completion?(state)
}
} else if CNContactStore.authorizationStatus(for: .contacts) != .authorized && CNContactStore.authorizationStatus(for: .contacts) != .restricted {
self.navigateToSystemSettings()
}
}
func navigateToSystemSettings() {
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
UIApplication.shared.open(settingsUrl, completionHandler: { _ in })
}
}
}
extension PermissionsController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.locationPermission = status == .authorizedAlways || status == .authorizedWhenInUse
self.authorizationCompletionBlock?(status == .authorizedAlways || status == .authorizedWhenInUse)
}
}
......@@ -69,5 +69,15 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>$(PRODUCT_NAME) wants Location Access.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>$(PRODUCT_NAME) wants Location Access.</string>
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) wants to access camera</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>$(PRODUCT_NAME) wants to access library</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) wants to access microphone</string>
</dict>
</plist>
......@@ -17,6 +17,11 @@ struct SettingsPane: View {
@AppStorage("/user/general/gui/showDateAgo") var showTimeAgo: Bool = false
@AppStorage("/device/debug/autoShowErrorConsole") var autoShowErrorConsole: Bool = false
@AppStorage("/device/debug/autoReloadCVU") var autoReloadCVU: Bool = true
@AppStorage("/device/permission/location") var isLocationEnabled: Bool = AppController.shared.permissionController.locationPermission
@AppStorage("/device/permission/photos") var isPhotosAvailable: Bool = AppController.shared.permissionController.libraryPermission
@AppStorage("/device/permission/camera") var isCameraAvailable: Bool = AppController.shared.permissionController.cameraPermission
@AppStorage("/device/permission/microphone") var isMicrophoneAvailable: Bool = AppController.shared.permissionController.microPhonePermission
@AppStorage("/device/permission/contacts") var isContactsAvailable: Bool = AppController.shared.permissionController.contactsPermission
// .keyboardType(.numberPad)
var body: some View {
......@@ -117,6 +122,70 @@ struct SettingsPane: View {
}) {
Text("Debug")
}
NavigationLink(destination: Form {
Section(
header: Text("Permissions")
) {
Toggle(isOn: $isLocationEnabled) {
Text("Location Access")
}
.onChange(of: isLocationEnabled) { value in
if value {
AppController.shared.permissionController.requestLocation { status in
isLocationEnabled = status
}
}
}
Toggle(isOn: $isPhotosAvailable) {
Text("Photos Access")
}
.onChange(of: isPhotosAvailable) { value in
if value {
AppController.shared.permissionController.requestPhotos { status in
isPhotosAvailable = status
}
}
}
Toggle(isOn: $isCameraAvailable) {
Text("Camera Access")
}
.onChange(of: isCameraAvailable) { value in
if value {
AppController.shared.permissionController.requestCamera { status in
isCameraAvailable = status
}
}
}
Toggle(isOn: $isMicrophoneAvailable) {
Text("Microphone Access")
}
.onChange(of: isMicrophoneAvailable) { value in
if value {
AppController.shared.permissionController.requestMicrophone { status in
isMicrophoneAvailable = status
}
}
}
Toggle(isOn: $isContactsAvailable) {
Text("Contacts Access")
}
.onChange(of: isContactsAvailable) { value in
if value {
AppController.shared.permissionController.requestContacts { status in
isContactsAvailable = status
}
}
}
}
}) {
Text("Permissions")
}
}
.navigationBarItems(leading:
Button(action: { self.presentationMode.wrappedValue.dismiss() }) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment