Skip to content

Commit 81da16d

Browse files
authored
Merge pull request #39 from jevonmao/develop
Merge 'develop' features into 'main' for version 2.1.4 release
2 parents 5e1b8dd + 284b4dd commit 81da16d

26 files changed

+710
-382
lines changed

Sources/PermissionsSwiftUI/Model/JMPermissionModel.swift

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// MockHealthManager.swift.swift
3+
//
4+
//
5+
// Created by Jevon Mao on 2/25/21.
6+
//
7+
8+
import Foundation
9+
import HealthKit
10+
11+
protocol HealthManager {
12+
func authorizationStatus(for type: HKObjectType) -> HKAuthorizationStatus
13+
func requestAuthorization(toShare typesToShare: Set<HKSampleType>?, read typesToRead: Set<HKObjectType>?, completion: @escaping (Bool, Error?) -> Void)
14+
static func isHealthDataAvailable() -> Bool
15+
16+
}
17+
extension HKHealthStore: HealthManager {}
18+
19+
class MockHealthManager: HealthManager {
20+
enum AuthorizationStatus: CaseIterable {
21+
case notDetermined, authorized, denied, mixedAuthorized, mixedDenied
22+
}
23+
var authStatusOverride: AuthorizationStatus = .notDetermined
24+
var requestSuccessOverride: Bool = true
25+
static var healthDataAvailableOverride: Bool = true
26+
27+
var lastStatus: HKAuthorizationStatus = .notDetermined
28+
func authorizationStatus(for type: HKObjectType) -> HKAuthorizationStatus {
29+
switch authStatusOverride {
30+
case .authorized:
31+
return .sharingAuthorized
32+
case .notDetermined:
33+
return .notDetermined
34+
case .denied:
35+
return .sharingDenied
36+
case .mixedAuthorized:
37+
return getCorrectPermission()
38+
case .mixedDenied:
39+
return getCorrectPermission()
40+
}
41+
42+
}
43+
44+
func getCorrectPermission() -> HKAuthorizationStatus {
45+
var keyStatus: HKAuthorizationStatus = .sharingAuthorized
46+
if self.authStatusOverride == .mixedDenied {
47+
keyStatus = .sharingDenied
48+
}
49+
if lastStatus == .notDetermined {
50+
self.lastStatus = keyStatus
51+
return keyStatus
52+
}
53+
else {
54+
self.lastStatus = .notDetermined
55+
return .notDetermined
56+
}
57+
}
58+
func requestAuthorization(toShare typesToShare: Set<HKSampleType>?, read typesToRead: Set<HKObjectType>?, completion: @escaping (Bool, Error?) -> Void) {
59+
let healthDataAvailable = MockHealthManager.healthDataAvailableOverride
60+
if requestSuccessOverride {
61+
completion(requestSuccessOverride, healthDataAvailable ? nil : fatalError("MockHealthManager - health data is not available"))
62+
}
63+
else {
64+
completion(false, NSError(domain: "", code: 0, userInfo: nil))
65+
}
66+
}
67+
68+
static func isHealthDataAvailable() -> Bool {
69+
healthDataAvailableOverride
70+
}
71+
72+
73+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//
2+
// JMHealthPermissionManager.swift
3+
//
4+
//
5+
// Created by Jevon Mao on 2/10/21.
6+
//
7+
8+
import Foundation
9+
import HealthKit
10+
11+
class JMHealthPermissionManager: PermissionManager{
12+
13+
typealias authorizationStatus = HKAuthorizationStatus
14+
typealias permissionManagerInstance = JMHealthPermissionManager
15+
typealias CountComparison = (Int, Int)
16+
17+
let healthStore: HealthManager
18+
static let shared: PermissionManager = JMHealthPermissionManager()
19+
20+
init(healthManager: HealthManager = HKHealthStore()) {
21+
self.healthStore = healthManager
22+
}
23+
//Get the health permission from stored permissions array
24+
var healthPermission: HKAccess? {
25+
get {
26+
//Search and get health permission from all permissions
27+
let health = PermissionStore.shared.permissions.first(where: {
28+
if case .health = $0{
29+
return true
30+
}
31+
return false
32+
})
33+
//Get the associated value of health permission
34+
if case .health(let permissionCategories) = health {
35+
return permissionCategories
36+
}
37+
return nil
38+
}
39+
40+
}
41+
var authorizationStatus: AuthorizationStatus{
42+
get {
43+
//Count to track total # of permissions allowed and denied each
44+
var allowDenyCount: CountComparison = (authorized: 0, denied: 0)
45+
var status: AuthorizationStatus {
46+
//Set to notDetermined if all permissions are not determined
47+
if allowDenyCount.0 == 0 && allowDenyCount.1 == 0 {
48+
return .notDetermined
49+
}
50+
//Set to authorized if majority are authorized
51+
if allowDenyCount.0 > allowDenyCount.1 {
52+
return .authorized
53+
}
54+
//Set to denied if majority are denied, or equal # of allowed and denied
55+
return .denied
56+
}
57+
58+
/**
59+
- Note: From Apple Developer Documentation: "to help prevent possible leaks of sensitive health information, your app cannot determine whether or not a user has granted permission to read data. If you are not given permission, it simply appears as if there is no data of the requested type in the HealthKit store."
60+
*/
61+
62+
var readPermissions = healthPermission?.readPermissions ?? []
63+
var writePermissions = healthPermission?.writePermissions ?? []
64+
//Map the authorization status, remove allowed and denied permissions from array.
65+
//Increase allowDenyCount as needed.
66+
mapPermissionAuthorizationStatus(for: &readPermissions, forCount: &allowDenyCount)
67+
mapPermissionAuthorizationStatus(for: &writePermissions, forCount: &allowDenyCount)
68+
return status
69+
}
70+
71+
}
72+
func mapPermissionAuthorizationStatus(for permissions: inout Set<HKSampleType>,
73+
forCount allowDenyCount: inout CountComparison) {
74+
for sampleType in permissions {
75+
switch healthStore.authorizationStatus(for: sampleType){
76+
case .sharingAuthorized:
77+
permissions.remove(sampleType)
78+
allowDenyCount.0 += 1
79+
case .sharingDenied:
80+
permissions.remove(sampleType)
81+
allowDenyCount.1 += 1
82+
default:
83+
()
84+
}
85+
}
86+
}
87+
func requestPermission(_ completion: @escaping (Bool) -> Void) {
88+
guard type(of: healthStore).isHealthDataAvailable() else {
89+
print("PermissionsSwiftUI - Health data is not available")
90+
completion(false)
91+
return
92+
}
93+
healthStore.requestAuthorization(toShare: Set(healthPermission?.readPermissions ?? []),
94+
read: Set(healthPermission?.writePermissions ?? [])) { authorized, error in
95+
guard error == nil else{
96+
print("PermissionSwiftUI - \(error!)")
97+
completion(false)
98+
return
99+
}
100+
completion(true)
101+
}
102+
103+
}
104+
}
105+
106+

Sources/PermissionsSwiftUI/Model/PermissionStore.swift

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import SwiftUI
1313
The global shared storage for PermissionsSwiftUI
1414
*/
1515
public struct PermissionStore {
16-
//MARK: Init and Singleton Management
16+
//MARK: Creating a new store
1717
/**
1818
Initalizes and returns a new instance of `PermissionStore`
1919

@@ -31,40 +31,52 @@ public struct PermissionStore {
3131
mutableShared
3232
}
3333
}
34+
//MARK: All Permissions
3435
///A global array of permissions that configures the permissions to request
3536
public var permissions: [PermissionType] = []
3637
var permissionsToAsk: [PermissionType]{
3738
FilterPermissions.filterForShouldAskPermission(for: permissions)
3839
}
39-
//MARK: Secondary Components
40+
//MARK: Configuring View Texts
4041
///The text for text label components, including header and descriptions
4142
public var mainTexts = MainTexts()
43+
/**
44+
Encapsulates the surrounding texts and title
45+
*/
46+
public struct MainTexts{
47+
///Text to display for header text
48+
public var headerText: String = "Need Permissions"
49+
///Text to display for header description text
50+
public var headerDescription: String = """
51+
In order for you use certain features of this app, you need to give permissions. See description for each permission
52+
"""
53+
///Text to display for bottom part description text
54+
public var bottomDescription: String = """
55+
Permission are necessary for all the features and functions to work properly. If not allowed, you have to enable permissions in settings
56+
"""
57+
}
58+
//MARK: Customizing Colors
4259
///The color configuration for permission allow buttons
4360
public var allButtonColors = AllButtonColors()
61+
62+
//MARK: Change Auto Dismiss Behaviors
4463
///Whether to auto dismiss the modal after last permission is allowed
4564
public var autoDismissModal: Bool = true
4665
///Whether to auto dismiss the alert after last permission is allowed
4766
public var autoDismissAlert: Bool = true
67+
68+
//MARK: Configure Auto Authorization Checking
4869
///Whether to auto check for authorization status before showing, and show the view only if permission is in `notDetermined`
4970
public var autoCheckModalAuth: Bool = true
5071
///Whether to auto check for authorization status before showing, and show the view only if permission is in `notDetermined`
5172
public var autoCheckAlertAuth: Bool = true
73+
74+
//MARK: `onAppear` and `onDisappear` Executions
5275
///Override point for executing action when PermissionsSwiftUI view appears
5376
public var onAppear: (()->Void)?
5477
///Override point for executing action when PermissionsSwiftUI view disappears
5578
public var onDisappear: (()->Void)?
56-
public struct MainTexts{
57-
///Text to display for header text
58-
public var headerText: String = "Need Permissions"
59-
///Text to display for header description text
60-
public var headerDescription: String = """
61-
In order for you use certain features of this app, you need to give permissions. See description for each permission
62-
"""
63-
///Text to display for bottom part description text
64-
public var bottomDescription: String = """
65-
Permission are necessary for all the features and functions to work properly. If not allowed, you have to enable permissions in settings
66-
"""
67-
}
79+
6880
//MARK: Permission Components
6981
///The displayed text and image icon for the camera permission
7082
public var cameraPermission = JMPermission(
@@ -162,6 +174,7 @@ extension PermissionStore{
162174
//Closure passes back PermissionStore instance, and the generic value passed in method
163175
property(&PermissionStore.mutableShared, value)
164176
}
177+
165178
}
166179
// MARK: - Button Customizations
167180
/**
@@ -210,7 +223,15 @@ public struct AllButtonColors{
210223
self.init()
211224
self.buttonDenied = buttonDenied
212225
}
213-
226+
/**
227+
Initializes a new `AllbuttonColors` from the primary and tertiary colors
228+
229+
Both `primaryColor` and `tertiaryColor` are non-required parameters. Colors without a given initializer parameter will be displayed with the default color.
230+
231+
- parameters:
232+
- primaryColor: The primary color, characterized by the default blue
233+
- tertiaryColor: The tertiary color, characterized by the default alert red
234+
*/
214235
public init(primaryColor: Color?=nil, tertiaryColor: Color?=nil){
215236
self.primaryColor = primaryColor ?? Color(.systemBlue)
216237
self.tertiaryColor = tertiaryColor ?? Color(.systemRed)

0 commit comments

Comments
 (0)