diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 5f614763d5..ca6c2ce6d9 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -119,7 +119,7 @@ dependencies {
implementation "ru.noties.markwon:core:$markwon_version"
// Image
- implementation 'androidx.exifinterface:exifinterface:1.1.0'
+ implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
implementation 'id.zelory:compressor:3.0.0'
// Database
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt
index e32bb9f21f..b80a17b017 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt
@@ -16,6 +16,7 @@
package im.vector.matrix.android.api.session.content
+import android.net.Uri
import android.os.Parcelable
import androidx.exifinterface.media.ExifInterface
import kotlinx.android.parcel.Parcelize
@@ -29,8 +30,7 @@ data class ContentAttachmentData(
val width: Long? = 0,
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
val name: String? = null,
- val queryUri: String,
- val path: String,
+ val queryUri: Uri,
private val mimeType: String?,
val type: Type
) : Parcelable {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
index 4071c9224f..4fa0cb5013 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
@@ -53,9 +53,9 @@ internal class FileUploader @Inject constructor(@Authenticated
suspend fun uploadByteArray(byteArray: ByteArray,
filename: String?,
- mimeType: String,
+ mimeType: String?,
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
- val uploadBody = byteArray.toRequestBody(mimeType.toMediaTypeOrNull())
+ val uploadBody = byteArray.toRequestBody(mimeType?.toMediaTypeOrNull())
return upload(uploadBody, filename, progressListener)
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt
index 083cac0278..eae2bf8f6d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt
@@ -16,12 +16,12 @@
package im.vector.matrix.android.internal.session.content
+import android.content.Context
import android.graphics.Bitmap
-import android.media.ThumbnailUtils
-import android.provider.MediaStore
+import android.media.MediaMetadataRetriever
import im.vector.matrix.android.api.session.content.ContentAttachmentData
+import timber.log.Timber
import java.io.ByteArrayOutputStream
-import java.io.File
internal object ThumbnailExtractor {
@@ -33,34 +33,40 @@ internal object ThumbnailExtractor {
val mimeType: String
)
- fun extractThumbnail(attachment: ContentAttachmentData): ThumbnailData? {
- val file = File(attachment.path)
- if (!file.exists() || !file.isFile) {
- return null
- }
+ fun extractThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? {
return if (attachment.type == ContentAttachmentData.Type.VIDEO) {
- extractVideoThumbnail(attachment)
+ extractVideoThumbnail(context, attachment)
} else {
null
}
}
- private fun extractVideoThumbnail(attachment: ContentAttachmentData): ThumbnailData? {
- val thumbnail = ThumbnailUtils.createVideoThumbnail(attachment.path, MediaStore.Video.Thumbnails.MINI_KIND) ?: return null
- val outputStream = ByteArrayOutputStream()
- thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
- val thumbnailWidth = thumbnail.width
- val thumbnailHeight = thumbnail.height
- val thumbnailSize = outputStream.size()
- val thumbnailData = ThumbnailData(
- width = thumbnailWidth,
- height = thumbnailHeight,
- size = thumbnailSize.toLong(),
- bytes = outputStream.toByteArray(),
- mimeType = "image/jpeg"
- )
- thumbnail.recycle()
- outputStream.reset()
+ private fun extractVideoThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? {
+ var thumbnailData: ThumbnailData? = null
+ val mediaMetadataRetriever = MediaMetadataRetriever()
+ try {
+ mediaMetadataRetriever.setDataSource(context, attachment.queryUri)
+ val thumbnail = mediaMetadataRetriever.frameAtTime
+
+ val outputStream = ByteArrayOutputStream()
+ thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+ val thumbnailWidth = thumbnail.width
+ val thumbnailHeight = thumbnail.height
+ val thumbnailSize = outputStream.size()
+ thumbnailData = ThumbnailData(
+ width = thumbnailWidth,
+ height = thumbnailHeight,
+ size = thumbnailSize.toLong(),
+ bytes = outputStream.toByteArray(),
+ mimeType = "image/jpeg"
+ )
+ thumbnail.recycle()
+ outputStream.reset()
+ } catch (e: Exception) {
+ Timber.e(e, "Cannot extract video thumbnail")
+ } finally {
+ mediaMetadataRetriever.release()
+ }
return thumbnailData
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
index 1c88f87804..1b736d349f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
@@ -17,12 +17,9 @@
package im.vector.matrix.android.internal.session.content
import android.content.Context
-import android.graphics.BitmapFactory
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
-import id.zelory.compressor.Compressor
-import id.zelory.compressor.constraint.default
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toContent
@@ -41,8 +38,6 @@ import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
import timber.log.Timber
import java.io.ByteArrayInputStream
-import java.io.File
-import java.io.FileInputStream
import javax.inject.Inject
private data class NewImageAttributes(
@@ -94,8 +89,90 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
var newImageAttributes: NewImageAttributes? = null
- val attachmentFile = try {
- File(attachment.path)
+ try {
+ val inputStream = context.contentResolver.openInputStream(attachment.queryUri)
+ ?: return Result.success(
+ WorkerParamsFactory.toData(
+ params.copy(
+ lastFailureMessage = "Cannot openInputStream for file: " + attachment.queryUri.toString()
+ )
+ )
+ )
+
+ inputStream.use {
+ var uploadedThumbnailUrl: String? = null
+ var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null
+
+ ThumbnailExtractor.extractThumbnail(context, params.attachment)?.let { thumbnailData ->
+ val thumbnailProgressListener = object : ProgressRequestBody.Listener {
+ override fun onProgress(current: Long, total: Long) {
+ notifyTracker(params) { contentUploadStateTracker.setProgressThumbnail(it, current, total) }
+ }
+ }
+
+ try {
+ val contentUploadResponse = if (params.isEncrypted) {
+ Timber.v("Encrypt thumbnail")
+ notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
+ val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
+ uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo
+ fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
+ "thumb_${attachment.name}",
+ "application/octet-stream",
+ thumbnailProgressListener)
+ } else {
+ fileUploader.uploadByteArray(thumbnailData.bytes,
+ "thumb_${attachment.name}",
+ thumbnailData.mimeType,
+ thumbnailProgressListener)
+ }
+
+ uploadedThumbnailUrl = contentUploadResponse.contentUri
+ } catch (t: Throwable) {
+ Timber.e(t, "Thumbnail update failed")
+ }
+ }
+
+ val progressListener = object : ProgressRequestBody.Listener {
+ override fun onProgress(current: Long, total: Long) {
+ notifyTracker(params) {
+ if (isStopped) {
+ contentUploadStateTracker.setFailure(it, Throwable("Cancelled"))
+ } else {
+ contentUploadStateTracker.setProgress(it, current, total)
+ }
+ }
+ }
+ }
+
+ var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
+
+ return try {
+ val contentUploadResponse = if (params.isEncrypted) {
+ Timber.v("Encrypt file")
+ notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
+
+ val encryptionResult = MXEncryptedAttachments.encryptAttachment(inputStream, attachment.getSafeMimeType())
+ uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
+
+ fileUploader
+ .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
+ } else {
+ fileUploader
+ .uploadByteArray(inputStream.readBytes(), attachment.name, attachment.getSafeMimeType(), progressListener)
+ }
+
+ handleSuccess(params,
+ contentUploadResponse.contentUri,
+ uploadedFileEncryptedFileInfo,
+ uploadedThumbnailUrl,
+ uploadedThumbnailEncryptedFileInfo,
+ newImageAttributes)
+ } catch (t: Throwable) {
+ Timber.e(t)
+ handleFailure(params, t)
+ }
+ }
} catch (e: Exception) {
Timber.e(e)
notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
@@ -106,109 +183,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
)
)
)
- }
- .let { originalFile ->
- if (attachment.type == ContentAttachmentData.Type.IMAGE) {
- if (params.compressBeforeSending) {
- Compressor.compress(context, originalFile) {
- default(
- width = MAX_IMAGE_SIZE,
- height = MAX_IMAGE_SIZE
- )
- }.also { compressedFile ->
- // Update the params
- val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
- BitmapFactory.decodeFile(compressedFile.absolutePath, options)
- val fileSize = compressedFile.length().toInt()
-
- newImageAttributes = NewImageAttributes(
- options.outWidth,
- options.outHeight,
- fileSize
- )
- }
- } else {
- // TODO Fix here the image rotation issue
- originalFile
- }
- } else {
- // Other type
- originalFile
- }
- }
-
- var uploadedThumbnailUrl: String? = null
- var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null
-
- ThumbnailExtractor.extractThumbnail(params.attachment)?.let { thumbnailData ->
- val thumbnailProgressListener = object : ProgressRequestBody.Listener {
- override fun onProgress(current: Long, total: Long) {
- notifyTracker(params) { contentUploadStateTracker.setProgressThumbnail(it, current, total) }
- }
- }
-
- try {
- val contentUploadResponse = if (params.isEncrypted) {
- Timber.v("Encrypt thumbnail")
- notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
- val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
- uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo
- fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
- "thumb_${attachment.name}",
- "application/octet-stream",
- thumbnailProgressListener)
- } else {
- fileUploader.uploadByteArray(thumbnailData.bytes,
- "thumb_${attachment.name}",
- thumbnailData.mimeType,
- thumbnailProgressListener)
- }
-
- uploadedThumbnailUrl = contentUploadResponse.contentUri
- } catch (t: Throwable) {
- Timber.e(t)
- return handleFailure(params, t)
- }
- }
-
- val progressListener = object : ProgressRequestBody.Listener {
- override fun onProgress(current: Long, total: Long) {
- notifyTracker(params) {
- if (isStopped) {
- contentUploadStateTracker.setFailure(it, Throwable("Cancelled"))
- } else {
- contentUploadStateTracker.setProgress(it, current, total)
- }
- }
- }
- }
-
- var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
-
- return try {
- val contentUploadResponse = if (params.isEncrypted) {
- Timber.v("Encrypt file")
- notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
-
- val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.getSafeMimeType())
- uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
-
- fileUploader
- .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
- } else {
- fileUploader
- .uploadFile(attachmentFile, attachment.name, attachment.getSafeMimeType(), progressListener)
- }
-
- handleSuccess(params,
- contentUploadResponse.contentUri,
- uploadedFileEncryptedFileInfo,
- uploadedThumbnailUrl,
- uploadedThumbnailEncryptedFileInfo,
- newImageAttributes)
- } catch (t: Throwable) {
- Timber.e(t)
- handleFailure(params, t)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
index f10c40ded5..a4a6eb6972 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
@@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.session.room.send
+import android.content.Context
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import androidx.exifinterface.media.ExifInterface
@@ -74,6 +75,7 @@ import javax.inject.Inject
* The transactionId is used as loc
*/
internal class LocalEchoEventFactory @Inject constructor(
+ private val context: Context,
@UserId private val userId: String,
private val stringProvider: StringProvider,
private val textPillsUtils: TextPillsUtils,
@@ -266,14 +268,14 @@ internal class LocalEchoEventFactory @Inject constructor(
height = height?.toInt() ?: 0,
size = attachment.size.toInt()
),
- url = attachment.path
+ url = attachment.queryUri.toString()
)
return createEvent(roomId, content)
}
private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event {
val mediaDataRetriever = MediaMetadataRetriever()
- mediaDataRetriever.setDataSource(attachment.path)
+ mediaDataRetriever.setDataSource(context, attachment.queryUri)
// Use frame to calculate height and width as we are sure to get the right ones
val firstFrame: Bitmap? = mediaDataRetriever.frameAtTime
@@ -281,7 +283,7 @@ internal class LocalEchoEventFactory @Inject constructor(
val width = firstFrame?.width ?: 0
mediaDataRetriever.release()
- val thumbnailInfo = ThumbnailExtractor.extractThumbnail(attachment)?.let {
+ val thumbnailInfo = ThumbnailExtractor.extractThumbnail(context, attachment)?.let {
ThumbnailInfo(
width = it.width,
height = it.height,
@@ -299,10 +301,10 @@ internal class LocalEchoEventFactory @Inject constructor(
size = attachment.size,
duration = attachment.duration?.toInt() ?: 0,
// Glide will be able to use the local path and extract a thumbnail.
- thumbnailUrl = attachment.path,
+ thumbnailUrl = attachment.queryUri.toString(),
thumbnailInfo = thumbnailInfo
),
- url = attachment.path
+ url = attachment.queryUri.toString()
)
return createEvent(roomId, content)
}
@@ -315,7 +317,7 @@ internal class LocalEchoEventFactory @Inject constructor(
mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
size = attachment.size
),
- url = attachment.path
+ url = attachment.queryUri.toString()
)
return createEvent(roomId, content)
}
@@ -329,7 +331,7 @@ internal class LocalEchoEventFactory @Inject constructor(
?: "application/octet-stream",
size = attachment.size
),
- url = attachment.path
+ url = attachment.queryUri.toString()
)
return createEvent(roomId, content)
}
diff --git a/multipicker/.gitignore b/multipicker/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/multipicker/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/multipicker/build.gradle b/multipicker/build.gradle
new file mode 100644
index 0000000000..8b08a9d3ef
--- /dev/null
+++ b/multipicker/build.gradle
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 29
+
+ defaultConfig {
+ minSdkVersion 19
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles 'consumer-rules.pro'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.core:core-ktx:1.2.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+
+ implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
+
+ // Log
+ implementation 'com.jakewharton.timber:timber:4.7.1'
+}
diff --git a/multipicker/consumer-rules.pro b/multipicker/consumer-rules.pro
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/multipicker/proguard-rules.pro b/multipicker/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/multipicker/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/multipicker/src/main/AndroidManifest.xml b/multipicker/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..e1f12697e0
--- /dev/null
+++ b/multipicker/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt
new file mode 100644
index 0000000000..05e4c337b6
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.media.MediaMetadataRetriever
+import android.provider.MediaStore
+import im.vector.riotx.multipicker.entity.MultiPickerAudioType
+
+/**
+ * Audio file picker implementation
+ */
+class AudioPicker(override val requestCode: Int) : Picker(requestCode) {
+
+ /**
+ * Call this function from onActivityResult(int, int, Intent).
+ * Returns selected audio files or empty list if request code is wrong
+ * or result code is not Activity.RESULT_OK
+ * or user did not select any files.
+ */
+ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List {
+ if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
+ return emptyList()
+ }
+
+ val audioList = mutableListOf()
+
+ getSelectedUriList(data).forEach { selectedUri ->
+ val projection = arrayOf(
+ MediaStore.Audio.Media.DISPLAY_NAME,
+ MediaStore.Audio.Media.SIZE
+ )
+
+ context.contentResolver.query(
+ selectedUri,
+ projection,
+ null,
+ null,
+ null
+ )?.use { cursor ->
+ val nameColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME)
+ val sizeColumn = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE)
+
+ if (cursor.moveToNext()) {
+ val name = cursor.getString(nameColumn)
+ val size = cursor.getLong(sizeColumn)
+ var duration = 0L
+
+ context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd ->
+ val mediaMetadataRetriever = MediaMetadataRetriever()
+ mediaMetadataRetriever.setDataSource(pfd.fileDescriptor)
+ duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
+ }
+
+ audioList.add(
+ MultiPickerAudioType(
+ name,
+ size,
+ context.contentResolver.getType(selectedUri),
+ selectedUri,
+ duration
+ )
+ )
+ }
+ }
+ }
+ return audioList
+ }
+
+ override fun createIntent(): Intent {
+ return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
+ type = "audio/*"
+ }
+ }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt
new file mode 100644
index 0000000000..240d809373
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.MediaStore
+import androidx.core.content.FileProvider
+import androidx.fragment.app.Fragment
+import im.vector.riotx.multipicker.entity.MultiPickerImageType
+import im.vector.riotx.multipicker.utils.ImageUtils
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+/**
+ * Implementation of taking a photo with Camera
+ */
+class CameraPicker(val requestCode: Int) {
+
+ /**
+ * Start camera by using an Activity
+ * @param activity Activity to handle onActivityResult().
+ * @return Uri of taken photo or null if the operation is cancelled.
+ */
+ fun startWithExpectingFile(activity: Activity): Uri? {
+ val photoUri = createPhotoUri(activity)
+ val intent = createIntent().apply {
+ putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
+ }
+ activity.startActivityForResult(intent, requestCode)
+ return photoUri
+ }
+
+ /**
+ * Start camera by using a Fragment
+ * @param fragment Fragment to handle onActivityResult().
+ * @return Uri of taken photo or null if the operation is cancelled.
+ */
+ fun startWithExpectingFile(fragment: Fragment): Uri? {
+ val photoUri = createPhotoUri(fragment.requireContext())
+ val intent = createIntent().apply {
+ putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
+ }
+ fragment.startActivityForResult(intent, requestCode)
+ return photoUri
+ }
+
+ /**
+ * Call this function from onActivityResult(int, int, Intent).
+ * @return Taken photo or null if request code is wrong
+ * or result code is not Activity.RESULT_OK
+ * or user cancelled the operation.
+ */
+ fun getTakenPhoto(context: Context, requestCode: Int, resultCode: Int, photoUri: Uri): MultiPickerImageType? {
+ if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) {
+ val projection = arrayOf(
+ MediaStore.Images.Media.DISPLAY_NAME,
+ MediaStore.Images.Media.SIZE
+ )
+
+ context.contentResolver.query(
+ photoUri,
+ projection,
+ null,
+ null,
+ null
+ )?.use { cursor ->
+ val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
+ val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE)
+
+ if (cursor.moveToNext()) {
+ val name = cursor.getString(nameColumn)
+ val size = cursor.getLong(sizeColumn)
+
+ val bitmap = ImageUtils.getBitmap(context, photoUri)
+ val orientation = ImageUtils.getOrientation(context, photoUri)
+
+ return MultiPickerImageType(
+ name,
+ size,
+ context.contentResolver.getType(photoUri),
+ photoUri,
+ bitmap?.width ?: 0,
+ bitmap?.height ?: 0,
+ orientation
+ )
+ }
+ }
+ }
+ return null
+ }
+
+ private fun createIntent(): Intent {
+ return Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+ }
+
+ private fun createPhotoUri(context: Context): Uri {
+ val file = createImageFile(context)
+ val authority = context.packageName + ".multipicker.fileprovider"
+ return FileProvider.getUriForFile(context, authority, file)
+ }
+
+ private fun createImageFile(context: Context): File {
+ val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
+ val storageDir: File = context.filesDir
+ return File.createTempFile(
+ "${timeStamp}_", /* prefix */
+ ".jpg", /* suffix */
+ storageDir /* directory */
+ )
+ }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt
new file mode 100644
index 0000000000..b0ae0e4cda
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker
+
+import android.app.Activity
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.provider.ContactsContract
+import im.vector.riotx.multipicker.entity.MultiPickerContactType
+
+/**
+ * Contact Picker implementation
+ */
+class ContactPicker(override val requestCode: Int) : Picker(requestCode) {
+
+ /**
+ * Call this function from onActivityResult(int, int, Intent).
+ * Returns selected contact or empty list if request code is wrong
+ * or result code is not Activity.RESULT_OK
+ * or user did not select any files.
+ */
+ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List {
+ if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
+ return emptyList()
+ }
+
+ val contactList = mutableListOf()
+
+ data?.data?.let { selectedUri ->
+ val projection = arrayOf(
+ ContactsContract.Contacts.DISPLAY_NAME,
+ ContactsContract.Contacts.PHOTO_URI,
+ ContactsContract.Contacts._ID
+ )
+
+ context.contentResolver.query(
+ selectedUri,
+ projection,
+ null,
+ null,
+ null
+ )?.use { cursor ->
+ if (cursor.moveToFirst()) {
+ val idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID)
+ val nameColumn = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
+ val photoUriColumn = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI)
+
+ val contactId = cursor.getInt(idColumn)
+ var name = cursor.getString(nameColumn)
+ var photoUri = cursor.getString(photoUriColumn)
+ var phoneNumberList = mutableListOf()
+ var emailList = mutableListOf()
+
+ getRawContactId(context.contentResolver, contactId)?.let { rawContactId ->
+ val selection = ContactsContract.Data.RAW_CONTACT_ID + " = ?"
+ val selectionArgs = arrayOf(rawContactId.toString())
+
+ context.contentResolver.query(
+ ContactsContract.Data.CONTENT_URI,
+ arrayOf(
+ ContactsContract.Data.MIMETYPE,
+ ContactsContract.Data.DATA1
+ ),
+ selection,
+ selectionArgs,
+ null
+ )?.use { cursor ->
+ while (cursor.moveToNext()) {
+ val mimeType = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE))
+ val contactData = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DATA1))
+
+ if (mimeType == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) {
+ name = contactData
+ }
+ if (mimeType == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) {
+ phoneNumberList.add(contactData)
+ }
+ if (mimeType == ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) {
+ emailList.add(contactData)
+ }
+ }
+ }
+ }
+ contactList.add(
+ MultiPickerContactType(
+ name,
+ photoUri,
+ phoneNumberList,
+ emailList
+ )
+ )
+ }
+ }
+ }
+
+ return contactList
+ }
+
+ private fun getRawContactId(contentResolver: ContentResolver, contactId: Int): Int? {
+ val projection = arrayOf(ContactsContract.RawContacts._ID)
+ val selection = ContactsContract.RawContacts.CONTACT_ID + " = ?"
+ val selectionArgs = arrayOf(contactId.toString() + "")
+ return contentResolver.query(
+ ContactsContract.RawContacts.CONTENT_URI,
+ projection,
+ selection,
+ selectionArgs,
+ null
+ )?.use { cursor ->
+ return if (cursor.moveToFirst()) cursor.getInt(cursor.getColumnIndex(ContactsContract.RawContacts._ID)) else null
+ }
+ }
+
+ override fun createIntent(): Intent {
+ return Intent(Intent.ACTION_PICK).apply {
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
+ type = ContactsContract.Contacts.CONTENT_TYPE
+ }
+ }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt
new file mode 100644
index 0000000000..e8c74fad19
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.provider.OpenableColumns
+import im.vector.riotx.multipicker.entity.MultiPickerFileType
+
+/**
+ * Implementation of selecting any type of files
+ */
+class FilePicker(override val requestCode: Int) : Picker(requestCode) {
+
+ /**
+ * Call this function from onActivityResult(int, int, Intent).
+ * Returns selected files or empty list if request code is wrong
+ * or result code is not Activity.RESULT_OK
+ * or user did not select any files.
+ */
+ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List {
+ if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
+ return emptyList()
+ }
+
+ val fileList = mutableListOf()
+
+ getSelectedUriList(data).forEach { selectedUri ->
+ context.contentResolver.query(selectedUri, null, null, null, null)
+ ?.use { cursor ->
+ val nameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ val sizeColumn = cursor.getColumnIndex(OpenableColumns.SIZE)
+ if (cursor.moveToFirst()) {
+ val name = cursor.getString(nameColumn)
+ val size = cursor.getLong(sizeColumn)
+
+ fileList.add(
+ MultiPickerFileType(
+ name,
+ size,
+ context.contentResolver.getType(selectedUri),
+ selectedUri
+ )
+ )
+ }
+ }
+ }
+ return fileList
+ }
+
+ override fun createIntent(): Intent {
+ return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
+ type = "*/*"
+ }
+ }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt
new file mode 100644
index 0000000000..d7bf383f03
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.provider.MediaStore
+import im.vector.riotx.multipicker.entity.MultiPickerImageType
+import im.vector.riotx.multipicker.utils.ImageUtils
+
+/**
+ * Image Picker implementation
+ */
+class ImagePicker(override val requestCode: Int) : Picker(requestCode) {
+
+ /**
+ * Call this function from onActivityResult(int, int, Intent).
+ * Returns selected image files or empty list if request code is wrong
+ * or result code is not Activity.RESULT_OK
+ * or user did not select any files.
+ */
+ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List {
+ if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
+ return emptyList()
+ }
+
+ val imageList = mutableListOf()
+
+ getSelectedUriList(data).forEach { selectedUri ->
+ val projection = arrayOf(
+ MediaStore.Images.Media.DISPLAY_NAME,
+ MediaStore.Images.Media.SIZE
+ )
+
+ context.contentResolver.query(
+ selectedUri,
+ projection,
+ null,
+ null,
+ null
+ )?.use { cursor ->
+ val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
+ val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE)
+
+ if (cursor.moveToNext()) {
+ val name = cursor.getString(nameColumn)
+ val size = cursor.getLong(sizeColumn)
+
+ val bitmap = ImageUtils.getBitmap(context, selectedUri)
+ val orientation = ImageUtils.getOrientation(context, selectedUri)
+
+ imageList.add(
+ MultiPickerImageType(
+ name,
+ size,
+ context.contentResolver.getType(selectedUri),
+ selectedUri,
+ bitmap?.width ?: 0,
+ bitmap?.height ?: 0,
+ orientation
+ )
+ )
+ }
+ }
+ }
+ return imageList
+ }
+
+ override fun createIntent(): Intent {
+ return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
+ type = "image/*"
+ }
+ }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt
new file mode 100644
index 0000000000..24769e11c3
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker
+
+class MultiPicker {
+
+ companion object Type {
+ val IMAGE by lazy { MultiPicker() }
+ val FILE by lazy { MultiPicker() }
+ val VIDEO by lazy { MultiPicker() }
+ val AUDIO by lazy { MultiPicker() }
+ val CONTACT by lazy { MultiPicker() }
+ val CAMERA by lazy { MultiPicker() }
+
+ const val REQUEST_CODE_PICK_IMAGE = 5000
+ const val REQUEST_CODE_PICK_VIDEO = 5001
+ const val REQUEST_CODE_PICK_FILE = 5002
+ const val REQUEST_CODE_PICK_AUDIO = 5003
+ const val REQUEST_CODE_PICK_CONTACT = 5004
+ const val REQUEST_CODE_TAKE_PHOTO = 5005
+
+ @Suppress("UNCHECKED_CAST")
+ fun get(type: MultiPicker): T {
+ return when (type) {
+ IMAGE -> ImagePicker(REQUEST_CODE_PICK_IMAGE) as T
+ VIDEO -> VideoPicker(REQUEST_CODE_PICK_VIDEO) as T
+ FILE -> FilePicker(REQUEST_CODE_PICK_FILE) as T
+ AUDIO -> AudioPicker(REQUEST_CODE_PICK_AUDIO) as T
+ CONTACT -> ContactPicker(REQUEST_CODE_PICK_CONTACT) as T
+ CAMERA -> CameraPicker(REQUEST_CODE_TAKE_PHOTO) as T
+ else -> throw IllegalArgumentException("Unsupported type $type")
+ }
+ }
+ }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt
new file mode 100644
index 0000000000..43ac5d5fdd
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.net.Uri
+import androidx.fragment.app.Fragment
+
+/**
+ * Abstract class to provide all types of Pickers
+ */
+abstract class Picker(open val requestCode: Int) {
+
+ protected var single = false
+
+ /**
+ * Call this function from onActivityResult(int, int, Intent).
+ * @return selected files or empty list if request code is wrong
+ * or result code is not Activity.RESULT_OK
+ * or user did not select any files.
+ */
+ abstract fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List
+
+ /**
+ * Use this function to retrieve files which are shared from another application or internally
+ * by using android.intent.action.SEND or android.intent.action.SEND_MULTIPLE actions.
+ */
+ fun getIncomingFiles(context: Context, data: Intent?): List {
+ if (data == null) return emptyList()
+
+ val uriList = mutableListOf()
+ if (data.action == Intent.ACTION_SEND) {
+ (data.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { uriList.add(it) }
+ } else if (data.action == Intent.ACTION_SEND_MULTIPLE) {
+ val extraUriList: List? = data.getParcelableArrayListExtra(Intent.EXTRA_STREAM)
+ extraUriList?.let { uriList.addAll(it) }
+ }
+
+ val resInfoList: List = context.packageManager.queryIntentActivities(data, PackageManager.MATCH_DEFAULT_ONLY)
+ uriList.forEach {
+ for (resolveInfo in resInfoList) {
+ val packageName: String = resolveInfo.activityInfo.packageName
+ context.grantUriPermission(packageName, it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+ }
+ return getSelectedFiles(context, requestCode, Activity.RESULT_OK, data)
+ }
+
+ /**
+ * Call this function to disable multiple selection of files.
+ */
+ fun single(): Picker {
+ single = true
+ return this
+ }
+
+ abstract fun createIntent(): Intent
+
+ /**
+ * Start Storage Access Framework UI by using an Activity.
+ * @param activity Activity to handle onActivityResult().
+ */
+ fun startWith(activity: Activity) {
+ activity.startActivityForResult(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }, requestCode)
+ }
+
+ /**
+ * Start Storage Access Framework UI by using a Fragment.
+ * @param fragment Fragment to handle onActivityResult().
+ */
+ fun startWith(fragment: Fragment) {
+ fragment.startActivityForResult(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }, requestCode)
+ }
+
+ protected fun getSelectedUriList(data: Intent?): List {
+ val selectedUriList = mutableListOf()
+ val dataUri = data?.data
+ val clipData = data?.clipData
+
+ if (clipData != null) {
+ for (i in 0 until clipData.itemCount) {
+ selectedUriList.add(clipData.getItemAt(i).uri)
+ }
+ } else if (dataUri != null) {
+ selectedUriList.add(dataUri)
+ } else {
+ data?.extras?.get(Intent.EXTRA_STREAM)?.let {
+ (it as? List<*>)?.filterIsInstance()?.let { uriList ->
+ selectedUriList.addAll(uriList)
+ }
+ if (it is Uri) {
+ selectedUriList.add(it)
+ }
+ }
+ }
+ return selectedUriList
+ }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt
new file mode 100644
index 0000000000..b85ffacd48
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.media.MediaMetadataRetriever
+import android.provider.MediaStore
+import im.vector.riotx.multipicker.entity.MultiPickerVideoType
+
+/**
+ * Video Picker implementation
+ */
+class VideoPicker(override val requestCode: Int) : Picker(requestCode) {
+
+ /**
+ * Call this function from onActivityResult(int, int, Intent).
+ * Returns selected video files or empty list if request code is wrong
+ * or result code is not Activity.RESULT_OK
+ * or user did not select any files.
+ */
+ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List {
+ if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
+ return emptyList()
+ }
+
+ val videoList = mutableListOf()
+
+ getSelectedUriList(data).forEach { selectedUri ->
+ val projection = arrayOf(
+ MediaStore.Video.Media.DISPLAY_NAME,
+ MediaStore.Video.Media.SIZE
+ )
+
+ context.contentResolver.query(
+ selectedUri,
+ projection,
+ null,
+ null,
+ null
+ )?.use { cursor ->
+ val nameColumn = cursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME)
+ val sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE)
+
+ if (cursor.moveToNext()) {
+ val name = cursor.getString(nameColumn)
+ val size = cursor.getLong(sizeColumn)
+ var duration = 0L
+ var width = 0
+ var height = 0
+ var orientation = 0
+
+ context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd ->
+ val mediaMetadataRetriever = MediaMetadataRetriever()
+ mediaMetadataRetriever.setDataSource(pfd.fileDescriptor)
+ duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
+ width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH).toInt()
+ height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT).toInt()
+ orientation = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION).toInt()
+ }
+
+ videoList.add(
+ MultiPickerVideoType(
+ name,
+ size,
+ context.contentResolver.getType(selectedUri),
+ selectedUri,
+ width,
+ height,
+ orientation,
+ duration
+ )
+ )
+ }
+ }
+ }
+ return videoList
+ }
+
+ override fun createIntent(): Intent {
+ return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
+ type = "video/*"
+ }
+ }
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt
new file mode 100644
index 0000000000..6afe022024
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker.entity
+
+import android.net.Uri
+
+data class MultiPickerAudioType(
+ override val displayName: String?,
+ override val size: Long,
+ override val mimeType: String?,
+ override val contentUri: Uri,
+ val duration: Long
+) : MultiPickerBaseType
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt
new file mode 100644
index 0000000000..777e4d8441
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker.entity
+
+import android.net.Uri
+
+interface MultiPickerBaseType {
+ val displayName: String?
+ val size: Long
+ val mimeType: String?
+ val contentUri: Uri
+}
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt
new file mode 100644
index 0000000000..a9135443a2
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker.entity
+
+data class MultiPickerContactType(
+ val displayName: String,
+ val photoUri: String?,
+ val phoneNumberList: List,
+ val emailList: List
+)
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt
new file mode 100644
index 0000000000..5417520d28
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker.entity
+
+import android.net.Uri
+
+data class MultiPickerFileType(
+ override val displayName: String?,
+ override val size: Long,
+ override val mimeType: String?,
+ override val contentUri: Uri
+) : MultiPickerBaseType
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt
new file mode 100644
index 0000000000..b1aef171b4
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker.entity
+
+import android.net.Uri
+
+data class MultiPickerImageType(
+ override val displayName: String?,
+ override val size: Long,
+ override val mimeType: String?,
+ override val contentUri: Uri,
+ val width: Int,
+ val height: Int,
+ val orientation: Int
+) : MultiPickerBaseType
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt
new file mode 100644
index 0000000000..ba9a8d233e
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker.entity
+
+import android.net.Uri
+
+data class MultiPickerVideoType(
+ override val displayName: String?,
+ override val size: Long,
+ override val mimeType: String?,
+ override val contentUri: Uri,
+ val width: Int,
+ val height: Int,
+ val orientation: Int,
+ val duration: Long
+) : MultiPickerBaseType
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt
new file mode 100644
index 0000000000..048b2ca199
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker.provider
+
+import androidx.core.content.FileProvider
+
+class MultiPickerFileProvider : FileProvider()
diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt
new file mode 100644
index 0000000000..c5171e7d84
--- /dev/null
+++ b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.multipicker.utils
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.ImageDecoder
+import android.net.Uri
+import android.os.Build
+import androidx.exifinterface.media.ExifInterface
+import timber.log.Timber
+
+object ImageUtils {
+
+ fun getBitmap(context: Context, uri: Uri): Bitmap? {
+ return try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri))
+ } else {
+ context.contentResolver.openInputStream(uri)?.use { inputStream ->
+ BitmapFactory.decodeStream(inputStream)
+ }
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Cannot decode Bitmap: %s", uri.toString())
+ null
+ }
+ }
+
+ fun getOrientation(context: Context, uri: Uri): Int {
+ var orientation = 0
+ context.contentResolver.openInputStream(uri)?.use { inputStream ->
+ try {
+ ExifInterface(inputStream).let {
+ orientation = it.rotationDegrees
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Cannot read orientation: %s", uri.toString())
+ }
+ }
+ return orientation
+ }
+}
diff --git a/multipicker/src/main/res/xml/multipicker_provider_paths.xml b/multipicker/src/main/res/xml/multipicker_provider_paths.xml
new file mode 100644
index 0000000000..ff9b81ce98
--- /dev/null
+++ b/multipicker/src/main/res/xml/multipicker_provider_paths.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index d020abade4..04307e89d9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,2 @@
include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx', ':diff-match-patch'
+include ':multipicker'
diff --git a/vector/build.gradle b/vector/build.gradle
index 263c561921..eea99d418e 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -254,6 +254,7 @@ dependencies {
implementation project(":matrix-sdk-android")
implementation project(":matrix-sdk-android-rx")
implementation project(":diff-match-patch")
+ implementation project(":multipicker")
implementation 'com.android.support:multidex:1.0.3'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@@ -347,9 +348,6 @@ dependencies {
// Badge for compatibility
implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
- // File picker
- implementation 'com.kbeanie:multipicker:1.6@aar'
-
// DI
implementation "com.google.dagger:dagger:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 2e56e20ce7..092817a6cc 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -8,6 +8,7 @@
+
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt
index c576ebe1b9..daea538e12 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt
@@ -18,20 +18,13 @@ package im.vector.riotx.features.attachments
import android.app.Activity
import android.content.Context
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.Fragment
-import com.kbeanie.multipicker.api.Picker.PICK_AUDIO
-import com.kbeanie.multipicker.api.Picker.PICK_CONTACT
-import com.kbeanie.multipicker.api.Picker.PICK_FILE
-import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_CAMERA
-import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_DEVICE
-import com.kbeanie.multipicker.core.ImagePickerImpl
-import com.kbeanie.multipicker.core.PickerManager
-import com.kbeanie.multipicker.utils.IntentUtils
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.riotx.core.platform.Restorable
-import im.vector.riotx.features.attachments.AttachmentsHelper.Callback
+import im.vector.riotx.multipicker.MultiPicker
import timber.log.Timber
private const val CAPTURE_PATH_KEY = "CAPTURE_PATH_KEY"
@@ -39,20 +32,8 @@ private const val PENDING_TYPE_KEY = "PENDING_TYPE_KEY"
/**
* This class helps to handle attachments by providing simple methods.
- * The process is asynchronous and you must implement [Callback] methods to get the data or a failure.
*/
-class AttachmentsHelper private constructor(private val context: Context,
- private val pickerManagerFactory: PickerManagerFactory) : Restorable {
-
- companion object {
- fun create(fragment: Fragment, callback: Callback): AttachmentsHelper {
- return AttachmentsHelper(fragment.requireContext(), FragmentPickerManagerFactory(fragment, callback))
- }
-
- fun create(activity: Activity, callback: Callback): AttachmentsHelper {
- return AttachmentsHelper(activity, ActivityPickerManagerFactory(activity, callback))
- }
- }
+class AttachmentsHelper(val context: Context, val callback: Callback) : Restorable {
interface Callback {
fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
@@ -66,39 +47,15 @@ class AttachmentsHelper private constructor(private val context: Context,
}
// Capture path allows to handle camera image picking. It must be restored if the activity gets killed.
- private var capturePath: String? = null
+ private var captureUri: Uri? = null
// The pending type is set if we have to handle permission request. It must be restored if the activity gets killed.
var pendingType: AttachmentTypeSelectorView.Type? = null
- private val imagePicker by lazy {
- pickerManagerFactory.createImagePicker()
- }
-
- private val videoPicker by lazy {
- pickerManagerFactory.createVideoPicker()
- }
-
- private val cameraImagePicker by lazy {
- pickerManagerFactory.createCameraImagePicker()
- }
-
- private val filePicker by lazy {
- pickerManagerFactory.createFilePicker()
- }
-
- private val audioPicker by lazy {
- pickerManagerFactory.createAudioPicker()
- }
-
- private val contactPicker by lazy {
- pickerManagerFactory.createContactPicker()
- }
-
// Restorable
override fun onSaveInstanceState(outState: Bundle) {
- capturePath?.also {
- outState.putString(CAPTURE_PATH_KEY, it)
+ captureUri?.also {
+ outState.putParcelable(CAPTURE_PATH_KEY, it)
}
pendingType?.also {
outState.putSerializable(PENDING_TYPE_KEY, it)
@@ -106,10 +63,7 @@ class AttachmentsHelper private constructor(private val context: Context,
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
- capturePath = savedInstanceState?.getString(CAPTURE_PATH_KEY)
- if (capturePath != null) {
- cameraImagePicker.reinitialize(capturePath)
- }
+ captureUri = savedInstanceState?.getParcelable(CAPTURE_PATH_KEY) as? Uri
pendingType = savedInstanceState?.getSerializable(PENDING_TYPE_KEY) as? AttachmentTypeSelectorView.Type
}
@@ -118,36 +72,36 @@ class AttachmentsHelper private constructor(private val context: Context,
/**
* Starts the process for handling file picking
*/
- fun selectFile() {
- filePicker.pickFile()
+ fun selectFile(fragment: Fragment) {
+ MultiPicker.get(MultiPicker.FILE).startWith(fragment)
}
/**
* Starts the process for handling image picking
*/
- fun selectGallery() {
- imagePicker.pickImage()
+ fun selectGallery(fragment: Fragment) {
+ MultiPicker.get(MultiPicker.IMAGE).startWith(fragment)
}
/**
* Starts the process for handling audio picking
*/
- fun selectAudio() {
- audioPicker.pickAudio()
+ fun selectAudio(fragment: Fragment) {
+ MultiPicker.get(MultiPicker.AUDIO).startWith(fragment)
}
/**
* Starts the process for handling capture image picking
*/
- fun openCamera() {
- capturePath = cameraImagePicker.pickImage()
+ fun openCamera(fragment: Fragment) {
+ captureUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(fragment)
}
/**
* Starts the process for handling contact picking
*/
- fun selectContact() {
- contactPicker.pickContact()
+ fun selectContact(fragment: Fragment) {
+ MultiPicker.get(MultiPicker.CONTACT).startWith(fragment)
}
/**
@@ -157,14 +111,58 @@ class AttachmentsHelper private constructor(private val context: Context,
*/
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (resultCode == Activity.RESULT_OK) {
- val pickerManager = getPickerManagerForRequestCode(requestCode)
- if (pickerManager != null) {
- if (pickerManager is ImagePickerImpl) {
- pickerManager.reinitialize(capturePath)
+ when (requestCode) {
+ MultiPicker.REQUEST_CODE_PICK_FILE -> {
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.FILE)
+ .getSelectedFiles(context, requestCode, resultCode, data)
+ .map { it.toContentAttachmentData() }
+ )
}
- pickerManager.submit(data)
- return true
+ MultiPicker.REQUEST_CODE_PICK_AUDIO -> {
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.AUDIO)
+ .getSelectedFiles(context, requestCode, resultCode, data)
+ .map { it.toContentAttachmentData() }
+ )
+ }
+ MultiPicker.REQUEST_CODE_PICK_CONTACT -> {
+ MultiPicker.get(MultiPicker.CONTACT)
+ .getSelectedFiles(context, requestCode, resultCode, data)
+ .firstOrNull()
+ ?.toContactAttachment()
+ ?.let {
+ callback.onContactAttachmentReady(it)
+ }
+ }
+ MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.IMAGE)
+ .getSelectedFiles(context, requestCode, resultCode, data)
+ .map { it.toContentAttachmentData() }
+ )
+ }
+ MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
+ captureUri?.let { captureUri ->
+ MultiPicker.get(MultiPicker.CAMERA)
+ .getTakenPhoto(context, requestCode, resultCode, captureUri)
+ ?.let {
+ callback.onContentAttachmentsReady(
+ listOf(it).map { it.toContentAttachmentData() }
+ )
+ }
+ }
+ }
+ MultiPicker.REQUEST_CODE_PICK_VIDEO -> {
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.VIDEO)
+ .getSelectedFiles(context, requestCode, resultCode, data)
+ .map { it.toContentAttachmentData() }
+ )
+ }
+ else -> return false
}
+ return true
}
return false
}
@@ -174,39 +172,35 @@ class AttachmentsHelper private constructor(private val context: Context,
*
* @return true if it can handle the intent data, false otherwise
*/
- fun handleShareIntent(intent: Intent): Boolean {
+ fun handleShareIntent(context: Context, intent: Intent): Boolean {
val type = intent.resolveType(context) ?: return false
if (type.startsWith("image")) {
- imagePicker.submit(safeShareIntent(intent))
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.IMAGE).getIncomingFiles(context, intent).map {
+ it.toContentAttachmentData()
+ }
+ )
} else if (type.startsWith("video")) {
- videoPicker.submit(safeShareIntent(intent))
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.VIDEO).getIncomingFiles(context, intent).map {
+ it.toContentAttachmentData()
+ }
+ )
} else if (type.startsWith("audio")) {
- videoPicker.submit(safeShareIntent(intent))
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.AUDIO).getIncomingFiles(context, intent).map {
+ it.toContentAttachmentData()
+ }
+ )
} else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) {
- filePicker.submit(safeShareIntent(intent))
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.FILE).getIncomingFiles(context, intent).map {
+ it.toContentAttachmentData()
+ }
+ )
} else {
return false
}
return true
}
-
- private fun safeShareIntent(intent: Intent): Intent {
- // Work around for getPickerIntentForSharing doing NPE in android 10
- return try {
- IntentUtils.getPickerIntentForSharing(intent)
- } catch (failure: Throwable) {
- intent
- }
- }
-
- private fun getPickerManagerForRequestCode(requestCode: Int): PickerManager? {
- return when (requestCode) {
- PICK_IMAGE_DEVICE -> imagePicker
- PICK_IMAGE_CAMERA -> cameraImagePicker
- PICK_FILE -> filePicker
- PICK_CONTACT -> contactPicker
- PICK_AUDIO -> audioPicker
- else -> null
- }
- }
}
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt
index a3de5084de..02b712b8a7 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt
@@ -16,51 +16,48 @@
package im.vector.riotx.features.attachments
-import com.kbeanie.multipicker.api.entity.ChosenAudio
-import com.kbeanie.multipicker.api.entity.ChosenContact
-import com.kbeanie.multipicker.api.entity.ChosenFile
-import com.kbeanie.multipicker.api.entity.ChosenImage
-import com.kbeanie.multipicker.api.entity.ChosenVideo
import im.vector.matrix.android.api.session.content.ContentAttachmentData
+import im.vector.riotx.multipicker.entity.MultiPickerAudioType
+import im.vector.riotx.multipicker.entity.MultiPickerBaseType
+import im.vector.riotx.multipicker.entity.MultiPickerContactType
+import im.vector.riotx.multipicker.entity.MultiPickerFileType
+import im.vector.riotx.multipicker.entity.MultiPickerImageType
+import im.vector.riotx.multipicker.entity.MultiPickerVideoType
import timber.log.Timber
-fun ChosenContact.toContactAttachment(): ContactAttachment {
+fun MultiPickerContactType.toContactAttachment(): ContactAttachment {
return ContactAttachment(
displayName = displayName,
photoUri = photoUri,
- emails = emails.toList(),
- phones = phones.toList()
+ emails = emailList.toList(),
+ phones = phoneNumberList.toList()
)
}
-fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
+fun MultiPickerFileType.toContentAttachmentData(): ContentAttachmentData {
if (mimeType == null) Timber.w("No mimeType")
return ContentAttachmentData(
- path = originalPath,
mimeType = mimeType,
type = mapType(),
size = size,
- date = createdAt?.time ?: System.currentTimeMillis(),
name = displayName,
- queryUri = queryUri
+ queryUri = contentUri
)
}
-fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData {
+fun MultiPickerAudioType.toContentAttachmentData(): ContentAttachmentData {
if (mimeType == null) Timber.w("No mimeType")
return ContentAttachmentData(
- path = originalPath,
mimeType = mimeType,
type = mapType(),
size = size,
- date = createdAt?.time ?: System.currentTimeMillis(),
name = displayName,
duration = duration,
- queryUri = queryUri
+ queryUri = contentUri
)
}
-private fun ChosenFile.mapType(): ContentAttachmentData.Type {
+private fun MultiPickerBaseType.mapType(): ContentAttachmentData.Type {
return when {
mimeType?.startsWith("image/") == true -> ContentAttachmentData.Type.IMAGE
mimeType?.startsWith("video/") == true -> ContentAttachmentData.Type.VIDEO
@@ -69,10 +66,9 @@ private fun ChosenFile.mapType(): ContentAttachmentData.Type {
}
}
-fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
+fun MultiPickerImageType.toContentAttachmentData(): ContentAttachmentData {
if (mimeType == null) Timber.w("No mimeType")
return ContentAttachmentData(
- path = originalPath,
mimeType = mimeType,
type = mapType(),
name = displayName,
@@ -80,23 +76,20 @@ fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
height = height.toLong(),
width = width.toLong(),
exifOrientation = orientation,
- date = createdAt?.time ?: System.currentTimeMillis(),
- queryUri = queryUri
+ queryUri = contentUri
)
}
-fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData {
+fun MultiPickerVideoType.toContentAttachmentData(): ContentAttachmentData {
if (mimeType == null) Timber.w("No mimeType")
return ContentAttachmentData(
- path = originalPath,
mimeType = mimeType,
type = ContentAttachmentData.Type.VIDEO,
size = size,
- date = createdAt?.time ?: System.currentTimeMillis(),
height = height.toLong(),
width = width.toLong(),
duration = duration,
name = displayName,
- queryUri = queryUri
+ queryUri = contentUri
)
}
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt
deleted file mode 100644
index 62956e08c8..0000000000
--- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.riotx.features.attachments
-
-import com.kbeanie.multipicker.api.callbacks.AudioPickerCallback
-import com.kbeanie.multipicker.api.callbacks.ContactPickerCallback
-import com.kbeanie.multipicker.api.callbacks.FilePickerCallback
-import com.kbeanie.multipicker.api.callbacks.ImagePickerCallback
-import com.kbeanie.multipicker.api.callbacks.VideoPickerCallback
-import com.kbeanie.multipicker.api.entity.ChosenAudio
-import com.kbeanie.multipicker.api.entity.ChosenContact
-import com.kbeanie.multipicker.api.entity.ChosenFile
-import com.kbeanie.multipicker.api.entity.ChosenImage
-import com.kbeanie.multipicker.api.entity.ChosenVideo
-
-/**
- * This class delegates the PickerManager callbacks to an [AttachmentsHelper.Callback]
- */
-class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback)
- : ImagePickerCallback,
- FilePickerCallback,
- VideoPickerCallback,
- AudioPickerCallback,
- ContactPickerCallback {
-
- override fun onContactChosen(contact: ChosenContact?) {
- if (contact == null) {
- callback.onAttachmentsProcessFailed()
- } else {
- val contactAttachment = contact.toContactAttachment()
- callback.onContactAttachmentReady(contactAttachment)
- }
- }
-
- override fun onAudiosChosen(audios: MutableList?) {
- if (audios.isNullOrEmpty()) {
- callback.onAttachmentsProcessFailed()
- } else {
- val attachments = audios.map {
- it.toContentAttachmentData()
- }
- callback.onContentAttachmentsReady(attachments)
- }
- }
-
- override fun onFilesChosen(files: MutableList?) {
- if (files.isNullOrEmpty()) {
- callback.onAttachmentsProcessFailed()
- } else {
- val attachments = files.map {
- it.toContentAttachmentData()
- }
- callback.onContentAttachmentsReady(attachments)
- }
- }
-
- override fun onImagesChosen(images: MutableList?) {
- if (images.isNullOrEmpty()) {
- callback.onAttachmentsProcessFailed()
- } else {
- val attachments = images.map {
- it.toContentAttachmentData()
- }
- callback.onContentAttachmentsReady(attachments)
- }
- }
-
- override fun onVideosChosen(videos: MutableList?) {
- if (videos.isNullOrEmpty()) {
- callback.onAttachmentsProcessFailed()
- } else {
- val attachments = videos.map {
- it.toContentAttachmentData()
- }
- callback.onContentAttachmentsReady(attachments)
- }
- }
-
- override fun onError(error: String?) {
- callback.onAttachmentsProcessFailed()
- }
-}
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt b/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt
deleted file mode 100644
index 6c03f21ab3..0000000000
--- a/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.riotx.features.attachments
-
-import android.app.Activity
-import androidx.fragment.app.Fragment
-import com.kbeanie.multipicker.api.AudioPicker
-import com.kbeanie.multipicker.api.CameraImagePicker
-import com.kbeanie.multipicker.api.ContactPicker
-import com.kbeanie.multipicker.api.FilePicker
-import com.kbeanie.multipicker.api.ImagePicker
-import com.kbeanie.multipicker.api.VideoPicker
-
-/**
- * Factory for creating different pickers. It allows to use with fragment or activity builders.
- */
-interface PickerManagerFactory {
-
- fun createImagePicker(): ImagePicker
-
- fun createCameraImagePicker(): CameraImagePicker
-
- fun createVideoPicker(): VideoPicker
-
- fun createFilePicker(): FilePicker
-
- fun createAudioPicker(): AudioPicker
-
- fun createContactPicker(): ContactPicker
-}
-
-class ActivityPickerManagerFactory(private val activity: Activity, callback: AttachmentsHelper.Callback) : PickerManagerFactory {
-
- private val attachmentsPickerCallback = AttachmentsPickerCallback(callback)
-
- override fun createImagePicker(): ImagePicker {
- return ImagePicker(activity).also {
- it.setImagePickerCallback(attachmentsPickerCallback)
- it.allowMultiple()
- }
- }
-
- override fun createCameraImagePicker(): CameraImagePicker {
- return CameraImagePicker(activity).also {
- it.setImagePickerCallback(attachmentsPickerCallback)
- }
- }
-
- override fun createVideoPicker(): VideoPicker {
- return VideoPicker(activity).also {
- it.setVideoPickerCallback(attachmentsPickerCallback)
- it.allowMultiple()
- }
- }
-
- override fun createFilePicker(): FilePicker {
- return FilePicker(activity).also {
- it.allowMultiple()
- it.setFilePickerCallback(attachmentsPickerCallback)
- }
- }
-
- override fun createAudioPicker(): AudioPicker {
- return AudioPicker(activity).also {
- it.allowMultiple()
- it.setAudioPickerCallback(attachmentsPickerCallback)
- }
- }
-
- override fun createContactPicker(): ContactPicker {
- return ContactPicker(activity).also {
- it.setContactPickerCallback(attachmentsPickerCallback)
- }
- }
-}
-
-class FragmentPickerManagerFactory(private val fragment: Fragment, callback: AttachmentsHelper.Callback) : PickerManagerFactory {
-
- private val attachmentsPickerCallback = AttachmentsPickerCallback(callback)
-
- override fun createImagePicker(): ImagePicker {
- return ImagePicker(fragment).also {
- it.setImagePickerCallback(attachmentsPickerCallback)
- it.allowMultiple()
- }
- }
-
- override fun createCameraImagePicker(): CameraImagePicker {
- return CameraImagePicker(fragment).also {
- it.setImagePickerCallback(attachmentsPickerCallback)
- }
- }
-
- override fun createVideoPicker(): VideoPicker {
- return VideoPicker(fragment).also {
- it.setVideoPickerCallback(attachmentsPickerCallback)
- it.allowMultiple()
- }
- }
-
- override fun createFilePicker(): FilePicker {
- return FilePicker(fragment).also {
- it.allowMultiple()
- it.setFilePickerCallback(attachmentsPickerCallback)
- }
- }
-
- override fun createAudioPicker(): AudioPicker {
- return AudioPicker(fragment).also {
- it.allowMultiple()
- it.setAudioPickerCallback(attachmentsPickerCallback)
- }
- }
-
- override fun createContactPicker(): ContactPicker {
- return ContactPicker(fragment).also {
- it.setContactPickerCallback(attachmentsPickerCallback)
- }
- }
-}
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt
index 34f018aaf9..60ee722116 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt
@@ -25,7 +25,7 @@ class AttachmentBigPreviewController @Inject constructor() : TypedEpoxyControlle
override fun buildModels(data: AttachmentsPreviewViewState) {
data.attachments.forEach {
attachmentBigPreviewItem {
- id(it.path)
+ id(it.queryUri.toString())
attachment(it)
}
}
@@ -43,7 +43,7 @@ class AttachmentMiniaturePreviewController @Inject constructor() : TypedEpoxyCon
override fun buildModels(data: AttachmentsPreviewViewState) {
data.attachments.forEachIndexed { index, contentAttachmentData ->
attachmentMiniaturePreviewItem {
- id(contentAttachmentData.path)
+ id(contentAttachmentData.queryUri.toString())
attachment(contentAttachmentData)
checked(data.currentAttachmentIndex == index)
clickListener { _ ->
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt
index 3b43fa6e20..373298bf31 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt
@@ -33,11 +33,10 @@ abstract class AttachmentPreviewItem : VectorE
abstract val attachment: ContentAttachmentData
override fun bind(holder: H) {
- val path = attachment.path
if (attachment.type == ContentAttachmentData.Type.VIDEO || attachment.type == ContentAttachmentData.Type.IMAGE) {
Glide.with(holder.view.context)
.asBitmap()
- .load(path)
+ .load(attachment.queryUri)
.apply(RequestOptions().frame(0))
.into(holder.imageView)
} else {
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt
index 5acc59b035..aef724331f 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt
@@ -17,10 +17,11 @@
package im.vector.riotx.features.attachments.preview
+import android.net.Uri
import im.vector.riotx.core.platform.VectorViewModelAction
sealed class AttachmentsPreviewAction : VectorViewModelAction {
object RemoveCurrentAttachment : AttachmentsPreviewAction()
data class SetCurrentAttachment(val index: Int): AttachmentsPreviewAction()
- data class UpdatePathOfCurrentAttachment(val newPath: String): AttachmentsPreviewAction()
+ data class UpdatePathOfCurrentAttachment(val newUri: Uri): AttachmentsPreviewAction()
}
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt
index e52b497df4..3b1972ffbc 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt
@@ -172,9 +172,9 @@ class AttachmentsPreviewFragment @Inject constructor(
}
private fun handleCropResult(result: Intent) {
- val resultPath = UCrop.getOutput(result)?.path
- if (resultPath != null) {
- viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultPath))
+ val resultUri = UCrop.getOutput(result)
+ if (resultUri != null) {
+ viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultUri))
} else {
Toast.makeText(requireContext(), "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show()
}
@@ -202,8 +202,7 @@ class AttachmentsPreviewFragment @Inject constructor(
private fun doHandleEditAction() = withState(viewModel) {
val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}")
- // Note: using currentAttachment.queryUri.toUri() make the app crash when sharing from Google Photos
- val uri = File(currentAttachment.path).toUri()
+ val uri = currentAttachment.queryUri
UCrop.of(uri, destinationFile.toUri())
.withOptions(
UCrop.Options()
diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt
index 1f6c8c2f8b..d1e44fa963 100644
--- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt
@@ -62,7 +62,7 @@ class AttachmentsPreviewViewModel @AssistedInject constructor(@Assisted initialS
private fun handleUpdatePathOfCurrentAttachment(action: AttachmentsPreviewAction.UpdatePathOfCurrentAttachment) = withState {
val attachments = it.attachments.mapIndexed { index, contentAttachmentData ->
if (index == it.currentAttachmentIndex) {
- contentAttachmentData.copy(path = action.newPath)
+ contentAttachmentData.copy(queryUri = action.newUri)
} else {
contentAttachmentData
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
index e748478e6a..f58d7be718 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
@@ -250,7 +250,7 @@ class RoomDetailFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
- attachmentsHelper = AttachmentsHelper.create(this, this).register()
+ attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
keyboardStateUtils = KeyboardStateUtils(requireActivity())
setupToolbar(roomToolbar)
setupRecyclerView()
@@ -290,9 +290,9 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.observeViewEvents {
when (it) {
- is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
- is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
- is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it)
+ is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
+ is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
+ is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it)
is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it)
is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG)
is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it)
@@ -665,7 +665,7 @@ class RoomDetailFragment @Inject constructor(
private fun sendUri(uri: Uri): Boolean {
roomDetailViewModel.preventAttachmentPreview = true
val shareIntent = Intent(Intent.ACTION_SEND, uri)
- val isHandled = attachmentsHelper.handleShareIntent(shareIntent)
+ val isHandled = attachmentsHelper.handleShareIntent(requireContext(), shareIntent)
if (!isHandled) {
roomDetailViewModel.preventAttachmentPreview = false
Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show()
@@ -1350,11 +1350,11 @@ class RoomDetailFragment @Inject constructor(
private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
when (type) {
- AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera()
- AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile()
- AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery()
- AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio()
- AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact()
+ AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(this)
+ AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(this)
+ AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this)
+ AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this)
+ AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this)
AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers")
}.exhaustive
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
index 2ad90f073a..cef172da73 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
@@ -610,7 +610,7 @@ class RoomDetailViewModel @AssistedInject constructor(
when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) {
null -> room.sendMedias(attachments, action.compressBeforeSending, emptySet())
else -> _viewEvents.post(RoomDetailViewEvents.FileTooBigError(
- tooBigFile.name ?: tooBigFile.path,
+ tooBigFile.name ?: tooBigFile.queryUri.toString(),
tooBigFile.size,
maxUploadFileSize
))
diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt
index 74821ab2fe..aa665b5653 100644
--- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt
@@ -72,18 +72,18 @@ class IncomingShareFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
setupToolbar(incomingShareToolbar)
- attachmentsHelper = AttachmentsHelper.create(this, this).register()
+ attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
val intent = vectorBaseActivity.intent
val isShareManaged = when (intent?.action) {
Intent.ACTION_SEND -> {
- var isShareManaged = attachmentsHelper.handleShareIntent(intent)
+ var isShareManaged = attachmentsHelper.handleShareIntent(requireContext(), intent)
if (!isShareManaged) {
isShareManaged = handleTextShare(intent)
}
isShareManaged
}
- Intent.ACTION_SEND_MULTIPLE -> attachmentsHelper.handleShareIntent(intent)
+ Intent.ACTION_SEND_MULTIPLE -> attachmentsHelper.handleShareIntent(requireContext(), intent)
else -> false
}