Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a216eae
Add `ReadContactPermission` to handle read-only contact permissions s…
ciprig Mar 18, 2025
de9c081
Fix build
Jun 10, 2025
163899d
Comments fix
Lobynya Jun 24, 2025
e38b9c8
podfile fix
Lobynya Jun 24, 2025
23a41d0
java version fix
Lobynya Jun 24, 2025
1f6a805
gradle fix
Lobynya Jun 24, 2025
17be31e
remove .ds_store
Lobynya Jun 24, 2025
fb3ab22
detekt fix
Lobynya Jun 24, 2025
3855b3e
detekt fix
Lobynya Jun 24, 2025
dcf37cd
remove jetbrains maven
Lobynya Jun 24, 2025
0cb008e
Merge branch 'out' into add-read-contact-permission
Lobynya Jun 24, 2025
d00bbc1
detekt fix
Lobynya Jun 24, 2025
e679e12
Merge pull request #170 from Lobynya/sample-fix
Alex009 Jun 24, 2025
057a2a0
Merge pull request #171 from Lobynya/add-read-contact-permission
Alex009 Jun 24, 2025
180dd9f
fix: iOS background location permission state returns granted when on…
ryancfogarty Jun 25, 2025
f9a515c
fix: requesting background location permissions
ryancfogarty Jun 26, 2025
b6af10a
:sparkles: Fix notification permission throwing unnecessary exception
Jul 4, 2025
a512e3f
Merge pull request #172 from ryancfogarty/patch-ios-background-locati…
Alex009 Aug 18, 2025
a1c5c8b
Merge pull request #175 from MaxMichel2/fix_notifications_permission_ios
Alex009 Aug 18, 2025
afd4daf
#177 fix ios location permission request loop
Alex009 Aug 18, 2025
54b370f
up version and migrate to central publishing
Alex009 Aug 18, 2025
514ff71
fix tests
Alex009 Aug 18, 2025
7000436
Merge pull request #179 from icerockdev/#177-stuck-location-ios
Alex009 Aug 18, 2025
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
4 changes: 2 additions & 2 deletions .github/workflows/compilation-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ jobs:

steps:
- uses: actions/checkout@v1
- name: Set up JDK 11
- name: Set up JDK 17
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
11 changes: 7 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ jobs:

steps:
- uses: actions/checkout@v1
- name: Set up JDK 11
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build and publish to Bintray
run: ./gradlew publish
java-version: 17
- name: Prebuild library
run: ./gradlew publishToMavenLocal
- name: Publish
run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository

release:
name: Create release
needs: publish
Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,23 @@ allprojects {
project **build.gradle**
```groovy
dependencies {
commonMainApi("dev.icerock.moko:permissions:0.19.1")
commonMainApi("dev.icerock.moko:permissions:0.20.0")

// specific permissions support
commonMainImplementation("dev.icerock.moko:permissions-bluetooth:0.19.1")
commonMainImplementation("dev.icerock.moko:permissions-camera:0.19.1")
commonMainImplementation("dev.icerock.moko:permissions-contacts:0.19.1")
commonMainImplementation("dev.icerock.moko:permissions-gallery:0.19.1")
commonMainImplementation("dev.icerock.moko:permissions-location:0.19.1")
commonMainImplementation("dev.icerock.moko:permissions-microphone:0.19.1")
commonMainImplementation("dev.icerock.moko:permissions-motion:0.19.1")
commonMainImplementation("dev.icerock.moko:permissions-notifications:0.19.1")
commonMainImplementation("dev.icerock.moko:permissions-storage:0.19.1")
commonMainImplementation("dev.icerock.moko:permissions-bluetooth:0.20.0")
commonMainImplementation("dev.icerock.moko:permissions-camera:0.20.0")
commonMainImplementation("dev.icerock.moko:permissions-contacts:0.20.0")
commonMainImplementation("dev.icerock.moko:permissions-gallery:0.20.0")
commonMainImplementation("dev.icerock.moko:permissions-location:0.20.0")
commonMainImplementation("dev.icerock.moko:permissions-microphone:0.20.0")
commonMainImplementation("dev.icerock.moko:permissions-motion:0.20.0")
commonMainImplementation("dev.icerock.moko:permissions-notifications:0.20.0")
commonMainImplementation("dev.icerock.moko:permissions-storage:0.20.0")

// compose multiplatform
commonMainApi("dev.icerock.moko:permissions-compose:0.19.1") // permissions api + compose extensions
commonMainApi("dev.icerock.moko:permissions-compose:0.20.0") // permissions api + compose extensions

commonTestImplementation("dev.icerock.moko:permissions-test:0.19.1")
commonTestImplementation("dev.icerock.moko:permissions-test:0.20.0")
}
```

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
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ androidLifecycleVersion = "2.2.0"
androidCoreTestingVersion = "2.2.0"
coroutinesVersion = "1.6.4"
mokoMvvmVersion = "0.16.0"
mokoPermissionsVersion = "0.19.1"
composeJetBrainsVersion = "1.5.1"
mokoPermissionsVersion = "0.20.0"
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.5.1" }
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 @@ -8,7 +8,10 @@ import platform.CoreLocation.CLAuthorizationStatus
import platform.CoreLocation.CLLocationManager
import platform.CoreLocation.CLLocationManagerDelegateProtocol
import platform.darwin.NSObject
import kotlin.experimental.ExperimentalObjCName

@OptIn(ExperimentalObjCName::class)
@ObjCName("LocationManagerDelegate")
internal class LocationManagerDelegate : NSObject(), CLLocationManagerDelegateProtocol {
private var callback: ((CLAuthorizationStatus) -> Unit)? = null

Expand All @@ -18,17 +21,25 @@ internal class LocationManagerDelegate : NSObject(), CLLocationManagerDelegatePr
locationManager.delegate = this
}

fun requestLocationAccess(callback: (CLAuthorizationStatus) -> Unit) {
fun authorizationStatus(): CLAuthorizationStatus {
return locationManager.authorizationStatus
}

fun requestWhenInUseAuthorization(callback: (CLAuthorizationStatus) -> Unit) {
this.callback = callback

locationManager.requestWhenInUseAuthorization()
}

override fun locationManager(
manager: CLLocationManager,
didChangeAuthorizationStatus: CLAuthorizationStatus
) {
callback?.invoke(didChangeAuthorizationStatus)
fun requestAlwaysAuthorization(callback: (CLAuthorizationStatus) -> Unit) {
this.callback = callback

locationManager.requestAlwaysAuthorization()
}

override fun locationManagerDidChangeAuthorization(manager: CLLocationManager) {
val authorizationStatus: CLAuthorizationStatus = manager.authorizationStatus
callback?.invoke(authorizationStatus)
callback = null
}
}
Loading
Loading