Skip to content

Commit

Permalink
Remove appCompat dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexey Nesterov committed Apr 17, 2024
1 parent 52631e4 commit cd4eaf9
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 153 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mokoMvvmVersion = "0.16.0"
mokoPermissionsVersion = "0.17.0"
composeJetBrainsVersion = "1.3.1"
lifecycleRuntime = "2.6.1"
activityKtxVersion = "1.7.2"

[libraries]
appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidAppCompatVersion" }
Expand All @@ -33,3 +34,4 @@ mobileMultiplatformGradlePlugin = { module = "dev.icerock:mobile-multiplatform",
kotlinSerializationGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlinVersion" }
composeJetBrainsGradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "composeJetBrainsVersion" }
detektGradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version = "1.22.0" }
activityKtx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtxVersion" }
1 change: 0 additions & 1 deletion permissions-compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,5 @@ dependencies {
commonMainApi(projects.permissions)
commonMainApi(compose.runtime)

androidMainImplementation(libs.appCompat)
androidMainImplementation(libs.composeActivity)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
import dev.icerock.moko.permissions.PermissionsController

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

LaunchedEffect(permissionsController, lifecycleOwner, context) {
val fragmentManager: FragmentManager = checkNotNull(context as? FragmentActivity) {
"$context context is not instance of FragmentActivity"
}.supportFragmentManager
val activity: ComponentActivity = checkNotNull(context as? ComponentActivity) {
"$context context is not instance of ComponentActivity"
}

permissionsController.bind(lifecycleOwner.lifecycle, fragmentManager)
permissionsController.bind(activity)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package dev.icerock.moko.permissions.test

import androidx.activity.ComponentActivity
import dev.icerock.moko.permissions.Permission
import dev.icerock.moko.permissions.PermissionsController

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

override fun bind(
lifecycle: androidx.lifecycle.Lifecycle,
fragmentManager: androidx.fragment.app.FragmentManager
activity: ComponentActivity
) {
TODO("Not yet implemented")
}
Expand Down
2 changes: 1 addition & 1 deletion permissions/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ android {

dependencies {
commonMainImplementation(libs.coroutines)
androidMainImplementation(libs.appCompat)
androidMainImplementation(libs.activityKtx)
androidMainImplementation(libs.lifecycleRuntime)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,21 @@
package dev.icerock.moko.permissions

import android.content.Context
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.activity.ComponentActivity

actual interface PermissionsController {
actual suspend fun providePermission(permission: Permission)
actual suspend fun isPermissionGranted(permission: Permission): Boolean
actual suspend fun getPermissionState(permission: Permission): PermissionState
actual fun openAppSettings()

fun bind(lifecycle: Lifecycle, fragmentManager: FragmentManager)
fun bind(activity: ComponentActivity)

companion object {
operator fun invoke(
resolverFragmentTag: String = "PermissionsControllerResolver",
applicationContext: Context
): PermissionsController {
return PermissionsControllerImpl(
resolverFragmentTag = resolverFragmentTag,
applicationContext = applicationContext
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,112 @@
package dev.icerock.moko.permissions

import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistryOwner
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.activity.ComponentActivity
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import java.util.UUID
import kotlin.coroutines.suspendCoroutine

@Suppress("TooManyFunctions")
class PermissionsControllerImpl(
private val resolverFragmentTag: String = "PermissionsControllerResolver",
private val applicationContext: Context,
) : PermissionsController {
private val fragmentManagerHolder = MutableStateFlow<FragmentManager?>(null)
private val activityHolder = MutableStateFlow<Activity?>(null)

private val mutex: Mutex = Mutex()

override fun bind(lifecycle: Lifecycle, fragmentManager: FragmentManager) {
this.fragmentManagerHolder.value = fragmentManager
private var launcher: ActivityResultLauncher<Array<String>>? = null

private var permissionCallback: PermissionCallback? = null

override fun bind(activity: ComponentActivity) {
this.activityHolder.value = activity
val activityResultRegistryOwner = activity as ActivityResultRegistryOwner

val key = UUID.randomUUID().toString()

launcher = activityResultRegistryOwner.activityResultRegistry.register(
key,
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val isCancelled = permissions.isEmpty()

val permissionCallback = permissionCallback ?: return@register

if (isCancelled) {
permissionCallback.callback.invoke(
Result.failure(RequestCanceledException(permissionCallback.permission))
)
return@register
}

val success = permissions.values.all { it }

if (success) {
permissionCallback.callback.invoke(Result.success(Unit))
} else {
if (shouldShowRequestPermissionRationale(permissions.keys.first())) {
permissionCallback.callback.invoke(
Result.failure(DeniedException(permissionCallback.permission))
)
} else {
permissionCallback.callback.invoke(
Result.failure(DeniedAlwaysException(permissionCallback.permission))
)
}
}
}

val observer = object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
this@PermissionsControllerImpl.fragmentManagerHolder.value = null
this@PermissionsControllerImpl.activityHolder.value = null
source.lifecycle.removeObserver(this)
}
}
}
lifecycle.addObserver(observer)
activity.lifecycle.addObserver(observer)
}

override suspend fun providePermission(permission: Permission) {
mutex.withLock {
val fragmentManager: FragmentManager = awaitFragmentManager()
val resolverFragment: ResolverFragment = getOrCreateResolverFragment(fragmentManager)

val platformPermission = permission.toPlatformPermission()
suspendCoroutine { continuation ->
resolverFragment.requestPermission(
requestPermission(
permission,
platformPermission
) { continuation.resumeWith(it) }
}
}
}

private fun requestPermission(
permission: Permission,
permissions: List<String>,
callback: (Result<Unit>) -> Unit
) {
permissionCallback = PermissionCallback(permission, callback)
launcher?.launch(permissions.toTypedArray())
}

override suspend fun isPermissionGranted(permission: Permission): Boolean {
return getPermissionState(permission) == PermissionState.Granted
}
Expand All @@ -87,16 +135,27 @@ class PermissionsControllerImpl(
val isAllGranted: Boolean = status.all { it == PackageManager.PERMISSION_GRANTED }
if (isAllGranted) return PermissionState.Granted

val fragmentManager: FragmentManager = awaitFragmentManager()
val resolverFragment: ResolverFragment = getOrCreateResolverFragment(fragmentManager)

val isAllRequestRationale: Boolean = permissions.all {
!resolverFragment.shouldShowRequestPermissionRationale(it)
shouldShowRequestPermissionRationale(it).not()
}
return if (isAllRequestRationale) PermissionState.NotDetermined
else PermissionState.Denied
}

private fun shouldShowRequestPermissionRationale(permission: String): Boolean {
val activity: Activity = checkNotNull(this.activityHolder.value) {
"${this.activityHolder.value} 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"
}
return ActivityCompat.shouldShowRequestPermissionRationale(
activity,
permission
)
}

override fun openAppSettings() {
val intent = Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
Expand All @@ -106,35 +165,6 @@ class PermissionsControllerImpl(
applicationContext.startActivity(intent)
}

private suspend fun awaitFragmentManager(): FragmentManager {
val fragmentManager: FragmentManager? = fragmentManagerHolder.value
if (fragmentManager != null) return fragmentManager

return withTimeoutOrNull(AWAIT_FRAGMENT_MANAGER_TIMEOUT_DURATION_MS) {
fragmentManagerHolder.filterNotNull().first()
} ?: error(
"fragmentManager is null, `bind` function was never called," +
" consider calling permissionsController.bind(lifecycle, fragmentManager)" +
" or BindEffect(permissionsController) in the composable function," +
" check the documentation for more info: " +
"https://github.com/icerockdev/moko-permissions/blob/master/README.md"
)
}

private fun getOrCreateResolverFragment(fragmentManager: FragmentManager): ResolverFragment {
val currentFragment: Fragment? = fragmentManager.findFragmentByTag(resolverFragmentTag)
return if (currentFragment != null) {
currentFragment as ResolverFragment
} else {
ResolverFragment().also { fragment ->
fragmentManager
.beginTransaction()
.add(fragment, resolverFragmentTag)
.commit()
}
}
}

@Suppress("CyclomaticComplexMethod")
private fun Permission.toPlatformPermission(): List<String> {
return when (this) {
Expand Down Expand Up @@ -256,6 +286,10 @@ class PermissionsControllerImpl(
private companion object {
val VERSIONS_WITHOUT_NOTIFICATION_PERMISSION =
Build.VERSION_CODES.KITKAT until Build.VERSION_CODES.TIRAMISU
private const val AWAIT_FRAGMENT_MANAGER_TIMEOUT_DURATION_MS = 2000L
}
}

private class PermissionCallback(
val permission: Permission,
val callback: (Result<Unit>) -> Unit
)

This file was deleted.

Loading

0 comments on commit cd4eaf9

Please sign in to comment.