diff --git a/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt index 37f6df1379..faf7524b8b 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt @@ -29,6 +29,8 @@ import androidx.core.content.FileProvider import androidx.fragment.app.Fragment import im.vector.riotx.BuildConfig import im.vector.riotx.R +import okio.buffer +import okio.sink import timber.log.Timber import java.io.File import java.text.SimpleDateFormat @@ -258,6 +260,58 @@ fun shareMedia(context: Context, file: File, mediaMimeType: String?) { } } +fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String?) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val externalContentUri: Uri + val values = ContentValues() + when { + mediaMimeType?.startsWith("image/") == true -> { + externalContentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + values.put(MediaStore.Images.Media.TITLE, title) + values.put(MediaStore.Images.Media.DISPLAY_NAME, title) + values.put(MediaStore.Images.Media.MIME_TYPE, mediaMimeType) + values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis()) + values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()) + } + mediaMimeType?.startsWith("video/") == true -> { + externalContentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + values.put(MediaStore.Video.Media.TITLE, title) + values.put(MediaStore.Video.Media.DISPLAY_NAME, title) + values.put(MediaStore.Video.Media.MIME_TYPE, mediaMimeType) + values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis()) + values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis()) + } + mediaMimeType?.startsWith("audio/") == true -> { + externalContentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + values.put(MediaStore.Audio.Media.TITLE, title) + values.put(MediaStore.Audio.Media.DISPLAY_NAME, title) + values.put(MediaStore.Audio.Media.MIME_TYPE, mediaMimeType) + values.put(MediaStore.Audio.Media.DATE_ADDED, System.currentTimeMillis()) + values.put(MediaStore.Audio.Media.DATE_TAKEN, System.currentTimeMillis()) + } + else -> { + externalContentUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI + values.put(MediaStore.Downloads.TITLE, title) + values.put(MediaStore.Downloads.DISPLAY_NAME, title) + values.put(MediaStore.Downloads.MIME_TYPE, mediaMimeType) + values.put(MediaStore.Downloads.DATE_ADDED, System.currentTimeMillis()) + values.put(MediaStore.Downloads.DATE_TAKEN, System.currentTimeMillis()) + } + } + context.contentResolver.insert(externalContentUri, values)?.let { uri -> + context.contentResolver.openOutputStream(uri)?.use { outputStream -> + outputStream.sink().buffer().write(file.inputStream().use { it.readBytes() }) + } + } + } else { + @Suppress("DEPRECATION") + Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent -> + mediaScanIntent.data = Uri.fromFile(file) + context.sendBroadcast(mediaScanIntent) + } + } +} + /** * Open the play store to the provided application Id, default to this app */ 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 86f4847ff7..6ca5a3ff27 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 @@ -115,6 +115,7 @@ import im.vector.riotx.core.utils.createUIHandler import im.vector.riotx.core.utils.getColorFromUserId import im.vector.riotx.core.utils.jsonViewerStyler import im.vector.riotx.core.utils.openUrlInExternalBrowser +import im.vector.riotx.core.utils.saveMedia import im.vector.riotx.core.utils.shareMedia import im.vector.riotx.core.utils.toast import im.vector.riotx.features.attachments.AttachmentTypeSelectorView @@ -1153,6 +1154,28 @@ class RoomDetailFragment @Inject constructor( ) } + private fun onSaveActionClicked(action: EventSharedAction.Save) { + session.downloadFile( + FileService.DownloadMode.FOR_EXTERNAL_SHARE, + action.eventId, + action.messageContent.body, + action.messageContent.getFileUrl(), + action.messageContent.encryptedFileInfo?.toElementToDecrypt(), + object : MatrixCallback { + override fun onSuccess(data: File) { + if (isAdded) { + saveMedia( + context = requireContext(), + file = data, + title = action.messageContent.body, + mediaMimeType = getMimeTypeFromUri(requireContext(), data.toUri()) + ) + } + } + } + ) + } + private fun handleActions(action: EventSharedAction) { when (action) { is EventSharedAction.OpenUserProfile -> { @@ -1176,6 +1199,9 @@ class RoomDetailFragment @Inject constructor( is EventSharedAction.Share -> { onShareActionClicked(action) } + is EventSharedAction.Save -> { + onSaveActionClicked(action) + } is EventSharedAction.ViewEditHistory -> { onEditedDecorationClicked(action.messageInformationData) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt index e38c055d52..f362b7939c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt @@ -50,6 +50,9 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, data class Share(val eventId: String, val messageContent: MessageWithAttachmentContent) : EventSharedAction(R.string.share, R.drawable.ic_share) + data class Save(val eventId: String, val messageContent: MessageWithAttachmentContent) : + EventSharedAction(R.string.save, R.drawable.ic_material_save) + data class Resend(val eventId: String) : EventSharedAction(R.string.global_retry, R.drawable.ic_refresh_cw) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 5212e1469d..38db5440d6 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -30,11 +30,11 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.isTextMessage import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent -import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent import im.vector.matrix.android.api.session.room.model.message.MessageFormat import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent +import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent @@ -290,6 +290,10 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted add(EventSharedAction.Share(timelineEvent.eventId, messageContent)) } + if (canSave(msgType) && messageContent is MessageWithAttachmentContent) { + add(EventSharedAction.Save(timelineEvent.eventId, messageContent)) + } + if (timelineEvent.root.sendState == SendState.SENT) { // TODO Can be redacted @@ -413,4 +417,14 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted else -> false } } + + private fun canSave(msgType: String?): Boolean { + return when (msgType) { + MessageType.MSGTYPE_IMAGE, + MessageType.MSGTYPE_AUDIO, + MessageType.MSGTYPE_VIDEO, + MessageType.MSGTYPE_FILE -> true + else -> false + } + } } diff --git a/vector/src/main/res/drawable/ic_material_save.xml b/vector/src/main/res/drawable/ic_material_save.xml new file mode 100644 index 0000000000..e299376f3a --- /dev/null +++ b/vector/src/main/res/drawable/ic_material_save.xml @@ -0,0 +1,5 @@ + + +