App integration to the new multipicker library.
This commit is contained in:
parent
5b875e0571
commit
f7fd23b153
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ class AudioPicker(override val requestCode: Int) : Picker<MultiPickerAudioType>(
|
||||
selectedUriList.add(dataUri)
|
||||
} else {
|
||||
data?.extras?.get(Intent.EXTRA_STREAM)?.let {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
when (it) {
|
||||
is List<*> -> selectedUriList.addAll(it as List<Uri>)
|
||||
else -> selectedUriList.add(it as Uri)
|
||||
|
@ -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
|
||||
|
@ -53,6 +53,7 @@ class FilePicker(override val requestCode: Int) : Picker<MultiPickerFileType>(re
|
||||
selectedUriList.add(dataUri)
|
||||
} else {
|
||||
data?.extras?.get(Intent.EXTRA_STREAM)?.let {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
when (it) {
|
||||
is List<*> -> selectedUriList.addAll(it as List<Uri>)
|
||||
else -> selectedUriList.add(it as Uri)
|
||||
|
@ -57,6 +57,7 @@ class ImagePicker(override val requestCode: Int) : Picker<MultiPickerImageType>(
|
||||
selectedUriList.add(dataUri)
|
||||
} else {
|
||||
data?.extras?.get(Intent.EXTRA_STREAM)?.let {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
when (it) {
|
||||
is List<*> -> selectedUriList.addAll(it as List<Uri>)
|
||||
else -> selectedUriList.add(it as Uri)
|
||||
|
@ -18,5 +18,4 @@ package im.vector.riotx.multipicker
|
||||
|
||||
import androidx.core.content.FileProvider
|
||||
|
||||
class MultiPickerFileProvider : FileProvider() {
|
||||
}
|
||||
class MultiPickerFileProvider : FileProvider()
|
||||
|
@ -54,6 +54,7 @@ class VideoPicker(override val requestCode: Int) : Picker<MultiPickerVideoType>(
|
||||
selectedUriList.add(dataUri)
|
||||
} else {
|
||||
data?.extras?.get(Intent.EXTRA_STREAM)?.let {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
when (it) {
|
||||
is List<*> -> selectedUriList.addAll(it as List<Uri>)
|
||||
else -> selectedUriList.add(it as Uri)
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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<ChosenAudio>?) {
|
||||
if (audios.isNullOrEmpty()) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
} else {
|
||||
val attachments = audios.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onContentAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFilesChosen(files: MutableList<ChosenFile>?) {
|
||||
if (files.isNullOrEmpty()) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
} else {
|
||||
val attachments = files.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onContentAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onImagesChosen(images: MutableList<ChosenImage>?) {
|
||||
if (images.isNullOrEmpty()) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
} else {
|
||||
val attachments = images.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onContentAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVideosChosen(videos: MutableList<ChosenVideo>?) {
|
||||
if (videos.isNullOrEmpty()) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
} else {
|
||||
val attachments = videos.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onContentAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 { _ ->
|
||||
|
@ -33,11 +33,10 @@ abstract class AttachmentPreviewItem<H : AttachmentPreviewItem.Holder> : 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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
))
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user