diff --git a/CHANGES.md b/CHANGES.md index 8a69aea6fd..ee09fc08cc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,7 +15,9 @@ Bugfix 🐛: - Room Topic not displayed correctly after visiting a link (#2551) - Hiding membership events works the exact opposite (#2603) - Tapping drawer having more than 1 room in notifications gives "malformed link" error (#2605) + - Sent image not displayed when opened immediately after sending (#409) - Initial sync is not retried correctly when there is some network error. (#2632) + Translations 🗣: - diff --git a/vector/src/main/java/im/vector/app/core/files/LocalFilesHelper.kt b/vector/src/main/java/im/vector/app/core/files/LocalFilesHelper.kt new file mode 100644 index 0000000000..d9cf4fc484 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/files/LocalFilesHelper.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 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.app.core.files + +import android.content.Context +import android.net.Uri +import androidx.documentfile.provider.DocumentFile +import org.matrix.android.sdk.api.extensions.orFalse +import java.io.InputStream +import javax.inject.Inject + +class LocalFilesHelper @Inject constructor(private val context: Context) { + fun isLocalFile(fileUri: String?): Boolean { + return fileUri + ?.let { Uri.parse(it) } + ?.let { DocumentFile.fromSingleUri(context, it) } + ?.exists() + .orFalse() + } + + fun openInputStream(fileUri: String?): InputStream? { + return fileUri + ?.takeIf { isLocalFile(it) } + ?.let { Uri.parse(it) } + ?.let { context.contentResolver.openInputStream(it) } + } +} diff --git a/vector/src/main/java/im/vector/app/core/glide/MyAppGlideModule.kt b/vector/src/main/java/im/vector/app/core/glide/MyAppGlideModule.kt index a51410165b..6ded33f823 100644 --- a/vector/src/main/java/im/vector/app/core/glide/MyAppGlideModule.kt +++ b/vector/src/main/java/im/vector/app/core/glide/MyAppGlideModule.kt @@ -24,7 +24,6 @@ import com.bumptech.glide.GlideBuilder import com.bumptech.glide.Registry import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.module.AppGlideModule -import im.vector.app.core.extensions.vectorComponent import im.vector.app.features.media.ImageContentRenderer import java.io.InputStream @@ -36,8 +35,10 @@ class MyAppGlideModule : AppGlideModule() { } override fun registerComponents(context: Context, glide: Glide, registry: Registry) { - registry.append(ImageContentRenderer.Data::class.java, + registry.append( + ImageContentRenderer.Data::class.java, InputStream::class.java, - VectorGlideModelLoaderFactory(context.vectorComponent().activeSessionHolder())) + VectorGlideModelLoaderFactory(context) + ) } } diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt index 9a7cf1eb76..81e81bb78a 100644 --- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt +++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt @@ -16,6 +16,7 @@ package im.vector.app.core.glide +import android.content.Context import com.bumptech.glide.Priority import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.Options @@ -24,7 +25,8 @@ import com.bumptech.glide.load.model.ModelLoader import com.bumptech.glide.load.model.ModelLoaderFactory import com.bumptech.glide.load.model.MultiModelLoaderFactory import com.bumptech.glide.signature.ObjectKey -import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.extensions.vectorComponent +import im.vector.app.core.files.LocalFilesHelper import im.vector.app.features.media.ImageContentRenderer import okhttp3.OkHttpClient import org.matrix.android.sdk.api.MatrixCallback @@ -33,11 +35,10 @@ import java.io.File import java.io.IOException import java.io.InputStream -class VectorGlideModelLoaderFactory(private val activeSessionHolder: ActiveSessionHolder) - : ModelLoaderFactory { +class VectorGlideModelLoaderFactory(private val context: Context) : ModelLoaderFactory { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { - return VectorGlideModelLoader(activeSessionHolder) + return VectorGlideModelLoader(context) } override fun teardown() { @@ -45,7 +46,7 @@ class VectorGlideModelLoaderFactory(private val activeSessionHolder: ActiveSessi } } -class VectorGlideModelLoader(private val activeSessionHolder: ActiveSessionHolder) +class VectorGlideModelLoader(private val context: Context) : ModelLoader { override fun handles(model: ImageContentRenderer.Data): Boolean { // Always handle @@ -53,16 +54,19 @@ class VectorGlideModelLoader(private val activeSessionHolder: ActiveSessionHolde } override fun buildLoadData(model: ImageContentRenderer.Data, width: Int, height: Int, options: Options): ModelLoader.LoadData? { - return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(activeSessionHolder, model, width, height)) + return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(context, model, width, height)) } } -class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolder, +class VectorGlideDataFetcher(context: Context, private val data: ImageContentRenderer.Data, private val width: Int, private val height: Int) : DataFetcher { + private val localFilesHelper = LocalFilesHelper(context) + private val activeSessionHolder = context.vectorComponent().activeSessionHolder() + private val client = activeSessionHolder.getSafeActiveSession()?.getOkHttpClient() ?: OkHttpClient() override fun getDataClass(): Class { @@ -97,9 +101,10 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { Timber.v("Load data: $data") - if (data.isLocalFile && data.url != null) { - val initialFile = File(data.url) - callback.onDataReady(initialFile.inputStream()) + if (localFilesHelper.isLocalFile(data.url)) { + localFilesHelper.openInputStream(data.url)?.use { + callback.onDataReady(it) + } return } // val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() diff --git a/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt b/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt index aa36dd0959..b5ce922487 100644 --- a/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt @@ -24,8 +24,6 @@ import java.util.Locale // Implementation should return true in case of success typealias ActionOnFile = (file: File) -> Boolean -internal fun String?.isLocalFile() = this != null && File(this).exists() - /* ========================================================================================== * Delete * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index a1e041b98f..4f52fcb54c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -26,12 +26,12 @@ import android.view.View import dagger.Lazy import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.containsOnlyEmojis -import im.vector.app.core.utils.isLocalFile import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder @@ -94,6 +94,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import javax.inject.Inject class MessageItemFactory @Inject constructor( + private val localFilesHelper: LocalFilesHelper, private val colorProvider: ColorProvider, private val dimensionConverter: DimensionConverter, private val timelineMediaSizeProvider: TimelineMediaSizeProvider, @@ -205,7 +206,7 @@ class MessageItemFactory @Inject constructor( } ?: "" return MessageFileItem_() .attributes(attributes) - .izLocalFile(fileUrl.isLocalFile()) + .izLocalFile(localFilesHelper.isLocalFile(fileUrl)) .izDownloaded(session.fileService().isFileInCache( fileUrl, messageContent.getFileName(), @@ -270,7 +271,7 @@ class MessageItemFactory @Inject constructor( return MessageFileItem_() .attributes(attributes) .leftGuideline(avatarSizeProvider.leftGuideline) - .izLocalFile(messageContent.getFileUrl().isLocalFile()) + .izLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl())) .izDownloaded(session.fileService().isFileInCache(messageContent)) .mxcUrl(mxcUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) @@ -305,7 +306,8 @@ class MessageItemFactory @Inject constructor( height = messageContent.info?.height, maxHeight = maxHeight, width = messageContent.info?.width, - maxWidth = maxWidth + maxWidth = maxWidth, + allowNonMxcUrls = informationData.sendState.isSending() ) return MessageImageVideoItem_() .attributes(attributes) @@ -343,7 +345,8 @@ class MessageItemFactory @Inject constructor( height = messageContent.videoInfo?.height, maxHeight = maxHeight, width = messageContent.videoInfo?.width, - maxWidth = maxWidth + maxWidth = maxWidth, + allowNonMxcUrls = informationData.sendState.isSending() ) val videoData = VideoContentRenderer.Data( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index 98db0bc9b9..5d14178088 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -25,6 +25,7 @@ import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.glide.GlideApp import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.media.ImageContentRenderer @@ -55,7 +56,7 @@ abstract class MessageImageVideoItem : AbsMessageItem