Skip to content

Commit

Permalink
Merge pull request #156 from MohamedRejeb/0.5.x
Browse files Browse the repository at this point in the history
Handle PermissionStatus.Denied.shouldShowRationale on iOS
  • Loading branch information
MohamedRejeb authored Aug 20, 2024
2 parents 08cba0e + 8c82f07 commit bfea04d
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ internal class MutablePermissionStateImpl(

private val permissionDelegate = permission.getPermissionDelegate()

override var status: PermissionStatus by mutableStateOf(PermissionStatus.Denied(false))
override var status: PermissionStatus by mutableStateOf(PermissionStatus.Denied(shouldShowRationale = false))

init {
refreshPermissionStatus()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,37 @@ import platform.AVFoundation.AVMediaTypeVideo

internal fun Permission.getPermissionDelegate(): PermissionHelper {
return when (this) {
Permission.Camera -> AVCapturePermissionHelper(AVMediaTypeVideo)
Permission.Gallery -> GalleryPermissionHelper()
Permission.ReadStorage, Permission.WriteStorage, Permission.Call -> GrantedPermissionHelper()
Permission.FineLocation, Permission.CoarseLocation -> LocationPermissionHelper()
Permission.Notification -> LocalNotificationPermissionHelper()
Permission.RemoteNotification -> RemoteNotificationPermissionHelper()
Permission.RecordAudio -> AVCapturePermissionHelper(AVMediaTypeAudio)
Permission.BluetoothLe, Permission.BluetoothScan,
Permission.BluetoothConnect, Permission.BluetoothAdvertise,
-> BluetoothPermissionHelper()
Permission.Camera ->
AVCapturePermissionHelper(AVMediaTypeVideo)

Permission.Gallery ->
GalleryPermissionHelper()

Permission.ReadStorage,
Permission.WriteStorage,
Permission.Call,
->
GrantedPermissionHelper()

Permission.FineLocation,
Permission.CoarseLocation,
->
LocationPermissionHelper()

Permission.Notification ->
LocalNotificationPermissionHelper()

Permission.RemoteNotification ->
RemoteNotificationPermissionHelper()

Permission.RecordAudio ->
AVCapturePermissionHelper(AVMediaTypeAudio)

Permission.BluetoothLe,
Permission.BluetoothScan,
Permission.BluetoothConnect,
Permission.BluetoothAdvertise,
->
BluetoothPermissionHelper()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,39 @@ import com.mohamedrejeb.calf.permissions.ExperimentalPermissionsApi
import com.mohamedrejeb.calf.permissions.PermissionStatus
import platform.AVFoundation.AVAuthorizationStatus
import platform.AVFoundation.AVAuthorizationStatusAuthorized
import platform.AVFoundation.AVAuthorizationStatusNotDetermined
import platform.AVFoundation.AVCaptureDevice
import platform.AVFoundation.AVMediaType
import platform.AVFoundation.authorizationStatusForMediaType
import platform.AVFoundation.requestAccessForMediaType

internal class AVCapturePermissionHelper(
private val type: AVMediaType,
): PermissionHelper {
) : PermissionHelper {
@OptIn(ExperimentalPermissionsApi::class)
override fun launchPermissionRequest(onPermissionResult: (Boolean) -> Unit) {
val status = getCurrentAuthorizationStatus()
when(status) {
AVAuthorizationStatusAuthorized -> onPermissionResult(true)
else -> {
handlePermissionRequest(
onPermissionResult = onPermissionResult,
launchPermissionRequest = {
AVCaptureDevice.requestAccessForMediaType(type) {
onPermissionResult(it)
}
}
}
)
}

@OptIn(ExperimentalPermissionsApi::class)
override fun getPermissionStatus(onPermissionResult: (PermissionStatus) -> Unit) {
val status = getCurrentAuthorizationStatus()
val permissionStatus = when(status) {
AVAuthorizationStatusAuthorized -> PermissionStatus.Granted
else -> PermissionStatus.Denied(false)
val permissionStatus = when (status) {
AVAuthorizationStatusAuthorized ->
PermissionStatus.Granted

AVAuthorizationStatusNotDetermined ->
PermissionStatus.Denied(shouldShowRationale = true)

else ->
PermissionStatus.Denied(shouldShowRationale = false)
}

onPermissionResult(permissionStatus)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,24 @@ import platform.CoreBluetooth.CBManager
import platform.CoreBluetooth.CBManagerAuthorization
import platform.CoreBluetooth.CBManagerAuthorizationAllowedAlways
import platform.CoreBluetooth.CBManagerAuthorizationNotDetermined
import platform.CoreBluetooth.CBManagerAuthorizationRestricted
import platform.CoreBluetooth.CBManagerState
import platform.CoreBluetooth.CBManagerStatePoweredOn
import platform.CoreBluetooth.CBManagerStateUnknown
import platform.Foundation.NSSelectorFromString
import platform.darwin.NSObject

internal class BluetoothPermissionHelper: PermissionHelper {
@OptIn(ExperimentalForeignApi::class)
internal class BluetoothPermissionHelper : PermissionHelper {
@OptIn(ExperimentalForeignApi::class, ExperimentalPermissionsApi::class)
override fun launchPermissionRequest(onPermissionResult: (Boolean) -> Unit) {
val isNotDetermined =
if (CBManager.resolveClassMethod(NSSelectorFromString("authorization"))) {
CBManager.authorization == CBManagerAuthorizationNotDetermined
} else {
CBCentralManager().state == CBManagerStateUnknown
handlePermissionRequest(
onPermissionResult = onPermissionResult,
launchPermissionRequest = {
CBCentralManager(object : NSObject(), CBCentralManagerDelegateProtocol {
override fun centralManagerDidUpdateState(central: CBCentralManager) {
onPermissionResult(central.state == CBManagerStatePoweredOn)
}
}, null)
}

if (isNotDetermined) {
CBCentralManager(object : NSObject(), CBCentralManagerDelegateProtocol {
override fun centralManagerDidUpdateState(central: CBCentralManager) {
handleBluetoothState(
state = central.state,
onPermissionResult = onPermissionResult
)
}
}, null)
} else {
handleBluetoothState(
state = CBCentralManager().state,
onPermissionResult = onPermissionResult
)
}
}

private fun handleBluetoothState(
state: CBManagerState,
onPermissionResult: (Boolean) -> Unit
) {
when (state) {
CBManagerStatePoweredOn -> onPermissionResult(true)
else -> onPermissionResult(false)
}
)
}

@OptIn(ExperimentalForeignApi::class)
Expand All @@ -59,15 +35,26 @@ internal class BluetoothPermissionHelper: PermissionHelper {
if (CBManager.resolveClassMethod(NSSelectorFromString("authorization"))) {
val state: CBManagerAuthorization = CBManager.authorization
when (state) {
CBManagerAuthorizationAllowedAlways,
CBManagerAuthorizationRestricted -> onPermissionResult(PermissionStatus.Granted)
else -> onPermissionResult(PermissionStatus.Denied(false))
CBManagerAuthorizationAllowedAlways ->
onPermissionResult(PermissionStatus.Granted)

CBManagerAuthorizationNotDetermined ->
onPermissionResult(PermissionStatus.Denied(shouldShowRationale = true))

else ->
onPermissionResult(PermissionStatus.Denied(shouldShowRationale = false))
}
} else {
val state: CBManagerState = CBCentralManager().state
val state = CBCentralManager().state
when (state) {
CBManagerStatePoweredOn -> onPermissionResult(PermissionStatus.Granted)
else -> onPermissionResult(PermissionStatus.Denied(false))
CBManagerStatePoweredOn ->
onPermissionResult(PermissionStatus.Granted)

CBManagerStateUnknown ->
onPermissionResult(PermissionStatus.Denied(shouldShowRationale = true))

else ->
onPermissionResult(PermissionStatus.Denied(shouldShowRationale = false))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,36 @@ package com.mohamedrejeb.calf.permissions.helper

import com.mohamedrejeb.calf.permissions.ExperimentalPermissionsApi
import com.mohamedrejeb.calf.permissions.PermissionStatus
import platform.AVFoundation.AVAuthorizationStatusAuthorized
import platform.Photos.PHAuthorizationStatus
import platform.Photos.PHAuthorizationStatusAuthorized
import platform.Photos.PHAuthorizationStatusNotDetermined
import platform.Photos.PHPhotoLibrary

internal class GalleryPermissionHelper : PermissionHelper {
@OptIn(ExperimentalPermissionsApi::class)
override fun launchPermissionRequest(onPermissionResult: (Boolean) -> Unit) {
val status = getCurrentAuthorizationStatus()
when(status) {
AVAuthorizationStatusAuthorized -> onPermissionResult(true)
else -> {
handlePermissionRequest(
onPermissionResult = onPermissionResult,
launchPermissionRequest = {
PHPhotoLibrary.requestAuthorization {
when (it) {
PHAuthorizationStatusAuthorized -> onPermissionResult(true)
else -> onPermissionResult(false)
}
onPermissionResult(it == PHAuthorizationStatusAuthorized)
}
}
}
)
}

@OptIn(ExperimentalPermissionsApi::class)
override fun getPermissionStatus(onPermissionResult: (PermissionStatus) -> Unit) {
val status = getCurrentAuthorizationStatus()
val permissionStatus = when(status) {
PHAuthorizationStatusAuthorized -> PermissionStatus.Granted
else -> PermissionStatus.Denied(false)
val permissionStatus = when (status) {
PHAuthorizationStatusAuthorized ->
PermissionStatus.Granted

PHAuthorizationStatusNotDetermined ->
PermissionStatus.Denied(shouldShowRationale = true)

else ->
PermissionStatus.Denied(shouldShowRationale = false)
}
onPermissionResult(permissionStatus)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,47 @@ import platform.UserNotifications.UNUserNotificationCenter

internal class LocalNotificationPermissionHelper : PermissionHelper {

@OptIn(ExperimentalPermissionsApi::class)
override fun launchPermissionRequest(onPermissionResult: (Boolean) -> Unit) {
val notificationCenter = UNUserNotificationCenter.currentNotificationCenter()

notificationCenter.getNotificationSettingsWithCompletionHandler { settings ->
when (settings?.authorizationStatus) {
UNAuthorizationStatusAuthorized,
UNAuthorizationStatusProvisional,
UNAuthorizationStatusEphemeral -> onPermissionResult(true)
UNAuthorizationStatusNotDetermined -> {
notificationCenter.requestAuthorizationWithOptions(
UNAuthorizationOptionSound.or(UNAuthorizationOptionAlert).or(UNAuthorizationOptionBadge)
handlePermissionRequest(
onPermissionResult = onPermissionResult,
launchPermissionRequest = {
getCurrentNotificationCenter()
.requestAuthorizationWithOptions(
UNAuthorizationOptionSound
.or(UNAuthorizationOptionAlert)
.or(UNAuthorizationOptionBadge)
) { isOk, error ->
if (isOk && error == null) {
if (isOk && error == null)
onPermissionResult(true)
} else {
else
onPermissionResult(false)
}
}
}
else -> onPermissionResult(false)
}
}
)
}

@ExperimentalPermissionsApi
override fun getPermissionStatus(onPermissionResult: (PermissionStatus) -> Unit) {
val notificationCenter = UNUserNotificationCenter.currentNotificationCenter()
val notificationCenter = getCurrentNotificationCenter()
notificationCenter.getNotificationSettingsWithCompletionHandler { settings ->
when (settings?.authorizationStatus) {
UNAuthorizationStatusAuthorized,
UNAuthorizationStatusProvisional,
UNAuthorizationStatusEphemeral -> onPermissionResult(PermissionStatus.Granted)
else -> onPermissionResult(PermissionStatus.Denied(false))
UNAuthorizationStatusEphemeral,
->
onPermissionResult(PermissionStatus.Granted)

UNAuthorizationStatusNotDetermined ->
onPermissionResult(PermissionStatus.Denied(shouldShowRationale = true))

else ->
onPermissionResult(PermissionStatus.Denied(shouldShowRationale = false))
}
}
}

private fun getCurrentNotificationCenter(): UNUserNotificationCenter {
return UNUserNotificationCenter.currentNotificationCenter()
}
}
Loading

0 comments on commit bfea04d

Please sign in to comment.