diff options
author | Allan Wang <me@allanwang.ca> | 2017-07-23 23:26:34 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-07-23 23:26:34 -0700 |
commit | 50ad7f0ae89fc52ce57fe03328f4221fb57f2eac (patch) | |
tree | 69ead8807bb7371428953a0363519343f03f9b5b /imagepicker | |
parent | 4706b8f6a8d08a6961da6ab34d15881b63356d79 (diff) | |
download | kau-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')
19 files changed, 468 insertions, 155 deletions
diff --git a/imagepicker/README.md b/imagepicker/README.md index e37e417..f70de2b 100644 --- a/imagepicker/README.md +++ b/imagepicker/README.md @@ -1,12 +1,35 @@ # KAU :imagepicker ImagePicker is a beautiful gallery activity that allows you to pick images -from your storage. It is backed by FastAdapter and Glide, and offers blur and fade transitions. +from your storage. It is backed by FastAdapter and Glide, and stems from the ImagePickerCore model -`ImagePickerActivityBase` is already fully functional, so you may directly extend it with no further changes -and add the activity to your manifest +Currently, there are two options: -You may also easily launch the activity through the simple binder: +-------------------------------- + +## ImagePickerActivityBase + +A full screen multi image picker with beautiful animations. +Images are blurred when selected, and there is a counter on the top right. +There is a FAB to send back the response. + +`R.style.Kau.ImagePicker` is added for your convenience. + +## ImagePickerActivityOverlayBase + +This overlaying activity makes use of transitions and nested scrolling, and is only for Lollipop and up. +Only one image can be selected, so the overlay exists immediately upon the first selection. +Having this model also means that each item is only one simple image, as opposed to the blurrable image view above. +As a result, this activity has faster loading on scrolling. + +`R.style.Kau.ImagePicker.Overlay` is added for your convenience. + +-------------------------------- + +Both activities work out of the box and can be extended without needing further modifications. +Their convenience styles default to a slide in slide out animation from the bottom edge. + +You may also easily launch either activity through the simple binder: ``` Activity.kauLaunchImagePicker(YourClass::class.java, yourRequestCode) ``` @@ -14,6 +37,5 @@ Activity.kauLaunchImagePicker(YourClass::class.java, yourRequestCode) Note that this launches the activity through a `startActivityForResult` call You may get the activity response by overriding your `onActivityResult` method -to first verify that the request code matches and then call `kauOnImagePickerResult` - -This module also has a template style `Kau.ImagePicker` that defaults to a slide up animation.
\ No newline at end of file +to first verify that the request code matches and then call `kauOnImagePickerResult`, +which will return the list of ImageModels.
\ No newline at end of file diff --git a/imagepicker/build.gradle b/imagepicker/build.gradle index a31fac0..93f52ac 100644 --- a/imagepicker/build.gradle +++ b/imagepicker/build.gradle @@ -4,7 +4,7 @@ apply from: '../android-lib.gradle' dependencies { - compile project(':adapter') + compile project(':core-ui') compile "com.github.bumptech.glide:glide:${GLIDE}" kapt "com.github.bumptech.glide:compiler:${GLIDE}" 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 diff --git a/imagepicker/src/main/res-public/values-v21/styles.xml b/imagepicker/src/main/res-public/values-v21/styles.xml new file mode 100644 index 0000000..bae68da --- /dev/null +++ b/imagepicker/src/main/res-public/values-v21/styles.xml @@ -0,0 +1,9 @@ +<resources> + + <style name="Kau.ImagePicker.Overlay" parent="Kau.Translucent"> + <item name="android:statusBarColor">@android:color/transparent</item> + <item name="android:windowEnterTransition">@transition/kau_image_enter</item> + <item name="android:windowReturnTransition">@transition/kau_image_exit_bottom</item> + </style> + +</resources> diff --git a/imagepicker/src/main/res-public/values/public.xml b/imagepicker/src/main/res-public/values/public.xml index 3a1d9c5..a892651 100644 --- a/imagepicker/src/main/res-public/values/public.xml +++ b/imagepicker/src/main/res-public/values/public.xml @@ -3,4 +3,5 @@ <public name='kau_blurred_image_selection_overlay' type='color' /> <public name='kau_image_minimum_size' type='dimen' /> <public name='Kau.ImagePicker' type='style' /> + <public name='Kau.ImagePicker.Overlay' type='style' /> </resources>
\ No newline at end of file diff --git a/imagepicker/src/main/res-public/values/styles.xml b/imagepicker/src/main/res-public/values/styles.xml index 4d4a135..99f294a 100644 --- a/imagepicker/src/main/res-public/values/styles.xml +++ b/imagepicker/src/main/res-public/values/styles.xml @@ -1,7 +1,12 @@ - <resources> +<resources> <style name="Kau.ImagePicker"> <item name="android:windowAnimationStyle">@style/KauSlideInSlideOutBottom</item> </style> + <!--Just as a placeholder for public.xml--> + <style name="Kau.ImagePicker.Overlay" parent="Kau.Translucent"> + <item name="android:windowAnimationStyle">@null</item> + </style> + </resources> diff --git a/imagepicker/src/main/res/layout-v21/kau_activity_image_picker_overlay.xml b/imagepicker/src/main/res/layout-v21/kau_activity_image_picker_overlay.xml new file mode 100644 index 0000000..a0ce301 --- /dev/null +++ b/imagepicker/src/main/res/layout-v21/kau_activity_image_picker_overlay.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<ca.allanwang.kau.ui.widgets.ElasticDragDismissFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/kau_draggable" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:dragDismissDistance="@dimen/kau_drag_dismiss_distance_large" + app:dragDismissScale="0.95"> + + <android.support.v7.widget.RecyclerView + android:id="@+id/kau_recyclerview" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/kau_drag_dismiss_distance" + android:background="?android:colorBackground" /> + +</ca.allanwang.kau.ui.widgets.ElasticDragDismissFrameLayout>
\ No newline at end of file diff --git a/imagepicker/src/main/res/layout/kau_activity_image_picker.xml b/imagepicker/src/main/res/layout/kau_activity_image_picker.xml index 5b0300d..6d991b0 100644 --- a/imagepicker/src/main/res/layout/kau_activity_image_picker.xml +++ b/imagepicker/src/main/res/layout/kau_activity_image_picker.xml @@ -4,6 +4,7 @@ android:id="@+id/kau_coordinator" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="?android:colorBackground" android:fitsSystemWindows="true"> <android.support.design.widget.AppBarLayout diff --git a/imagepicker/src/main/res/layout/kau_iitem_image_basic.xml b/imagepicker/src/main/res/layout/kau_iitem_image_basic.xml new file mode 100644 index 0000000..b89e41d --- /dev/null +++ b/imagepicker/src/main/res/layout/kau_iitem_image_basic.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<ca.allanwang.kau.ui.views.MeasuredImageView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/kau_image" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="2dp" + android:foreground="@drawable/kau_selectable_white" + android:scaleType="centerCrop" + app:relativeHeight="1" />
\ No newline at end of file diff --git a/imagepicker/src/main/res/transition-v21/kau_image_enter.xml b/imagepicker/src/main/res/transition-v21/kau_image_enter.xml new file mode 100644 index 0000000..447c0c9 --- /dev/null +++ b/imagepicker/src/main/res/transition-v21/kau_image_enter.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:interpolator/fast_out_linear_in" + android:transitionOrdering="together"> + + <slide + android:duration="400" + android:slideEdge="bottom"> + <targets android:targetId="@id/kau_draggable" /> + </slide> + + <fade + android:duration="200" + android:startDelay="200" /> + +</transitionSet> diff --git a/imagepicker/src/main/res/transition-v21/kau_image_exit_bottom.xml b/imagepicker/src/main/res/transition-v21/kau_image_exit_bottom.xml new file mode 100644 index 0000000..447c0c9 --- /dev/null +++ b/imagepicker/src/main/res/transition-v21/kau_image_exit_bottom.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:interpolator/fast_out_linear_in" + android:transitionOrdering="together"> + + <slide + android:duration="400" + android:slideEdge="bottom"> + <targets android:targetId="@id/kau_draggable" /> + </slide> + + <fade + android:duration="200" + android:startDelay="200" /> + +</transitionSet> diff --git a/imagepicker/src/main/res/transition-v21/kau_image_exit_top.xml b/imagepicker/src/main/res/transition-v21/kau_image_exit_top.xml new file mode 100644 index 0000000..8d64f48 --- /dev/null +++ b/imagepicker/src/main/res/transition-v21/kau_image_exit_top.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:interpolator/fast_out_linear_in" + android:transitionOrdering="together"> + + <slide + android:duration="400" + android:slideEdge="top"> + <targets android:targetId="@id/kau_draggable" /> + </slide> + + <fade + android:duration="200" + android:startDelay="200" /> + +</transitionSet> diff --git a/imagepicker/src/main/res/values/strings.xml b/imagepicker/src/main/res/values/strings.xml index 7aa7f3e..17af1d8 100644 --- a/imagepicker/src/main/res/values/strings.xml +++ b/imagepicker/src/main/res/values/strings.xml @@ -3,4 +3,5 @@ <string name="kau_no_images_found">No images found</string> <string name="kau_no_images_selected">No images have been selected</string> <string name="kau_blurrable_imageview">Blurrable ImageView</string> + <string name="kau_no_images_loaded">No images loaded</string> </resources>
\ No newline at end of file |