aboutsummaryrefslogtreecommitdiff
path: root/imagepicker/src/main/kotlin/ca
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-07-23 23:26:34 -0700
committerGitHub <noreply@github.com>2017-07-23 23:26:34 -0700
commit50ad7f0ae89fc52ce57fe03328f4221fb57f2eac (patch)
tree69ead8807bb7371428953a0363519343f03f9b5b /imagepicker/src/main/kotlin/ca
parent4706b8f6a8d08a6961da6ab34d15881b63356d79 (diff)
downloadkau-50ad7f0ae89fc52ce57fe03328f4221fb57f2eac.tar.gz
kau-50ad7f0ae89fc52ce57fe03328f4221fb57f2eac.tar.bz2
kau-50ad7f0ae89fc52ce57fe03328f4221fb57f2eac.zip
Fully implement imagepicker and create play store showcase (#12)3.1.1
* Update changelog * Add uri to imagemodel * Revamp image pickers * Prepare play store showcase * Add encrypted files * Test showcase * Clean elastic recycler activity
Diffstat (limited to 'imagepicker/src/main/kotlin/ca')
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt37
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItemBasic.kt71
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt4
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt118
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityOverlayBase.kt51
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerBinder.kt10
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerCore.kt200
7 files changed, 345 insertions, 146 deletions
diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt
index 2bfc57f..ffeb560 100644
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt
@@ -1,22 +1,16 @@
package ca.allanwang.kau.imagepicker
-import android.content.Context
-import android.graphics.Color
import android.graphics.drawable.Drawable
import android.support.v7.widget.RecyclerView
import android.view.View
import ca.allanwang.kau.iitems.KauIItem
import ca.allanwang.kau.utils.bindView
-import ca.allanwang.kau.utils.gone
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.mikepenz.fastadapter.FastAdapter
-import com.mikepenz.google_material_typeface_library.GoogleMaterial
-import com.mikepenz.iconics.IconicsDrawable
-import com.mikepenz.iconics.typeface.IIcon
/**
* Created by Allan Wang on 2017-07-04.
@@ -25,7 +19,6 @@ class ImageItem(val data: ImageModel)
: KauIItem<ImageItem, ImageItem.ViewHolder>(R.layout.kau_iitem_image, { ViewHolder(it) }) {
private var failedToLoad = false
- var withFade = true
companion object {
fun bindEvents(fastAdapter: FastAdapter<ImageItem>) {
@@ -45,50 +38,28 @@ class ImageItem(val data: ImageModel)
override fun bindView(holder: ViewHolder, payloads: List<Any>?) {
super.bindView(holder, payloads)
- if (withFade) holder.container.alpha = 0f
Glide.with(holder.itemView)
.load(data.data)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable>, isFirstResource: Boolean): Boolean {
- failedToLoad = true;
- holder.container.setIcon(GoogleMaterial.Icon.gmd_error);
- if (withFade) holder.container.animate().alpha(1f).start();
+ failedToLoad = true
+ holder.container.imageBase.setImageDrawable(ImagePickerCore.getErrorDrawable(holder.itemView.context))
return true;
}
override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
holder.container.imageBase.setImageDrawable(resource)
if (isSelected) holder.container.blurInstantly()
- if (withFade) holder.container.animate().alpha(1f).start();
return true;
}
})
.into(holder.container.imageBase)
}
- private fun BlurredImageView.setIcon(icon: IIcon) {
- val sizePx = computeViewSize(context)
- imageBase.setImageDrawable(IconicsDrawable(context, icon)
- .sizePx(sizePx)
- .paddingPx(sizePx / 3)
- .color(Color.WHITE))
- imageBase.setBackgroundColor(ImagePickerActivityBase.accentColor)
- imageForeground.gone()
- }
-
- private fun computeViewSize(context: Context): Int {
- val screenWidthPx = context.resources.displayMetrics.widthPixels
- return screenWidthPx / ImagePickerActivityBase.computeColumnCount(context)
- }
-
override fun unbindView(holder: ViewHolder) {
super.unbindView(holder)
- if (!failedToLoad) {
- Glide.with(holder.itemView).clear(holder.container.imageBase)
- holder.container.removeBlurInstantly()
- } else {
- holder.container.fullReset()
- }
+ Glide.with(holder.itemView).clear(holder.container.imageBase)
+ holder.container.removeBlurInstantly()
failedToLoad = false
}
diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItemBasic.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItemBasic.kt
new file mode 100644
index 0000000..62a0b09
--- /dev/null
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItemBasic.kt
@@ -0,0 +1,71 @@
+package ca.allanwang.kau.imagepicker
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.support.v7.app.AppCompatActivity
+import android.support.v7.widget.RecyclerView
+import android.view.View
+import ca.allanwang.kau.iitems.KauIItem
+import ca.allanwang.kau.ui.views.MeasuredImageView
+import ca.allanwang.kau.utils.bindView
+import ca.allanwang.kau.utils.buildIsLollipopAndUp
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.engine.GlideException
+import com.bumptech.glide.request.RequestListener
+import com.bumptech.glide.request.target.Target
+import com.mikepenz.fastadapter.FastAdapter
+
+/**
+ * Created by Allan Wang on 2017-07-04.
+ */
+class ImageItemBasic(val data: ImageModel)
+ : KauIItem<ImageItem, ImageItemBasic.ViewHolder>(R.layout.kau_iitem_image_basic, { ViewHolder(it) }) {
+
+ companion object {
+ @SuppressLint("NewApi")
+ fun bindEvents(activity: Activity, fastAdapter: FastAdapter<ImageItemBasic>) {
+ fastAdapter.withSelectable(false)
+ //add image data and return right away
+ .withOnClickListener { v, _, item, _ ->
+ val intent = Intent()
+ val data = arrayListOf(item.data)
+ intent.putParcelableArrayListExtra(IMAGE_PICKER_RESULT, data)
+ activity.setResult(AppCompatActivity.RESULT_OK, intent)
+ if (buildIsLollipopAndUp) activity.finishAfterTransition()
+ else activity.finish()
+ true
+ }
+ }
+ }
+
+ override fun isSelectable(): Boolean = false
+
+ override fun bindView(holder: ViewHolder, payloads: List<Any>?) {
+ super.bindView(holder, payloads)
+ Glide.with(holder.itemView)
+ .load(data.data)
+ .listener(object : RequestListener<Drawable> {
+ override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable>, isFirstResource: Boolean): Boolean {
+ holder.image.setImageDrawable(ImagePickerCore.getErrorDrawable(holder.itemView.context))
+ return true;
+ }
+
+ override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
+ return false
+ }
+ })
+ .into(holder.image)
+ }
+
+ override fun unbindView(holder: ViewHolder) {
+ super.unbindView(holder)
+ Glide.with(holder.itemView).clear(holder.image)
+ }
+
+ class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
+ val image: MeasuredImageView by bindView(R.id.kau_image)
+ }
+} \ No newline at end of file
diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt
index d744650..202805c 100644
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt
@@ -1,10 +1,12 @@
package ca.allanwang.kau.imagepicker
import android.database.Cursor
+import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import android.provider.MediaStore
import android.support.annotation.NonNull
+import java.io.File
/**
@@ -34,6 +36,8 @@ data class ImageModel(val size: Long, val dateModified: Long, val data: String,
parcel.writeString(this.displayName)
}
+ val uri: Uri by lazy { Uri.fromFile(File(data)) }
+
override fun describeContents(): Int {
return 0
}
diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt
index 9d988d1..3a9809c 100644
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt
@@ -1,40 +1,27 @@
package ca.allanwang.kau.imagepicker
-import android.Manifest
-import android.app.Activity
-import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.os.Bundle
-import android.provider.MediaStore
import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.support.design.widget.FloatingActionButton
-import android.support.v4.app.LoaderManager
-import android.support.v4.content.CursorLoader
import android.support.v4.content.Loader
-import android.support.v7.app.AppCompatActivity
-import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.widget.TextView
-import ca.allanwang.kau.animators.FadeScaleAnimatorAdd
-import ca.allanwang.kau.animators.KauAnimator
-import ca.allanwang.kau.permissions.kauRequestPermissions
import ca.allanwang.kau.utils.*
-import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
import com.mikepenz.google_material_typeface_library.GoogleMaterial
-
/**
* Created by Allan Wang on 2017-07-04.
*
* Base activity for selecting images from storage
+ * Images are blurred when selected, and multiple images can be selected at a time.
+ * Having three layered images makes this slightly slower than [ImagePickerActivityOverlayBase]
*/
-abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
-
- val imageAdapter = FastItemAdapter<ImageItem>()
+abstract class ImagePickerActivityBase : ImagePickerCore<ImageItem>() {
val coordinator: CoordinatorLayout by bindView(R.id.kau_coordinator)
val toolbar: Toolbar by bindView(R.id.kau_toolbar)
@@ -42,28 +29,6 @@ abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.Load
val recycler: RecyclerView by bindView(R.id.kau_recyclerview)
val fab: FloatingActionButton by bindView(R.id.kau_fab)
- companion object {
- /**
- * Given the dimensions of our device and a minimum image size,
- * Computer the optimal column count for our grid layout
- *
- * @return column count
- */
- fun computeColumnCount(context: Context): Int {
- val minImageSizePx = context.dimenPixelSize(R.dimen.kau_image_minimum_size)
- val screenWidthPx = context.resources.displayMetrics.widthPixels
- return screenWidthPx / minImageSizePx
- }
-
- var accentColor: Int = 0xff666666.toInt()
-
- fun onImagePickerResult(resultCode: Int, data: Intent?): List<ImageModel> {
- if (resultCode != Activity.RESULT_OK || data == null || !data.hasExtra(IMAGE_PICKER_RESULT))
- return emptyList()
- return data.getParcelableArrayListExtra(IMAGE_PICKER_RESULT)
- }
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -79,12 +44,7 @@ abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.Load
}
toolbar.setNavigationOnClickListener { onBackPressed() }
- recycler.apply {
- layoutManager = GridLayoutManager(context, computeColumnCount(context))
- adapter = imageAdapter
- setHasFixedSize(true)
- itemAnimator = KauAnimator(FadeScaleAnimatorAdd(0.8f))
- }
+ initializeRecycler(recycler)
ImageItem.bindEvents(imageAdapter)
imageAdapter.withSelectionListener({ _, _ -> selectionCount.text = imageAdapter.selections.size.toString() })
@@ -110,24 +70,7 @@ abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.Load
loadImages()
}
- /**
- * Request read permissions and load all external images
- * The result will be filtered through {@link #onLoadFinished(Loader, Cursor)}
- * Call this to make sure that we request permissions each time
- * The adapter will be cleared on each successful call
- */
- private fun loadImages() {
- kauRequestPermissions(Manifest.permission.READ_EXTERNAL_STORAGE) {
- granted, _ ->
- if (granted) {
- supportLoaderManager.initLoader(LOADER_ID, null, this)
- setToolbarScrollable(true)
- } else {
- toast(R.string.kau_permission_denied)
- setToolbarScrollable(false)
- }
- }
- }
+ override fun converter(model: ImageModel): ImageItem = ImageItem(model)
/**
* Decide whether the toolbar can hide itself
@@ -145,56 +88,13 @@ abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.Load
params.scrollFlags = 0
}
- override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
- val columns = arrayOf(
- MediaStore.Images.Media._ID,
- MediaStore.Images.Media.TITLE,
- MediaStore.Images.Media.DATA,
- MediaStore.Images.Media.SIZE,
- MediaStore.Images.Media.DISPLAY_NAME,
- MediaStore.Images.Media.DATE_MODIFIED
- )
- return CursorLoader(this,
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- columns,
- null,
- null,
- //Sort by descending date
- MediaStore.Images.Media.DATE_MODIFIED + " DESC")
- }
-
- override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) {
- reset()
- if (data == null || !data.moveToFirst()) {
- toast(R.string.kau_no_images_found)
- setToolbarScrollable(false)
- return
- }
- do {
- val model = ImageModel(data)
- if (!shouldLoad(model)) continue
- imageAdapter.add(ImageItem(model))
- } while (data.moveToNext())
+ override fun onLoadFinished(loader: Loader<Cursor>?, data: Cursor?) {
+ super.onLoadFinished(loader, data)
setToolbarScrollable((recycler.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition() < imageAdapter.getItemCount() - 1)
}
- /**
- * Optional filter to decide which images get displayed
- * Defaults to checking their sizes to filter out
- * very small images such as lurking drawables/icons
- *
- * Returns true if model should be displayed, false otherwise
- */
- open fun shouldLoad(model: ImageModel): Boolean = model.size > 10000L
-
- private fun reset() {
- imageAdapter.clear();
+ override fun onStatusChange(loaded: Boolean) {
+ setToolbarScrollable(loaded)
}
- override fun onLoaderReset(loader: Loader<Cursor>) = reset()
-
- override fun onBackPressed() {
- setResult(RESULT_CANCELED)
- super.onBackPressed()
- }
} \ No newline at end of file
diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityOverlayBase.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityOverlayBase.kt
new file mode 100644
index 0000000..ed8eee4
--- /dev/null
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityOverlayBase.kt
@@ -0,0 +1,51 @@
+package ca.allanwang.kau.imagepicker
+
+import android.os.Build
+import android.os.Bundle
+import android.support.annotation.RequiresApi
+import android.support.v7.widget.RecyclerView
+import ca.allanwang.kau.ui.widgets.ElasticDragDismissFrameLayout
+import ca.allanwang.kau.utils.bindView
+import ca.allanwang.kau.utils.toast
+
+/**
+ * Created by Allan Wang on 2017-07-23.
+ *
+ * Base activity for selecting images from storage
+ * This variant is an overlay and selects one image only before returning directly
+ * It is more efficient than [ImagePickerActivityBase], as all images are one layer deep
+ * as opposed to three layers deep
+ */
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+abstract class ImagePickerActivityOverlayBase : ImagePickerCore<ImageItemBasic>() {
+
+ val draggable: ElasticDragDismissFrameLayout by bindView(R.id.kau_draggable)
+ val recycler: RecyclerView by bindView(R.id.kau_recyclerview)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.kau_activity_image_picker_overlay)
+ initializeRecycler(recycler)
+ ImageItemBasic.bindEvents(this, imageAdapter)
+
+ draggable.addExitListener(this, R.transition.kau_image_exit_bottom, R.transition.kau_image_exit_top)
+ draggable.setOnClickListener { finishAfterTransition() }
+
+ loadImages()
+ }
+
+ override fun finishAfterTransition() {
+ recycler.stopScroll()
+ super.finishAfterTransition()
+ }
+
+ override fun onStatusChange(loaded: Boolean) {
+ if (!loaded) toast(R.string.kau_no_images_loaded)
+ }
+
+ override fun converter(model: ImageModel): ImageItemBasic = ImageItemBasic(model)
+
+ override fun onBackPressed() {
+ finishAfterTransition()
+ }
+} \ No newline at end of file
diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerBinder.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerBinder.kt
index 8e8a69c..db8d113 100644
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerBinder.kt
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerBinder.kt
@@ -2,6 +2,7 @@ package ca.allanwang.kau.imagepicker
import android.app.Activity
import android.content.Intent
+import ca.allanwang.kau.utils.startActivityForResult
/**
* Created by Allan Wang on 2017-07-21.
@@ -11,10 +12,11 @@ import android.content.Intent
*/
/**
- * Image picker launcher
+ * Image picker launchers
*/
-fun Activity.kauLaunchImagePicker(clazz: Class<out ImagePickerActivityBase>, requestCode: Int) {
- startActivityForResult(Intent(this, clazz), requestCode)
+fun Activity.kauLaunchImagePicker(clazz: Class<out ImagePickerCore<*>>, requestCode: Int) {
+// startActivityForResult(clazz, requestCode, true)
+ startActivityForResult(clazz, requestCode, transition = ImagePickerActivityOverlayBase::class.java.isAssignableFrom(clazz))
}
/**
@@ -22,7 +24,7 @@ fun Activity.kauLaunchImagePicker(clazz: Class<out ImagePickerActivityBase>, req
* call under [Activity.onActivityResult]
* and make sure that the requestCode matches first
*/
-fun Activity.kauOnImagePickerResult(resultCode: Int, data: Intent?) = ImagePickerActivityBase.onImagePickerResult(resultCode, data)
+fun Activity.kauOnImagePickerResult(resultCode: Int, data: Intent?) = ImagePickerCore.onImagePickerResult(resultCode, data)
internal const val LOADER_ID = 42
internal const val IMAGE_PICKER_RESULT = "image_picker_result"
diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerCore.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerCore.kt
new file mode 100644
index 0000000..f197a5e
--- /dev/null
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerCore.kt
@@ -0,0 +1,200 @@
+package ca.allanwang.kau.imagepicker
+
+import android.Manifest
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.database.Cursor
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.provider.MediaStore
+import android.support.v4.app.LoaderManager
+import android.support.v4.content.CursorLoader
+import android.support.v4.content.Loader
+import android.support.v7.app.AppCompatActivity
+import android.support.v7.widget.GridLayoutManager
+import android.support.v7.widget.RecyclerView
+import ca.allanwang.kau.animators.FadeScaleAnimatorAdd
+import ca.allanwang.kau.animators.KauAnimator
+import ca.allanwang.kau.permissions.kauRequestPermissions
+import ca.allanwang.kau.utils.dimenPixelSize
+import ca.allanwang.kau.utils.toast
+import com.mikepenz.fastadapter.IItem
+import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.mikepenz.iconics.IconicsDrawable
+
+/**
+ * Created by Allan Wang on 2017-07-23.
+ *
+ * Container for the main logic behind the image pickers
+ */
+abstract class ImagePickerCore<T : IItem<*, *>> : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
+
+ companion object {
+ /**
+ * Given the dimensions of our device and a minimum image size,
+ * Computer the optimal column count for our grid layout
+ *
+ * @return column count
+ */
+ fun computeColumnCount(context: Context): Int {
+ val minImageSizePx = context.dimenPixelSize(R.dimen.kau_image_minimum_size)
+ val screenWidthPx = context.resources.displayMetrics.widthPixels
+ return screenWidthPx / minImageSizePx
+ }
+
+ /**
+ * Compute our resulting image size
+ */
+ fun computeViewSize(context: Context): Int {
+ val screenWidthPx = context.resources.displayMetrics.widthPixels
+ return screenWidthPx / computeColumnCount(context)
+ }
+
+ /**
+ * Create error tile for a given item
+ */
+ fun getErrorDrawable(context: Context): Drawable {
+ val sizePx = ImagePickerCore.computeViewSize(context)
+ return IconicsDrawable(context, GoogleMaterial.Icon.gmd_error)
+ .sizePx(sizePx)
+ .backgroundColor(accentColor)
+ .paddingPx(sizePx / 3)
+ .color(Color.WHITE)
+ }
+
+ var accentColor: Int = 0xff666666.toInt()
+
+ /**
+ * Helper method to retrieve the images from our iamge picker
+ * This is used for both single and multiple photo picks
+ */
+ fun onImagePickerResult(resultCode: Int, data: Intent?): List<ImageModel> {
+ if (resultCode != Activity.RESULT_OK || data == null || !data.hasExtra(IMAGE_PICKER_RESULT))
+ return emptyList()
+ return data.getParcelableArrayListExtra(IMAGE_PICKER_RESULT)
+ }
+
+ /**
+ * Number of loaded images we should cache
+ * This is arbitrary
+ */
+ const val CACHE_SIZE = 80
+
+ /**
+ * We know that Glide takes a while to initially fetch the images
+ * My as well make it look pretty
+ */
+ const val INITIAL_LOAD_DELAY = 600L
+ }
+
+ val imageAdapter: FastItemAdapter<T> = FastItemAdapter()
+
+ /**
+ * Further improve preloading by extending the layout space
+ */
+ val extraSpace: Int by lazy { resources.displayMetrics.heightPixels }
+
+ fun initializeRecycler(recycler: RecyclerView) {
+ recycler.apply {
+ val manager = object : GridLayoutManager(context, computeColumnCount(context)) {
+ override fun getExtraLayoutSpace(state: RecyclerView.State?): Int {
+ return extraSpace
+ }
+ }
+ setItemViewCacheSize(CACHE_SIZE)
+ isDrawingCacheEnabled = true
+ layoutManager = manager
+ adapter = imageAdapter
+ setHasFixedSize(true)
+ itemAnimator = object : KauAnimator(FadeScaleAnimatorAdd(0.8f)) {
+ override fun startDelay(holder: RecyclerView.ViewHolder, duration: Long, factor: Float): Long {
+ return super.startDelay(holder, duration, factor) + INITIAL_LOAD_DELAY
+ }
+ }
+ }
+ }
+
+ //Sort by descending date
+ var sortQuery = MediaStore.Images.Media.DATE_MODIFIED + " DESC"
+
+ /**
+ * Request read permissions and load all external images
+ * The result will be filtered through {@link #onLoadFinished(Loader, Cursor)}
+ * Call this to make sure that we request permissions each time
+ * The adapter will be cleared on each successful call
+ */
+ open fun loadImages() {
+ kauRequestPermissions(Manifest.permission.READ_EXTERNAL_STORAGE) {
+ granted, _ ->
+ if (granted) {
+ supportLoaderManager.initLoader(LOADER_ID, null, this)
+ onStatusChange(true)
+ } else {
+ toast(R.string.kau_permission_denied)
+ onStatusChange(false)
+ }
+ }
+ }
+
+ override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
+ val columns = arrayOf(
+ MediaStore.Images.Media._ID,
+ MediaStore.Images.Media.TITLE,
+ MediaStore.Images.Media.DATA,
+ MediaStore.Images.Media.SIZE,
+ MediaStore.Images.Media.DISPLAY_NAME,
+ MediaStore.Images.Media.DATE_MODIFIED
+ )
+ return CursorLoader(this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, sortQuery)
+ }
+
+ override fun onLoadFinished(loader: Loader<Cursor>?, data: Cursor?) {
+ reset()
+ if (data == null || !data.moveToFirst()) {
+ toast(R.string.kau_no_images_found)
+ onStatusChange(false)
+ return
+ }
+ val items = mutableListOf<T>()
+ do {
+ val model = ImageModel(data)
+ if (!shouldLoad(model)) continue
+ items.add(converter(model))
+ } while (data.moveToNext())
+ addItems(items)
+ }
+
+ abstract fun converter(model: ImageModel): T
+
+ override fun onLoaderReset(loader: Loader<Cursor>?) = reset()
+
+ /**
+ * Called at the end of [onLoadFinished]
+ * when the adapter should add the items
+ */
+ open fun addItems(items: List<T>) {
+ imageAdapter.add(items)
+ }
+
+ /**
+ * Clears the adapter to prepare for a new load
+ */
+ open fun reset() {
+ imageAdapter.clear()
+ }
+
+ /**
+ * Optional filter to decide which images get displayed
+ * Defaults to checking their sizes to filter out
+ * very small images such as lurking drawables/icons
+ *
+ * Returns true if model should be displayed, false otherwise
+ */
+ open fun shouldLoad(model: ImageModel): Boolean = model.size > 10000L
+
+ open fun onStatusChange(loaded: Boolean) {}
+
+} \ No newline at end of file