mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Download file - WIP
This commit is contained in:
parent
12bd85e0a9
commit
a07f8b615e
14 changed files with 193 additions and 62 deletions
|
@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.cache.CacheService
|
||||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
|
import im.vector.matrix.android.api.session.file.FileService
|
||||||
import im.vector.matrix.android.api.session.group.GroupService
|
import im.vector.matrix.android.api.session.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||||
|
@ -46,6 +47,7 @@ interface Session :
|
||||||
CacheService,
|
CacheService,
|
||||||
SignOutService,
|
SignOutService,
|
||||||
FilterService,
|
FilterService,
|
||||||
|
FileService,
|
||||||
PushRuleService,
|
PushRuleService,
|
||||||
PushersService {
|
PushersService {
|
||||||
|
|
||||||
|
|
|
@ -105,13 +105,6 @@ interface CryptoService {
|
||||||
|
|
||||||
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
|
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt a file.
|
|
||||||
* Result will be a decrypted file, stored in the cache folder. id parameter will be used to create a sub folder to avoid name collision.
|
|
||||||
* You can pass the eventId
|
|
||||||
*/
|
|
||||||
fun decryptFile(id: String, filename: String, url: String, elementToDecrypt: ElementToDecrypt, callback: MatrixCallback<File>)
|
|
||||||
|
|
||||||
fun getEncryptionAlgorithm(roomId: String): String?
|
fun getEncryptionAlgorithm(roomId: String): String?
|
||||||
|
|
||||||
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
|
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.file
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines methods to get files.
|
||||||
|
*/
|
||||||
|
interface FileService {
|
||||||
|
|
||||||
|
enum class DownloadMode {
|
||||||
|
/**
|
||||||
|
* Download file in external storage
|
||||||
|
*/
|
||||||
|
TO_EXPORT,
|
||||||
|
/**
|
||||||
|
* Download file in cache
|
||||||
|
*/
|
||||||
|
FOR_INTERNAL_USE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a file.
|
||||||
|
* Result will be a decrypted file, stored in the cache folder. id parameter will be used to create a sub folder to avoid name collision.
|
||||||
|
* You can pass the eventId
|
||||||
|
*/
|
||||||
|
fun downloadFile(
|
||||||
|
downloadMode: DownloadMode,
|
||||||
|
id: String,
|
||||||
|
fileName: String,
|
||||||
|
url: String?,
|
||||||
|
elementToDecrypt: ElementToDecrypt?,
|
||||||
|
callback: MatrixCallback<File>)
|
||||||
|
}
|
|
@ -114,8 +114,6 @@ internal class CryptoManager @Inject constructor(
|
||||||
private val keysBackup: KeysBackup,
|
private val keysBackup: KeysBackup,
|
||||||
//
|
//
|
||||||
private val objectSigner: ObjectSigner,
|
private val objectSigner: ObjectSigner,
|
||||||
// File decryptor
|
|
||||||
private val fileDecryptor: FileDecryptor,
|
|
||||||
//
|
//
|
||||||
private val oneTimeKeysUploader: OneTimeKeysUploader,
|
private val oneTimeKeysUploader: OneTimeKeysUploader,
|
||||||
//
|
//
|
||||||
|
@ -611,10 +609,6 @@ internal class CryptoManager @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decryptFile(id: String, filename: String, url: String, elementToDecrypt: ElementToDecrypt, callback: MatrixCallback<File>) {
|
|
||||||
fileDecryptor.decryptFile(id, filename, url, elementToDecrypt, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt an event
|
* Decrypt an event
|
||||||
*
|
*
|
||||||
|
|
|
@ -14,17 +14,18 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.session
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Environment
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
|
import im.vector.matrix.android.api.session.file.FileService
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.md5
|
import im.vector.matrix.android.internal.util.md5
|
||||||
import im.vector.matrix.android.internal.util.writeToFile
|
import im.vector.matrix.android.internal.util.writeToFile
|
||||||
|
@ -38,33 +39,29 @@ import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
internal class DefaultFileService @Inject constructor(private val context: Context,
|
||||||
internal class FileDecryptor @Inject constructor(private val context: Context,
|
private val sessionParams: SessionParams,
|
||||||
private val sessionParams: SessionParams,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
|
|
||||||
|
|
||||||
val okHttpClient = OkHttpClient()
|
val okHttpClient = OkHttpClient()
|
||||||
|
|
||||||
fun decryptFile(id: String,
|
/**
|
||||||
fileName: String,
|
* Download file in the cache folder, and eventually decrypt it
|
||||||
url: String,
|
* TODO implement clear file, to delete "MF"
|
||||||
elementToDecrypt: ElementToDecrypt,
|
*/
|
||||||
callback: MatrixCallback<File>) {
|
override fun downloadFile(downloadMode: FileService.DownloadMode,
|
||||||
|
id: String,
|
||||||
|
fileName: String,
|
||||||
|
url: String?,
|
||||||
|
elementToDecrypt: ElementToDecrypt?,
|
||||||
|
callback: MatrixCallback<File>) {
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
withContext(coroutineDispatchers.io) {
|
withContext(coroutineDispatchers.io) {
|
||||||
Try {
|
Try {
|
||||||
// Create dir tree:
|
val folder = getFolder(downloadMode, id)
|
||||||
// <cache>/DF/<md5(userId)>/<md5(id)>/
|
|
||||||
val tmpFolderRoot = File(context.cacheDir, "DF")
|
|
||||||
val tmpFolderUser = File(tmpFolderRoot, sessionParams.credentials.userId.md5())
|
|
||||||
val tmpFolder = File(tmpFolderUser, id.md5())
|
|
||||||
|
|
||||||
if (!tmpFolder.exists()) {
|
File(folder, fileName)
|
||||||
tmpFolder.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
File(tmpFolder, fileName)
|
|
||||||
}.map { destFile ->
|
}.map { destFile ->
|
||||||
if (!destFile.exists()) {
|
if (!destFile.exists()) {
|
||||||
Try {
|
Try {
|
||||||
|
@ -79,11 +76,16 @@ internal class FileDecryptor @Inject constructor(private val context: Context,
|
||||||
val response = okHttpClient.newCall(request).execute()
|
val response = okHttpClient.newCall(request).execute()
|
||||||
val inputStream = response.body()?.byteStream()
|
val inputStream = response.body()?.byteStream()
|
||||||
Timber.v("Response size ${response.body()?.contentLength()} - Stream available: ${inputStream?.available()}")
|
Timber.v("Response size ${response.body()?.contentLength()} - Stream available: ${inputStream?.available()}")
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful
|
||||||
|
|| inputStream == null) {
|
||||||
throw IOException()
|
throw IOException()
|
||||||
}
|
}
|
||||||
|
|
||||||
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) ?: throw IllegalStateException("Decryption error")
|
if (elementToDecrypt != null) {
|
||||||
|
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) ?: throw IllegalStateException("Decryption error")
|
||||||
|
} else {
|
||||||
|
inputStream
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.map { inputStream ->
|
.map { inputStream ->
|
||||||
writeToFile(inputStream, destFile)
|
writeToFile(inputStream, destFile)
|
||||||
|
@ -96,4 +98,24 @@ internal class FileDecryptor @Inject constructor(private val context: Context,
|
||||||
.foldToCallback(callback)
|
.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getFolder(downloadMode: FileService.DownloadMode, id: String): File {
|
||||||
|
return when (downloadMode) {
|
||||||
|
FileService.DownloadMode.FOR_INTERNAL_USE -> {
|
||||||
|
// Create dir tree (MF stands for Matrix File):
|
||||||
|
// <cache>/MF/<md5(userId)>/<md5(id)>/
|
||||||
|
val tmpFolderRoot = File(context.cacheDir, "MF")
|
||||||
|
val tmpFolderUser = File(tmpFolderRoot, sessionParams.credentials.userId.md5())
|
||||||
|
File(tmpFolderUser, id.md5())
|
||||||
|
}
|
||||||
|
FileService.DownloadMode.TO_EXPORT -> {
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.also { folder ->
|
||||||
|
if (!folder.exists()) {
|
||||||
|
folder.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -21,7 +21,6 @@ import android.os.Looper
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.pushrules.PushRuleService
|
import im.vector.matrix.android.api.pushrules.PushRuleService
|
||||||
|
@ -30,6 +29,7 @@ import im.vector.matrix.android.api.session.cache.CacheService
|
||||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
|
import im.vector.matrix.android.api.session.file.FileService
|
||||||
import im.vector.matrix.android.api.session.group.GroupService
|
import im.vector.matrix.android.api.session.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||||
|
@ -61,20 +61,22 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
||||||
private val pushRuleService: PushRuleService,
|
private val pushRuleService: PushRuleService,
|
||||||
private val pushersService: PushersService,
|
private val pushersService: PushersService,
|
||||||
private val cryptoService: CryptoManager,
|
private val cryptoService: CryptoManager,
|
||||||
|
private val fileService: FileService,
|
||||||
private val syncThread: SyncThread,
|
private val syncThread: SyncThread,
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
private val contentUploadProgressTracker: ContentUploadStateTracker)
|
private val contentUploadProgressTracker: ContentUploadStateTracker)
|
||||||
: Session,
|
: Session,
|
||||||
RoomService by roomService,
|
RoomService by roomService,
|
||||||
RoomDirectoryService by roomDirectoryService,
|
RoomDirectoryService by roomDirectoryService,
|
||||||
GroupService by groupService,
|
GroupService by groupService,
|
||||||
UserService by userService,
|
UserService by userService,
|
||||||
CryptoService by cryptoService,
|
CryptoService by cryptoService,
|
||||||
CacheService by cacheService,
|
CacheService by cacheService,
|
||||||
SignOutService by signOutService,
|
SignOutService by signOutService,
|
||||||
FilterService by filterService,
|
FilterService by filterService,
|
||||||
PushRuleService by pushRuleService,
|
FileService by fileService,
|
||||||
PushersService by pushersService {
|
PushRuleService by pushRuleService,
|
||||||
|
PushersService by pushersService {
|
||||||
|
|
||||||
private var isOpen = false
|
private var isOpen = false
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import im.vector.matrix.android.api.session.file.FileService
|
||||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
import im.vector.matrix.android.api.session.room.members.MembershipService
|
||||||
|
@ -27,6 +28,7 @@ import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
import im.vector.matrix.android.api.session.room.send.SendService
|
import im.vector.matrix.android.api.session.room.send.SendService
|
||||||
import im.vector.matrix.android.api.session.room.state.StateService
|
import im.vector.matrix.android.api.session.room.state.StateService
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||||
|
import im.vector.matrix.android.internal.session.DefaultFileService
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask
|
import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask
|
||||||
|
@ -138,4 +140,6 @@ internal abstract class RoomModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
|
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindFileService(fileService: DefaultFileService): FileService
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail
|
||||||
|
|
||||||
import com.jaiselrahman.filepicker.model.MediaFile
|
import com.jaiselrahman.filepicker.model.MediaFile
|
||||||
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ sealed class RoomDetailActions {
|
||||||
data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailActions()
|
data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailActions()
|
||||||
data class ShowEditHistoryAction(val event: String, val editAggregatedSummary: EditAggregatedSummary) : RoomDetailActions()
|
data class ShowEditHistoryAction(val event: String, val editAggregatedSummary: EditAggregatedSummary) : RoomDetailActions()
|
||||||
data class NavigateToEvent(val eventId: String, val position: Int?) : RoomDetailActions()
|
data class NavigateToEvent(val eventId: String, val position: Int?) : RoomDetailActions()
|
||||||
|
data class DownloadFile(val eventId: String, val messageFileContent: MessageFileContent) : RoomDetailActions()
|
||||||
object AcceptInvite : RoomDetailActions()
|
object AcceptInvite : RoomDetailActions()
|
||||||
object RejectInvite : RoomDetailActions()
|
object RejectInvite : RoomDetailActions()
|
||||||
|
|
||||||
|
|
|
@ -63,19 +63,14 @@ import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.core.dialogs.DialogListItem
|
import im.vector.riotx.core.dialogs.DialogListItem
|
||||||
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.core.extensions.hideKeyboard
|
import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
import im.vector.riotx.core.extensions.observeEvent
|
import im.vector.riotx.core.extensions.observeEvent
|
||||||
import im.vector.riotx.core.extensions.setTextOrHide
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.core.files.addEntryToDownloadManager
|
||||||
import im.vector.riotx.core.glide.GlideApp
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
import im.vector.riotx.core.utils.*
|
||||||
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
|
||||||
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA
|
|
||||||
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA
|
|
||||||
import im.vector.riotx.core.utils.checkPermissions
|
|
||||||
import im.vector.riotx.core.utils.copyToClipboard
|
|
||||||
import im.vector.riotx.core.utils.openCamera
|
|
||||||
import im.vector.riotx.core.utils.shareMedia
|
|
||||||
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
|
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
|
||||||
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
||||||
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
||||||
|
@ -180,6 +175,7 @@ class RoomDetailFragment :
|
||||||
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
||||||
@Inject lateinit var roomDetailViewModelFactory: RoomDetailViewModel.Factory
|
@Inject lateinit var roomDetailViewModelFactory: RoomDetailViewModel.Factory
|
||||||
@Inject lateinit var textComposerViewModelFactory: TextComposerViewModel.Factory
|
@Inject lateinit var textComposerViewModelFactory: TextComposerViewModel.Factory
|
||||||
|
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||||
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
|
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
|
||||||
private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback
|
private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback
|
||||||
|
|
||||||
|
@ -220,6 +216,15 @@ class RoomDetailFragment :
|
||||||
scrollOnHighlightedEventCallback.scheduleScrollTo(it)
|
scrollOnHighlightedEventCallback.scheduleScrollTo(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
roomDetailViewModel.downloadedFileEvent.observeEvent(this) { downloadFileState ->
|
||||||
|
if (downloadFileState.throwable != null) {
|
||||||
|
requireActivity().toast(errorFormatter.toHumanReadable(downloadFileState.throwable))
|
||||||
|
} else if (downloadFileState.file != null) {
|
||||||
|
requireActivity().toast(getString(R.string.downloaded_file, downloadFileState.file.path))
|
||||||
|
addEntryToDownloadManager(requireContext(), downloadFileState.file, downloadFileState.mimeType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
roomDetailViewModel.selectSubscribe(
|
roomDetailViewModel.selectSubscribe(
|
||||||
RoomDetailViewState::sendMode,
|
RoomDetailViewState::sendMode,
|
||||||
RoomDetailViewState::selectedEvent,
|
RoomDetailViewState::selectedEvent,
|
||||||
|
@ -615,8 +620,8 @@ class RoomDetailFragment :
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFileMessageClicked(messageFileContent: MessageFileContent) {
|
override fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent) {
|
||||||
vectorBaseActivity.notImplemented("open file")
|
roomDetailViewModel.process(RoomDetailActions.DownloadFile(eventId, messageFileContent))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) {
|
override fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.riotx.features.home.room.detail
|
package im.vector.riotx.features.home.room.detail
|
||||||
|
|
||||||
|
import android.content.ClipDescription
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
@ -31,10 +32,12 @@ import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.file.FileService
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.intent.getFilenameFromUri
|
import im.vector.riotx.core.intent.getFilenameFromUri
|
||||||
|
@ -50,6 +53,7 @@ import io.reactivex.rxkotlin.subscribeBy
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
import org.commonmark.renderer.html.HtmlRenderer
|
import org.commonmark.renderer.html.HtmlRenderer
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -113,6 +117,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
is RoomDetailActions.EnterEditMode -> handleEditAction(action)
|
is RoomDetailActions.EnterEditMode -> handleEditAction(action)
|
||||||
is RoomDetailActions.EnterQuoteMode -> handleQuoteAction(action)
|
is RoomDetailActions.EnterQuoteMode -> handleQuoteAction(action)
|
||||||
is RoomDetailActions.EnterReplyMode -> handleReplyAction(action)
|
is RoomDetailActions.EnterReplyMode -> handleReplyAction(action)
|
||||||
|
is RoomDetailActions.DownloadFile -> handleDownloadFile(action)
|
||||||
is RoomDetailActions.NavigateToEvent -> handleNavigateToEvent(action)
|
is RoomDetailActions.NavigateToEvent -> handleNavigateToEvent(action)
|
||||||
else -> Timber.e("Unhandled Action: $action")
|
else -> Timber.e("Unhandled Action: $action")
|
||||||
}
|
}
|
||||||
|
@ -149,6 +154,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
val navigateToEvent: LiveData<LiveEvent<String>>
|
val navigateToEvent: LiveData<LiveEvent<String>>
|
||||||
get() = _navigateToEvent
|
get() = _navigateToEvent
|
||||||
|
|
||||||
|
private val _downloadedFileEvent = MutableLiveData<LiveEvent<DownloadFileState>>()
|
||||||
|
val downloadedFileEvent: LiveData<LiveEvent<DownloadFileState>>
|
||||||
|
get() = _downloadedFileEvent
|
||||||
|
|
||||||
|
|
||||||
// PRIVATE METHODS *****************************************************************************
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
|
@ -433,6 +442,46 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class DownloadFileState(
|
||||||
|
val mimeType: String,
|
||||||
|
val file: File?,
|
||||||
|
val throwable: Throwable?
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun handleDownloadFile(action: RoomDetailActions.DownloadFile) {
|
||||||
|
session.downloadFile(
|
||||||
|
FileService.DownloadMode.TO_EXPORT,
|
||||||
|
action.eventId,
|
||||||
|
action.messageFileContent.filename ?: "file.dat",
|
||||||
|
action.messageFileContent.url,
|
||||||
|
action.messageFileContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
|
object : MatrixCallback<File> {
|
||||||
|
override fun onSuccess(data: File) {
|
||||||
|
_downloadedFileEvent.postValue(LiveEvent(DownloadFileState(
|
||||||
|
// Mimetype default to plain text, should not be used
|
||||||
|
action.messageFileContent.encryptedFileInfo?.mimetype
|
||||||
|
?: action.messageFileContent.info?.mimeType
|
||||||
|
?: ClipDescription.MIMETYPE_TEXT_PLAIN,
|
||||||
|
data,
|
||||||
|
null
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
_downloadedFileEvent.postValue(LiveEvent(DownloadFileState(
|
||||||
|
// Mimetype default to plain text, should not be used
|
||||||
|
action.messageFileContent.encryptedFileInfo?.mimetype
|
||||||
|
?: action.messageFileContent.info?.mimeType
|
||||||
|
?: ClipDescription.MIMETYPE_TEXT_PLAIN,
|
||||||
|
null,
|
||||||
|
failure
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun handleNavigateToEvent(action: RoomDetailActions.NavigateToEvent) {
|
private fun handleNavigateToEvent(action: RoomDetailActions.NavigateToEvent) {
|
||||||
val targetEventId = action.eventId
|
val targetEventId = action.eventId
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
||||||
fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View)
|
fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View)
|
||||||
fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View)
|
fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View)
|
||||||
fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
|
fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
|
||||||
fun onFileMessageClicked(messageFileContent: MessageFileContent)
|
fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent)
|
||||||
fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
|
fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
|
||||||
fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?)
|
fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,7 +162,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
.clickListener(
|
.clickListener(
|
||||||
DebouncedClickListener(View.OnClickListener { _ ->
|
DebouncedClickListener(View.OnClickListener { _ ->
|
||||||
callback?.onFileMessageClicked(messageContent)
|
callback?.onFileMessageClicked(informationData.eventId, messageContent)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.widget.TextView
|
||||||
import android.widget.VideoView
|
import android.widget.VideoView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.file.FileService
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
|
@ -64,7 +65,9 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||||
loadingView.isVisible = true
|
loadingView.isVisible = true
|
||||||
|
|
||||||
activeSessionHolder.getActiveSession()
|
activeSessionHolder.getActiveSession()
|
||||||
.decryptFile(data.eventId,
|
.downloadFile(
|
||||||
|
FileService.DownloadMode.FOR_INTERNAL_USE,
|
||||||
|
data.eventId,
|
||||||
data.filename,
|
data.filename,
|
||||||
data.url,
|
data.url,
|
||||||
data.elementToDecrypt,
|
data.elementToDecrypt,
|
||||||
|
|
|
@ -11,5 +11,8 @@
|
||||||
<string name="send_file_step_encrypting_file">Encrypting file…</string>
|
<string name="send_file_step_encrypting_file">Encrypting file…</string>
|
||||||
<string name="send_file_step_sending_file">Sending file (%1$s / %2$s)</string>
|
<string name="send_file_step_sending_file">Sending file (%1$s / %2$s)</string>
|
||||||
|
|
||||||
|
<string name="downloading_file">Downloading file %1$s…</string>
|
||||||
|
<string name="downloaded_file">File %1$s has been downloaded!</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue