aboutsummaryrefslogtreecommitdiff
path: root/imagepicker
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-07-18 20:16:23 -0700
committerGitHub <noreply@github.com>2017-07-18 20:16:23 -0700
commit8f2b5ac043f47cc44f43c3788d1377083fb339a2 (patch)
tree8f91042414de211cbfe67a76298300884f46a765 /imagepicker
parent4eee8d59c21b2061b9f5fd0e805ca60ab84c3585 (diff)
downloadkau-8f2b5ac043f47cc44f43c3788d1377083fb339a2.tar.gz
kau-8f2b5ac043f47cc44f43c3788d1377083fb339a2.tar.bz2
kau-8f2b5ac043f47cc44f43c3788d1377083fb339a2.zip
Dev 2.1 (#8)
* Rewrite animation interfaces * Update changelog * Add scale factor for slide * Remove margins in iitems and replace with decorators * Remove mutable list * Switch cardiitem to use lambdas for click * status * Utils update and imagepicker fixes * Remove stringholder * Add fade in fade out * Increment about version * Rename fromedge to direction in javadocs * More logging * Add logging and docs * Make card icons visible * Update email builder and icon padding * Create elastic recycler activity * Fix card iitem * Add lint check and plurals * Inline all the things * Format and sort xml * Update dependencies and increment version
Diffstat (limited to 'imagepicker')
-rw-r--r--imagepicker/build.gradle1
-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
-rw-r--r--imagepicker/src/main/res/layout/kau_blurred_imageview.xml32
-rw-r--r--imagepicker/src/main/res/layout/kau_iitem_image.xml18
-rw-r--r--imagepicker/src/main/res/values/colors.xml6
-rw-r--r--imagepicker/src/main/res/values/dimens.xml6
-rw-r--r--imagepicker/src/main/res/values/styles.xml5
10 files changed, 373 insertions, 41 deletions
diff --git a/imagepicker/build.gradle b/imagepicker/build.gradle
index 14c07ac..d63f5fd 100644
--- a/imagepicker/build.gradle
+++ b/imagepicker/build.gradle
@@ -10,6 +10,7 @@ dependencies {
compile "com.github.bumptech.glide:glide:${GLIDE}"
kapt "com.github.bumptech.glide:compiler:${GLIDE}"
+ compile "jp.wasabeef:blurry:${BLURRY}"
}
apply from: '../artifacts.gradle'
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
diff --git a/imagepicker/src/main/res/layout/kau_blurred_imageview.xml b/imagepicker/src/main/res/layout/kau_blurred_imageview.xml
new file mode 100644
index 0000000..e28cb9a
--- /dev/null
+++ b/imagepicker/src/main/res/layout/kau_blurred_imageview.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/image_base"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:contentDescription="@string/kau_blurrable_imageview"
+ android:scaleType="centerCrop" />
+
+ <ImageView
+ android:id="@+id/image_blur"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:alpha="0"
+ android:contentDescription="@string/kau_blurrable_imageview"
+ android:scaleType="centerCrop" />
+
+ <ImageView
+ android:id="@+id/image_foreground"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:alpha="0"
+ android:background="@color/kau_blurred_image_selection_overlay"
+ android:contentDescription="@string/kau_blurrable_imageview"
+ android:scaleType="centerInside" />
+
+</merge> \ No newline at end of file
diff --git a/imagepicker/src/main/res/layout/kau_iitem_image.xml b/imagepicker/src/main/res/layout/kau_iitem_image.xml
index 22cc998..9d51d77 100644
--- a/imagepicker/src/main/res/layout/kau_iitem_image.xml
+++ b/imagepicker/src/main/res/layout/kau_iitem_image.xml
@@ -1,15 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<ca.allanwang.kau.imagepicker.BlurredImageView 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">
-
- <ImageView
- android:id="@+id/kau_image"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:scaleType="centerCrop"
- android:background="#f0f"
- app:layout_constraintDimensionRatio="1:1" />
-
-</android.support.constraint.ConstraintLayout> \ No newline at end of file
+ android:layout_height="match_parent"
+ android:layout_margin="2dp"
+ android:foreground="@drawable/kau_selectable_white"
+ app:relativeHeight="1" /> \ No newline at end of file
diff --git a/imagepicker/src/main/res/values/colors.xml b/imagepicker/src/main/res/values/colors.xml
new file mode 100644
index 0000000..ebaa3f7
--- /dev/null
+++ b/imagepicker/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <color name="kau_blurred_image_selection_overlay">#30000000</color>
+
+</resources> \ No newline at end of file
diff --git a/imagepicker/src/main/res/values/dimens.xml b/imagepicker/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..3ff2dd7
--- /dev/null
+++ b/imagepicker/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <dimen name="kau_image_minimum_size">120dp</dimen>
+
+</resources> \ No newline at end of file
diff --git a/imagepicker/src/main/res/values/styles.xml b/imagepicker/src/main/res/values/styles.xml
index e2d7280..1fbb184 100644
--- a/imagepicker/src/main/res/values/styles.xml
+++ b/imagepicker/src/main/res/values/styles.xml
@@ -1,8 +1,5 @@
<resources>
- <style name="Kau.Translucent.ImagePicker">
- <!--<item name="android:windowEnterTransition">@transition/kau_enter_slide_bottom</item>-->
- <!--<item name="android:windowReturnTransition">@transition/kau_about_return_downward</item>-->
- </style>
+ <style name="Kau.Translucent.ImagePicker" parent="Kau.Translucent.SlideBottom" />
</resources>