Skip to content

Commit cd4eaf9

Browse files
author
Alexey Nesterov
committed
Remove appCompat dependency
1 parent 52631e4 commit cd4eaf9

File tree

9 files changed

+96
-153
lines changed

9 files changed

+96
-153
lines changed

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mokoMvvmVersion = "0.16.0"
1111
mokoPermissionsVersion = "0.17.0"
1212
composeJetBrainsVersion = "1.3.1"
1313
lifecycleRuntime = "2.6.1"
14+
activityKtxVersion = "1.7.2"
1415

1516
[libraries]
1617
appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidAppCompatVersion" }
@@ -33,3 +34,4 @@ mobileMultiplatformGradlePlugin = { module = "dev.icerock:mobile-multiplatform",
3334
kotlinSerializationGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlinVersion" }
3435
composeJetBrainsGradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "composeJetBrainsVersion" }
3536
detektGradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version = "1.22.0" }
37+
activityKtx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtxVersion" }

permissions-compose/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,5 @@ dependencies {
2222
commonMainApi(projects.permissions)
2323
commonMainApi(compose.runtime)
2424

25-
androidMainImplementation(libs.appCompat)
2625
androidMainImplementation(libs.composeActivity)
2726
}

permissions-compose/src/androidMain/kotlin/dev/icerock/moko/permissions/compose/BindEffect.android.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import androidx.compose.runtime.Composable
99
import androidx.compose.runtime.LaunchedEffect
1010
import androidx.compose.ui.platform.LocalContext
1111
import androidx.compose.ui.platform.LocalLifecycleOwner
12-
import androidx.fragment.app.FragmentActivity
13-
import androidx.fragment.app.FragmentManager
12+
import androidx.activity.ComponentActivity
1413
import androidx.lifecycle.LifecycleOwner
1514
import dev.icerock.moko.permissions.PermissionsController
1615

@@ -21,10 +20,10 @@ actual fun BindEffect(permissionsController: PermissionsController) {
2120
val context: Context = LocalContext.current
2221

2322
LaunchedEffect(permissionsController, lifecycleOwner, context) {
24-
val fragmentManager: FragmentManager = checkNotNull(context as? FragmentActivity) {
25-
"$context context is not instance of FragmentActivity"
26-
}.supportFragmentManager
23+
val activity: ComponentActivity = checkNotNull(context as? ComponentActivity) {
24+
"$context context is not instance of ComponentActivity"
25+
}
2726

28-
permissionsController.bind(lifecycleOwner.lifecycle, fragmentManager)
27+
permissionsController.bind(activity)
2928
}
3029
}

permissions-test/src/androidMain/kotlin/dev/icerock/moko/permissions/test/PermissionsControllerMock.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package dev.icerock.moko.permissions.test
66

7+
import androidx.activity.ComponentActivity
78
import dev.icerock.moko.permissions.Permission
89
import dev.icerock.moko.permissions.PermissionsController
910

