aboutsummaryrefslogtreecommitdiff
path: root/imagepicker/src/main/kotlin/ca
diff options
context:
space:
mode:
Diffstat (limited to 'imagepicker/src/main/kotlin/ca')
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt166
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt79
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt21
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt80
4 files changed, 321 insertions, 25 deletions
diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt
new file mode 100644
index 0000000..8fb5cf3
--- /dev/null
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt
@@ -0,0 +1,166 @@
+package ca.allanwang.kau.imagepicker
+
+import android.content.Context
+import android.graphics.Color
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageView
+import ca.allanwang.kau.ui.views.MeasureSpecContract
+import ca.allanwang.kau.ui.views.MeasureSpecDelegate
+import ca.allanwang.kau.utils.*
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import com.mikepenz.iconics.IconicsDrawable
+import jp.wasabeef.blurry.internal.BlurFactor
+import jp.wasabeef.blurry.internal.BlurTask
+
+/**
+ * Created by Allan Wang on 2017-07-14.
+ *
+ * ImageView that is can be blurred and selected
+ * The frame is composed of three layers: the base, the blur, and the foreground
+ * Images should be placed in the base view, and the blur view should not be touched
+ * as the class will handle it
+ * The foreground by default contains a white checkmark, but can be customized or hidden depending on the situation
+ */
+class BlurredImageView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0
+) : FrameLayout(context, attrs, defStyleAttr, defStyleRes), MeasureSpecContract by MeasureSpecDelegate() {
+
+ private var blurred = false
+ val imageBase: ImageView by bindView(R.id.image_base)
+ internal val imageBlur: ImageView by bindView(R.id.image_blur)
+ val imageForeground: ImageView by bindView(R.id.image_foreground)
+
+ init {
+ inflate(R.layout.kau_blurred_imageview, true)
+ initAttrs(context, attrs)
+ imageForeground.setIcon(GoogleMaterial.Icon.gmd_check, 30)
+ }
+
+ companion object {
+ const val ANIMATION_DURATION = 200L
+ const val ANIMATION_SCALE = 0.95f
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val result = onMeasure(this, widthMeasureSpec, heightMeasureSpec)
+ super.onMeasure(result.first, result.second)
+ }
+
+ override fun clearAnimation() {
+ super.clearAnimation()
+ imageBase.clearAnimation()
+ imageBlur.clearAnimation()
+ imageForeground.clearAnimation()
+ }
+
+ private fun View.scaleAnimate(scale: Float) = animate().scaleX(scale).scaleY(scale).setDuration(ANIMATION_DURATION)
+ private fun View.alphaAnimate(alpha: Float) = animate().alpha(alpha).setDuration(ANIMATION_DURATION)
+
+
+ fun isBlurred(): Boolean {
+ return blurred
+ }
+
+ /**
+ * Applies a blur and fills the blur image asynchronously
+ * When ready, scales the image down and shows the blur & foreground
+ */
+ fun blur() {
+ if (blurred) return
+ blurred = true
+ val factor = BlurFactor()
+ factor.width = width
+ factor.height = height
+ val task = BlurTask(imageBase, factor) {
+ imageBlur.setImageDrawable(it)
+ scaleAnimate(ANIMATION_SCALE).start()
+ imageBlur.alphaAnimate(1f).start()
+ imageForeground.alphaAnimate(1f).start()
+ }
+ task.execute()
+ }
+
+ /**
+ * Clears animations and blurs the image without further animations
+ * This method is relatively instantaneous, as retrieving the blurred image
+ * is still asynchronous and takes time
+ */
+ fun blurInstantly() {
+ blurred = true
+ clearAnimation()
+ val factor = BlurFactor()
+ factor.width = width
+ factor.height = height
+ BlurTask(imageBase, factor) { drawable ->
+ imageBlur.setImageDrawable(drawable)
+ scaleX = ANIMATION_SCALE
+ scaleY = ANIMATION_SCALE
+ imageBlur.alpha = 1f
+ imageForeground.alpha = 1f
+ }.execute()
+ }
+
+ /**
+ * Animate view back to original state and remove drawable when finished
+ */
+ fun removeBlur() {
+ if (!blurred) return
+ blurred = false
+ scaleAnimate(1.0f).start()
+ imageBlur.alphaAnimate(0f).withEndAction { imageBlur.setImageDrawable(null) }.start()
+ imageForeground.alphaAnimate(0f).start()
+ }
+
+
+ /**
+ * Clear all animations and unblur the image
+ */
+ fun removeBlurInstantly() {
+ blurred = false
+ clearAnimation()
+ scaleX = 1.0f
+ scaleX = 1.0f
+ imageBlur.alpha = 0f
+ imageBlur.setImageDrawable(null)
+ imageForeground.alpha = 0f
+ }
+
+ /**
+ * Switch blur state and apply transition
+ *
+ * @return true if new state is blurred; false otherwise
+ */
+ fun toggleBlur(): Boolean {
+ if (blurred) removeBlur()
+ else blur()
+ return blurred
+ }
+
+ /**
+ * Clears all of the blur effects to restore the original states
+ * If views were modified in other ways, this method won't affect it
+ */
+ fun reset() {
+ removeBlurInstantly()
+ imageBase.setImageDrawable(null)
+ }
+
+ /**
+ * Reset most of possible changes to the view
+ */
+ fun fullReset() {
+ reset()
+ fullAction({ it.visible().background = null })
+ imageForeground.setBackgroundColorRes(R.color.kau_blurred_image_selection_overlay)
+ imageForeground.setIcon(GoogleMaterial.Icon.gmd_check, 30, Color.WHITE)
+ }
+
+ private fun fullAction(action: (View) -> Unit) {
+ action(this)
+ action(imageBase)
+ action(imageBlur)
+ action(imageForeground)
+ }
+} \ No newline at end of file
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 0b13a30..852e1e8 100644
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt
@@ -1,18 +1,89 @@
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 android.widget.ImageView
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.
*/
-class ImageItem(data:String)
- : KauIItem<ImageItem, ImageItem.ViewHolder>(R.layout.kau_iitem_card, { ViewHolder(it) }) {
+class ImageItem(val data: ImageModel)
+ : KauIItem<ImageItem, ImageItem.ViewHolder>(R.layout.kau_iitem_image, { ViewHolder(it) }) {
+
+ private var failedToLoad = false
+
+ fun bindEvents(fastAdapter: FastAdapter<ImageItem>) {
+ fastAdapter.withMultiSelect(true)
+ fastAdapter.withSelectable(true)
+ fastAdapter.withOnClickListener { v, _, _, _ ->
+ val image = v as BlurredImageView
+ image.toggleBlur()
+ true
+ }
+ }
+
+ override fun bindView(holder: ViewHolder, payloads: List<Any>?) {
+ super.bindView(holder, payloads)
+ 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);
+ holder.container.animate().alpha(1f).start();
+ return true;
+ }
+
+ override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
+ holder.container.animate().alpha(1f).start();
+ return false;
+ }
+ })
+ .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))
+ //todo add background
+ 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()
+ }
+ }
class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
- val image: ImageView by bindView(R.id.kau_image)
+ val container: BlurredImageView 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
new file mode 100644
index 0000000..26e4137
--- /dev/null
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt
@@ -0,0 +1,21 @@
+package ca.allanwang.kau.imagepicker
+
+import android.database.Cursor
+import android.provider.MediaStore
+import android.support.annotation.NonNull
+
+
+/**
+ * Created by Allan Wang on 2017-07-14.
+ */
+class ImageModel(@NonNull cursor: Cursor) {
+
+ val size = cursor.getLong(MediaStore.Images.Media.SIZE)
+ val dateModified = cursor.getLong(MediaStore.Images.Media.DATE_MODIFIED)
+ val data = cursor.getString(MediaStore.Images.Media.DATA)
+ val displayName = cursor.getString(MediaStore.Images.Media.DISPLAY_NAME)
+
+ private fun Cursor.getString(name: String) = getString(getColumnIndex(name))
+ private fun Cursor.getLong(name: String) = getLong(getColumnIndex(name))
+
+} \ No newline at end of file
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 8dfbeab..24c2db7 100644
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt
@@ -1,5 +1,7 @@
package ca.allanwang.kau.imagepicker
+import android.Manifest
+import android.content.Context
import android.database.Cursor
import android.os.Bundle
import android.provider.MediaStore
@@ -10,9 +12,10 @@ import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
-import ca.allanwang.kau.logging.KL
+import ca.allanwang.kau.permissions.kauRequestPermissions
import ca.allanwang.kau.ui.widgets.ElasticDragDismissFrameLayout
import ca.allanwang.kau.utils.bindView
+import ca.allanwang.kau.utils.dimenPixelSize
import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
@@ -27,14 +30,37 @@ abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.Load
val recycler: RecyclerView by bindView(R.id.kau_recycler)
val imageAdapter = FastItemAdapter<ImageItem>()
+ 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()
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.kau_activity_image_picker)
- with(recycler) {
- layoutManager = GridLayoutManager(this@ImagePickerActivityBase, 3)
- adapter = this@ImagePickerActivityBase.imageAdapter
+ recycler.layoutManager = GridLayoutManager(this, computeColumnCount(this))
+ recycler.adapter = imageAdapter
+
+ with(imageAdapter) {
+ withPositionBasedStateManagement(false)
+ withMultiSelect(true)
+ withSelectable(true)
+ withOnClickListener { v, _, _, _ ->
+ (v as BlurredImageView).toggleBlur()
+ true
+ }
}
- imageAdapter.add(arrayOf("a", "b", "c").map { ImageItem(it) })
draggableFrame.addListener(object : ElasticDragDismissFrameLayout.SystemChromeFader(this) {
override fun onDragDismissed() {
if (draggableFrame.translationY < 0) {
@@ -44,33 +70,45 @@ abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.Load
finishAfterTransition()
}
})
+ kauRequestPermissions(Manifest.permission.READ_EXTERNAL_STORAGE) {
+ granted, _ ->
+ if (granted) {
+ supportLoaderManager.initLoader(42, null, this)
+ }
+ }
}
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, null)
+ MediaStore.Images.Media.DATE_MODIFIED
+ )
+ return CursorLoader(this,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ columns,
+ null,
+ null,
+ MediaStore.Images.Media.DATE_MODIFIED + " DESC")
}
- override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {
- val dataIndex = data.getColumnIndex(MediaStore.Images.Media.DATA)
- val alstPhotos = mutableListOf<String>()
-
- data.moveToLast()
- while (!data.isBeforeFirst) {
- val photoPath = data.getString(dataIndex)
- KL.d(photoPath)
- alstPhotos.add(photoPath)
- data.moveToPrevious()
+ override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) {
+ reset()
+ if (data == null) return
+ if (data.moveToFirst()) {
+ do {
+ val img = ImageModel(data)
+ imageAdapter.add(ImageItem(img))
+ } while (data.moveToNext())
}
- imageAdapter.add(alstPhotos.map { ImageItem(it) })
}
- override fun onLoaderReset(loader: Loader<Cursor>) {
- imageAdapter.clear()
+ private fun reset() {
+ imageAdapter.clear();
}
+
+ override fun onLoaderReset(loader: Loader<Cursor>) = reset()
} \ No newline at end of file