aboutsummaryrefslogtreecommitdiff
path: root/mediapicker/src/main/kotlin/ca
diff options
context:
space:
mode:
Diffstat (limited to 'mediapicker/src/main/kotlin/ca')
-rw-r--r--mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaActionItem.kt154
-rw-r--r--mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaItemBasic.kt7
-rw-r--r--mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaModel.kt8
-rw-r--r--mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerActivityBase.kt12
-rw-r--r--mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerActivityOverlayBase.kt5
-rw-r--r--mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaPickerCore.kt109
-rw-r--r--mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaType.kt16
-rw-r--r--mediapicker/src/main/kotlin/ca/allanwang/kau/mediapicker/MediaUtils.kt59
8 files changed, 348 insertions, 22 deletions
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