diff --git a/calf-permissions/src/androidMain/kotlin/com/mohamedrejeb/calf/permissions/PermissionsUtil.android.kt b/calf-permissions/src/androidMain/kotlin/com/mohamedrejeb/calf/permissions/PermissionsUtil.android.kt index 6750a85..252e0ed 100644 --- a/calf-permissions/src/androidMain/kotlin/com/mohamedrejeb/calf/permissions/PermissionsUtil.android.kt +++ b/calf-permissions/src/androidMain/kotlin/com/mohamedrejeb/calf/permissions/PermissionsUtil.android.kt @@ -1,5 +1,6 @@ package com.mohamedrejeb.calf.permissions +import android.Manifest import android.app.Activity import android.content.Context import android.content.ContextWrapper @@ -101,31 +102,37 @@ internal fun Activity.shouldShowRationale(permission: String): Boolean { internal fun Permission.toAndroidPermission(): String { return when (this) { - Permission.Call -> android.Manifest.permission.CALL_PHONE - Permission.Camera -> android.Manifest.permission.CAMERA - Permission.Gallery -> android.Manifest.permission.READ_EXTERNAL_STORAGE - Permission.ReadStorage -> android.Manifest.permission.READ_EXTERNAL_STORAGE - Permission.WriteStorage -> android.Manifest.permission.WRITE_EXTERNAL_STORAGE - Permission.FineLocation -> android.Manifest.permission.ACCESS_FINE_LOCATION - Permission.CoarseLocation -> android.Manifest.permission.ACCESS_COARSE_LOCATION - Permission.RemoteNotification -> android.Manifest.permission.RECEIVE_BOOT_COMPLETED - Permission.RecordAudio -> android.Manifest.permission.RECORD_AUDIO - Permission.BluetoothLe -> android.Manifest.permission.BLUETOOTH + Permission.Call -> Manifest.permission.CALL_PHONE + Permission.Camera -> Manifest.permission.CAMERA + Permission.Gallery -> Manifest.permission.READ_EXTERNAL_STORAGE + Permission.ReadStorage -> Manifest.permission.READ_EXTERNAL_STORAGE + Permission.WriteStorage -> Manifest.permission.WRITE_EXTERNAL_STORAGE + Permission.FineLocation -> Manifest.permission.ACCESS_FINE_LOCATION + Permission.CoarseLocation -> Manifest.permission.ACCESS_COARSE_LOCATION + Permission.RemoteNotification -> Manifest.permission.RECEIVE_BOOT_COMPLETED + Permission.RecordAudio -> Manifest.permission.RECORD_AUDIO + Permission.BluetoothLe -> Manifest.permission.BLUETOOTH Permission.BluetoothScan -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - android.Manifest.permission.BLUETOOTH_SCAN + Manifest.permission.BLUETOOTH_SCAN } else { "" } Permission.BluetoothConnect -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - android.Manifest.permission.BLUETOOTH_CONNECT + Manifest.permission.BLUETOOTH_CONNECT } else { "" } Permission.BluetoothAdvertise -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - android.Manifest.permission.BLUETOOTH_ADVERTISE + Manifest.permission.BLUETOOTH_ADVERTISE + } else { + "" + } + Permission.Notification -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Manifest.permission.POST_NOTIFICATIONS } else { "" } @@ -150,6 +157,7 @@ internal fun getPermissionFromAndroidPermission(androidPermission: String): Perm android.Manifest.permission.WRITE_EXTERNAL_STORAGE -> Permission.WriteStorage android.Manifest.permission.ACCESS_FINE_LOCATION -> Permission.FineLocation android.Manifest.permission.ACCESS_COARSE_LOCATION -> Permission.CoarseLocation + android.Manifest.permission.POST_NOTIFICATIONS -> Permission.Notification android.Manifest.permission.RECEIVE_BOOT_COMPLETED -> Permission.RemoteNotification android.Manifest.permission.RECORD_AUDIO -> Permission.RecordAudio android.Manifest.permission.BLUETOOTH -> Permission.BluetoothLe diff --git a/calf-permissions/src/commonMain/kotlin/com.mohamedrejeb.calf/permissions/PermissionState.kt b/calf-permissions/src/commonMain/kotlin/com.mohamedrejeb.calf/permissions/PermissionState.kt index c3e5cfa..29bfb90 100644 --- a/calf-permissions/src/commonMain/kotlin/com.mohamedrejeb.calf/permissions/PermissionState.kt +++ b/calf-permissions/src/commonMain/kotlin/com.mohamedrejeb.calf/permissions/PermissionState.kt @@ -76,6 +76,7 @@ enum class Permission { WriteStorage, FineLocation, CoarseLocation, + Notification, RemoteNotification, RecordAudio, BluetoothLe, diff --git a/calf-permissions/src/iosMain/kotlin/com.mohamedrejeb.calf/permissions/PermissionsUtil.kt b/calf-permissions/src/iosMain/kotlin/com.mohamedrejeb.calf/permissions/PermissionsUtil.kt index a7a660a..7d67f2c 100644 --- a/calf-permissions/src/iosMain/kotlin/com.mohamedrejeb.calf/permissions/PermissionsUtil.kt +++ b/calf-permissions/src/iosMain/kotlin/com.mohamedrejeb.calf/permissions/PermissionsUtil.kt @@ -4,6 +4,7 @@ import com.mohamedrejeb.calf.permissions.helper.AVCapturePermissionHelper import com.mohamedrejeb.calf.permissions.helper.BluetoothPermissionHelper import com.mohamedrejeb.calf.permissions.helper.GalleryPermissionHelper import com.mohamedrejeb.calf.permissions.helper.GrantedPermissionHelper +import com.mohamedrejeb.calf.permissions.helper.LocalNotificationPermissionHelper import com.mohamedrejeb.calf.permissions.helper.LocationPermissionHelper import com.mohamedrejeb.calf.permissions.helper.PermissionHelper import com.mohamedrejeb.calf.permissions.helper.RemoteNotificationPermissionHelper @@ -16,10 +17,12 @@ internal fun Permission.getPermissionDelegate(): PermissionHelper { 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() + } } diff --git a/calf-permissions/src/iosMain/kotlin/com.mohamedrejeb.calf/permissions/helper/LocalNotificationPermissionHelper.kt b/calf-permissions/src/iosMain/kotlin/com.mohamedrejeb.calf/permissions/helper/LocalNotificationPermissionHelper.kt new file mode 100644 index 0000000..d87914e --- /dev/null +++ b/calf-permissions/src/iosMain/kotlin/com.mohamedrejeb.calf/permissions/helper/LocalNotificationPermissionHelper.kt @@ -0,0 +1,52 @@ +package com.mohamedrejeb.calf.permissions.helper + +import com.mohamedrejeb.calf.permissions.ExperimentalPermissionsApi +import com.mohamedrejeb.calf.permissions.PermissionStatus +import platform.UserNotifications.UNAuthorizationOptionAlert +import platform.UserNotifications.UNAuthorizationOptionBadge +import platform.UserNotifications.UNAuthorizationOptionSound +import platform.UserNotifications.UNAuthorizationStatusAuthorized +import platform.UserNotifications.UNAuthorizationStatusEphemeral +import platform.UserNotifications.UNAuthorizationStatusNotDetermined +import platform.UserNotifications.UNAuthorizationStatusProvisional +import platform.UserNotifications.UNUserNotificationCenter + +internal class LocalNotificationPermissionHelper : PermissionHelper { + + 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) + ) { isOk, error -> + if (isOk && error == null) { + onPermissionResult(true) + } else { + onPermissionResult(false) + } + } + } + else -> onPermissionResult(false) + } + } + } + + @ExperimentalPermissionsApi + override fun getPermissionStatus(onPermissionResult: (PermissionStatus) -> Unit) { + val notificationCenter = UNUserNotificationCenter.currentNotificationCenter() + notificationCenter.getNotificationSettingsWithCompletionHandler { settings -> + when (settings?.authorizationStatus) { + UNAuthorizationStatusAuthorized, + UNAuthorizationStatusProvisional, + UNAuthorizationStatusEphemeral -> onPermissionResult(PermissionStatus.Granted) + else -> onPermissionResult(PermissionStatus.Denied(false)) + } + } + } +} \ No newline at end of file diff --git a/docs/permissions.md b/docs/permissions.md index 271826b..b411666 100644 --- a/docs/permissions.md +++ b/docs/permissions.md @@ -212,3 +212,27 @@ On iOS you need to add the following key to your `Info.plist` file: ``` The string value is the message that will be displayed to the user when the permission is requested. + +### Post Notifications Permission + +To request the post notifications permission, use `Permission.Notification`. + +#### Android + +On Android API version 33 and up, you need to add the following permission to your `AndroidManifest.xml` file: + +```xml + + +``` + +#### iOS + +On iOS you need to add the following key to your `Info.plist` file: + +```xml +NSUserNotificationsUsageDescription +Notifications permission is required to show notifications +``` + +The string value is the message that will be displayed to the user when the permission is requested. diff --git a/sample/android/src/main/AndroidManifest.xml b/sample/android/src/main/AndroidManifest.xml index a4b94ef..106e7e8 100644 --- a/sample/android/src/main/AndroidManifest.xml +++ b/sample/android/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ + Unit) { Box( modifier = - Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - .windowInsetsPadding(WindowInsets.systemBars) - .windowInsetsPadding(WindowInsets.ime), + Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .windowInsetsPadding(WindowInsets.systemBars) + .windowInsetsPadding(WindowInsets.ime), ) { LazyColumn( horizontalAlignment = Alignment.CenterHorizontally, modifier = - Modifier - .fillMaxSize() - .padding(16.dp), + Modifier + .fillMaxSize() + .padding(16.dp), ) { items(Permission.entries) { permission -> PermissionItem(permission = permission) @@ -57,14 +59,14 @@ fun PermissionScreen(navigateBack: () -> Unit) { navigateBack() }, colors = - IconButtonDefaults.iconButtonColors( - contentColor = MaterialTheme.colorScheme.onBackground, - containerColor = MaterialTheme.colorScheme.surface, - ), + IconButtonDefaults.iconButtonColors( + contentColor = MaterialTheme.colorScheme.onBackground, + containerColor = MaterialTheme.colorScheme.surface, + ), modifier = - Modifier - .align(Alignment.TopStart) - .padding(16.dp), + Modifier + .align(Alignment.TopStart) + .padding(16.dp), ) { Icon( Icons.Filled.ArrowBackIosNew, @@ -90,6 +92,13 @@ private fun PermissionItem(permission: Permission) { text = "Is permission granted: ${permissionState.status.isGranted}", ) + LaunchedEffect(permissionState.status) { + println("${permission.name}: ${permissionState.status}") + if (!permissionState.status.isGranted && permissionState.status.shouldShowRationale) { + println("${permission.name}: Show Rationale") + } + } + Button( onClick = { println("Click") diff --git a/sample/ios/Calf/Info.plist b/sample/ios/Calf/Info.plist index 8b323ee..1142d5e 100644 --- a/sample/ios/Calf/Info.plist +++ b/sample/ios/Calf/Info.plist @@ -4,17 +4,19 @@ CADisableMinimumFrameDurationOnPhone + NSUserNotificationsUsageDescription + Just for tests NSCameraUsageDescription - Just for tests + Just for tests NSPhotoLibraryUsageDescription - Just for tests + Just for tests NSLocationWhenInUseUsageDescription - Just for tests - NSMicrophoneUsageDescription - Just for tests - NSBluetoothAlwaysUsageDescription - Just for tests - NSBluetoothPeripheralUsageDescription - Just for tests + Just for tests + NSMicrophoneUsageDescription + Just for tests + NSBluetoothAlwaysUsageDescription + Just for tests + NSBluetoothPeripheralUsageDescription + Just for tests