aboutsummaryrefslogtreecommitdiff
path: root/imagepicker/src/main/kotlin
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-07-22 16:08:08 -0700
committerGitHub <noreply@github.com>2017-07-22 16:08:08 -0700
commit61d87976e8b29ed25061ae98743a6cf4f4274542 (patch)
treefa4d9bca5fe1b9478ba2f1cc1e6c7d8d18bf15ce /imagepicker/src/main/kotlin
parent8f2b5ac043f47cc44f43c3788d1377083fb339a2 (diff)
downloadkau-61d87976e8b29ed25061ae98743a6cf4f4274542.tar.gz
kau-61d87976e8b29ed25061ae98743a6cf4f4274542.tar.bz2
kau-61d87976e8b29ed25061ae98743a6cf4f4274542.zip
Support sdk 19 where possible and add image picker (#10)3.0
* Fix plural * Switch to long * Test plural again * Comment * Major update to image picker and view utils * Make image activity full screen * Update min sdk and prefix * Lower sdk requirement and make string private * Bring kpref activity to sdk 19
Diffstat (limited to 'imagepicker/src/main/kotlin')
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt17
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageHelper.kt10
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt37
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt49
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivity.kt200
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt114
-rw-r--r--imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerBinder.kt34
7 files changed, 304 insertions, 157 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
index 8fb5cf3..2ce00ba 100644
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt
@@ -2,6 +2,7 @@ package ca.allanwang.kau.imagepicker
import android.content.Context
import android.graphics.Color
+import android.support.annotation.StyleRes
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
@@ -17,15 +18,15 @@ import jp.wasabeef.blurry.internal.BlurTask
/**
* Created by Allan Wang on 2017-07-14.
*
- * ImageView that is can be blurred and selected
+ * ImageView that 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() {
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : FrameLayout(context, attrs, defStyleAttr), MeasureSpecContract by MeasureSpecDelegate() {
private var blurred = false
val imageBase: ImageView by bindView(R.id.image_base)
@@ -38,11 +39,6 @@ class BlurredImageView @JvmOverloads constructor(
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)
@@ -73,13 +69,12 @@ class BlurredImageView @JvmOverloads constructor(
val factor = BlurFactor()
factor.width = width
factor.height = height
- val task = BlurTask(imageBase, factor) {
+ BlurTask(imageBase, factor) {
imageBlur.setImageDrawable(it)
scaleAnimate(ANIMATION_SCALE).start()
imageBlur.alphaAnimate(1f).start()
imageForeground.alphaAnimate(1f).start()
- }
- task.execute()
+ }.execute()
}
/**
diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageHelper.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageHelper.kt
deleted file mode 100644
index 9b45679..0000000
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageHelper.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package ca.allanwang.kau.imagepicker
-
-import com.bumptech.glide.annotation.GlideModule
-import com.bumptech.glide.module.LibraryGlideModule
-
-/**
- * Created by Allan Wang on 2017-07-04.
- */
-@GlideModule
-class KauGlide : LibraryGlideModule() \ 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 852e1e8..d258822 100644
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt
@@ -25,33 +25,42 @@ class ImageItem(val data: ImageModel)
: KauIItem<ImageItem, ImageItem.ViewHolder>(R.layout.kau_iitem_image, { ViewHolder(it) }) {
private var failedToLoad = false
+ var withFade = true
- fun bindEvents(fastAdapter: FastAdapter<ImageItem>) {
- fastAdapter.withMultiSelect(true)
- fastAdapter.withSelectable(true)
- fastAdapter.withOnClickListener { v, _, _, _ ->
- val image = v as BlurredImageView
- image.toggleBlur()
- true
+ companion object {
+ fun bindEvents(fastAdapter: FastAdapter<ImageItem>) {
+ fastAdapter.withMultiSelect(true)
+ .withSelectable(true)
+ //adapter selector occurs before the on click event
+ .withOnClickListener { v, _, item, _ ->
+ val image = v as BlurredImageView
+ if (item.isSelected) image.blur()
+ else image.removeBlur()
+ true
+ }
}
}
+ override fun isSelectable(): Boolean = !failedToLoad
+
override fun bindView(holder: ViewHolder, payloads: List<Any>?) {
super.bindView(holder, payloads)
- holder.container.alpha = 0f
+ 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);
- holder.container.animate().alpha(1f).start();
+ if (withFade) 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;
+ holder.container.imageBase.setImageDrawable(resource)
+ if (isSelected) holder.container.blurInstantly()
+ if (withFade) holder.container.animate().alpha(1f).start();
+ return true;
}
})
.into(holder.container.imageBase)
@@ -63,14 +72,13 @@ class ImageItem(val data: ImageModel)
.sizePx(sizePx)
.paddingPx(sizePx / 3)
.color(Color.WHITE))
- //todo add background
- imageBase.setBackgroundColor(ImagePickerActivityBase.accentColor)
+ imageBase.setBackgroundColor(ImagePickerActivity.accentColor)
imageForeground.gone()
}
private fun computeViewSize(context: Context): Int {
val screenWidthPx = context.resources.displayMetrics.widthPixels
- return screenWidthPx / ImagePickerActivityBase.computeColumnCount(context)
+ return screenWidthPx / ImagePickerActivity.computeColumnCount(context)
}
override fun unbindView(holder: ViewHolder) {
@@ -81,6 +89,7 @@ class ImageItem(val data: ImageModel)
} else {
holder.container.fullReset()
}
+ failedToLoad = false
}
class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
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 26e4137..d744650 100644
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt
@@ -1,6 +1,8 @@
package ca.allanwang.kau.imagepicker
import android.database.Cursor
+import android.os.Parcel
+import android.os.Parcelable
import android.provider.MediaStore
import android.support.annotation.NonNull
@@ -8,14 +10,45 @@ 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)
+data class ImageModel(val size: Long, val dateModified: Long, val data: String, val displayName: String) : Parcelable {
- private fun Cursor.getString(name: String) = getString(getColumnIndex(name))
- private fun Cursor.getLong(name: String) = getLong(getColumnIndex(name))
+ constructor(@NonNull cursor: Cursor) : this(
+ cursor.getLong(MediaStore.Images.Media.SIZE),
+ cursor.getLong(MediaStore.Images.Media.DATE_MODIFIED),
+ cursor.getString(MediaStore.Images.Media.DATA),
+ cursor.getString(MediaStore.Images.Media.DISPLAY_NAME)
+ )
-} \ No newline at end of file
+ constructor(parcel: Parcel) : this(
+ parcel.readLong(),
+ parcel.readLong(),
+ parcel.readString(),
+ parcel.readString())
+
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeLong(this.size)
+ parcel.writeLong(this.dateModified)
+ parcel.writeString(this.data)
+ parcel.writeString(this.displayName)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator<ImageModel> {
+ override fun createFromParcel(parcel: Parcel): ImageModel {
+ return ImageModel(parcel)
+ }
+
+ override fun newArray(size: Int): Array<ImageModel?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+}
+
+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/ImagePickerActivity.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivity.kt
new file mode 100644
index 0000000..814cde4
--- /dev/null
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivity.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.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
+ */
+open class ImagePickerActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
+
+ val imageAdapter = FastItemAdapter<ImageItem>()
+
+ val coordinator: CoordinatorLayout by bindView(R.id.kau_coordinator)
+ val toolbar: Toolbar by bindView(R.id.kau_toolbar)
+ val selectionCount: TextView by bindView(R.id.kau_selection_count)
+ 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)
+
+ setContentView(R.layout.kau_activity_image_picker)
+
+ selectionCount.setCompoundDrawables(null, null, GoogleMaterial.Icon.gmd_image.toDrawable(this, 18), null)
+
+ setSupportActionBar(toolbar)
+ supportActionBar?.apply {
+ setDisplayHomeAsUpEnabled(true)
+ setDisplayShowHomeEnabled(true)
+ setHomeAsUpIndicator(GoogleMaterial.Icon.gmd_close.toDrawable(this@ImagePickerActivity, 18))
+ }
+ toolbar.setNavigationOnClickListener { onBackPressed() }
+
+ recycler.apply {
+ layoutManager = GridLayoutManager(context, computeColumnCount(context))
+ adapter = imageAdapter
+ setHasFixedSize(true)
+ itemAnimator = KauAnimator(FadeScaleAnimatorAdd(0.8f))
+ }
+
+ ImageItem.bindEvents(imageAdapter)
+ imageAdapter.withSelectionListener({ _, _ -> selectionCount.text = imageAdapter.selections.size.toString() })
+
+ fab.apply {
+ show()
+ setIcon(GoogleMaterial.Icon.gmd_send)
+ setOnClickListener {
+ val selection = imageAdapter.selectedItems
+ if (selection.isEmpty()) {
+ toast(R.string.kau_no_images_selected)
+ } else {
+ val intent = Intent()
+ val data = ArrayList(selection.map { it.data })
+ intent.putParcelableArrayListExtra(IMAGE_PICKER_RESULT, data)
+ setResult(RESULT_OK, intent)
+ finish()
+ }
+ }
+ hideOnDownwardsScroll(recycler)
+ }
+
+ 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)
+ }
+ }
+ }
+
+ /**
+ * Decide whether the toolbar can hide itself
+ * We typically want this behaviour unless we don't have enough images
+ * to fill the entire screen. In that case we don't want the recyclerview to be scrollable
+ * which means the toolbar shouldn't scroll either
+
+ * @param scrollable true if scroll flags are enabled, false otherwise
+ */
+ private fun setToolbarScrollable(scrollable: Boolean) {
+ val params = toolbar.layoutParams as AppBarLayout.LayoutParams
+ if (scrollable)
+ params.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS or AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
+ else
+ 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())
+ 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 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/ImagePickerActivityBase.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt
deleted file mode 100644
index 24c2db7..0000000
--- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-package ca.allanwang.kau.imagepicker
-
-import android.Manifest
-import android.content.Context
-import android.database.Cursor
-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 android.support.v7.widget.Toolbar
-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
-
-
-/**
- * Created by Allan Wang on 2017-07-04.
- *
- */
-abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
-
- val toolbar: Toolbar by bindView(R.id.kau_toolbar)
- val draggableFrame: ElasticDragDismissFrameLayout by bindView(R.id.kau_draggable)
- 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)
- recycler.layoutManager = GridLayoutManager(this, computeColumnCount(this))
- recycler.adapter = imageAdapter
-
- with(imageAdapter) {
- withPositionBasedStateManagement(false)
- withMultiSelect(true)
- withSelectable(true)
- withOnClickListener { v, _, _, _ ->
- (v as BlurredImageView).toggleBlur()
- true
- }
- }
- draggableFrame.addListener(object : ElasticDragDismissFrameLayout.SystemChromeFader(this) {
- override fun onDragDismissed() {
- if (draggableFrame.translationY < 0) {
-// window.returnTransition = TransitionInflater.from(this@ImagePickerActivityBase)
-// .inflateTransition(R.transition.kau_about_return_upwards)
- }
- 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,
- MediaStore.Images.Media.DATE_MODIFIED + " DESC")
- }
-
- 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())
- }
- }
-
- private fun reset() {
- imageAdapter.clear();
- }
-
- override fun onLoaderReset(loader: Loader<Cursor>) = reset()
-} \ 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
new file mode 100644
index 0000000..9e63464
--- /dev/null
+++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerBinder.kt
@@ -0,0 +1,34 @@
+package ca.allanwang.kau.imagepicker
+
+import android.app.Activity
+import android.content.Intent
+
+/**
+ * Created by Allan Wang on 2017-07-21.
+ *
+ * Extension functions for interacting with the image picker
+ * as well as internal constants
+ */
+
+/**
+ * Image picker launcher
+ */
+fun Activity.kauLaunchImagePicker(clazz: Class<out ImagePickerActivity>, requestCode: Int) {
+ startActivityForResult(Intent(this, clazz), requestCode)
+}
+
+fun Activity.kauLaunchImagePicker(requestCode: Int) = kauLaunchImagePicker(ImagePickerActivity::class.java, requestCode)
+
+/**
+ * Image picker result
+ * call under [Activity.onActivityResult]
+ * and make sure that the requestCode matches first
+ */
+fun Activity.kauOnImagePickerResult(resultCode: Int, data: Intent?) = ImagePickerActivity.onImagePickerResult(resultCode, data)
+
+internal const val LOADER_ID = 42
+internal const val IMAGE_PICKER_RESULT = "image_picker_result"
+
+internal const val ANIMATION_DURATION = 200L
+internal const val ANIMATION_SCALE = 0.95f
+