Skip to content

Commit

Permalink
refactor and locale support
Browse files Browse the repository at this point in the history
Changes:
* Refactoring, code cleanup
* Added support to different locales
* Improved docs
  • Loading branch information
lorenzofelletti committed Feb 14, 2023
1 parent 28deee6 commit 80b7d2d
Show file tree
Hide file tree
Showing 16 changed files with 184 additions and 122 deletions.
17 changes: 17 additions & 0 deletions .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 28 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
# Permissions
[![](https://jitpack.io/v/lorenzofelletti/permissions.svg)](https://jitpack.io/#lorenzofelletti/permissions)

## Easy permissions management library for Android
## Easy Permissions Management Library for Android

An easy to use permissions management library designed to be used with Kotlin.
An easy to use permissions management library written in Kotlin.

The library is deployed on JitPack here, follow the steps indicated in the link to add it to your project.
### Import the Library
The library is deployed on JitPack [here](https://jitpack.io/#lorenzofelletti/permissions).
To add it to your project, add to `settings.gradle`:
```Groovy
dependencyResolutionManagement {
...
repositories {
...
maven { url 'https://jitpack.io' }
}
}
```
And to your module level build.gradle:
```Groovy
dependencies {
...
implementation 'com.github.lorenzofelletti:permissions:0.4.2'
}
```

To use it in your project, just:
### Usage
To use the library in your project, just:
* Declare the permissions your app will use in your application's Manifest
* Example: add to the app `AndroidManifest.xml`
```xml
Expand Down Expand Up @@ -48,9 +67,7 @@ To use it in your project, just:
* Call `permissionManager.checkRequestAndDispatch` where you want to check for a set of permissions (and ask them if not granted)
* Example: in your Activity, where you want to check (and request) permissions add
```Kotlin
permissionManager.checkRequestAndDispatch(
POSITION_REQUEST_CODE
)
permissionManager checkRequestAndDispatch POSITION_REQUEST_CODE
```
* Override `onRequestPermissionsResult` and call `PermissionsUtilities.dispatchOnRequestPermissionsResult` in it
* Example: inside your `MainActivity`
Expand All @@ -64,3 +81,7 @@ To use it in your project, just:
permissionManager.dispatchOnRequestPermissionsResult(requestCode, grantResults)
}
```

## Contributing
All kinds of contributions are welcome (bug reports, feature requests, pull requests, etc.).
Suggestions and improvements on documentation, tests, code quality, translations, etc. are also welcome.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
id 'com.android.application' version '7.4.1' apply false
id 'com.android.library' version '7.4.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Mon Dec 05 12:12:35 CET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ class PermissionManager(val activity: Activity) {
*/
@PermissionDispatcherDsl
infix fun buildRequestResultsDispatcher(init: RequestResultsDispatcher.() -> Unit) {
dispatcher = RequestResultsDispatcher(this).apply(init)
dispatcher = RequestResultsDispatcher(this)
dispatcher.apply(init)
}

/**
Expand All @@ -63,35 +64,11 @@ class PermissionManager(val activity: Activity) {
* [RequestResultsDispatcher] is dispatched.
*
* @param requestCode The request code associated to the permissions
* @param comingFromRationale true if the method is called from the rationale, defaults to false
*
* @throws UnhandledRequestCodeException if the request code is not handled by the dispatcher
*/
fun checkRequestAndDispatch(requestCode: Int, comingFromRationale: Boolean = false) {
val permissions =
dispatcher.getPermissions(requestCode) ?: throw UnhandledRequestCodeException(
requestCode
)
val permissionsNotGranted = permissions.filter { permission ->
ActivityCompat.checkSelfPermission(
activity, permission
) != PackageManager.PERMISSION_GRANTED

}.toTypedArray()

if (permissionsNotGranted.isEmpty()) {
// All permissions are granted
dispatcher.getOnGranted(requestCode)?.invoke()
} else {
// Some permissions are not granted
val shouldShowRationale = permissionsNotGranted.any { permission ->
shouldShowRequestPermissionRationale(activity, permission)
}

if (shouldShowRationale && !comingFromRationale) {
dispatchRationale(permissionsNotGranted, requestCode)
} else {
ActivityCompat.requestPermissions(activity, permissionsNotGranted, requestCode)
}
}
infix fun checkRequestAndDispatch(requestCode: Int) {
checkRequestAndDispatch(requestCode, false)
}

/**
Expand All @@ -103,41 +80,63 @@ class PermissionManager(val activity: Activity) {
* If all the permissions are already granted, the action associated to onGranted in the
* [RequestResultsDispatcher] is dispatched.
*
* @param requestCode The request code associated to the permissions
*/
infix fun checkRequestAndDispatch(requestCode: Int) {
checkRequestAndDispatch(requestCode, false)
}

/**
* Checks whether a set of permissions is granted or not.
* Note: `comingFromRationale` is used internally to avoid showing the rationale dialog in loop.
*
* @param permissions The permissions to be checked
* @param requestCode The request code associated to the permissions
* @param comingFromRationale true if the method is called from the rationale, defaults to false
*
* @return true if all permissions are granted, false otherwise
* @throws UnhandledRequestCodeException if the request code is not handled by the dispatcher
*/
fun checkPermissionsGranted(permissions: Array<out String>): Boolean {
for (permission in permissions) {
if (!checkPermissionGranted(activity, permission)) return false
internal fun checkRequestAndDispatch(requestCode: Int, comingFromRationale: Boolean = false) {
val permissionsNotGranted = dispatcher.getPermissions(requestCode)?.filter { permission ->
ActivityCompat.checkSelfPermission(
activity, permission
) != PackageManager.PERMISSION_GRANTED
}?.toTypedArray() ?: throw UnhandledRequestCodeException(requestCode, activity)

if (permissionsNotGranted.isEmpty()) {
// All permissions are granted
dispatcher.dispatchOnGranted(requestCode)
} else {
// Some permissions are not granted
dispatchSomePermissionsNotGranted(
permissionsNotGranted,
requestCode,
comingFromRationale
)
}
return true
}

/**
* Checks whether the permissions for a given request code are granted or not.
* Throws an [UnhandledRequestCodeException] if the request code is not handled.
* Dispatches the case in which some permissions are not granted
*
* @param requestCode The request code associated to the permissions to be checked
* @return true if all permissions are granted, false otherwise
* @param permissionsNotGranted
* @param requestCode
* @param comingFromRationale
*/
fun checkPermissionsGranted(requestCode: Int): Boolean {
val permissions =
dispatcher.getPermissions(requestCode) ?: throw UnhandledRequestCodeException(
requestCode
)
return checkPermissionsGranted(permissions)
private fun dispatchSomePermissionsNotGranted(
permissionsNotGranted: Array<out String>,
requestCode: Int,
comingFromRationale: Boolean
) {
// if not coming from rationale, gets the list of permissions that require rationale to be shown
val permissionsRequiringRationale =
if (!comingFromRationale) getPermissionsRequiringRationale(permissionsNotGranted) else emptyList()

// if some permissions require rationale, show rationale, otherwise ask for permissions
// Note: if coming from rationale, the list will be empty, so the else branch will be executed
if (permissionsRequiringRationale.isNotEmpty()) {
dispatcher.showRationale(requestCode, permissionsRequiringRationale)
} else {
ActivityCompat.requestPermissions(activity, permissionsNotGranted, requestCode)
}
}

private fun getPermissionsRequiringRationale(permissionsNotGranted: Array<out String>) =
permissionsNotGranted.filter { permission ->
shouldShowRequestPermissionRationale(activity, permission)
}.toList()

/**
* Checks the result of a permission request, and dispatches the appropriate action.
* The actions can be defined by the user by using the [buildRequestResultsDispatcher] function
Expand All @@ -152,42 +151,12 @@ class PermissionManager(val activity: Activity) {
dispatcher.dispatchAction(requestCode, grantResults)?.invoke()
}

/**
* Checks whether a permission is granted in the context.
*
* @param context The context to be used for checking the permission
* @param permission The permission to be checked
*
* @return true if the permission is granted, false otherwise
*/
private fun checkPermissionGranted(context: Context, permission: String): Boolean {
return ActivityCompat.checkSelfPermission(
context, permission
) == PackageManager.PERMISSION_GRANTED
}

/**
* Dispatches the rationale action associated to the request code, if any.
*
* @param permissionsNotGranted The permissions that are not granted
* @param requestCode The request code associated to the permissions
*/
private fun dispatchRationale(permissionsNotGranted: Array<out String>, requestCode: Int) {
/* if a rationale is defined, dispatch it, otherwise it calls checkRequestAndDispatch with
* comingFromRationale = true */
val toInvoke = dispatcher.getOnShowRationale(requestCode) ?: fun(
_: List<String>, requestCode: Int
) {
checkRequestAndDispatch(requestCode, true)
}
toInvoke.invoke(permissionsNotGranted.toList(), requestCode)
}
}

/**
* Exception thrown when the request code is not handled by the [RequestResultsDispatcher] object.
*/
class UnhandledRequestCodeException(requestCode: Int) : Throwable() {
class UnhandledRequestCodeException(requestCode: Int, context: Context) : Throwable() {
override val message: String =
"Request code $requestCode is not handled by the RequestResultsDispatcher object. Please add a withRequestCode block to the buildRequestResultsDispatcher function."
context.getString(R.string.unhandled_request_code_exception_message, requestCode)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.lorenzofelletti.permissions.dispatcher

import com.lorenzofelletti.permissions.PermissionManager
import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcher


Expand All @@ -10,13 +9,12 @@ import com.lorenzofelletti.permissions.dispatcher.dsl.PermissionDispatcher
* in case the permissions are granted or denied.
*/
class DispatcherEntry(
internal val manager: PermissionManager, val requestCode: Int
internal val dispatcher: RequestResultsDispatcher, val requestCode: Int
) : PermissionDispatcher() {
/**
* The permissions associated to this entry
*/
lateinit var permissions: Array<out String>
internal set
private lateinit var permissions: Array<out String>

/**
* The action to be dispatched if the permissions are granted.
Expand All @@ -36,6 +34,12 @@ class DispatcherEntry(
var onShowRationale: ((List<String>, requestCode: Int) -> Unit)? = null
internal set

internal fun setPermissions(permissions: Array<out String>) {
this.permissions = permissions
}

internal fun getPermissions(): Array<out String> = permissions

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,23 @@ class RequestResultsDispatcher(internal val manager: PermissionManager) : Permis
}

internal fun getPermissions(requestCode: Int): Array<out String>? =
entries[requestCode]?.permissions
entries[requestCode]?.getPermissions()

internal fun getOnGranted(requestCode: Int): (() -> Unit)? = entries[requestCode]?.onGranted

internal fun getOnDenied(requestCode: Int): (() -> Unit)? = entries[requestCode]?.onDenied
internal fun dispatchOnGranted(requestCode: Int) {
entries[requestCode]?.onGranted?.invoke()
}

internal fun getOnShowRationale(requestCode: Int): ((List<String>, Int) -> Unit)? =
entries[requestCode]?.onShowRationale
/**
* Shows the rationale for the permissions associated to the given request code, otherwise
* it calls [PermissionManager.checkRequestAndDispatch] with `comingFromRationale = true`.
*
* @param requestCode The request code associated to the permissions
* @param permissions The permissions requiring a rationale
*/
internal fun showRationale(requestCode: Int, permissions: List<String>) {
entries[requestCode]?.onShowRationale?.invoke(permissions, requestCode)
?: manager.checkRequestAndDispatch(requestCode, true)
}

/**
* Checks the results of a permission request
Expand Down
Loading

0 comments on commit 80b7d2d

Please sign in to comment.