From 139f2dd8207d3a9cd67157a3e3754a9982c7f69d Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 3 Jul 2017 20:09:35 -0700 Subject: Initial creation of the Permission Manager --- docs/Changelog.md | 4 ++ .../kotlin/ca/allanwang/kau/iitems/KotlinIItem.kt | 23 +++++++++ .../allanwang/kau/permissions/PermissionManager.kt | 57 ++++++++++++++++++++++ .../allanwang/kau/permissions/PermissionResult.kt | 26 ++++++++++ .../ca/allanwang/kau/permissions/Permissions.kt | 26 ++++++++++ .../kotlin/ca/allanwang/kau/utils/ContextUtils.kt | 13 +++-- .../main/kotlin/ca/allanwang/kau/utils/Utils.kt | 4 +- .../kotlin/ca/allanwang/kau/sample/AnimActivity.kt | 9 +++- .../ca/allanwang/kau/sample/PermissionCheckbox.kt | 20 ++++++++ sample/src/main/res/layout/permission_checkbox.xml | 23 +++++++++ 10 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 library/src/main/kotlin/ca/allanwang/kau/iitems/KotlinIItem.kt create mode 100644 library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt create mode 100644 library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionResult.kt create mode 100644 library/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt create mode 100644 sample/src/main/kotlin/ca/allanwang/kau/sample/PermissionCheckbox.kt create mode 100644 sample/src/main/res/layout/permission_checkbox.xml diff --git a/docs/Changelog.md b/docs/Changelog.md index 2f8b61c..de6779d 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,5 +1,9 @@ # Changelog +## v1.5 +* Change snackbar builder +* Change addBundle to withArguments to match ANKO + ## v1.4 * Added about activities * Added themed fast item adapter diff --git a/library/src/main/kotlin/ca/allanwang/kau/iitems/KotlinIItem.kt b/library/src/main/kotlin/ca/allanwang/kau/iitems/KotlinIItem.kt new file mode 100644 index 0000000..7e4bda6 --- /dev/null +++ b/library/src/main/kotlin/ca/allanwang/kau/iitems/KotlinIItem.kt @@ -0,0 +1,23 @@ +package ca.allanwang.kau.iitems + +import android.support.annotation.LayoutRes +import android.support.v7.widget.RecyclerView +import android.view.View +import com.mikepenz.fastadapter.IClickable +import com.mikepenz.fastadapter.IItem +import com.mikepenz.fastadapter.items.AbstractItem + +/** + * Created by Allan Wang on 2017-07-03. + * + * Kotlin implementation of the [AbstractItem] to make things shorter + */ +open class KotlinIItem( + private val type: Int, + @param:LayoutRes private val layoutRes: Int, + private val viewHolder: (v: View) -> VH +) : AbstractItem() where Item : IItem<*, *>, Item : IClickable<*> { + override final fun getType(): Int = type + override final fun getViewHolder(v: View): VH = viewHolder(v) + override final fun getLayoutRes(): Int = layoutRes +} \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt b/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt new file mode 100644 index 0000000..7181e2c --- /dev/null +++ b/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt @@ -0,0 +1,57 @@ +package ca.allanwang.kau.permissions + +import android.app.Activity +import android.content.Context +import android.support.v4.app.ActivityCompat +import ca.allanwang.kau.logging.KL +import ca.allanwang.kau.utils.KauException +import ca.allanwang.kau.utils.buildIsMarshmallowAndUp +import ca.allanwang.kau.utils.hasPermission +import java.lang.ref.WeakReference + +/** + * Created by Allan Wang on 2017-07-03. + */ +internal object PermissionManager { + + var requestInProgress = false + val pendingResults: MutableList> by lazy { mutableListOf>() } + + operator fun invoke(context: Context, permissions: Array, callback: (granted: Boolean, deniedPerm: String?) -> Unit) { + if (!buildIsMarshmallowAndUp) return callback(true, null) + val missingPermissions = permissions.filter { !context.hasPermission(it) } + if (missingPermissions.isEmpty()) return callback(true, null) + pendingResults.add(WeakReference(PermissionResult(permissions, callback = callback))) + if (!requestInProgress) { + requestInProgress = true + requestPermissions(context, missingPermissions.toTypedArray()) + } else KL.d("Request is postponed since another one is still in progress") + } + + @Synchronized internal fun requestPermissions(context: Context, permissions: Array) { + val activity = (context as? Activity) ?: throw KauException("Context is not an instance of an activity; cannot request permissions") + ActivityCompat.requestPermissions(activity, permissions, 1) + } + + fun onRequestPermissionsResult(context: Context, permissions: Array, grantResults: IntArray) { + val count = Math.min(permissions.size, grantResults.size) + val iter = pendingResults.iterator() + while (iter.hasNext()) { + val action = iter.next().get() + if ((0 until count).any { action?.onResult(permissions[it], grantResults[it]) ?: true }) + iter.remove() + } + if (pendingResults.isEmpty()) + requestInProgress = false + else { + val action = pendingResults.map { it.get() }.firstOrNull { it != null } + if (action == null) { //actions have been unlinked from their weak references + pendingResults.clear() + requestInProgress = false + return + } + requestPermissions(context, action.permissions.toTypedArray()) + } + } + +} \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionResult.kt b/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionResult.kt new file mode 100644 index 0000000..14bfdff --- /dev/null +++ b/library/src/main/kotlin/ca/allanwang/kau/permissions/PermissionResult.kt @@ -0,0 +1,26 @@ +package ca.allanwang.kau.permissions + +import android.content.pm.PackageManager + +/** + * Created by Allan Wang on 2017-07-03. + */ +class PermissionResult(permissions: Array, val callback: (granted: Boolean, deniedPerm: String?) -> Unit) { + val permissions = mutableSetOf(*permissions) + + /** + * Called from the manager whenever a permission has changed + * Returns true if result is completed, false otherwise + */ + fun onResult(permission: String, result: Int): Boolean { + if (result != PackageManager.PERMISSION_GRANTED) { + callback(false, permission) + permissions.clear() + return true + } + permissions.remove(permission) + if (permissions.isNotEmpty()) return false + callback(true, null) + return true + } +} \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt b/library/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt new file mode 100644 index 0000000..d466cb3 --- /dev/null +++ b/library/src/main/kotlin/ca/allanwang/kau/permissions/Permissions.kt @@ -0,0 +1,26 @@ +package ca.allanwang.kau.permissions + +import android.app.Activity +import android.content.Context + +/** + * Created by Allan Wang on 2017-07-02. + * + * Bindings for the permission manager + */ + +/** + * Hook that should be added inside all [Activity.onRequestPermissionsResult] so that the Permission manager can handle the responses + */ +fun Activity.kauOnRequestPermissionsResult(permissions: Array, grantResults: IntArray) + = PermissionManager.onRequestPermissionsResult(this, permissions, grantResults) + +/** + * Request a permission with a callback + * In reality, an activity is needed to fulfill the request, but a context is enough if those permissions are already granted + * To be safe, you may want to check that the context can be casted successfully first + * The [callback] returns [granted], which is true if all permissions are granted + * [deniedPerm] is the first denied permission, if granted is false + */ +fun Context.requestPermissions(vararg permissions: String, callback: (granted: Boolean, deniedPerm: String?) -> Unit) + = PermissionManager(this, permissions, callback) \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt index c56a09d..21021e2 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.app.ActivityOptions import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.net.ConnectivityManager import android.net.Uri @@ -131,13 +132,13 @@ fun Context.resolveString(@AttrRes attr: Int, fallback: String = ""): String { * Wrapper function for the MaterialDialog adapterBuilder * There is no need to call build() or show() as those are done by default */ -fun Context.materialDialog(action: MaterialDialog.Builder.() -> Unit): MaterialDialog { +inline fun Context.materialDialog(action: MaterialDialog.Builder.() -> Unit): MaterialDialog { val builder = MaterialDialog.Builder(this) builder.action() return builder.show() } -val Context.isNetworkAvailable: Boolean +inline val Context.isNetworkAvailable: Boolean get() { val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val activeNetworkInfo = connectivityManager.activeNetworkInfo @@ -146,17 +147,19 @@ val Context.isNetworkAvailable: Boolean fun Context.getDip(value: Float): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, resources.displayMetrics) -val Context.isRtl: Boolean +inline val Context.isRtl: Boolean get() = resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL /** * Determine if the navigation bar will be on the bottom of the screen, based on logic in * PhoneWindowManager. */ -val Context.isNavBarOnBottom: Boolean +inline val Context.isNavBarOnBottom: Boolean get() { val cfg = resources.configuration val dm = resources.displayMetrics val canMove = dm.widthPixels != dm.heightPixels && cfg.smallestScreenWidthDp < 600 return !canMove || dm.widthPixels < dm.heightPixels - } \ No newline at end of file + } + +fun Context.hasPermission(permissions: String) = !buildIsMarshmallowAndUp || ContextCompat.checkSelfPermission(this, permissions) == PackageManager.PERMISSION_GRANTED \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt index aa0736e..84794f9 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt @@ -106,4 +106,6 @@ inline fun T.use(block: (T) -> R): R { fun postDelayed(delay: Long, action: () -> Unit) { Handler().postDelayed(action, delay) -} \ No newline at end of file +} + +class KauException(message: String) : RuntimeException(message) \ No newline at end of file diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/AnimActivity.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/AnimActivity.kt index 10f2065..2fd846c 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/AnimActivity.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/AnimActivity.kt @@ -2,17 +2,24 @@ package ca.allanwang.kau.sample import android.os.Bundle import android.support.v7.app.AppCompatActivity +import ca.allanwang.kau.utils.fullLinearRecycler import ca.allanwang.kau.utils.startActivitySlideOut +import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter /** * Created by Allan Wang on 2017-06-12. * - * Empty Activity for animations + * Activity for animations + * Now also showcases permissions */ class AnimActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val adapter = FastItemAdapter + val recycler = fullLinearRecycler { + + } setContentView(R.layout.sample) } diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/PermissionCheckbox.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/PermissionCheckbox.kt new file mode 100644 index 0000000..012d523 --- /dev/null +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/PermissionCheckbox.kt @@ -0,0 +1,20 @@ +package ca.allanwang.kau.sample + +import android.support.v7.widget.RecyclerView +import android.view.View +import android.widget.CheckBox +import android.widget.TextView +import ca.allanwang.kau.iitems.KotlinIItem +import ca.allanwang.kau.utils.bindView + +/** + * Created by Allan Wang on 2017-07-03. + */ +class PermissionCheckbox(val permission: String) : KotlinIItem( + R.layout.permission_checkbox, R.layout.permission_checkbox, { ViewHolder(it) }) { + + class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { + val text: TextView by bindView(R.id.perm_text) + val checkbox: CheckBox by bindView(R.id.perm_checkbox) + } +} \ No newline at end of file diff --git a/sample/src/main/res/layout/permission_checkbox.xml b/sample/src/main/res/layout/permission_checkbox.xml new file mode 100644 index 0000000..6de8296 --- /dev/null +++ b/sample/src/main/res/layout/permission_checkbox.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file -- cgit v1.2.3