Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/compilation-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
java-version: 17
- name: Build and publish local
run: ./gradlew build publishToMavenLocal syncMultiPlatformLibraryDebugFrameworkIosX64
- name: Install pods
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
java-version: 17
- name: Build and publish to Bintray
run: ./gradlew publish
release:
Expand Down
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx4096m
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
org.gradle.configureondemand=false
org.gradle.parallel=true

Expand All @@ -8,8 +8,8 @@ kotlin.mpp.androidSourceSetLayoutVersion=2

android.useAndroidX=true

moko.android.targetSdk=33
moko.android.compileSdk=33
moko.android.targetSdk=34
moko.android.compileSdk=34
moko.android.minSdk=16

moko.publish.name=MOKO permissions
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ androidCoreTestingVersion = "2.2.0"
coroutinesVersion = "1.6.4"
mokoMvvmVersion = "0.16.0"
mokoPermissionsVersion = "0.19.1"
composeJetBrainsVersion = "1.5.1"
composeJetBrainsVersion = "1.7.1"
lifecycleRuntime = "2.6.1"
composeUiVersion = "1.0.1"

Expand All @@ -34,7 +34,7 @@ androidCoreTesting = { module = "androidx.arch.core:core-testing", version.ref =

kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinVersion" }
androidGradlePlugin = { module = "com.android.tools.build:gradle", version = "7.4.2" }
mokoGradlePlugin = { module = "dev.icerock.moko:moko-gradle-plugin", version = "0.2.0" }
mokoGradlePlugin = { module = "dev.icerock.moko:moko-gradle-plugin", version = "0.4.2" }
mobileMultiplatformGradlePlugin = { module = "dev.icerock:mobile-multiplatform", version = "0.14.2" }
kotlinSerializationGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlinVersion" }
composeJetBrainsGradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "composeJetBrainsVersion" }
Expand Down
3 changes: 2 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Tue Jun 10 21:09:29 CEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import kotlin.coroutines.suspendCoroutine

