diff options
author | Allan Wang <me@allanwang.ca> | 2017-08-18 14:39:40 -0700 |
---|---|---|
committer | Allan Wang <me@allanwang.ca> | 2017-08-30 12:24:19 -0400 |
commit | a101b528efdee74fc1970b7f1fe68263f0b20269 (patch) | |
tree | 8fbd0fea544ec543cdf5ea2b74d81099d4ebd554 | |
parent | 9432652b03ae4d01e3dda4325984637d9523b9e2 (diff) | |
download | kau-a101b528efdee74fc1970b7f1fe68263f0b20269.tar.gz kau-a101b528efdee74fc1970b7f1fe68263f0b20269.tar.bz2 kau-a101b528efdee74fc1970b7f1fe68263f0b20269.zip |
Create media picker action items (#40)
* Create action items
* Increment version
* Update camera action
* Abstract camera action
* Add and test
* Refactor and add docs
17 files changed, 423 insertions, 49 deletions
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/FileUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/FileUtils.kt index 25e0519..b03707d 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/FileUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/FileUtils.kt @@ -11,23 +11,5 @@ import java.util.* /** * Created by Allan Wang on 2017-08-04. */ -@Throws(IOException::class) -fun createMediaFile(prefix: String, extension: String): File { - val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val imageFileName = "${prefix}_${timeStamp}_" - val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) - val frostDir = File(storageDir, prefix) - if (!frostDir.exists()) frostDir.mkdirs() - return File.createTempFile(imageFileName, extension, frostDir) -} - -@Throws(IOException::class) -fun Context.createPrivateMediaFile(prefix: String, extension: String): File { - val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val imageFileName = "${prefix}_${timeStamp}_" - val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES) - return File.createTempFile(imageFileName, extension, storageDir) -} - fun File.copyFromInputStream(inputStream: InputStream) = inputStream.use { input -> outputStream().use { output -> input.copyTo(output) } }
\ No newline at end of file diff --git a/gradle.properties b/gradle.properties index b738642..d7837c8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ TARGET_SDK=26 BUILD_TOOLS=26.0.1 ANDROID_SUPPORT_LIBS=26.0.0 -VERSION_NAME=3.3.0 +VERSION_NAME=3.3.3 KOTLIN=1.1.3-2 ABOUT_LIBRARIES=5.9.7 diff --git a/mediapicker/README.md b/mediapicker/README.md index a743a47..0eb6fd5 100644 --- a/mediapicker/README.md +++ b/mediapicker/README.md @@ -40,4 +40,23 @@ 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 `kauOnMediaPickerResult`, -which will return the list of MediaModels.
\ No newline at end of file +which will return the list of MediaModels. + +## MediaActions + +On top of retrieving your media file, you may also add action items to the start +of the grid. All actions will return their results immediately, and retrieve media types based on the activity. + +### MediaActionCamera + +Gets an image or a video from the default camera. No permissions are necessary. +Note that since api 24, passing general uris may throw a [FileUriExposedException](https://developer.android.com/reference/android/os/FileUriExposedException.html), +so your own resolvers need to be passed for this to work. See the sample xml folder for an example. + +### MediaActionCameraVideo + +Given that getting videos do not require resolvers, this item can be used for videos only without any required arguments. + +### MediaActionGallery + +Defines whether you want to pick one or more media items from the default gallery app.
\ No newline at end of file diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaActionItem.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaActionItem.kt new file mode 100644 index 0000000..5910650 --- /dev/null +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaActionItem.kt @@ -0,0 +1,154 @@ +package ca.allanwang.kau.mediapicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.MediaStore +import ca.allanwang.kau.iitems.KauIItem +import ca.allanwang.kau.permissions.PERMISSION_READ_EXTERNAL_STORAGE +import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE +import ca.allanwang.kau.permissions.kauRequestPermissions +import ca.allanwang.kau.utils.materialDialog +import ca.allanwang.kau.utils.string +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.mikepenz.iconics.typeface.IIcon +import java.io.File + + +/** + * Created by Allan Wang on 2017-08-17. + */ +class MediaActionItem( + val action: MediaAction, + val mediaType: MediaType +) : KauIItem<MediaActionItem, MediaItemBasic.ViewHolder>(R.layout.kau_iitem_image_basic, { MediaItemBasic.ViewHolder(it) }, R.id.kau_item_media_action) { + + override fun isSelectable(): Boolean = false + + override fun bindView(holder: MediaItemBasic.ViewHolder, payloads: MutableList<Any>?) { + super.bindView(holder, payloads) + holder.image.apply { + setImageDrawable(MediaPickerCore.getIconDrawable(context, action.iicon(this@MediaActionItem), action.color)) + setOnClickListener { action.invoke(context, this@MediaActionItem) } + } + } + + override fun unbindView(holder: MediaItemBasic.ViewHolder) { + super.unbindView(holder) + holder.image.apply { + setImageDrawable(null) + setOnClickListener(null) + } + } +} + +interface MediaAction { + var color: Int + fun iicon(item: MediaActionItem): IIcon + fun invoke(c: Context, item: MediaActionItem) +} + +internal const val MEDIA_ACTION_REQUEST_CAMERA = 100 +internal const val MEDIA_ACTION_REQUEST_PICKER = 101 + +/** + * Dynamic camera items for both images and videos + * Given that images require a uri to save the file, they must be implemented on top + * of this abstract class. + * + * If you just wish to use videos, see [MediaActionCameraVideo] + */ +abstract class MediaActionCamera( + override var color: Int = MediaPickerCore.accentColor +) : MediaAction { + + abstract fun createFile(context: Context): File + abstract fun createUri(context: Context, file: File): Uri + + override fun iicon(item: MediaActionItem) = when (item.mediaType) { + MediaType.IMAGE -> GoogleMaterial.Icon.gmd_photo_camera + MediaType.VIDEO -> GoogleMaterial.Icon.gmd_videocam + } + + override fun invoke(c: Context, item: MediaActionItem) { + c.kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) { + granted, _ -> + if (granted) { + val intent = Intent(item.mediaType.captureType) + if (intent.resolveActivity(c.packageManager) == null) { + c.materialDialog { + title(R.string.kau_no_camera_found) + content(R.string.kau_no_camera_found_content) + } + return@kauRequestPermissions + } + if (item.mediaType == MediaType.IMAGE) { + val file: File = try { + createFile(c) + } catch (e: java.io.IOException) { + c.materialDialog { + title(R.string.kau_error) + content(R.string.kau_temp_file_creation_failed) + } + return@kauRequestPermissions + } + intent.putExtra(MediaStore.EXTRA_OUTPUT, createUri(c, file)) + (c as? MediaPickerCore<*>)?.tempPath = file.absolutePath + } + (c as Activity).startActivityForResult(intent, MEDIA_ACTION_REQUEST_CAMERA) + } + } + } +} + +/** + * Basic camera action just for videos + */ +class MediaActionCameraVideo( + override var color: Int = MediaPickerCore.accentColor +) : MediaAction { + override fun iicon(item: MediaActionItem) = GoogleMaterial.Icon.gmd_videocam + override fun invoke(c: Context, item: MediaActionItem) { + val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE) + if (intent.resolveActivity(c.packageManager) == null) { + c.materialDialog { + title(R.string.kau_no_camera_found) + content(R.string.kau_no_camera_found_content) + } + return + } + (c as Activity).startActivityForResult(intent, MEDIA_ACTION_REQUEST_CAMERA) + } +} + +/** + * Opens a picker for the type specified in the activity + * The type will be added programmatically + */ +class MediaActionGallery( + val multiple: Boolean = false, + override var color: Int = MediaPickerCore.accentColor +) : MediaAction { + + override fun iicon(item: MediaActionItem) = when (item.mediaType) { + MediaType.IMAGE -> if (multiple) GoogleMaterial.Icon.gmd_photo_library else GoogleMaterial.Icon.gmd_photo + MediaType.VIDEO -> GoogleMaterial.Icon.gmd_video_library + } + + override fun invoke(c: Context, item: MediaActionItem) { + c.kauRequestPermissions(PERMISSION_READ_EXTERNAL_STORAGE) { + granted, _ -> + if (granted) { + val intent = Intent().apply { + type = item.mediaType.mimeType + action = Intent.ACTION_GET_CONTENT + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple) + } + (c as Activity).startActivityForResult( + Intent.createChooser(intent, c.string(R.string.kau_select_media)), + MEDIA_ACTION_REQUEST_PICKER) + } + } + } +}
\ No newline at end of file diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaItemBasic.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaItemBasic.kt index c28ed29..e546afb 100644 --- a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaItemBasic.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaItemBasic.kt @@ -30,12 +30,7 @@ class MediaItemBasic(val data: MediaModel) fastAdapter.withSelectable(false) //add image data and return right away .withOnClickListener { _, _, item, _ -> - val intent = Intent() - val data = arrayListOf(item.data) - intent.putParcelableArrayListExtra(MEDIA_PICKER_RESULT, data) - activity.setResult(AppCompatActivity.RESULT_OK, intent) - if (buildIsLollipopAndUp) activity.finishAfterTransition() - else activity.finish() + activity.finish(arrayListOf(item.data)) true } } diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaModel.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaModel.kt index 9188dd6..ae85558 100644 --- a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaModel.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaModel.kt @@ -27,6 +27,14 @@ data class MediaModel( cursor.getString(4) ) + constructor(f: File) : this( + f.absolutePath, + f.extension, // this isn't a mime type, but it does give some info + f.length(), + f.lastModified(), + f.nameWithoutExtension + ) + constructor(parcel: Parcel) : this( parcel.readString(), parcel.readString(), diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerActivityBase.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerActivityBase.kt index 2ba6b43..c3b6396 100644 --- a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerActivityBase.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerActivityBase.kt @@ -1,6 +1,5 @@ package ca.allanwang.kau.mediapicker -import android.content.Intent import android.database.Cursor import android.os.Bundle import android.support.design.widget.AppBarLayout @@ -21,7 +20,10 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial * Images are blurred when selected, and multiple images can be selected at a time. * Having three layered images makes this slightly slower than [MediaPickerActivityOverlayBase] */ -abstract class MediaPickerActivityBase(mediaType: MediaType) : MediaPickerCore<MediaItem>(mediaType) { +abstract class MediaPickerActivityBase( + mediaType: MediaType, + mediaActions: List<MediaAction> = emptyList() +) : MediaPickerCore<MediaItem>(mediaType, mediaActions) { val coordinator: CoordinatorLayout by bindView(R.id.kau_coordinator) val toolbar: Toolbar by bindView(R.id.kau_toolbar) @@ -57,11 +59,7 @@ abstract class MediaPickerActivityBase(mediaType: MediaType) : MediaPickerCore<M if (selection.isEmpty()) { toast(R.string.kau_no_items_selected) } else { - val intent = Intent() - val data = ArrayList(selection.map { it.data }) - intent.putParcelableArrayListExtra(MEDIA_PICKER_RESULT, data) - setResult(RESULT_OK, intent) - finish() + finish(ArrayList(selection.map { it.data })) } } hideOnDownwardsScroll(recycler) diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerActivityOverlayBase.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerActivityOverlayBase.kt index b5da345..67f9577 100644 --- a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerActivityOverlayBase.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerActivityOverlayBase.kt @@ -17,7 +17,10 @@ import ca.allanwang.kau.utils.toast * as opposed to three layers deep */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) -abstract class MediaPickerActivityOverlayBase(mediaType: MediaType) : MediaPickerCore<MediaItemBasic>(mediaType) { +abstract class MediaPickerActivityOverlayBase( + mediaType: MediaType, + mediaActions: List<MediaAction> = emptyList() +) : MediaPickerCore<MediaItemBasic>(mediaType, mediaActions) { val draggable: ElasticDragDismissFrameLayout by bindView(R.id.kau_draggable) val recycler: RecyclerView by bindView(R.id.kau_recyclerview) diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt index 71449d3..6f0241c 100644 --- a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt @@ -2,12 +2,16 @@ package ca.allanwang.kau.mediapicker import android.Manifest import android.app.Activity +import android.content.ContentResolver import android.content.Context import android.content.Intent import android.database.Cursor import android.graphics.Color import android.graphics.drawable.Drawable +import android.net.Uri import android.os.Bundle +import android.provider.BaseColumns +import android.provider.DocumentsContract import android.provider.MediaStore import android.support.v4.app.LoaderManager import android.support.v4.content.CursorLoader @@ -18,15 +22,19 @@ import ca.allanwang.kau.animators.FadeScaleAnimatorAdd import ca.allanwang.kau.animators.KauAnimator import ca.allanwang.kau.internal.KauBaseActivity import ca.allanwang.kau.kotlin.lazyContext +import ca.allanwang.kau.logging.KL import ca.allanwang.kau.permissions.kauRequestPermissions import ca.allanwang.kau.utils.dimenPixelSize import ca.allanwang.kau.utils.toast import com.bumptech.glide.Glide import com.mikepenz.fastadapter.IItem +import com.mikepenz.fastadapter.adapters.HeaderAdapter import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon import org.jetbrains.anko.doAsync +import java.io.File import java.util.concurrent.ExecutionException import java.util.concurrent.Future @@ -35,7 +43,10 @@ import java.util.concurrent.Future * * Container for the main logic behind the both pickers */ -abstract class MediaPickerCore<T : IItem<*, *>>(val mediaType: MediaType) : KauBaseActivity(), LoaderManager.LoaderCallbacks<Cursor> { +abstract class MediaPickerCore<T : IItem<*, *>>( + val mediaType: MediaType, + val mediaActions: List<MediaAction> +) : KauBaseActivity(), LoaderManager.LoaderCallbacks<Cursor> { companion object { val viewSize = lazyContext { computeViewSize(it) } @@ -62,11 +73,13 @@ abstract class MediaPickerCore<T : IItem<*, *>>(val mediaType: MediaType) : KauB /** * Create error tile for a given item */ - fun getErrorDrawable(context: Context): Drawable { + fun getErrorDrawable(context: Context) = getIconDrawable(context, GoogleMaterial.Icon.gmd_error, accentColor) + + fun getIconDrawable(context: Context, iicon: IIcon, color: Int): Drawable { val sizePx = MediaPickerCore.computeViewSize(context) - return IconicsDrawable(context, GoogleMaterial.Icon.gmd_error) + return IconicsDrawable(context, iicon) .sizePx(sizePx) - .backgroundColor(accentColor) + .backgroundColor(color) .paddingPx(sizePx / 3) .color(Color.WHITE) } @@ -101,6 +114,9 @@ abstract class MediaPickerCore<T : IItem<*, *>>(val mediaType: MediaType) : KauB val extraSpace: Int by lazy { resources.displayMetrics.heightPixels } fun initializeRecycler(recycler: RecyclerView) { + val adapterWrapper = HeaderAdapter<MediaActionItem>() + adapterWrapper.wrap(adapter) + adapterWrapper.add(mediaActions.map { MediaActionItem(it, mediaType) }) recycler.apply { val manager = object : GridLayoutManager(context, computeColumnCount(context)) { override fun getExtraLayoutSpace(state: RecyclerView.State?): Int { @@ -110,7 +126,7 @@ abstract class MediaPickerCore<T : IItem<*, *>>(val mediaType: MediaType) : KauB setItemViewCacheSize(CACHE_SIZE) isDrawingCacheEnabled = true layoutManager = manager - adapter = this@MediaPickerCore.adapter + adapter = adapterWrapper setHasFixedSize(true) itemAnimator = KauAnimator(FadeScaleAnimatorAdd(0.8f)) } @@ -209,4 +225,87 @@ abstract class MediaPickerCore<T : IItem<*, *>>(val mediaType: MediaType) : KauB prefetcher?.cancel(true) super.onDestroy() } + + /** + * Method used to retrieve uri data for API 19+ + * See <a href="http://hmkcode.com/android-display-selected-image-and-its-real-path/"></a> + */ + private fun <R> ContentResolver.query(baseUri: Uri, uris: List<Uri>, block: (cursor: Cursor) -> R) { + val ids = uris.map { + DocumentsContract.getDocumentId(it).split(":").getOrNull(1) + }.filterNotNull().joinToString(prefix = "(", separator = ",", postfix = ")") + //? query replacements are done for one arg at a time + //since we potentially have a list of ids, we'll just format the WHERE clause ourself + query(baseUri, MediaModel.projection, "${BaseColumns._ID} IN $ids", null, sortQuery)?.use(block) + } + + internal var tempPath: String? = null + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode != RESULT_OK) { + if (tempPath != null) { + val f = File(tempPath) + if (f.exists()) f.delete() + tempPath = null + } + return super.onActivityResult(requestCode, resultCode, data) + } + KL.d("Media result received") + if (data == null) { + KL.d("Media null intent") + return super.onActivityResult(requestCode, resultCode, data) + } + when (requestCode) { + MEDIA_ACTION_REQUEST_CAMERA -> onCameraResult(data) + MEDIA_ACTION_REQUEST_PICKER -> onPickerResult(data) + else -> super.onActivityResult(requestCode, resultCode, data) + } + } + + private fun onCameraResult(data: Intent) { + val f: File + if (tempPath != null) { + f = File(tempPath) + tempPath = null + } else if (data.data != null) { + f = File(data.data.path) + } else { + KL.d("Media camera no file found") + return + } + if (f.exists()) { + KL.v("Media camera path found", f.absolutePath) + scanMedia(f) + finish(arrayListOf(MediaModel(f))) + } else { + KL.d("Media camera file not found") + } + } + + private fun onPickerResult(data: Intent) { + val items = mutableListOf<Uri>() + if (data.data != null) { + KL.v("Media picker data uri", data.data.path) + items.add(data.data) + } else { + val clip = data.clipData + if (clip != null) { + items.addAll((0 until clip.itemCount).map { + clip.getItemAt(it).uri.apply { + KL.v("Media picker clip uri", path) + } + }) + } + } + if (items.isEmpty()) return KL.d("Media picker empty intent") + contentResolver.query(mediaType.contentUri, items) { + if (it.moveToFirst()) { + val models = arrayListOf<MediaModel>() + do { + models.add(MediaModel(it)) + } while (it.moveToNext()) + finish(models) + } + } + } }
\ No newline at end of file diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaType.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaType.kt index cfec331..0af4c2e 100644 --- a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaType.kt +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaType.kt @@ -7,7 +7,17 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy /** * Created by Allan Wang on 2017-07-30. */ -enum class MediaType(val cacheStrategy: DiskCacheStrategy, val contentUri: Uri) { - IMAGE(DiskCacheStrategy.AUTOMATIC, MediaStore.Images.Media.EXTERNAL_CONTENT_URI), - VIDEO(DiskCacheStrategy.AUTOMATIC, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) +enum class MediaType(val cacheStrategy: DiskCacheStrategy, + val mimeType: String, + val captureType: String, + val contentUri: Uri) { + IMAGE(DiskCacheStrategy.AUTOMATIC, + "image/*", + MediaStore.ACTION_IMAGE_CAPTURE, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI), + + VIDEO(DiskCacheStrategy.AUTOMATIC, + "video/*", + MediaStore.ACTION_VIDEO_CAPTURE, + MediaStore.Video.Media.EXTERNAL_CONTENT_URI) }
\ No newline at end of file diff --git a/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaUtils.kt b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaUtils.kt new file mode 100644 index 0000000..0fb5824 --- /dev/null +++ b/mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaUtils.kt @@ -0,0 +1,59 @@ +package ca.allanwang.kau.mediapicker + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Environment +import android.support.v7.app.AppCompatActivity +import ca.allanwang.kau.utils.buildIsLollipopAndUp +import com.mikepenz.fastadapter.IItem +import java.io.File +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.* + + +/** + * Created by Allan Wang on 2017-08-17. + */ +@SuppressLint("NewApi") +internal fun Activity.finish(data: ArrayList<MediaModel>) { + val intent = Intent() + intent.putParcelableArrayListExtra(MEDIA_PICKER_RESULT, data) + setResult(AppCompatActivity.RESULT_OK, intent) + if (buildIsLollipopAndUp) finishAfterTransition() + else finish() +} + +@Throws(IOException::class) +fun createMediaFile(prefix: String, extension: String): File { + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val imageFileName = "${prefix}_${timeStamp}_" + val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + val frostDir = File(storageDir, prefix) + if (!frostDir.exists()) frostDir.mkdirs() + return File.createTempFile(imageFileName, extension, frostDir) +} + +@Throws(IOException::class) +fun Context.createPrivateMediaFile(prefix: String, extension: String): File { + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val imageFileName = "${prefix}_${timeStamp}_" + val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES) + return File.createTempFile(imageFileName, extension, storageDir) +} + +/** + * Scan the path so that the media item is properly added to galleries + * + * See <a href="https://developer.android.com/training/camera/photobasics.html#TaskGallery">Docs</a> + */ +fun Context.scanMedia(f: File) { + if (!f.exists()) return + val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) + val contentUri = Uri.fromFile(f) + mediaScanIntent.data = contentUri + sendBroadcast(mediaScanIntent) +}
\ No newline at end of file diff --git a/mediapicker/src/main/res/values/ids.xml b/mediapicker/src/main/res/values/ids.xml new file mode 100644 index 0000000..a533ecc --- /dev/null +++ b/mediapicker/src/main/res/values/ids.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <item name="kau_item_media_action" type="id" /> +</resources>
\ No newline at end of file diff --git a/mediapicker/src/main/res/values/strings.xml b/mediapicker/src/main/res/values/strings.xml index 39ab16b..717e12b 100644 --- a/mediapicker/src/main/res/values/strings.xml +++ b/mediapicker/src/main/res/values/strings.xml @@ -4,4 +4,11 @@ <string name="kau_no_items_selected">No items have been selected</string> <string name="kau_blurrable_imageview">Blurrable ImageView</string> <string name="kau_no_items_loaded">No items loaded</string> + + <string name="kau_no_camera_found">No camera found</string> + <string name="kau_no_camera_found_content">Please install a camera app and try again.</string> + + <string name="kau_temp_file_creation_failed">Failed to create a temporary file.</string> + + <string name="kau_select_media">Select Media</string> </resources>
\ No newline at end of file diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index ee6cc86..d8bbe51 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:name=".SampleApp" @@ -46,6 +47,16 @@ <activity android:name=".AdapterActivity" android:theme="@style/Kau.Translucent.SlideBottom" /> + + <provider + android:name="android.support.v4.content.FileProvider" + android:authorities="${applicationId}.provider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/kau_file_paths" /> + </provider> </application> </manifest>
\ No newline at end of file diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt index 5d0bd36..09002d4 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt @@ -169,6 +169,10 @@ class MainActivity : KPrefActivity() { onClick = { _, _, _ -> startActivityWithEdge(SWIPE_EDGE_LEFT); false } } + plainText(R.string.image_showcase) { + onClick = { _, _, _ -> kauLaunchMediaPicker(ImagePickerActivity::class.java, REQUEST_MEDIA); false } + } + plainText(R.string.video_overlay_showcase) { onClick = { _, _, _ -> kauLaunchMediaPicker(VideoPickerActivityOverlay::class.java, REQUEST_MEDIA); false } } diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/MediaPicker.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/MediaPicker.kt index 7aac0ef..618ca07 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/MediaPicker.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/MediaPicker.kt @@ -1,16 +1,28 @@ package ca.allanwang.kau.sample -import ca.allanwang.kau.mediapicker.MediaPickerActivityBase -import ca.allanwang.kau.mediapicker.MediaPickerActivityOverlayBase -import ca.allanwang.kau.mediapicker.MediaType +import android.content.Context +import android.net.Uri +import android.support.v4.content.FileProvider +import ca.allanwang.kau.mediapicker.* +import java.io.File /** * Created by Allan Wang on 2017-07-23. */ -class ImagePickerActivity : MediaPickerActivityBase(MediaType.IMAGE) +private fun actions(multiple: Boolean) = listOf(object : MediaActionCamera() { -class ImagePickerActivityOverlay : MediaPickerActivityOverlayBase(MediaType.IMAGE) + override fun createFile(context: Context): File + = createMediaFile("KAU", ".jpg") -class VideoPickerActivity : MediaPickerActivityBase(MediaType.VIDEO) + override fun createUri(context: Context, file: File): Uri + = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file) -class VideoPickerActivityOverlay : MediaPickerActivityOverlayBase(MediaType.VIDEO)
\ No newline at end of file +}, MediaActionGallery(multiple)) + +class ImagePickerActivity : MediaPickerActivityBase(MediaType.IMAGE, actions(true)) + +class ImagePickerActivityOverlay : MediaPickerActivityOverlayBase(MediaType.IMAGE, actions(false)) + +class VideoPickerActivity : MediaPickerActivityBase(MediaType.VIDEO, actions(true)) + +class VideoPickerActivityOverlay : MediaPickerActivityOverlayBase(MediaType.VIDEO, actions(false))
\ No newline at end of file diff --git a/sample/src/main/res/xml/kau_file_paths.xml b/sample/src/main/res/xml/kau_file_paths.xml new file mode 100644 index 0000000..83b00bb --- /dev/null +++ b/sample/src/main/res/xml/kau_file_paths.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<paths> + <external-path + name="KAU_images" + path="Android/data/ca.allanwang.kau.sample/files/Pictures" /> + <external-path + name="KAU_public_images" + path="Pictures/KAU" /> +</paths>
\ No newline at end of file |