Use ContentResolver and DocumentFile instead of legacy File.

Fixes #409.
This commit is contained in:
Onuray Sahin 2021-01-07 19:32:04 +03:00
parent 7249c7d25a
commit 55f5f90c45
8 changed files with 51 additions and 22 deletions

View File

@ -13,6 +13,7 @@ Bugfix 🐛:
- Url previews sometimes attached to wrong message (#2561) - Url previews sometimes attached to wrong message (#2561)
- Hiding membership events works the exact opposite (#2603) - Hiding membership events works the exact opposite (#2603)
- Tapping drawer having more than 1 room in notifications gives "malformed link" error (#2605) - 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 🗣: Translations 🗣:
- -

View File

@ -38,6 +38,6 @@ class MyAppGlideModule : AppGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) { override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
registry.append(ImageContentRenderer.Data::class.java, registry.append(ImageContentRenderer.Data::class.java,
InputStream::class.java, InputStream::class.java,
VectorGlideModelLoaderFactory(context.vectorComponent().activeSessionHolder())) VectorGlideModelLoaderFactory(context, context.vectorComponent().activeSessionHolder()))
} }
} }

View File

@ -16,6 +16,7 @@
package im.vector.app.core.glide package im.vector.app.core.glide
import android.content.Context
import com.bumptech.glide.Priority import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Options 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.load.model.MultiModelLoaderFactory
import com.bumptech.glide.signature.ObjectKey import com.bumptech.glide.signature.ObjectKey
import im.vector.app.core.di.ActiveSessionHolder 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 im.vector.app.features.media.ImageContentRenderer
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
@ -33,11 +36,12 @@ import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
class VectorGlideModelLoaderFactory(private val activeSessionHolder: ActiveSessionHolder) class VectorGlideModelLoaderFactory(private val context: Context,
: ModelLoaderFactory<ImageContentRenderer.Data, InputStream> { private val activeSessionHolder: ActiveSessionHolder
) : ModelLoaderFactory<ImageContentRenderer.Data, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ImageContentRenderer.Data, InputStream> { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ImageContentRenderer.Data, InputStream> {
return VectorGlideModelLoader(activeSessionHolder) return VectorGlideModelLoader(context, activeSessionHolder)
} }
override fun teardown() { 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> { : ModelLoader<ImageContentRenderer.Data, InputStream> {
override fun handles(model: ImageContentRenderer.Data): Boolean { override fun handles(model: ImageContentRenderer.Data): Boolean {
// Always handle // 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>? { 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 data: ImageContentRenderer.Data,
private val width: Int, private val width: Int,
private val height: 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>) { override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
Timber.v("Load data: $data") Timber.v("Load data: $data")
if (data.isLocalFile && data.url != null) { if (data.url.isLocalFile(context)) {
val initialFile = File(data.url) data.url.openInputStream(context)?.use {
callback.onDataReady(initialFile.inputStream()) callback.onDataReady(it)
}
return return
} }
// val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() // val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()

View File

@ -17,14 +17,28 @@
package im.vector.app.core.utils package im.vector.app.core.utils
import android.content.Context 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 timber.log.Timber
import java.io.File import java.io.File
import java.io.InputStream
import java.util.Locale import java.util.Locale
// Implementation should return true in case of success // Implementation should return true in case of success
typealias ActionOnFile = (file: File) -> Boolean 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 * Delete

View File

@ -16,6 +16,7 @@
package im.vector.app.features.home.room.detail.timeline.factory package im.vector.app.features.home.room.detail.timeline.factory
import android.content.Context
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.Spanned import android.text.Spanned
import android.text.TextPaint import android.text.TextPaint
@ -94,6 +95,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import javax.inject.Inject import javax.inject.Inject
class MessageItemFactory @Inject constructor( class MessageItemFactory @Inject constructor(
private val context: Context,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val dimensionConverter: DimensionConverter, private val dimensionConverter: DimensionConverter,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
@ -205,7 +207,7 @@ class MessageItemFactory @Inject constructor(
} ?: "" } ?: ""
return MessageFileItem_() return MessageFileItem_()
.attributes(attributes) .attributes(attributes)
.izLocalFile(fileUrl.isLocalFile()) .izLocalFile(fileUrl.isLocalFile(context))
.izDownloaded(session.fileService().isFileInCache( .izDownloaded(session.fileService().isFileInCache(
fileUrl, fileUrl,
messageContent.getFileName(), messageContent.getFileName(),
@ -270,7 +272,7 @@ class MessageItemFactory @Inject constructor(
return MessageFileItem_() return MessageFileItem_()
.attributes(attributes) .attributes(attributes)
.leftGuideline(avatarSizeProvider.leftGuideline) .leftGuideline(avatarSizeProvider.leftGuideline)
.izLocalFile(messageContent.getFileUrl().isLocalFile()) .izLocalFile(messageContent.getFileUrl().isLocalFile(context))
.izDownloaded(session.fileService().isFileInCache(messageContent)) .izDownloaded(session.fileService().isFileInCache(messageContent))
.mxcUrl(mxcUrl) .mxcUrl(mxcUrl)
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
@ -305,7 +307,8 @@ class MessageItemFactory @Inject constructor(
height = messageContent.info?.height, height = messageContent.info?.height,
maxHeight = maxHeight, maxHeight = maxHeight,
width = messageContent.info?.width, width = messageContent.info?.width,
maxWidth = maxWidth maxWidth = maxWidth,
allowNonMxcUrls = informationData.sendState.isSending()
) )
return MessageImageVideoItem_() return MessageImageVideoItem_()
.attributes(attributes) .attributes(attributes)
@ -343,7 +346,8 @@ class MessageItemFactory @Inject constructor(
height = messageContent.videoInfo?.height, height = messageContent.videoInfo?.height,
maxHeight = maxHeight, maxHeight = maxHeight,
width = messageContent.videoInfo?.width, width = messageContent.videoInfo?.width,
maxWidth = maxWidth maxWidth = maxWidth,
allowNonMxcUrls = informationData.sendState.isSending()
) )
val videoData = VideoContentRenderer.Data( val videoData = VideoContentRenderer.Data(

View File

@ -26,6 +26,7 @@ import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.glide.GlideApp 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.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
@ -55,7 +56,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
super.bind(holder) super.bind(holder)
imageContentRenderer.render(mediaData, mode, holder.imageView) imageContentRenderer.render(mediaData, mode, holder.imageView)
if (!attributes.informationData.sendState.hasFailed()) { 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 { } else {
holder.progressLayout.isVisible = false holder.progressLayout.isVisible = false
} }

View File

@ -16,6 +16,7 @@
package im.vector.app.features.media package im.vector.app.features.media
import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
@ -59,7 +60,8 @@ interface AttachmentData : Parcelable {
val allowNonMxcUrls: Boolean 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) { private val dimensionConverter: DimensionConverter) {
@Parcelize @Parcelize
@ -73,7 +75,6 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
val maxHeight: Int, val maxHeight: Int,
val width: Int?, val width: Int?,
val maxWidth: 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 // If true will load non mxc url, be careful to set it only for images sent by you
override val allowNonMxcUrls: Boolean = false override val allowNonMxcUrls: Boolean = false
) : AttachmentData ) : AttachmentData
@ -291,7 +292,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
private fun resolveUrl(data: Data) = private fun resolveUrl(data: Data) =
(activeSessionHolder.getActiveSession().contentUrlResolver().resolveFullSize(data.url) (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 { private fun processSize(data: Data, mode: Mode): Size {
val maxImageWidth = data.maxWidth val maxImageWidth = data.maxWidth

View File

@ -16,6 +16,7 @@
package im.vector.app.features.media package im.vector.app.features.media
import android.content.Context
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
@ -33,7 +34,8 @@ import java.io.File
import java.net.URLEncoder import java.net.URLEncoder
import javax.inject.Inject 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) { private val errorFormatter: ErrorFormatter) {
@Parcelize @Parcelize
@ -63,7 +65,7 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder:
loadingView.isVisible = false loadingView.isVisible = false
errorView.isVisible = true errorView.isVisible = true
errorView.setText(R.string.unknown_error) errorView.setText(R.string.unknown_error)
} else if (data.url.isLocalFile() && data.allowNonMxcUrls) { } else if (data.url.isLocalFile(context) && data.allowNonMxcUrls) {
thumbnailView.isVisible = false thumbnailView.isVisible = false
loadingView.isVisible = false loadingView.isVisible = false
videoView.isVisible = true videoView.isVisible = true
@ -98,7 +100,7 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder:
} }
} else { } else {
val resolvedUrl = contentUrlResolver.resolveFullSize(data.url) 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) { if (resolvedUrl == null) {
thumbnailView.isVisible = false thumbnailView.isVisible = false