Skip to content

Commit

Permalink
Release 1.5.8 (#138)
Browse files Browse the repository at this point in the history
* Add API boilerplate for new calendar and reminder

Addresses iOS 17 API changes

* Abstract calendar/reminder into event manager

* Fix obsolete API mark and revise message

* Squashed commit of the following:

commit 5ecef99e3b2ef04a2a05b34509e8a449031d92c4
Author: Jevon Mao <[email protected]>
Date:   Tue Aug 8 20:13:01 2023 -0400

    Add documentation snippet for permission managers

commit 44e3f41
Author: Jevon Mao <[email protected]>
Date:   Mon Aug 7 14:41:46 2023 -0400

    Implement custom permission description color (#137)

commit f3ed32c
Author: Jevon Mao <[email protected]>
Date:   Mon Aug 7 12:58:42 2023 -0400

    Disable stale check CICD

* Erase type for custom foreground color

* Add NSLog warning deprecated EventKit permissisons

* Remove available limitation
  • Loading branch information
jevonmao authored Aug 26, 2023
1 parent 44e3f41 commit fbb88cf
Show file tree
Hide file tree
Showing 32 changed files with 325 additions and 230 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ let permissionsTargets: [Target] = [
let package = Package(
name: "PermissionsSwiftUI",
defaultLocalization: "en",
platforms: [.iOS(.v13)],
platforms: [.iOS(.v13), .macOS(.v10_15)],
products: permissionsTargets.map{Product.library(name: $0.name, targets: [$0.name])},
dependencies: [
// Dependencies declare other packages that this package depends on.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// PermissionManagerProctocol.swift
//
// AuthorizationStatus.swift
//
//
// Created by Jevon Mao on 2/18/21.
//
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// EventPermissionManager.swift
// PermissionsSwiftUI-Example
//
// Created by Jevon Mao on 8/26/23.
//

import Foundation
import EventKit

open class EventPermissionManager: PermissionManager {
public init(requestedAccessLevel: AccessLevel = .legacy) {
self.requestedAccessLevel = requestedAccessLevel
if requestedAccessLevel == .legacy {
NSLog("[PermissionsSwiftUI]: WARNING! Using legacy calendar or reminder permission, which will NOT work in iOS 17 and always return denied due to Apple EventKit API changes. Learn more: https://developer.apple.com/documentation/eventkit/accessing_the_event_store")
}
}


public var requestedAccessLevel: AccessLevel
public let eventStore = EKEventStore()
open var entityType: EKEntityType {
get {
preconditionFailure("This property must be overridden.")
}
}

public enum AccessLevel {
case writeOnly
case full
case legacy
}

public override var authorizationStatus: AuthorizationStatus {
switch EKEventStore.authorizationStatus(for: entityType){
case .authorized:
return .authorized
case .notDetermined:
return .notDetermined
default:
return .denied
}
}

public func requestLegacyPermission( _ completion: @escaping (Bool, Error?) -> Void) {
eventStore.requestAccess(to: entityType, completion: {
(accessGranted: Bool, error: Error?) in
DispatchQueue.main.async {
completion(accessGranted, error)
}
})
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// PermissionManager.swift
// PermissionsSwiftUI-Example
//
// Created by Jevon Mao on 8/26/23.
//

import Foundation

/**
A Permission Manager object that contains properties and functions related to a specific permission. Will be subclassed by any permission type.

- warning: `PermissionManager` shoud never be referenced directly and used. It serves as an abstract interface for PermissionsSwiftUI's many permission modules.
*/
open class PermissionManager: NSObject, Identifiable {
///Holds the permission UI component, containing UI elements like text and image
open var permissionComponent: JMPermission {
get {
preconditionFailure("This property must be overridden.")
}
}
///The type of permission
open var permissionType: PermissionType {
preconditionFailure("This property must be overridden.")
}

///The authorization status of the permission
open var authorizationStatus: AuthorizationStatus {
get {
preconditionFailure("This property must be overridden.")
}
}

#if PERMISSIONSWIFTUI_HEALTH

///Holds the health permission subcategories, in case of health permission type subclass
open var healthPermissionCategories: Set<HKSampleType>?

/**
Creates a new `PermissionManager` for health permission.

- parameters:
- healthPermissionCategories: Subcategory permissions of health permission to request
*/
public init(_ healthPermissionCategories: Set<HKSampleType>? = nil) {
self.healthPermissionCategories = healthPermissionCategories
}
#else
///Creates a new `PermissionManager` for any type of child implemented permission
public override init() {}
#endif

/**
Requests authorization for the current implemented type of permission.

- parameters:
- completion: Returns back whether the permission authorization is granted, and any errors
*/
open func requestPermission(completion: @escaping (Bool, Error?) -> Void) {
preconditionFailure("This method must be overridden.")
}
}
166 changes: 57 additions & 109 deletions Sources/CorePermissionsSwiftUI/Model/PermissionType/PermissionType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,117 +25,65 @@ public enum PermissionType: Hashable, Equatable {
lhs.rawValue == rhs.rawValue ? true : false
}

///The `location` permission allows the device's positoin to be tracked
case location

///Used to access the user's photo library
case photo

///The `notification` permission allows the iOS system to receive notification from app
case notification

///Permission that allows app to access device's bluetooth technologies
case bluetooth

///Permission that allows Siri and Maps to communicate with your app
case siri

///In order for app to track user's data across apps and websites, the tracking permission is needed
@available(iOS 14, tvOS 14, *) case tracking
#if !os(tvOS)
/**
A Permission Manager object that contains properties and functions related to a specific permission. Will be subclassed by any permission type.

- warning: `PermissionManager` shoud never be referenced directly and used. It serves as an abstract interface for PermissionsSwiftUI's many permission modules.
Permission that allows app to access healthkit information

- 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

For example, passing in a `Set` of `HKSampleType`:
```
[.health(categories: .init(readAndWrite: Set([HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!])))]
```
*/
open class PermissionManager: NSObject, Identifiable {
///Holds the permission UI component, containing UI elements like text and image
open var permissionComponent: JMPermission {
get {
preconditionFailure("This property must be overridden.")
}
}
///The type of permission
open var permissionType: PermissionType {
preconditionFailure("This property must be overridden.")
}

///The authorization status of the permission
open var authorizationStatus: AuthorizationStatus {
get {
preconditionFailure("This property must be overridden.")
}
}

#if PERMISSIONSWIFTUI_HEALTH

///Holds the health permission subcategories, in case of health permission type subclass
open var healthPermissionCategories: Set<HKSampleType>?

/**
Creates a new `PermissionManager` for health permission.

- parameters:
- healthPermissionCategories: Subcategory permissions of health permission to request
*/
public init(_ healthPermissionCategories: Set<HKSampleType>? = nil) {
self.healthPermissionCategories = healthPermissionCategories
}
#else
///Creates a new `PermissionManager` for any type of child implemented permission
public override init() {}
#endif

/**
Requests authorization for the current implemented type of permission.

- parameters:
- completion: Returns back whether the permission authorization is granted, and any errors
*/
open func requestPermission(completion: @escaping (Bool, Error?) -> Void) {
preconditionFailure("This method must be overridden.")
}
}
///The `location` permission allows the device's positoin to be tracked
case location

///Used to access the user's photo library
case photo

///The `notification` permission allows the iOS system to receive notification from app
case notification

///Permission that allows app to access device's bluetooth technologies
case bluetooth

///Permission that allows Siri and Maps to communicate with your app
case siri

///In order for app to track user's data across apps and websites, the tracking permission is needed
@available(iOS 14, tvOS 14, *) case tracking
#if !os(tvOS)
/**
Permission that allows app to access healthkit information

- 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

For example, passing in a `Set` of `HKSampleType`:
```
[.health(categories: .init(readAndWrite: Set([HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!])))]
```
*/
case health
#endif

///The `locationAlways` permission provides location data even if app is in background
@available(tvOS, unavailable) case locationAlways

///Permission allows developers to interact with the device microphone
@available(tvOS, unavailable) case microphone

///Permission that allows developers to interact with on-device camera
@available(tvOS, unavailable) case camera

///A permission that allows developers to read & write to device contacts
@available(tvOS, unavailable) case contacts

///Permission that give app access to motion and fitness related sensor data
@available(tvOS, unavailable) case motion

///The `reminders` permission is needed to interact with device reminders
@available(tvOS, unavailable) case reminders

///Permission that allows app to read & write to device calendar
@available(tvOS, unavailable) case calendar

///Permission that allows app to use speech recognition
@available(tvOS, unavailable) case speech

///Permission that allows app to control audio playback of the device
@available(tvOS, unavailable) case music

case health
#endif

///The `locationAlways` permission provides location data even if app is in background
@available(tvOS, unavailable) case locationAlways

///Permission allows developers to interact with the device microphone
@available(tvOS, unavailable) case microphone

///Permission that allows developers to interact with on-device camera
@available(tvOS, unavailable) case camera

///A permission that allows developers to read & write to device contacts
@available(tvOS, unavailable) case contacts

///Permission that give app access to motion and fitness related sensor data
@available(tvOS, unavailable) case motion

///Permission that allows app to read & write to device reminder before iOS 17
@available(tvOS, unavailable, deprecated: 16.0, obsoleted: 17.0)
case reminders

///Permission that allows app to read & write to device calendar before iOS 17
@available(tvOS, unavailable, deprecated: 16.0, obsoleted: 17.0)
case calendar

///Permission that allows app to use speech recognition
@available(tvOS, unavailable) case speech

///Permission that allows app to control audio playback of the device
@available(tvOS, unavailable) case music
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Foundation
@available(iOS 13.0, tvOS 13.0, *)
struct FilterPermissions {
// Based on struct boolean property, dependent on memory
static func filterForUnauthorized(with permissions: [PermissionType.PermissionManager],
store: PermissionSchemaStore) -> [PermissionType.PermissionManager] {
static func filterForUnauthorized(with permissions: [PermissionManager],
store: PermissionSchemaStore) -> [PermissionManager] {
let filteredPermissions = permissions.filter {
store.permissionComponentsStore.getPermissionComponent(for: $0.permissionType).authorized == false
}
Expand All @@ -25,8 +25,8 @@ struct FilterPermissions {
// }
// }
// Based on system API query, independent from memory
static func filterForShouldAskPermission(for permissions: [PermissionType.PermissionManager]) -> [PermissionType.PermissionManager] {
var filteredPermissions = [PermissionType.PermissionManager]()
static func filterForShouldAskPermission(for permissions: [PermissionManager]) -> [PermissionManager] {
var filteredPermissions = [PermissionManager]()

for permission in permissions {
if permission.authorizationStatus == .notDetermined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ extension View {
@ViewBuilder
func compatibleForegroundStyle(_ style: any ShapeStyle) -> some View {
if #available(iOS 15, *) {
self.foregroundStyle(style)
self.foregroundStyle(style).typeErased()
}
else {
self.foregroundColor(style as? Color)
self.foregroundColor(style as? Color).typeErased()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import Combine
public class PermissionSchemaStore: ObservableObject {

//MARK: Filtered permission arrays
var undeterminedPermissions: [PermissionType.PermissionManager] {
var undeterminedPermissions: [PermissionManager] {
FilterPermissions.filterForShouldAskPermission(for: permissions)
}
var interactedPermissions: [PermissionType.PermissionManager] {
var interactedPermissions: [PermissionManager] {
//Filter for permissions that are not interacted
permissions.filter {
permissionComponentsStore.getPermissionComponent(for: $0.permissionType).interacted
Expand All @@ -43,7 +43,7 @@ public class PermissionSchemaStore: ObservableObject {
//MARK: Initialized configuration properties
var configStore: ConfigStore
var store: PermissionStore
@Published var permissions: [PermissionType.PermissionManager]
@Published var permissions: [PermissionManager]
var permissionViewStyle: PermissionViewStyle
@usableFromInline var permissionComponentsStore: PermissionComponentsStore
init(store: PermissionStore, permissionViewStyle: PermissionViewStyle) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class PermissionStore: ObservableObject {
public init(){}

///An array of permissions that configures the permissions to request
public var permissions: [PermissionType.PermissionManager] = []
public var permissions: [PermissionManager] = []

//MARK: Configuration store
///Custom configurations that alters PermissionsSwiftUI view's behaviors
Expand Down
Loading

0 comments on commit fbb88cf

Please sign in to comment.