class AVCaptureDelegate(
private val type: AVMediaType,
private val permission: Permission
private val permission: Permission,
) : PermissionDelegate {
override suspend fun providePermission() {
val status: AVAuthorizationStatus = currentAuthorizationStatus()
Expand All @@ -33,8 +33,11 @@ class AVCaptureDelegate(
val isGranted: Boolean = suspendCoroutine { continuation ->
AVCaptureDevice.requestAccess(type) { continuation.resume(it) }
}
if (isGranted) return
else throw DeniedAlwaysException(permission)
if (isGranted) {
return
} else {
throw DeniedAlwaysException(permission)
}
}

AVAuthorizationStatusDenied -> throw DeniedAlwaysException(permission)
Expand All @@ -60,9 +63,12 @@ class AVCaptureDelegate(

private fun AVCaptureDevice.Companion.requestAccess(
type: AVMediaType,
callback: (isGranted: Boolean) -> Unit
callback: (isGranted: Boolean) -> Unit,
) {
this.requestAccessForMediaType(type, mainContinuation { isGranted: Boolean ->
callback(isGranted)
})
this.requestAccessForMediaType(
type,
mainContinuation { isGranted: Boolean ->
callback(isGranted)
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

internal class BluetoothPermissionDelegate(
private val permission: Permission
private val permission: Permission,
) : PermissionDelegate {
@OptIn(ExperimentalForeignApi::class)
override suspend fun providePermission() {
Expand All @@ -45,11 +45,14 @@ internal class BluetoothPermissionDelegate(

val state: CBManagerState = if (isNotDetermined) {
suspendCoroutine { continuation ->
CBCentralManager(object : NSObject(), CBCentralManagerDelegateProtocol {
override fun centralManagerDidUpdateState(central: CBCentralManager) {
continuation.resume(central.state)
}
}, null)
CBCentralManager(
object : NSObject(), CBCentralManagerDelegateProtocol {
override fun centralManagerDidUpdateState(central: CBCentralManager) {
continuation.resume(central.state)
}
},
null
)
}
} else {
CBCentralManager().state
Expand Down Expand Up @@ -91,7 +94,8 @@ internal class BluetoothPermissionDelegate(
return when (state) {
CBManagerStatePoweredOn -> PermissionState.Granted
CBManagerStateUnauthorized, CBManagerStatePoweredOff,
CBManagerStateResetting, CBManagerStateUnsupported -> PermissionState.DeniedAlways
CBManagerStateResetting, CBManagerStateUnsupported,
-> PermissionState.DeniedAlways

CBManagerStateUnknown -> PermissionState.NotDetermined
else -> error("unknown state $state")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
package dev.icerock.moko.permissions.compose

import android.content.Context
import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
import dev.icerock.moko.permissions.PermissionsController

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ actual val contactsDelegate = object : PermissionDelegate {
Manifest.permission.WRITE_CONTACTS
)
}

actual val readContactsDelegate = object : PermissionDelegate {
override fun getPermissionStateOverride(applicationContext: Context) = null

override fun getPlatformPermission() =
listOf(
Manifest.permission.READ_CONTACTS
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,28 @@ import dev.icerock.moko.permissions.Permission
import dev.icerock.moko.permissions.PermissionDelegate

internal expect val contactsDelegate: PermissionDelegate
internal expect val readContactsDelegate: PermissionDelegate

/**
* Permission to read and write contacts.
*
* On Android, declare both `READ_CONTACTS` and `WRITE_CONTACTS` permissions
* in `AndroidManifest.xml`
*/
object ContactPermission : Permission {
override val delegate get() = contactsDelegate
}

/**
* Permission to read contacts
*
* On Android, declare `READ_CONTACTS` permission in `AndroidManifest.xml`
*
* On iOS this permission is the same with [ContactPermission]
*/
object ReadContactPermission : Permission {
override val delegate get() = readContactsDelegate
}

val Permission.Companion.CONTACTS get() = ContactPermission
val Permission.Companion.READ_CONTACTS get() = ReadContactPermission
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ private class ContactsPermissionDelegate(
}
}

// declared as constant because at now we use kotlin 1.9.10 that not know about
// declared as constant because at now we use kotlin 1.9.10 that not know about
// platform.Contacts.CNAuthorizationStatusLimited
@Suppress("TopLevelPropertyNaming")
private const val CNAuthorizationStatusLimited: Long = 4

actual val contactsDelegate: PermissionDelegate = ContactsPermissionDelegate(ContactPermission)
actual val readContactsDelegate: PermissionDelegate = ContactsPermissionDelegate(ReadContactPermission)
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ internal class GalleryPermissionDelegate : PermissionDelegate {
}

private fun requestGalleryAccess(callback: (PHAuthorizationStatus) -> Unit) {
PHPhotoLibrary.requestAuthorization(mainContinuation { status: PHAuthorizationStatus ->
callback(status)
})
PHPhotoLibrary.requestAuthorization(
mainContinuation { status: PHAuthorizationStatus ->
callback(status)
}
)
}

actual val galleryDelegate: PermissionDelegate = GalleryPermissionDelegate()
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ package dev.icerock.moko.permissions.location

import dev.icerock.moko.permissions.DeniedAlwaysException
import dev.icerock.moko.permissions.Permission
import dev.icerock.moko.permissions.PermissionState
import dev.icerock.moko.permissions.PermissionDelegate
import dev.icerock.moko.permissions.PermissionState
import platform.CoreLocation.CLAuthorizationStatus
import platform.CoreLocation.CLLocationManager
import platform.CoreLocation.kCLAuthorizationStatusAuthorizedAlways
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ private class MotionPermissionDelegate : PermissionDelegate {
val status = CMMotionActivityManager.authorizationStatus()
return when (status) {
CMAuthorizationStatusAuthorized,
CMAuthorizationStatusRestricted,
-> PermissionState.Granted

CMAuthorizationStatusRestricted, -> PermissionState.Granted
CMAuthorizationStatusDenied -> PermissionState.DeniedAlways
CMAuthorizationStatusNotDetermined -> PermissionState.NotDetermined
else -> error("unknown motion authorization status $status")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,14 @@ internal class RemoteNotificationPermissionDelegate : PermissionDelegate {
settings?.authorizationStatus ?: UNAuthorizationStatusNotDetermined
)
)
})
}
)
}
return when (status) {
UNAuthorizationStatusAuthorized,
UNAuthorizationStatusProvisional,
UNAuthorizationStatusEphemeral -> PermissionState.Granted
UNAuthorizationStatusEphemeral,
-> PermissionState.Granted

UNAuthorizationStatusNotDetermined -> PermissionState.NotDetermined
UNAuthorizationStatusDenied -> PermissionState.DeniedAlways
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ expect abstract class PermissionsControllerMock constructor() : PermissionsContr

fun createPermissionControllerMock(
allow: Set<Permission> = emptySet(),
granted: Set<Permission> = emptySet()
granted: Set<Permission> = emptySet(),
): PermissionsControllerMock = object : PermissionsControllerMock() {
private val granted = mutableSetOf<Permission>().apply { addAll(granted) }

Expand All @@ -40,7 +40,10 @@ fun createPermissionControllerMock(
override fun openAppSettings() = Unit

override suspend fun getPermissionState(permission: Permission): PermissionState {
return if (isPermissionGranted(permission)) PermissionState.Granted
else PermissionState.NotDetermined
return if (isPermissionGranted(permission)) {
PermissionState.Granted
} else {
PermissionState.NotDetermined
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ class PermissionsControllerImpl(
launcherHolder.filterNotNull().first()
} ?: error(
"activityResultLauncher is null, `bind` function was never called," +
" consider calling permissionsController.bind(activity)" +
" or BindEffect(permissionsController) in the composable function," +
" check the documentation for more info: " +
"https://github.com/icerockdev/moko-permissions/blob/master/README.md"
" consider calling permissionsController.bind(activity)" +
" or BindEffect(permissionsController) in the composable function," +
" check the documentation for more info: " +
"https://github.com/icerockdev/moko-permissions/blob/master/README.md"
)
}

Expand All @@ -139,10 +139,10 @@ class PermissionsControllerImpl(
activityHolder.filterNotNull().first()
} ?: error(
"activity is null, `bind` function was never called," +
" consider calling permissionsController.bind(activity)" +
" or BindEffect(permissionsController) in the composable function," +
" check the documentation for more info: " +
"https://github.com/icerockdev/moko-permissions/blob/master/README.md"
" consider calling permissionsController.bind(activity)" +
" or BindEffect(permissionsController) in the composable function," +
" check the documentation for more info: " +
"https://github.com/icerockdev/moko-permissions/blob/master/README.md"
)
}

Expand All @@ -163,8 +163,11 @@ class PermissionsControllerImpl(
val isAllRequestRationale: Boolean = permissions.all {
shouldShowRequestPermissionRationale(it)
}
return if (isAllRequestRationale) PermissionState.Denied
else PermissionState.NotGranted
return if (isAllRequestRationale) {
PermissionState.Denied
} else {
PermissionState.NotGranted
}
}

private suspend fun shouldShowRequestPermissionRationale(permission: String): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,4 @@ class PermissionsController : PermissionsControllerProtocol {
val settingsUrl: NSURL = NSURL.URLWithString(UIApplicationOpenSettingsURLString)!!
UIApplication.sharedApplication.openURL(settingsUrl, mapOf<Any?, Any>(), null)
}

}
2 changes: 2 additions & 0 deletions sample/android-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ plugins {
}

android {
namespace = "com.icerockdev"

defaultConfig {
applicationId = "dev.icerock.moko.samples.permissions"

Expand Down
2 changes: 2 additions & 0 deletions sample/compose-android-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ plugins {
}

android {
namespace = "com.icerockdev"

defaultConfig {
applicationId = "dev.icerock.moko.samples.permissions"
minSdk = 21
Expand Down
4 changes: 2 additions & 2 deletions sample/ios-app/TestProj.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
287627FB1F319065007FA12B /* Sources */,
287627FC1F319065007FA12B /* Frameworks */,
287627FD1F319065007FA12B /* Resources */,
71F325B8711762B9691FFE9F /* [CP] Embed Pods Frameworks */,
27BD989196B22F0F966AEC23 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -171,7 +171,7 @@
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
71F325B8711762B9691FFE9F /* [CP] Embed Pods Frameworks */ = {
27BD989196B22F0F966AEC23 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
Expand Down
4 changes: 4 additions & 0 deletions sample/mpp-library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ plugins {
id("dev.icerock.moko.gradle.detekt")
}

android {
namespace = "com.icerockdev.library"
}

dependencies {
commonMainImplementation(libs.coroutines)

Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
* Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

enableFeaturePreview("VERSION_CATALOGS")
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

dependencyResolutionManagement {
repositories {
mavenCentral()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}

Expand Down