diff --git a/CHANGES.md b/CHANGES.md index d3e17e6980..01f5aa31a1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: - Delete and react to stickers (#3250) - Compress video before sending (#442) - Improve file too big error detection (#3245) + - User can now select video when selecting Gallery to send attachments to a room Bugfix 🐛: - Message states cosmetic changes (#3007) diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/MediaPicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/MediaPicker.kt new file mode 100644 index 0000000000..76929b52bd --- /dev/null +++ b/multipicker/src/main/java/im/vector/lib/multipicker/MediaPicker.kt @@ -0,0 +1,124 @@ +/* + * 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.lib.multipicker + +import android.content.Context +import android.content.Intent +import android.media.MediaMetadataRetriever +import android.provider.MediaStore +import im.vector.lib.multipicker.entity.MultiPickerBaseMediaType +import im.vector.lib.multipicker.entity.MultiPickerImageType +import im.vector.lib.multipicker.entity.MultiPickerVideoType +import im.vector.lib.multipicker.utils.ImageUtils + +/** + * Image/Video Picker implementation + */ +class MediaPicker : Picker() { + + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected image/video files or empty list if user did not select any files. + */ + override fun getSelectedFiles(context: Context, data: Intent?): List { + val mediaList = mutableListOf() + + getSelectedUriList(data).forEach { selectedUri -> + val projection = arrayOf( + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.SIZE, + MediaStore.Images.Media.MIME_TYPE + ) + + 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) + val mimeTypeColumn = cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE) + + if (cursor.moveToNext()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + val mimeType = cursor.getString(mimeTypeColumn) + + if (mimeType.isMimeTypeVideo()) { + 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() ?: 0L + width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt() ?: 0 + height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt() ?: 0 + orientation = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)?.toInt() ?: 0 + } + + mediaList.add( + MultiPickerVideoType( + name, + size, + context.contentResolver.getType(selectedUri), + selectedUri, + width, + height, + orientation, + duration + ) + ) + } else { + // Assume it's an image + val bitmap = ImageUtils.getBitmap(context, selectedUri) + val orientation = ImageUtils.getOrientation(context, selectedUri) + + mediaList.add( + MultiPickerImageType( + name, + size, + context.contentResolver.getType(selectedUri), + selectedUri, + bitmap?.width ?: 0, + bitmap?.height ?: 0, + orientation + ) + ) + } + } + } + } + return mediaList + } + + override fun createIntent(): Intent { + return Intent(Intent.ACTION_GET_CONTENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = "video/*, image/*" + val mimeTypes = arrayOf("image/*", "video/*") + putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) + } + } + + private fun String?.isMimeTypeVideo() = this?.startsWith("video/") == true +} diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt index 7e639a9bd3..f7ed4e5cd9 100644 --- a/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt +++ b/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt @@ -20,6 +20,7 @@ class MultiPicker { companion object Type { val IMAGE by lazy { MultiPicker() } + val MEDIA by lazy { MultiPicker() } val FILE by lazy { MultiPicker() } val VIDEO by lazy { MultiPicker() } val AUDIO by lazy { MultiPicker() } @@ -31,6 +32,7 @@ class MultiPicker { return when (type) { IMAGE -> ImagePicker() as T VIDEO -> VideoPicker() as T + MEDIA -> MediaPicker() as T FILE -> FilePicker() as T AUDIO -> AudioPicker() as T CONTACT -> ContactPicker() as T diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerImageType.kt b/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerImageType.kt index a3f30fc0d5..9efae715cd 100644 --- a/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerImageType.kt +++ b/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerImageType.kt @@ -23,7 +23,7 @@ data class MultiPickerImageType( override val size: Long, override val mimeType: String?, override val contentUri: Uri, - val width: Int, - val height: Int, - val orientation: Int -) : MultiPickerBaseType + override val width: Int, + override val height: Int, + override val orientation: Int +) : MultiPickerBaseMediaType diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerMediaType.kt b/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerMediaType.kt new file mode 100644 index 0000000000..9357e22a74 --- /dev/null +++ b/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerMediaType.kt @@ -0,0 +1,23 @@ +/* + * 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.lib.multipicker.entity + +interface MultiPickerBaseMediaType : MultiPickerBaseType { + val width: Int + val height: Int + val orientation: Int +} diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerVideoType.kt b/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerVideoType.kt index 0015052c7c..20eb844c8a 100644 --- a/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerVideoType.kt +++ b/multipicker/src/main/java/im/vector/lib/multipicker/entity/MultiPickerVideoType.kt @@ -23,8 +23,8 @@ data class MultiPickerVideoType( override val size: Long, override val mimeType: String?, override val contentUri: Uri, - val width: Int, - val height: Int, - val orientation: Int, + override val width: Int, + override val height: Int, + override val orientation: Int, val duration: Long -) : MultiPickerBaseType +) : MultiPickerBaseMediaType diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt index d4efb22eb8..a9fcd353db 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt @@ -77,10 +77,10 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab } /** - * Starts the process for handling image picking + * Starts the process for handling image/video picking */ fun selectGallery(activityResultLauncher: ActivityResultLauncher) { - MultiPicker.get(MultiPicker.IMAGE).startWith(activityResultLauncher) + MultiPicker.get(MultiPicker.MEDIA).startWith(activityResultLauncher) } /** @@ -133,9 +133,9 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab } } - fun onImageResult(data: Intent?) { + fun onMediaResult(data: Intent?) { callback.onContentAttachmentsReady( - MultiPicker.get(MultiPicker.IMAGE) + MultiPicker.get(MultiPicker.MEDIA) .getSelectedFiles(context, data) .map { it.toContentAttachmentData() } ) diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt index 4e8dcaacb7..7f8021ea72 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt @@ -17,6 +17,7 @@ package im.vector.app.features.attachments import im.vector.lib.multipicker.entity.MultiPickerAudioType +import im.vector.lib.multipicker.entity.MultiPickerBaseMediaType import im.vector.lib.multipicker.entity.MultiPickerBaseType import im.vector.lib.multipicker.entity.MultiPickerContactType import im.vector.lib.multipicker.entity.MultiPickerFileType @@ -69,6 +70,14 @@ private fun MultiPickerBaseType.mapType(): ContentAttachmentData.Type { } } +fun MultiPickerBaseMediaType.toContentAttachmentData(): ContentAttachmentData { + return when (this) { + is MultiPickerImageType -> toContentAttachmentData() + is MultiPickerVideoType -> toContentAttachmentData() + else -> throw IllegalStateException("Unknown media type") + } +} + fun MultiPickerImageType.toContentAttachmentData(): ContentAttachmentData { if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 09f17c3367..f78dad4e16 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -988,9 +988,9 @@ class RoomDetailFragment @Inject constructor( } } - private val attachmentImageActivityResultLauncher = registerStartForActivityResult { + private val attachmentMediaActivityResultLauncher = registerStartForActivityResult { if (it.resultCode == Activity.RESULT_OK) { - attachmentsHelper.onImageResult(it.data) + attachmentsHelper.onMediaResult(it.data) } } @@ -1991,7 +1991,7 @@ class RoomDetailFragment @Inject constructor( when (type) { AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher) AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) - AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentImageActivityResultLauncher) + AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)