@@ -13,8 +14,7 @@ actual abstract class PermissionsControllerMock : PermissionsController {
1314
actual abstract override suspend fun isPermissionGranted(permission: Permission): Boolean
1415

1516
override fun bind(
16-
lifecycle: androidx.lifecycle.Lifecycle,
17-
fragmentManager: androidx.fragment.app.FragmentManager
17+
activity: ComponentActivity
1818
) {
1919
TODO("Not yet implemented")
2020
}

permissions/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ android {
1515

1616
dependencies {
1717
commonMainImplementation(libs.coroutines)
18-
androidMainImplementation(libs.appCompat)
18+
androidMainImplementation(libs.activityKtx)
1919
androidMainImplementation(libs.lifecycleRuntime)
2020
}

permissions/src/androidMain/kotlin/dev/icerock/moko/permissions/PermissionsController.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,21 @@
55
package dev.icerock.moko.permissions
66

77
import android.content.Context
8-
import androidx.fragment.app.FragmentManager
9-
import androidx.lifecycle.Lifecycle
8+
import androidx.activity.ComponentActivity
109

1110
actual interface PermissionsController {
1211
actual suspend fun providePermission(permission: Permission)
1312
actual suspend fun isPermissionGranted(permission: Permission): Boolean
1413
actual suspend fun getPermissionState(permission: Permission): PermissionState
1514
actual fun openAppSettings()
1615

17-
fun bind(lifecycle: Lifecycle, fragmentManager: FragmentManager)
16+
fun bind(activity: ComponentActivity)
1817

1918
companion object {
2019
operator fun invoke(
21-
resolverFragmentTag: String = "PermissionsControllerResolver",
2220
applicationContext: Context
2321
): PermissionsController {
2422
return PermissionsControllerImpl(
25-
resolverFragmentTag = resolverFragmentTag,
2623
applicationContext = applicationContext
2724
)
2825
}

permissions/src/androidMain/kotlin/dev/icerock/moko/permissions/PermissionsControllerImpl.kt

Lines changed: 83 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,64 +5,112 @@
55
package dev.icerock.moko.permissions
66

77
import android.Manifest
8+
import android.app.Activity
89
import android.content.Context
10+
import android.content.ContextWrapper
911
import android.content.Intent
1012
import android.content.pm.PackageManager
1113
import android.net.Uri
1214
import android.os.Build
1315
import android.provider.Settings
16+
import androidx.activity.result.ActivityResultLauncher
17+
import androidx.activity.result.ActivityResultRegistryOwner
18+
import androidx.activity.result.contract.ActivityResultContracts
19+
import androidx.core.app.ActivityCompat
20+
import androidx.activity.ComponentActivity
1421
import androidx.core.app.NotificationManagerCompat
1522
import androidx.core.content.ContextCompat
16-
import androidx.fragment.app.Fragment
17-
import androidx.fragment.app.FragmentManager
1823
import androidx.lifecycle.Lifecycle
1924
import androidx.lifecycle.LifecycleEventObserver
2025
import androidx.lifecycle.LifecycleOwner
2126
import kotlinx.coroutines.flow.MutableStateFlow
22-
import kotlinx.coroutines.flow.filterNotNull
23-
import kotlinx.coroutines.flow.first
2427
import kotlinx.coroutines.sync.Mutex
2528
import kotlinx.coroutines.sync.withLock
26-
import kotlinx.coroutines.withTimeoutOrNull
29+
import java.util.UUID
2730
import kotlin.coroutines.suspendCoroutine
2831

2932
@Suppress("TooManyFunctions")
3033
class PermissionsControllerImpl(
31-
private val resolverFragmentTag: String = "PermissionsControllerResolver",
3234
private val applicationContext: Context,
3335
) : PermissionsController {
34-
private val fragmentManagerHolder = MutableStateFlow<FragmentManager?>(null)
36+
private val activityHolder = MutableStateFlow<Activity?>(null)
37+
3538
private val mutex: Mutex = Mutex()
3639

37-
override fun bind(lifecycle: Lifecycle, fragmentManager: FragmentManager) {
38-
this.fragmentManagerHolder.value = fragmentManager
40+
private var launcher: ActivityResultLauncher<Array<String>>? = null
41+
42+
private var permissionCallback: PermissionCallback? = null
43+
44+
override fun bind(activity: ComponentActivity) {
45+
this.activityHolder.value = activity
46+
val activityResultRegistryOwner = activity as ActivityResultRegistryOwner
47+
48+
val key = UUID.randomUUID().toString()
49+
50+
launcher = activityResultRegistryOwner.activityResultRegistry.register(
51+
key,
52+
ActivityResultContracts.RequestMultiplePermissions()
53+
) { permissions ->
54+
val isCancelled = permissions.isEmpty()
55+
56+
val permissionCallback = permissionCallback ?: return@register
57+
58+
if (isCancelled) {
59+
permissionCallback.callback.invoke(
60+
Result.failure(RequestCanceledException(permissionCallback.permission))
61+
)
62+
return@register
63+
}
64+
65+
val success = permissions.values.all { it }
66+
67+
if (success) {
68+
permissionCallback.callback.invoke(Result.success(Unit))
69+
} else {
70+
if (shouldShowRequestPermissionRationale(permissions.keys.first())) {
71+
permissionCallback.callback.invoke(
72+
Result.failure(DeniedException(permissionCallback.permission))
73+
)
74+
} else {
75+
permissionCallback.callback.invoke(
76+
Result.failure(DeniedAlwaysException(permissionCallback.permission))
77+
)
78+
}
79+
}
80+
}
3981

4082
val observer = object : LifecycleEventObserver {
4183
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
4284
if (event == Lifecycle.Event.ON_DESTROY) {
43-
this@PermissionsControllerImpl.fragmentManagerHolder.value = null
85+
this@PermissionsControllerImpl.activityHolder.value = null
4486
source.lifecycle.removeObserver(this)
4587
}
4688
}
4789
}
48-
lifecycle.addObserver(observer)
90+
activity.lifecycle.addObserver(observer)
4991
}
5092

5193
override suspend fun providePermission(permission: Permission) {
5294
mutex.withLock {
53-
val fragmentManager: FragmentManager = awaitFragmentManager()
54-
val resolverFragment: ResolverFragment = getOrCreateResolverFragment(fragmentManager)
55-
5695
val platformPermission = permission.toPlatformPermission()
5796
suspendCoroutine { continuation ->
58-
resolverFragment.requestPermission(
97+
requestPermission(
5998
permission,
6099
platformPermission
61100
) { continuation.resumeWith(it) }
62101
}
63102
}
64103
}
65104

105+
private fun requestPermission(
106+
permission: Permission,
107+
permissions: List<String>,
108+
callback: (Result<Unit>) -> Unit
109+
) {
110+
permissionCallback = PermissionCallback(permission, callback)
111+
launcher?.launch(permissions.toTypedArray())
112+
}
113+
66114
override suspend fun isPermissionGranted(permission: Permission): Boolean {
67115
return getPermissionState(permission) == PermissionState.Granted
68116
}
@@ -87,16 +135,27 @@ class PermissionsControllerImpl(
87135
val isAllGranted: Boolean = status.all { it == PackageManager.PERMISSION_GRANTED }
88136
if (isAllGranted) return PermissionState.Granted
89137

90-
val fragmentManager: FragmentManager = awaitFragmentManager()
91-
val resolverFragment: ResolverFragment = getOrCreateResolverFragment(fragmentManager)
92-
93138
val isAllRequestRationale: Boolean = permissions.all {
94-
!resolverFragment.shouldShowRequestPermissionRationale(it)
139+
shouldShowRequestPermissionRationale(it).not()
95140
}
96141
return if (isAllRequestRationale) PermissionState.NotDetermined
97142
else PermissionState.Denied
98143
}
99144

145+
private fun shouldShowRequestPermissionRationale(permission: String): Boolean {
146+
val activity: Activity = checkNotNull(this.activityHolder.value) {
147+
"${this.activityHolder.value} activity is null, `bind` function was never called," +
148+
" consider calling permissionsController.bind(activity)" +
149+
" or BindEffect(permissionsController) in the composable function," +
150+
" check the documentation for more info: " +
151+
"https://github.com/icerockdev/moko-permissions/blob/master/README.md"
152+
}
153+
return ActivityCompat.shouldShowRequestPermissionRationale(
154+
activity,
155+
permission
156+
)
157+
}
158+
100159
override fun openAppSettings() {
101160
val intent = Intent().apply {
102161
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
@@ -106,35 +165,6 @@ class PermissionsControllerImpl(
106165
applicationContext.startActivity(intent)
107166
}
108167

109-
private suspend fun awaitFragmentManager(): FragmentManager {
110-
val fragmentManager: FragmentManager? = fragmentManagerHolder.value
111-
if (fragmentManager != null) return fragmentManager
112-
113-
return withTimeoutOrNull(AWAIT_FRAGMENT_MANAGER_TIMEOUT_DURATION_MS) {
114-
fragmentManagerHolder.filterNotNull().first()
115-
} ?: error(
116-
"fragmentManager is null, `bind` function was never called," +
117-
" consider calling permissionsController.bind(lifecycle, fragmentManager)" +
118-
" or BindEffect(permissionsController) in the composable function," +
119-
" check the documentation for more info: " +
120-
"https://github.com/icerockdev/moko-permissions/blob/master/README.md"
121-
)
122-
}
123-
124-
private fun getOrCreateResolverFragment(fragmentManager: FragmentManager): ResolverFragment {
125-
val currentFragment: Fragment? = fragmentManager.findFragmentByTag(resolverFragmentTag)
126-
return if (currentFragment != null) {
127-
currentFragment as ResolverFragment
128-
} else {
129-
ResolverFragment().also { fragment ->
130-
fragmentManager
131-
.beginTransaction()
132-
.add(fragment, resolverFragmentTag)
133-
.commit()
134-
}
135-
}
136-
}
137-
138168
@Suppress("CyclomaticComplexMethod")
139169
private fun Permission.toPlatformPermission(): List<String> {
140170
return when (this) {
@@ -256,6 +286,10 @@ class PermissionsControllerImpl(
256286
private companion object {
257287
val VERSIONS_WITHOUT_NOTIFICATION_PERMISSION =
258288
Build.VERSION_CODES.KITKAT until Build.VERSION_CODES.TIRAMISU
259-
private const val AWAIT_FRAGMENT_MANAGER_TIMEOUT_DURATION_MS = 2000L
260289
}
261290
}
291+
292+
private class PermissionCallback(
293+
val permission: Permission,
294+
val callback: (Result<Unit>) -> Unit
295+
)

permissions/src/androidMain/kotlin/dev/icerock/moko/permissions/ResolverFragment.kt

Lines changed: 0 additions & 88 deletions
This file was deleted.

0 commit comments

Comments
 (0)