User can now select video when selecting Gallery to send attachments to a room
This commit is contained in:
parent
570cffd3ed
commit
30a54cfdbc
|
@ -9,6 +9,7 @@ Improvements 🙌:
|
||||||
- Delete and react to stickers (#3250)
|
- Delete and react to stickers (#3250)
|
||||||
- Compress video before sending (#442)
|
- Compress video before sending (#442)
|
||||||
- Improve file too big error detection (#3245)
|
- Improve file too big error detection (#3245)
|
||||||
|
- User can now select video when selecting Gallery to send attachments to a room
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Message states cosmetic changes (#3007)
|
- Message states cosmetic changes (#3007)
|
||||||
|
|
|
@ -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<MultiPickerBaseMediaType>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<MultiPickerBaseMediaType> {
|
||||||
|
val mediaList = mutableListOf<MultiPickerBaseMediaType>()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ class MultiPicker<T> {
|
||||||
|
|
||||||
companion object Type {
|
companion object Type {
|
||||||
val IMAGE by lazy { MultiPicker<ImagePicker>() }
|
val IMAGE by lazy { MultiPicker<ImagePicker>() }
|
||||||
|
val MEDIA by lazy { MultiPicker<MediaPicker>() }
|
||||||
val FILE by lazy { MultiPicker<FilePicker>() }
|
val FILE by lazy { MultiPicker<FilePicker>() }
|
||||||
val VIDEO by lazy { MultiPicker<VideoPicker>() }
|
val VIDEO by lazy { MultiPicker<VideoPicker>() }
|
||||||
val AUDIO by lazy { MultiPicker<AudioPicker>() }
|
val AUDIO by lazy { MultiPicker<AudioPicker>() }
|
||||||
|
@ -31,6 +32,7 @@ class MultiPicker<T> {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
IMAGE -> ImagePicker() as T
|
IMAGE -> ImagePicker() as T
|
||||||
VIDEO -> VideoPicker() as T
|
VIDEO -> VideoPicker() as T
|
||||||
|
MEDIA -> MediaPicker() as T
|
||||||
FILE -> FilePicker() as T
|
FILE -> FilePicker() as T
|
||||||
AUDIO -> AudioPicker() as T
|
AUDIO -> AudioPicker() as T
|
||||||
CONTACT -> ContactPicker() as T
|
CONTACT -> ContactPicker() as T
|
||||||
|
|
|
@ -23,7 +23,7 @@ data class MultiPickerImageType(
|
||||||
override val size: Long,
|
override val size: Long,
|
||||||
override val mimeType: String?,
|
override val mimeType: String?,
|
||||||
override val contentUri: Uri,
|
override val contentUri: Uri,
|
||||||
val width: Int,
|
override val width: Int,
|
||||||
val height: Int,
|
override val height: Int,
|
||||||
val orientation: Int
|
override val orientation: Int
|
||||||
) : MultiPickerBaseType
|
) : MultiPickerBaseMediaType
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -23,8 +23,8 @@ data class MultiPickerVideoType(
|
||||||
override val size: Long,
|
override val size: Long,
|
||||||
override val mimeType: String?,
|
override val mimeType: String?,
|
||||||
override val contentUri: Uri,
|
override val contentUri: Uri,
|
||||||
val width: Int,
|
override val width: Int,
|
||||||
val height: Int,
|
override val height: Int,
|
||||||
val orientation: Int,
|
override val orientation: Int,
|
||||||
val duration: Long
|
val duration: Long
|
||||||
) : MultiPickerBaseType
|
) : MultiPickerBaseMediaType
|
||||||
|
|
|
@ -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<Intent>) {
|
fun selectGallery(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||||
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(
|
callback.onContentAttachmentsReady(
|
||||||
MultiPicker.get(MultiPicker.IMAGE)
|
MultiPicker.get(MultiPicker.MEDIA)
|
||||||
.getSelectedFiles(context, data)
|
.getSelectedFiles(context, data)
|
||||||
.map { it.toContentAttachmentData() }
|
.map { it.toContentAttachmentData() }
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.features.attachments
|
package im.vector.app.features.attachments
|
||||||
|
|
||||||
import im.vector.lib.multipicker.entity.MultiPickerAudioType
|
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.MultiPickerBaseType
|
||||||
import im.vector.lib.multipicker.entity.MultiPickerContactType
|
import im.vector.lib.multipicker.entity.MultiPickerContactType
|
||||||
import im.vector.lib.multipicker.entity.MultiPickerFileType
|
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 {
|
fun MultiPickerImageType.toContentAttachmentData(): ContentAttachmentData {
|
||||||
if (mimeType == null) Timber.w("No mimeType")
|
if (mimeType == null) Timber.w("No mimeType")
|
||||||
return ContentAttachmentData(
|
return ContentAttachmentData(
|
||||||
|
|
|
@ -988,9 +988,9 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val attachmentImageActivityResultLauncher = registerStartForActivityResult {
|
private val attachmentMediaActivityResultLauncher = registerStartForActivityResult {
|
||||||
if (it.resultCode == Activity.RESULT_OK) {
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
attachmentsHelper.onImageResult(it.data)
|
attachmentsHelper.onMediaResult(it.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1991,7 +1991,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
when (type) {
|
when (type) {
|
||||||
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher)
|
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher)
|
||||||
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher)
|
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.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher)
|
||||||
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
|
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
|
||||||
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
|
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
|
||||||
|
|
Loading…
Reference in New Issue