Skip to content

Commit 652c22e

Browse files
committed
Merge Develop into Main to resolve conflict
2 parents a997c1b + 29c6ff3 commit 652c22e

File tree

61 files changed

+445
-163
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+445
-163
lines changed

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ let package = Package(
2222
// Targets can depend on other targets in this package, and on products in packages this package depends on.
2323
.target(
2424
name: "PermissionsSwiftUI",
25-
dependencies: []
25+
dependencies: [],
26+
exclude: ["Tests/PermissionsSwiftUITests/__Snapshots__"]
2627
),
2728
.testTarget(
2829
name: "PermissionsSwiftUITests",

Sources/PermissionsSwiftUI/Components/AllowButtonSection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct AllowButtonSection: View {
1212
@Binding var allowButtonStatus: AllowButtonStatus
1313
var body: some View {
1414
Button(action: action, label: {
15-
Text(allowButtonStatus == .allowed ? "ALLOWED" : "ALLOW")
15+
Text(allowButtonStatus == .allowed ? "ALLOWED" : allowButtonStatus == .idle ? "ALLOW" : "DENIED")
1616
.fontWeight(.bold)
1717
.buttonStatusColor(for: allowButtonStatus)
1818
})

Sources/PermissionsSwiftUI/Components/PermissionSection.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ struct PermissionSection: View {
2424
.clipShape(RoundedRectangle(cornerRadius: 15))
2525
.padding()
2626
.padding(.horizontal, 5)
27+
.frame(maxWidth:UIScreen.main.bounds.width-30)
28+
2729
}
2830
}
2931

30-
enum AllowButtonStatus {
32+
enum AllowButtonStatus:CaseIterable {
3133
case idle
3234
case allowed
3335
case denied
@@ -63,7 +65,7 @@ struct PermissionSectionCell: View {
6365
.lineLimit(3)
6466
.foregroundColor(Color(.systemGray2))
6567
}
66-
.padding(.horizontal, 5)
68+
.padding(.horizontal, 3)
6769

6870
Spacer()
6971
AllowButtonSection(action: {
@@ -88,6 +90,7 @@ struct PermissionSectionCell: View {
8890
}
8991
}, allowButtonStatus: $allowButtonStatus)
9092
}
93+
.fixedSize(horizontal: false, vertical: true)
9194
.padding(15)
9295
.frame(maxHeight:.infinity)
9396
}

Sources/PermissionsSwiftUI/MainView.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,22 @@ struct MainView: View {
1919
bodyView
2020
.sheet(isPresented: showModal, content: {
2121
ModalView(showModal: showModal)
22+
.onAppear(perform:PermissionStore.shared.onAppear)
23+
.onDisappear(perform:PermissionStore.shared.onDisappear)
24+
2225
})
2326

2427

2528
}
29+
#if DEBUG
30+
static func testCallOnAppear(){
31+
guard let onAppear = PermissionStore.shared.onAppear else {return}
32+
onAppear()
33+
}
34+
static func testCallOnDisappear(){
35+
guard let onDisappear = PermissionStore.shared.onDisappear else {return}
36+
onDisappear()
37+
}
38+
#endif
39+
2640
}

Sources/PermissionsSwiftUI/Model/JMPermissionModel.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import SwiftUI
1313

1414
`JMPermission` conforms to `Equatable` and `Hashable`. Is used for abstractly store data for each permission.
1515
*/
16-
public struct JMPermission:Equatable, Hashable{
16+
17+
public struct JMPermission:Equatable{
18+
1719

1820
/**
1921
Initializes a new instance of JMPermission
@@ -41,11 +43,7 @@ public struct JMPermission:Equatable, Hashable{
4143
return false
4244
}
4345
}
44-
public func hash(into hasher: inout Hasher) {
45-
hasher.combine(title)
46-
hasher.combine(description)
47-
hasher.combine(authorized)
48-
}
46+
4947

5048
var imageIcon: AnyView
5149
var title: String

Sources/PermissionsSwiftUI/Model/Mocks/MockLocationManager.swift

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,49 @@ import Foundation
99
import MapKit
1010

1111
protocol LocationManager {
12-
var location:CLLocation? {get}
12+
var delegate: CLLocationManagerDelegate? {get set}
13+
func authorizationStatus() -> CLAuthorizationStatus
1314
func requestWhenInUseAuthorization()
1415
func requestAlwaysAuthorization()
1516
}
1617

17-
class MockCLLocationManager:CLLocationManager{
18-
var authStatus:CLAuthorizationStatus = .notDetermined
19-
func authorizationStatus() -> CLAuthorizationStatus{
20-
return authStatus
18+
19+
extension CLLocationManager:LocationManager{
20+
func authorizationStatus() -> CLAuthorizationStatus {
21+
CLLocationManager.authorizationStatus()
22+
}
23+
24+
25+
}
26+
struct MockCLLocationManager:LocationManager{
27+
weak var delegate: CLLocationManagerDelegate?
28+
29+
private static var status:CLAuthorizationStatus = .notDetermined
30+
func authorizationStatus() -> CLAuthorizationStatus {
31+
MockCLLocationManager.status
2132
}
22-
override func requestAlwaysAuthorization() {
23-
self.authStatus = .authorizedAlways
33+
34+
func requestWhenInUseAuthorization() {
35+
MockCLLocationManager.status = .authorizedWhenInUse
2436
}
25-
override func requestWhenInUseAuthorization() {
26-
self.authStatus = .authorizedWhenInUse
27-
}
37+
38+
func requestAlwaysAuthorization() {
39+
MockCLLocationManager.status = .authorizedAlways
40+
41+
}
42+
43+
2844
}
45+
//class MockCLLocationManager:CLLocationManager{
46+
// var authStatus:CLAuthorizationStatus = .notDetermined
47+
// func authorizationStatus() -> CLAuthorizationStatus{
48+
// return authStatus
49+
// }
50+
// override func requestAlwaysAuthorization() {
51+
// self.authStatus = .authorizedAlways
52+
// }
53+
// override func requestWhenInUseAuthorization() {
54+
// self.authStatus = .authorizedWhenInUse
55+
// }
56+
//}
57+
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// MockNotificationManager.swift
3+
//
4+
//
5+
// Created by Jevon Mao on 2/10/21.
6+
//
7+
8+
import Foundation
9+
import UserNotifications
10+
11+
protocol NotificationManager {
12+
static func shared() -> NotificationManager
13+
func requestPermission(options: UNAuthorizationOptions, completionHandler: @escaping (Bool, Error?) -> Void)
14+
func getNotificationSettings(completionHandler: @escaping (UNNotificationSettings) -> Void)
15+
}
16+
extension UNUserNotificationCenter:NotificationManager{
17+
static func shared() -> NotificationManager {
18+
UNUserNotificationCenter.current()
19+
}
20+
func requestPermission(options: UNAuthorizationOptions=[], completionHandler: @escaping (Bool, Error?) -> Void) {
21+
self.requestAuthorization(options: options) { granted, error in
22+
completionHandler(granted,error)
23+
}
24+
}
25+
26+
}
27+
class MockNotificationManager:NotificationManager{
28+
var authStatus:UNAuthorizationStatus = .notDetermined
29+
func getNotificationSettings(completionHandler: @escaping (UNNotificationSettings) -> Void) {
30+
completionHandler(UNNotificationSettings(coder: MockNSCoder(authorizationStatus: authStatus.rawValue))!)
31+
32+
}
33+
34+
static func shared() -> NotificationManager {
35+
MockNotificationManager()
36+
}
37+
38+
39+
func requestPermission(options: UNAuthorizationOptions, completionHandler: @escaping (Bool, Error?) -> Void) {
40+
switch authStatus{
41+
case .denied:
42+
completionHandler(false,NSError(domain:"", code:0, userInfo:nil))
43+
case .ephemeral:
44+
completionHandler(false,nil)
45+
default:
46+
completionHandler(true,nil)
47+
authStatus = .authorized
48+
}
49+
}
50+
51+
52+
}
53+
class MockNSCoder: NSCoder {
54+
var authorizationStatus:Int
55+
init(authorizationStatus:Int){
56+
self.authorizationStatus = authorizationStatus
57+
}
58+
override func decodeInt64(forKey key: String) -> Int64 {
59+
return Int64(authorizationStatus)
60+
}
61+
62+
override func decodeBool(forKey key: String) -> Bool {
63+
return true
64+
}
65+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// MockPhotoManager.swift
3+
//
4+
//
5+
// Created by Jevon Mao on 2/10/21.
6+
//
7+
8+
import Foundation
9+
import Photos
10+
11+
class MockPhotoManager:PHPhotoLibrary{
12+
static override func requestAuthorization(_ handler: @escaping (PHAuthorizationStatus) -> Void) {
13+
handler(PHAuthorizationStatus.authorized)
14+
}
15+
}

Sources/PermissionsSwiftUI/Model/PermissionStore.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ struct PermissionStore {
1919
}
2020
var permissions: [PermissionType] = []
2121
var mainTexts = MainTexts()
22+
var onAppear:(()->Void)?
23+
var onDisappear:(()->Void)?
2224
struct MainTexts{
2325
var headerText:String = "Need Permissions"
2426
var headerDescription:String = """
@@ -93,6 +95,10 @@ struct PermissionStore {
9395
title: "Speech",
9496
description: "Allow to access speech recognition", authorized: false
9597
)
98+
var healthPermission = JMPermission(imageIcon: AnyView(Image(systemName: "heart.fill")),
99+
title: "Health",
100+
description: "Allow to access your health information",
101+
authorized: false)
96102

97103
}
98104
// MARK: Updating methods

Sources/PermissionsSwiftUI/Model/PermissionType.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import Foundation
99
import SwiftUI
10+
import HealthKit
1011

1112
/**
1213
The types of iOS system permission for show in the JMPermissions view
@@ -40,6 +41,21 @@ public enum PermissionType {
4041
case motion
4142
///The `reminders` permission is needed to interact with device reminders
4243
case reminders
44+
/**
45+
Permission that allows app to access healthkit information
46+
47+
- Note: Extensive Info.plist values and configurations are required for HealthKit authorization. Please see Apple Developer [website](https://developer.apple.com/documentation/healthkit/authorizing_access_to_health_data) for details. \n
48+
49+
For example, passing in a `Set` of `HKSampleType`:
50+
```
51+
[.health(Set([HKObjectType.workoutType(),
52+
HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,
53+
HKObjectType.quantityType(forIdentifier: .distanceCycling)!,
54+
HKObjectType.quantityType(forIdentifier: .distanceWalkingRunning)!,
55+
HKObjectType.quantityType(forIdentifier: .heartRate)!]))]
56+
```
57+
*/
58+
case health(Set<HKSampleType>)
4359
///Permission that allows app to use speech recognition
4460
case speech
4561
///In order for app to track user's data across apps and websites, the tracking permission is needed

Sources/PermissionsSwiftUI/Model/PermissionTypeGetSet.swift

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
import Foundation
99
import SwiftUI
1010

11-
extension PermissionType{
11+
protocol PermissionTypeProtocol {
12+
var permissions:[PermissionType]{get}
13+
func requestPermission(isPermissionGranted: @escaping (Bool) -> Void)
14+
}
15+
extension PermissionType:PermissionTypeProtocol{
16+
1217
var permissions:[PermissionType]{
1318
get{
1419
PermissionStore.shared.permissions
@@ -44,6 +49,9 @@ extension PermissionType{
4449
return store.remindersPermission
4550
case .speech:
4651
return store.speechPermission
52+
case .health:
53+
return store.healthPermission
54+
4755
}
4856
}
4957
set{
@@ -74,6 +82,8 @@ extension PermissionType{
7482
PermissionStore.shared.updateStore(property: {$0.remindersPermission=$1}, value: newValue)
7583
case .speech:
7684
PermissionStore.shared.updateStore(property: {$0.speechPermission=$1}, value: newValue)
85+
case .health:
86+
PermissionStore.shared.updateStore(property: {$0.healthPermission=$1}, value: newValue)
7787
}
7888
}
7989

@@ -82,12 +92,10 @@ extension PermissionType{
8292
switch self {
8393
case .location:
8494
JMLocationPermissionManager.shared.requestInUsePermission { authorized in
85-
print("Permission \(authorized)")
8695
isPermissionGranted(authorized)
8796
}
8897
case .locationAlways:
8998
JMLocationPermissionManager.shared.requestAlwaysPermission { authorized in
90-
print("Permission \(authorized)")
9199
isPermissionGranted(authorized)
92100
}
93101
case .photo:
@@ -117,7 +125,7 @@ extension PermissionType{
117125

118126
}
119127
case .tracking:
120-
if #available(iOS 14, *) {
128+
if #available(iOS 14.5, *) {
121129
JMTrackingPermissionManager.shared.requestPermission{authorized in
122130
print(authorized)
123131
isPermissionGranted(authorized)
@@ -139,16 +147,21 @@ extension PermissionType{
139147
JMSpeechPermissionManager.shared.requestPermission{
140148
isPermissionGranted($0)
141149
}
150+
case .health:
151+
JMHealthPermissionManager.shared.requestPermission{
152+
isPermissionGranted($0)
153+
}
142154
}
143155
}
144156

145157
}
146158
extension PermissionType:CaseIterable{
147159
public static var allCases: [PermissionType]{
148-
if #available(iOS 14.5, *) {
149-
return [.location,.locationAlways,.photo,microphone,.camera,.notification,.calendar,.bluetooth,.contacts,.motion,.reminders,.speech,.tracking]
150-
} else {
151-
return [.location,.locationAlways,.photo,microphone,.camera,.notification,.calendar,.bluetooth,.contacts,.motion,.reminders,.speech]
152-
}
160+
//return [.location,.locationAlways,.photo,microphone,.camera,.notification,.calendar,.bluetooth,.contacts,.motion,.reminders,.speech]
161+
if #available(iOS 14.5, *) {
162+
return [.location,.locationAlways,.photo,microphone,.camera,.notification,.calendar,.bluetooth,.contacts,.motion,.reminders,.speech,.tracking]
163+
} else {
164+
return [.location,.locationAlways,.photo,microphone,.camera,.notification,.calendar,.bluetooth,.contacts,.motion,.reminders,.speech]
165+
}
153166
}
154167
}

Sources/PermissionsSwiftUI/Model/Permissions/JMBluetoothPermissionManager.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import CoreBluetooth
99
import UIKit
1010

11+
1112
class JMBluetoothPermissionManager: NSObject {
1213
typealias JMBluetoothPermissionHandler = (Bool) -> Void?
1314
private var completion: JMBluetoothPermissionHandler?

0 commit comments

Comments
 (0)