more idiomatic kotlin
This commit is contained in:
parent
752749bafe
commit
bb96f2f65a
7 changed files with 76 additions and 69 deletions
|
@ -74,7 +74,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getImageEntry(call: MethodCall, result: MethodChannel.Result) {
|
private suspend fun getImageEntry(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val mimeType = call.argument<String>("mimeType") // MIME type is optional
|
val mimeType = call.argument<String>("mimeType") // MIME type is optional
|
||||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
|
@ -94,7 +94,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rename(call: MethodCall, result: MethodChannel.Result) {
|
private suspend fun rename(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val entryMap = call.argument<FieldMap>("entry")
|
val entryMap = call.argument<FieldMap>("entry")
|
||||||
val newName = call.argument<String>("newName")
|
val newName = call.argument<String>("newName")
|
||||||
if (entryMap == null || newName == null) {
|
if (entryMap == null || newName == null) {
|
||||||
|
|
|
@ -62,7 +62,7 @@ class ImageOpStreamHandler(private val context: Context, private val arguments:
|
||||||
handler.post { eventSink.endOfStream() }
|
handler.post { eventSink.endOfStream() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun move() {
|
private suspend fun move() {
|
||||||
if (arguments !is Map<*, *> || entryMapList.isEmpty()) {
|
if (arguments !is Map<*, *> || entryMapList.isEmpty()) {
|
||||||
endOfStream()
|
endOfStream()
|
||||||
return
|
return
|
||||||
|
|
|
@ -40,7 +40,7 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
|
||||||
handler.post { eventSink.endOfStream() }
|
handler.post { eventSink.endOfStream() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchAll() {
|
private suspend fun fetchAll() {
|
||||||
MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap()) { success(it) }
|
MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap()) { success(it) }
|
||||||
endOfStream()
|
endOfStream()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import android.provider.MediaStore
|
||||||
import deckers.thibault.aves.model.SourceImageEntry
|
import deckers.thibault.aves.model.SourceImageEntry
|
||||||
|
|
||||||
internal class ContentImageProvider : ImageProvider() {
|
internal class ContentImageProvider : ImageProvider() {
|
||||||
override fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
|
override suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
|
||||||
if (mimeType == null) {
|
if (mimeType == null) {
|
||||||
callback.onFailure(Exception("MIME type is null for uri=$uri"))
|
callback.onFailure(Exception("MIME type is null for uri=$uri"))
|
||||||
return
|
return
|
||||||
|
|
|
@ -6,7 +6,7 @@ import deckers.thibault.aves.model.SourceImageEntry
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
internal class FileImageProvider : ImageProvider() {
|
internal class FileImageProvider : ImageProvider() {
|
||||||
override fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
|
override suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
|
||||||
if (mimeType == null) {
|
if (mimeType == null) {
|
||||||
callback.onFailure(Exception("MIME type is null for uri=$uri"))
|
callback.onFailure(Exception("MIME type is null for uri=$uri"))
|
||||||
return
|
return
|
||||||
|
|
|
@ -21,9 +21,12 @@ import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
abstract class ImageProvider {
|
abstract class ImageProvider {
|
||||||
open fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
|
open suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
|
||||||
callback.onFailure(UnsupportedOperationException())
|
callback.onFailure(UnsupportedOperationException())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,11 +34,11 @@ abstract class ImageProvider {
|
||||||
return Futures.immediateFailedFuture(UnsupportedOperationException())
|
return Futures.immediateFailedFuture(UnsupportedOperationException())
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun moveMultiple(context: Context, copy: Boolean, destinationDir: String, entries: List<AvesImageEntry>, callback: ImageOpCallback) {
|
open suspend fun moveMultiple(context: Context, copy: Boolean, destinationDir: String, entries: List<AvesImageEntry>, callback: ImageOpCallback) {
|
||||||
callback.onFailure(UnsupportedOperationException())
|
callback.onFailure(UnsupportedOperationException())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rename(context: Context, oldPath: String, oldMediaUri: Uri, mimeType: String, newFilename: String, callback: ImageOpCallback) {
|
suspend fun rename(context: Context, oldPath: String, oldMediaUri: Uri, mimeType: String, newFilename: String, callback: ImageOpCallback) {
|
||||||
val oldFile = File(oldPath)
|
val oldFile = File(oldPath)
|
||||||
val newFile = File(oldFile.parent, newFilename)
|
val newFile = File(oldFile.parent, newFilename)
|
||||||
if (oldFile == newFile) {
|
if (oldFile == newFile) {
|
||||||
|
@ -57,7 +60,11 @@ abstract class ImageProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaScannerConnection.scanFile(context, arrayOf(oldPath), arrayOf(mimeType), null)
|
MediaScannerConnection.scanFile(context, arrayOf(oldPath), arrayOf(mimeType), null)
|
||||||
scanNewPath(context, newFile.path, mimeType, callback)
|
try {
|
||||||
|
callback.onSuccess(scanNewPath(context, newFile.path, mimeType))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
callback.onFailure(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeOrientation(context: Context, path: String, uri: Uri, mimeType: String, op: ExifOrientationOp, callback: ImageOpCallback) {
|
fun changeOrientation(context: Context, path: String, uri: Uri, mimeType: String, op: ExifOrientationOp, callback: ImageOpCallback) {
|
||||||
|
@ -132,54 +139,56 @@ abstract class ImageProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun scanNewPath(context: Context, path: String, mimeType: String, callback: ImageOpCallback) {
|
protected suspend fun scanNewPath(context: Context, path: String, mimeType: String): FieldMap =
|
||||||
MediaScannerConnection.scanFile(context, arrayOf(path), arrayOf(mimeType)) { _, newUri: Uri? ->
|
suspendCoroutine { cont ->
|
||||||
var contentId: Long = 0
|
MediaScannerConnection.scanFile(context, arrayOf(path), arrayOf(mimeType)) { _, newUri: Uri? ->
|
||||||
var contentUri: Uri? = null
|
var contentId: Long = 0
|
||||||
if (newUri != null) {
|
var contentUri: Uri? = null
|
||||||
// `newURI` is possibly a file media URI (e.g. "content://media/12a9-8b42/file/62872")
|
if (newUri != null) {
|
||||||
// but we need an image/video media URI (e.g. "content://media/external/images/media/62872")
|
// `newURI` is possibly a file media URI (e.g. "content://media/12a9-8b42/file/62872")
|
||||||
contentId = ContentUris.parseId(newUri)
|
// but we need an image/video media URI (e.g. "content://media/external/images/media/62872")
|
||||||
if (isImage(mimeType)) {
|
contentId = ContentUris.parseId(newUri)
|
||||||
contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentId)
|
if (isImage(mimeType)) {
|
||||||
} else if (isVideo(mimeType)) {
|
contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentId)
|
||||||
contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentId)
|
} else if (isVideo(mimeType)) {
|
||||||
|
contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (contentUri == null) {
|
||||||
|
cont.resumeWithException(Exception("failed to get content URI of item at path=$path"))
|
||||||
|
return@scanFile
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (contentUri == null) {
|
|
||||||
callback.onFailure(Exception("failed to get content URI of item at path=$path"))
|
|
||||||
return@scanFile
|
|
||||||
}
|
|
||||||
|
|
||||||
val newFields = HashMap<String, Any?>()
|
val newFields = HashMap<String, Any?>()
|
||||||
// we retrieve updated fields as the renamed/moved file became a new entry in the Media Store
|
// we retrieve updated fields as the renamed/moved file became a new entry in the Media Store
|
||||||
val projection = arrayOf(
|
val projection = arrayOf(
|
||||||
MediaStore.MediaColumns.DATE_MODIFIED,
|
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||||
MediaStore.MediaColumns.TITLE,
|
MediaStore.MediaColumns.TITLE,
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
val cursor = context.contentResolver.query(contentUri, projection, null, null, null)
|
val cursor = context.contentResolver.query(contentUri, projection, null, null, null)
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
newFields["uri"] = contentUri.toString()
|
newFields["uri"] = contentUri.toString()
|
||||||
newFields["contentId"] = contentId
|
newFields["contentId"] = contentId
|
||||||
newFields["path"] = path
|
newFields["path"] = path
|
||||||
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) }
|
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) }
|
||||||
cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME).let { if (it != -1) newFields["displayName"] = cursor.getString(it) }
|
cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME).let { if (it != -1) newFields["displayName"] = cursor.getString(it) }
|
||||||
cursor.getColumnIndex(MediaStore.MediaColumns.TITLE).let { if (it != -1) newFields["title"] = cursor.getString(it) }
|
cursor.getColumnIndex(MediaStore.MediaColumns.TITLE).let { if (it != -1) newFields["title"] = cursor.getString(it) }
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
cont.resumeWithException(e)
|
||||||
|
return@scanFile
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newFields.isEmpty()) {
|
||||||
|
cont.resumeWithException(Exception("failed to get item details from provider at contentUri=$contentUri"))
|
||||||
|
} else {
|
||||||
|
cont.resume(newFields)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
callback.onFailure(e)
|
|
||||||
return@scanFile
|
|
||||||
}
|
|
||||||
if (newFields.isEmpty()) {
|
|
||||||
callback.onFailure(Exception("failed to get item details from provider at contentUri=$contentUri"))
|
|
||||||
} else {
|
|
||||||
callback.onSuccess(newFields)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
interface ImageOpCallback {
|
interface ImageOpCallback {
|
||||||
fun onSuccess(fields: FieldMap)
|
fun onSuccess(fields: FieldMap)
|
||||||
|
|
|
@ -20,13 +20,14 @@ import deckers.thibault.aves.utils.StorageUtils.createDirectoryIfAbsent
|
||||||
import deckers.thibault.aves.utils.StorageUtils.ensureTrailingSeparator
|
import deckers.thibault.aves.utils.StorageUtils.ensureTrailingSeparator
|
||||||
import deckers.thibault.aves.utils.StorageUtils.getDocumentFile
|
import deckers.thibault.aves.utils.StorageUtils.getDocumentFile
|
||||||
import deckers.thibault.aves.utils.StorageUtils.requireAccessPermission
|
import deckers.thibault.aves.utils.StorageUtils.requireAccessPermission
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
|
||||||
class MediaStoreImageProvider : ImageProvider() {
|
class MediaStoreImageProvider : ImageProvider() {
|
||||||
fun fetchAll(context: Context, knownEntries: Map<Int, Int?>, handleNewEntry: NewEntryHandler) {
|
suspend fun fetchAll(context: Context, knownEntries: Map<Int, Int?>, handleNewEntry: NewEntryHandler) {
|
||||||
val isModified = fun(contentId: Int, dateModifiedSecs: Int): Boolean {
|
val isModified = fun(contentId: Int, dateModifiedSecs: Int): Boolean {
|
||||||
val knownDate = knownEntries[contentId]
|
val knownDate = knownEntries[contentId]
|
||||||
return knownDate == null || knownDate < dateModifiedSecs
|
return knownDate == null || knownDate < dateModifiedSecs
|
||||||
|
@ -35,7 +36,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
fetchFrom(context, isModified, handleNewEntry, VIDEO_CONTENT_URI, VIDEO_PROJECTION)
|
fetchFrom(context, isModified, handleNewEntry, VIDEO_CONTENT_URI, VIDEO_PROJECTION)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
|
override suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
|
||||||
val id = ContentUris.parseId(uri)
|
val id = ContentUris.parseId(uri)
|
||||||
val onSuccess = fun(entry: FieldMap) {
|
val onSuccess = fun(entry: FieldMap) {
|
||||||
entry["uri"] = uri.toString()
|
entry["uri"] = uri.toString()
|
||||||
|
@ -84,7 +85,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
return foundContentIds
|
return foundContentIds
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchFrom(
|
private suspend fun fetchFrom(
|
||||||
context: Context,
|
context: Context,
|
||||||
isValidEntry: NewEntryChecker,
|
isValidEntry: NewEntryChecker,
|
||||||
handleNewEntry: NewEntryHandler,
|
handleNewEntry: NewEntryHandler,
|
||||||
|
@ -159,7 +160,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
handleNewEntry(entryMap)
|
handleNewEntry(entryMap)
|
||||||
// TODO TLAD is this necessary?
|
// TODO TLAD is this necessary?
|
||||||
if (newEntryCount % 30 == 0) {
|
if (newEntryCount % 30 == 0) {
|
||||||
Thread.sleep(10)
|
delay(10)
|
||||||
}
|
}
|
||||||
newEntryCount++
|
newEntryCount++
|
||||||
}
|
}
|
||||||
|
@ -212,7 +213,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
return future
|
return future
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun moveMultiple(
|
override suspend fun moveMultiple(
|
||||||
context: Context,
|
context: Context,
|
||||||
copy: Boolean,
|
copy: Boolean,
|
||||||
destinationDir: String,
|
destinationDir: String,
|
||||||
|
@ -266,7 +267,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun moveSingleByTreeDocAndScan(
|
private suspend fun moveSingleByTreeDocAndScan(
|
||||||
context: Context,
|
context: Context,
|
||||||
sourcePath: String,
|
sourcePath: String,
|
||||||
sourceUri: Uri,
|
sourceUri: Uri,
|
||||||
|
@ -322,16 +323,13 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scanNewPath(context, destinationFullPath, mimeType, object : ImageOpCallback {
|
try {
|
||||||
override fun onSuccess(fields: FieldMap) {
|
val fields = scanNewPath(context, destinationFullPath, mimeType)
|
||||||
fields["deletedSource"] = deletedSource
|
fields["deletedSource"] = deletedSource
|
||||||
future.set(fields)
|
future.set(fields)
|
||||||
}
|
} catch (e: Exception) {
|
||||||
|
future.setException(e)
|
||||||
override fun onFailure(throwable: Throwable) {
|
}
|
||||||
future.setException(throwable)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(LOG_TAG, "failed to ${(if (copy) "copy" else "move")} entry", e)
|
Log.e(LOG_TAG, "failed to ${(if (copy) "copy" else "move")} entry", e)
|
||||||
|
|
Loading…
Reference in a new issue