aboutsummaryrefslogtreecommitdiff
path: root/imagepicker
diff options
context:
space:
mode:
Diffstat (limited to 'imagepicker')
-rw-r--r--imagepicker/README.md36
-rw-r--r--imagepicker/build.gradle2
-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
-rw-r--r--imagepicker/src/main/res-public/values-v21/styles.xml9
-rw-r--r--imagepicker/src/main/res-public/values/public.xml1
-rw-r--r--imagepicker/src/main/res-public/values/styles.xml7
-rw-r--r--imagepicker/src/main/res/layout-v21/kau_activity_image_picker_overlay.xml17
-rw-r--r--imagepicker/src/main/res/layout/kau_activity_image_picker.xml1
-rw-r--r--imagepicker/src/main/res/layout/kau_iitem_image_basic.xml10
-rw-r--r--imagepicker/src/main/res/transition-v21/kau_image_enter.xml16
-rw-r--r--imagepicker/src/main/res/transition-v21/kau_image_exit_bottom.xml16
-rw-r--r--imagepicker/src/main/res/transition-v21/kau_image_exit_top.xml16
-rw-r--r--imagepicker/src/main/res/values/strings.xml1
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