diff --git a/CHANGES.md b/CHANGES.md index 7d4678fe40..a92a0e6d09 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Bugfix 🐛: - Url previews sometimes attached to wrong message (#2561) - 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) Translations 🗣: - 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..d73c64b087 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 @@ -38,6 +38,6 @@ class MyAppGlideModule : AppGlideModule() { override fun registerComponents(context: Context, glide: Glide, registry: Registry) { registry.append(ImageContentRenderer.Data::class.java, InputStream::class.java, - VectorGlideModelLoaderFactory(context.vectorComponent().activeSessionHolder())) + VectorGlideModelLoaderFactory(context, context.vectorComponent().activeSessionHolder())) } } 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..600ed0317e 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 @@ -25,6 +26,8 @@ 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.utils.isLocalFile +import im.vector.app.core.utils.openInputStream import im.vector.app.features.media.ImageContentRenderer import okhttp3.OkHttpClient import org.matrix.android.sdk.api.MatrixCallback @@ -33,11 +36,12 @@ import java.io.File import java.io.IOException import java.io.InputStream -class VectorGlideModelLoaderFactory(private val activeSessionHolder: ActiveSessionHolder) - : ModelLoaderFactory<ImageContentRenderer.Data, InputStream> { +class VectorGlideModelLoaderFactory(private val context: Context, + private val activeSessionHolder: ActiveSessionHolder +) : ModelLoaderFactory<ImageContentRenderer.Data, InputStream> { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ImageContentRenderer.Data, InputStream> { - return VectorGlideModelLoader(activeSessionHolder) + return VectorGlideModelLoader(context, activeSessionHolder) } override fun teardown() { @@ -45,7 +49,7 @@ class VectorGlideModelLoaderFactory(private val activeSessionHolder: ActiveSessi } } -class VectorGlideModelLoader(private val activeSessionHolder: ActiveSessionHolder) +class VectorGlideModelLoader(private val context: Context, private val activeSessionHolder: ActiveSessionHolder) : ModelLoader<ImageContentRenderer.Data, InputStream> { override fun handles(model: ImageContentRenderer.Data): Boolean { // Always handle @@ -53,11 +57,12 @@ class VectorGlideModelLoader(private val activeSessionHolder: ActiveSessionHolde } override fun buildLoadData(model: ImageContentRenderer.Data, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream>? { - return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(activeSessionHolder, model, width, height)) + return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(context, activeSessionHolder, model, width, height)) } } -class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolder, +class VectorGlideDataFetcher(private val context: Context, + private val activeSessionHolder: ActiveSessionHolder, private val data: ImageContentRenderer.Data, private val width: Int, private val height: Int) @@ -97,9 +102,10 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) { Timber.v("Load data: $data") - if (data.isLocalFile && data.url != null) { - val initialFile = File(data.url) - callback.onDataReady(initialFile.inputStream()) + if (data.url.isLocalFile(context)) { + data.url.openInputStream(context)?.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..2be06c09a5 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 @@ -17,14 +17,28 @@ package im.vector.app.core.utils import android.content.Context +import android.net.Uri +import androidx.documentfile.provider.DocumentFile +import org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber import java.io.File +import java.io.InputStream 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() +internal fun String?.isLocalFile(context: Context): Boolean { + return this?.let { + DocumentFile.fromSingleUri(context, Uri.parse(it))?.exists() + }.orFalse() +} + +internal fun String?.openInputStream(context: Context): InputStream? { + return if (isLocalFile(context)) { + context.contentResolver.openInputStream(Uri.parse(this)) + } else null +} /* ========================================================================================== * 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..3dea83032c 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 @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.factory +import android.content.Context import android.text.SpannableStringBuilder import android.text.Spanned import android.text.TextPaint @@ -94,6 +95,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import javax.inject.Inject class MessageItemFactory @Inject constructor( + private val context: Context, private val colorProvider: ColorProvider, private val dimensionConverter: DimensionConverter, private val timelineMediaSizeProvider: TimelineMediaSizeProvider, @@ -205,7 +207,7 @@ class MessageItemFactory @Inject constructor( } ?: "" return MessageFileItem_() .attributes(attributes) - .izLocalFile(fileUrl.isLocalFile()) + .izLocalFile(fileUrl.isLocalFile(context)) .izDownloaded(session.fileService().isFileInCache( fileUrl, messageContent.getFileName(), @@ -270,7 +272,7 @@ class MessageItemFactory @Inject constructor( return MessageFileItem_() .attributes(attributes) .leftGuideline(avatarSizeProvider.leftGuideline) - .izLocalFile(messageContent.getFileUrl().isLocalFile()) + .izLocalFile(messageContent.getFileUrl().isLocalFile(context)) .izDownloaded(session.fileService().isFileInCache(messageContent)) .mxcUrl(mxcUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) @@ -305,7 +307,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 +346,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..a8d5ed1631 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 @@ -26,6 +26,7 @@ import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.glide.GlideApp +import im.vector.app.core.utils.isLocalFile import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.session.room.send.SendState @@ -55,7 +56,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold super.bind(holder) imageContentRenderer.render(mediaData, mode, holder.imageView) if (!attributes.informationData.sendState.hasFailed()) { - contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, mediaData.isLocalFile, holder.progressLayout) + contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, mediaData.url.isLocalFile(holder.view.context), holder.progressLayout) } else { holder.progressLayout.isVisible = false } diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 75c7912646..83da8aa542 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -16,6 +16,7 @@ package im.vector.app.features.media +import android.content.Context import android.graphics.drawable.Drawable import android.net.Uri import android.os.Parcelable @@ -59,7 +60,8 @@ interface AttachmentData : Parcelable { val allowNonMxcUrls: Boolean } -class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, +class ImageContentRenderer @Inject constructor(private val context: Context, + private val activeSessionHolder: ActiveSessionHolder, private val dimensionConverter: DimensionConverter) { @Parcelize @@ -73,7 +75,6 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: val maxHeight: Int, val width: Int?, val maxWidth: Int, - val isLocalFile: Boolean = url.isLocalFile(), // If true will load non mxc url, be careful to set it only for images sent by you override val allowNonMxcUrls: Boolean = false ) : AttachmentData @@ -291,7 +292,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: private fun resolveUrl(data: Data) = (activeSessionHolder.getActiveSession().contentUrlResolver().resolveFullSize(data.url) - ?: data.url?.takeIf { data.isLocalFile && data.allowNonMxcUrls }) + ?: data.url?.takeIf { data.url.isLocalFile(context) && data.allowNonMxcUrls }) private fun processSize(data: Data, mode: Mode): Size { val maxImageWidth = data.maxWidth diff --git a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt index c771eece8f..46e1437268 100644 --- a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt @@ -16,6 +16,7 @@ package im.vector.app.features.media +import android.content.Context import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView @@ -33,7 +34,8 @@ import java.io.File import java.net.URLEncoder import javax.inject.Inject -class VideoContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, +class VideoContentRenderer @Inject constructor(private val context: Context, + private val activeSessionHolder: ActiveSessionHolder, private val errorFormatter: ErrorFormatter) { @Parcelize @@ -63,7 +65,7 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder: loadingView.isVisible = false errorView.isVisible = true errorView.setText(R.string.unknown_error) - } else if (data.url.isLocalFile() && data.allowNonMxcUrls) { + } else if (data.url.isLocalFile(context) && data.allowNonMxcUrls) { thumbnailView.isVisible = false loadingView.isVisible = false videoView.isVisible = true @@ -98,7 +100,7 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder: } } else { val resolvedUrl = contentUrlResolver.resolveFullSize(data.url) - ?: data.url?.takeIf { data.url.isLocalFile() && data.allowNonMxcUrls } + ?: data.url?.takeIf { data.url.isLocalFile(context) && data.allowNonMxcUrls } if (resolvedUrl == null) { thumbnailView.isVisible = false