diff options
author | Allan Wang <me@allanwang.ca> | 2017-07-03 20:09:35 -0700 |
---|---|---|
committer | Allan Wang <me@allanwang.ca> | 2017-07-03 20:09:35 -0700 |
commit | 139f2dd8207d3a9cd67157a3e3754a9982c7f69d (patch) | |
tree | 548f23ff4f6ddce0a24e740fc550c75ad997fc29 | |
parent | b88a8834dc3be12a37856e9e2584eee7ef52c22e (diff) | |
download | kau-139f2dd8207d3a9cd67157a3e3754a9982c7f69d.tar.gz kau-139f2dd8207d3a9cd67157a3e3754a9982c7f69d.tar.bz2 kau-139f2dd8207d3a9cd67157a3e3754a9982c7f69d.zip |
Initial creation of the Permission Manager
10 files changed, 198 insertions, 7 deletions
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<Item, VH : RecyclerView.ViewHolder>( + private val type: Int, + @param:LayoutRes private val layoutRes: Int, + private val viewHolder: (v: View) -> VH +) : AbstractItem<Item, VH>() 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<WeakReference<PermissionResult>> by lazy { mutableListOf<WeakReference<PermissionResult>>() } + + operator fun invoke(context: Context, permissions: Array<out String>, 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<String>) { + 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<String>, 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<out String>, 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<String>, 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 : AutoCloseable, R> 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<KP> + 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<PermissionCheckbox, PermissionCheckbox.ViewHolder>( + 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:orientation="horizontal" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingEnd="@dimen/activity_horizontal_margin" + android:paddingStart="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin"> + + <TextView + android:id="@+id/perm_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <CheckBox + android:id="@+id/perm_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ No newline at end of file |