From f7fd23b153c3821b7e98efdf7047eca9a9405f23 Mon Sep 17 00:00:00 2001 From: onurays Date: Mon, 23 Mar 2020 16:31:32 +0300 Subject: [PATCH] App integration to the new multipicker library. --- .../session/content/ContentAttachmentData.kt | 4 +- .../internal/session/content/FileUploader.kt | 4 +- .../session/content/ThumbnailExtractor.kt | 68 ++++--- .../session/content/UploadContentWorker.kt | 188 ++++++++---------- .../room/send/LocalEchoEventFactory.kt | 16 +- .../vector/riotx/multipicker/AudioPicker.kt | 1 + .../vector/riotx/multipicker/CameraPicker.kt | 1 - .../im/vector/riotx/multipicker/FilePicker.kt | 1 + .../vector/riotx/multipicker/ImagePicker.kt | 1 + .../multipicker/MultiPickerFileProvider.kt | 3 +- .../vector/riotx/multipicker/VideoPicker.kt | 1 + vector/build.gradle | 3 - .../features/attachments/AttachmentsHelper.kt | 182 ++++++++--------- .../features/attachments/AttachmentsMapper.kt | 43 ++-- .../attachments/AttachmentsPickerCallback.kt | 96 --------- .../attachments/PickerManagerFactory.kt | 134 ------------- .../preview/AttachmentPreviewControllers.kt | 4 +- .../preview/AttachmentPreviewItems.kt | 3 +- .../preview/AttachmentsPreviewAction.kt | 3 +- .../preview/AttachmentsPreviewFragment.kt | 8 +- .../preview/AttachmentsPreviewViewModel.kt | 2 +- .../home/room/detail/RoomDetailFragment.kt | 40 +--- .../home/room/detail/RoomDetailViewModel.kt | 2 +- .../features/share/IncomingShareFragment.kt | 6 +- 24 files changed, 265 insertions(+), 549 deletions(-) delete mode 100644 vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt 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..2ce249ab80 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,14 @@ 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 +import kotlin.math.max +import kotlin.math.roundToInt internal object ThumbnailExtractor { @@ -33,34 +35,48 @@ 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() - return thumbnailData + private fun extractVideoThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? { + val mediaMetadataRetriever = MediaMetadataRetriever() + try { + mediaMetadataRetriever.setDataSource(context, attachment.queryUri) + var thumbnail = mediaMetadataRetriever.frameAtTime + // Scale down the bitmap if it's too large. + val width: Int = thumbnail.width + val height: Int = thumbnail.height + val max = max(width, height) + if (max > 512) { + val scale = 512f / max + val w = (scale * width).roundToInt() + val h = (scale * height).roundToInt() + thumbnail = Bitmap.createScaledBitmap(thumbnail, w, h, true) + } + + 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() + return thumbnailData + } catch (e: Exception) { + Timber.e(e, "Cannot extract video thumbnail") + return null + } } } 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..21ab649c23 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,84 @@ 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() + + 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) + 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(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 +177,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/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 8fb23ec49d..23873aae1c 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -54,6 +54,7 @@ class AudioPicker(override val requestCode: Int) : Picker( selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt index 6962f2098a..81e665a43f 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -23,7 +23,6 @@ import android.graphics.BitmapFactory import android.graphics.ImageDecoder import android.net.Uri import android.os.Build -import android.os.Environment import android.provider.MediaStore import androidx.core.content.FileProvider import androidx.exifinterface.media.ExifInterface diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt index 9d3292c115..0e1169755e 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -53,6 +53,7 @@ class FilePicker(override val requestCode: Int) : Picker(re selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index f33ceb816c..8bf589800d 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -57,6 +57,7 @@ class ImagePicker(override val requestCode: Int) : Picker( selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt index 832d721eef..1549b43fd7 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt @@ -18,5 +18,4 @@ package im.vector.riotx.multipicker import androidx.core.content.FileProvider -class MultiPickerFileProvider : FileProvider() { -} +class MultiPickerFileProvider : FileProvider() diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index 92d1e9c240..d4b8d6a985 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -54,6 +54,7 @@ class VideoPicker(override val requestCode: Int) : Picker( selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/vector/build.gradle b/vector/build.gradle index 447ad7a767..2887ebba46 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -348,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/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..f059da7d85 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() } @@ -203,7 +203,7 @@ class AttachmentsPreviewFragment @Inject constructor( 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 13de137e8f..779b7fb089 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 @@ -251,7 +251,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() @@ -517,29 +517,6 @@ class RoomDetailFragment @Inject constructor( } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - when (requestCode) { - MultiPicker.REQUEST_CODE_PICK_IMAGE -> { - MultiPicker.get(MultiPicker.IMAGE).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_VIDEO -> { - MultiPicker.get(MultiPicker.VIDEO).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_FILE -> { - MultiPicker.get(MultiPicker.FILE).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_AUDIO -> { - MultiPicker.get(MultiPicker.AUDIO).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_CONTACT -> { - MultiPicker.get(MultiPicker.CONTACT).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_TAKE_PHOTO -> { - cameraPhotoUri?.let { - MultiPicker.get(MultiPicker.CAMERA).getTakenPhoto(requireContext(), requestCode, resultCode, it) - } - } - } - val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data) if (!hasBeenHandled && resultCode == RESULT_OK && data != null) { when (requestCode) { @@ -689,7 +666,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() @@ -1372,16 +1349,13 @@ class RoomDetailFragment @Inject constructor( } } - private var cameraPhotoUri: Uri? = null private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> { - cameraPhotoUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this) - } - AttachmentTypeSelectorView.Type.FILE -> MultiPicker.get(MultiPicker.FILE).startWith(this) - AttachmentTypeSelectorView.Type.GALLERY -> MultiPicker.get(MultiPicker.IMAGE).startWith(this) - AttachmentTypeSelectorView.Type.AUDIO -> MultiPicker.get(MultiPicker.AUDIO).startWith(this) - AttachmentTypeSelectorView.Type.CONTACT -> MultiPicker.get(MultiPicker.CONTACT).startWith(this) + 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 }