From 50ad7f0ae89fc52ce57fe03328f4221fb57f2eac Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 23 Jul 2017 23:26:34 -0700 Subject: Fully implement imagepicker and create play store showcase (#12) * Update changelog * Add uri to imagemodel * Revamp image pickers * Prepare play store showcase * Add encrypted files * Test showcase * Clean elastic recycler activity --- .../ca/allanwang/kau/imagepicker/ImageItem.kt | 37 +--- .../ca/allanwang/kau/imagepicker/ImageItemBasic.kt | 71 ++++++++ .../ca/allanwang/kau/imagepicker/ImageModel.kt | 4 + .../kau/imagepicker/ImagePickerActivityBase.kt | 118 +----------- .../imagepicker/ImagePickerActivityOverlayBase.kt | 51 ++++++ .../allanwang/kau/imagepicker/ImagePickerBinder.kt | 10 +- .../allanwang/kau/imagepicker/ImagePickerCore.kt | 200 +++++++++++++++++++++ 7 files changed, 345 insertions(+), 146 deletions(-) create mode 100644 imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItemBasic.kt create mode 100644 imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityOverlayBase.kt create mode 100644 imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerCore.kt (limited to 'imagepicker/src/main/kotlin/ca/allanwang/kau') 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(R.layout.kau_iitem_image, { ViewHolder(it) }) { private var failedToLoad = false - var withFade = true companion object { fun bindEvents(fastAdapter: FastAdapter) { @@ -45,50 +38,28 @@ class ImageItem(val data: ImageModel) override fun bindView(holder: ViewHolder, payloads: List?) { super.bindView(holder, payloads) - if (withFade) holder.container.alpha = 0f Glide.with(holder.itemView) .load(data.data) .listener(object : RequestListener { override fun onLoadFailed(e: GlideException?, model: Any, target: Target, 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, 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(R.layout.kau_iitem_image_basic, { ViewHolder(it) }) { + + companion object { + @SuppressLint("NewApi") + fun bindEvents(activity: Activity, fastAdapter: FastAdapter) { + 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?) { + super.bindView(holder, payloads) + Glide.with(holder.itemView) + .load(data.data) + .listener(object : RequestListener { + override fun onLoadFailed(e: GlideException?, model: Any, target: Target, isFirstResource: Boolean): Boolean { + holder.image.setImageDrawable(ImagePickerCore.getErrorDrawable(holder.itemView.context)) + return true; + } + + override fun onResourceReady(resource: Drawable, model: Any, target: Target, 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 { - - val imageAdapter = FastItemAdapter() +abstract class ImagePickerActivityBase : ImagePickerCore() { 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 { - 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 { - 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, 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?, 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) = 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() { + + 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, requestCode: Int) { - startActivityForResult(Intent(this, clazz), requestCode) +fun Activity.kauLaunchImagePicker(clazz: Class>, requestCode: Int) { +// startActivityForResult(clazz, requestCode, true) + startActivityForResult(clazz, requestCode, transition = ImagePickerActivityOverlayBase::class.java.isAssignableFrom(clazz)) } /** @@ -22,7 +24,7 @@ fun Activity.kauLaunchImagePicker(clazz: Class, 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> : AppCompatActivity(), LoaderManager.LoaderCallbacks { + + 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 { + 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 = 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 { + 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?, data: Cursor?) { + reset() + if (data == null || !data.moveToFirst()) { + toast(R.string.kau_no_images_found) + onStatusChange(false) + return + } + val items = mutableListOf() + 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?) = reset() + + /** + * Called at the end of [onLoadFinished] + * when the adapter should add the items + */ + open fun addItems(items: List) { + 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 -- cgit v1.2.3