Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ androidExifInterface = "1.3.6"
androidMediaFilePicker = "1.9.1"
coroutinesVersion = "1.6.4"
mokoMvvmVersion = "0.16.0"
mokoPermissionsVersion = "0.15.0"
mokoPermissionsVersion = "0.16.0"
mokoTestVersion = "0.6.1"
mokoMediaVersion = "0.11.0"
composeJetBrainsVersion = "1.3.1"
Expand Down
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
3 changes: 0 additions & 3 deletions media/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,4 @@ dependencies {

androidMainImplementation(libs.appCompat)
androidMainImplementation(libs.exifInterface)

// TODO #34 remove external dependency
androidMainImplementation(libs.mediaFilePicker)
}

This file was deleted.

35 changes: 11 additions & 24 deletions media/src/androidMain/kotlin/dev/icerock/moko/media/MediaFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

package dev.icerock.moko.media

import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.Context
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.provider.MediaStore
import android.provider.OpenableColumns
import androidx.exifinterface.media.ExifInterface
import dev.icerock.moko.media.BitmapUtils.getBitmapOrientation
import dev.icerock.moko.media.BitmapUtils.getNormalizedBitmap
Expand Down Expand Up @@ -52,18 +54,14 @@ object MediaFactory {
}
}

@Suppress("ThrowsCount")
@SuppressLint("Range")
private fun createPhotoMedia(
contentResolver: ContentResolver,
uri: Uri
): Media {
val projection = arrayOf(
MediaStore.Images.ImageColumns.ORIENTATION,
MediaStore.Images.ImageColumns.TITLE
)

val cursorRef = contentResolver
.query(uri, projection, null, null, null)
.query(uri, null, null, null, null)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why projection removed? we use only 2 columns at all

?: throw IllegalArgumentException("can't open cursor")

return cursorRef.use { cursor ->
Expand All @@ -75,47 +73,36 @@ object MediaFactory {
getBitmapOrientation(it)
} ?: ExifInterface.ORIENTATION_UNDEFINED

val titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.TITLE)
val title = cursor.getString(titleIndex) ?: uri.lastPathSegment
val title = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))

val normalizedBitmap = contentResolver.openInputStream(uri)?.use {
getNormalizedBitmap(it, orientation, sampleSize = null)
} ?: throw IOException("can't open stream")

Media(
name = title.orEmpty(),
path = uri.toString(),
path = uri.path ?: uri.toString(),
type = MediaType.PHOTO,
preview = Bitmap(normalizedBitmap)
)
}
}

@Suppress("ThrowsCount")
@SuppressLint("Range")
private fun createVideoMedia(
contentResolver: ContentResolver,
uri: Uri
): Media {
val projection = arrayOf(
MediaStore.Video.VideoColumns._ID,
MediaStore.Video.VideoColumns.TITLE
)

val cursorRef = contentResolver
.query(uri, projection, null, null, null)
.query(uri, null, null, null, null)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why projection removed? we use only 2 columns at all

?: throw IllegalArgumentException("can't open cursor")

return cursorRef.use { cursor ->
if (!cursor.moveToFirst()) {
error("cursor should have one element")
}

val titleColumn = cursor.getColumnIndex(MediaStore.Video.VideoColumns.TITLE)
val title = if (titleColumn != -1) {
cursor.getString(titleColumn)
} else {
null
} ?: uri.lastPathSegment ?: "file"
val title = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))

val idColumn = cursor.getColumnIndex(MediaStore.Video.VideoColumns._ID)
val thumbnail: android.graphics.Bitmap = if (idColumn != -1) {
Expand All @@ -135,8 +122,8 @@ object MediaFactory {
} ?: throw IOException("can't read thumbnail")

Media(
name = title,
path = uri.toString(),
name = title.orEmpty(),
path = uri.path ?: uri.toString(),
type = MediaType.VIDEO,
preview = Bitmap(thumbnail)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

package dev.icerock.moko.media.picker

import android.app.Activity
import android.content.Intent
import android.os.Environment
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.provider.OpenableColumns
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import com.nbsp.materialfilepicker.MaterialFilePicker
import com.nbsp.materialfilepicker.ui.FilePickerActivity
import dev.icerock.moko.media.FileMedia
import java.io.File

Expand All @@ -19,49 +18,83 @@ class FilePickerFragment : Fragment() {
retainInstance = true
}

private val codeCallbackMap = mutableMapOf<Int, CallbackData>()
private var callback: CallbackData? = null

fun pickFile(callback: (Result<FileMedia>) -> Unit) {
val requestCode = codeCallbackMap.keys.maxOrNull() ?: 0

codeCallbackMap[requestCode] = CallbackData(callback)

// TODO нужно убрать использование внешней зависимости, сделать конфигурацию способа
// выбора файла из вне (аргументом в контроллер передавать)
val externalStorage = Environment.getExternalStorageDirectory()
MaterialFilePicker().withSupportFragment(this)
.withCloseMenu(true)
.withRootPath(externalStorage.absolutePath)
.withRequestCode(requestCode)
.start()
}
@SuppressLint("Range")
private val pickDocument =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
val callbackData = callback ?: return@registerForActivityResult
callback = null

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val callback = callbackData.callback

val callbackData = codeCallbackMap[requestCode] ?: return
codeCallbackMap.remove(requestCode)
if (uri == null) {
callback.invoke(Result.failure(CanceledException()))
return@registerForActivityResult
}

val callback = callbackData.callback
if (uri.path == null) {
callback.invoke(Result.failure(java.lang.IllegalStateException("File is null")))
return@registerForActivityResult
}

if (resultCode == Activity.RESULT_CANCELED) {
callback.invoke(Result.failure(CanceledException()))
return
}
val fileNameWithExtension = if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
val cursor = requireContext().contentResolver.query(uri, null, null, null, null)
cursor?.use {
if (!it.moveToFirst()) null
else it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
}
} else null

processResult(callback, data)
}
uri.path?.let { path ->
val file = File(path)
val name = file.name
val byteArray = requireContext()
.contentResolver
.openInputStream(uri)
?.readBytes() ?: return@registerForActivityResult
Comment on lines +52 to +55
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we should read stream in this time? can we read it later?


private fun processResult(
callback: (Result<FileMedia>) -> Unit,
data: Intent?
) {
val filePath = data?.getStringExtra(FilePickerActivity.RESULT_FILE_PATH)
callback(
Result.success(
FileMedia(
fileNameWithExtension ?: name,
uri.toString(),
byteArray
)
)
)
}
}

filePath?.let { path ->
val name = File(path).name
callback(Result.success(FileMedia(name, path)))

fun pickFile(callback: (Result<FileMedia>) -> Unit) {
this.callback?.let {
it.callback.invoke(Result.failure(IllegalStateException("Callback should be null")))
this.callback = null
}

this.callback = CallbackData(callback)

pickDocument.launch(
arrayOf(
"application/pdf",
"application/octet-stream",
"application/doc",
"application/msword",
"application/ms-doc",
"application/vnd.ms-excel",
"application/vnd.ms-powerpoint",
"application/json",
"application/zip",
"text/plain",
"text/html",
"text/xml",
"audio/mpeg",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
Comment on lines +80 to +95
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use something like **/*?

)
)
}

class CallbackData(val callback: (Result<FileMedia>) -> Unit)
Expand Down
Loading