Introduce LocalFilesHelper to avoid using the Context directly

This commit is contained in:
Benoit Marty 2021-01-08 12:23:02 +01:00
parent 55f5f90c45
commit da33cbedda
8 changed files with 65 additions and 43 deletions

View file

@ -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) }
}
}

View file

@ -25,6 +25,7 @@ import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.module.AppGlideModule
import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.extensions.vectorComponent
import im.vector.app.core.files.LocalFilesHelper
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
import java.io.InputStream import java.io.InputStream
@ -38,6 +39,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, context.vectorComponent().activeSessionHolder())) VectorGlideModelLoaderFactory(LocalFilesHelper(context), context.vectorComponent().activeSessionHolder()))
} }
} }

View file

@ -16,7 +16,6 @@
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
@ -26,8 +25,7 @@ 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.files.LocalFilesHelper
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
@ -36,12 +34,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 context: Context, class VectorGlideModelLoaderFactory(private val localFilesHelper: LocalFilesHelper,
private val activeSessionHolder: ActiveSessionHolder private val activeSessionHolder: ActiveSessionHolder
) : ModelLoaderFactory<ImageContentRenderer.Data, InputStream> { ) : ModelLoaderFactory<ImageContentRenderer.Data, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ImageContentRenderer.Data, InputStream> { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ImageContentRenderer.Data, InputStream> {
return VectorGlideModelLoader(context, activeSessionHolder) return VectorGlideModelLoader(localFilesHelper, activeSessionHolder)
} }
override fun teardown() { override fun teardown() {
@ -49,7 +47,8 @@ class VectorGlideModelLoaderFactory(private val context: Context,
} }
} }
class VectorGlideModelLoader(private val context: Context, private val activeSessionHolder: ActiveSessionHolder) class VectorGlideModelLoader(private val localFilesHelper: LocalFilesHelper,
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
@ -57,11 +56,11 @@ class VectorGlideModelLoader(private val context: Context, private val activeSes
} }
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(context, activeSessionHolder, model, width, height)) return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(localFilesHelper, activeSessionHolder, model, width, height))
} }
} }
class VectorGlideDataFetcher(private val context: Context, class VectorGlideDataFetcher(private val localFilesHelper: LocalFilesHelper,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val data: ImageContentRenderer.Data, private val data: ImageContentRenderer.Data,
private val width: Int, private val width: Int,
@ -102,8 +101,8 @@ class VectorGlideDataFetcher(private val context: Context,
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.url.isLocalFile(context)) { if (localFilesHelper.isLocalFile(data.url)) {
data.url.openInputStream(context)?.use { localFilesHelper.openInputStream(data.url)?.use {
callback.onDataReady(it) callback.onDataReady(it)
} }
return return

View file

@ -17,29 +17,13 @@
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(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,7 +16,6 @@
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
@ -27,12 +26,12 @@ import android.view.View
import dagger.Lazy import dagger.Lazy
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyModel 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.ColorProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.core.utils.containsOnlyEmojis 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.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
@ -95,7 +94,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 localFilesHelper: LocalFilesHelper,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val dimensionConverter: DimensionConverter, private val dimensionConverter: DimensionConverter,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
@ -207,7 +206,7 @@ class MessageItemFactory @Inject constructor(
} ?: "" } ?: ""
return MessageFileItem_() return MessageFileItem_()
.attributes(attributes) .attributes(attributes)
.izLocalFile(fileUrl.isLocalFile(context)) .izLocalFile(localFilesHelper.isLocalFile(fileUrl))
.izDownloaded(session.fileService().isFileInCache( .izDownloaded(session.fileService().isFileInCache(
fileUrl, fileUrl,
messageContent.getFileName(), messageContent.getFileName(),
@ -272,7 +271,7 @@ class MessageItemFactory @Inject constructor(
return MessageFileItem_() return MessageFileItem_()
.attributes(attributes) .attributes(attributes)
.leftGuideline(avatarSizeProvider.leftGuideline) .leftGuideline(avatarSizeProvider.leftGuideline)
.izLocalFile(messageContent.getFileUrl().isLocalFile(context)) .izLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl()))
.izDownloaded(session.fileService().isFileInCache(messageContent)) .izDownloaded(session.fileService().isFileInCache(messageContent))
.mxcUrl(mxcUrl) .mxcUrl(mxcUrl)
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)

View file

@ -25,8 +25,8 @@ import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute 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.files.LocalFilesHelper
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
@ -56,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.url.isLocalFile(holder.view.context), holder.progressLayout) contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, LocalFilesHelper(holder.view.context).isLocalFile(mediaData.url), holder.progressLayout)
} else { } else {
holder.progressLayout.isVisible = false holder.progressLayout.isVisible = false
} }

View file

@ -16,7 +16,6 @@
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
@ -34,12 +33,12 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.ORIENTATION
import com.github.piasy.biv.view.BigImageView import com.github.piasy.biv.view.BigImageView
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.files.LocalFilesHelper
import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideApp
import im.vector.app.core.glide.GlideRequest import im.vector.app.core.glide.GlideRequest
import im.vector.app.core.glide.GlideRequests import im.vector.app.core.glide.GlideRequests
import im.vector.app.core.ui.model.Size import im.vector.app.core.ui.model.Size
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.core.utils.isLocalFile
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.content.ContentUrlResolver
@ -60,7 +59,7 @@ interface AttachmentData : Parcelable {
val allowNonMxcUrls: Boolean val allowNonMxcUrls: Boolean
} }
class ImageContentRenderer @Inject constructor(private val context: Context, class ImageContentRenderer @Inject constructor(private val localFilesHelper: LocalFilesHelper,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val dimensionConverter: DimensionConverter) { private val dimensionConverter: DimensionConverter) {
@ -292,7 +291,7 @@ class ImageContentRenderer @Inject constructor(private val context: Context,
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.url.isLocalFile(context) && data.allowNonMxcUrls }) ?: data.url?.takeIf { localFilesHelper.isLocalFile(data.url) && 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,7 +16,6 @@
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
@ -25,7 +24,7 @@ import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.utils.isLocalFile import im.vector.app.core.files.LocalFilesHelper
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
@ -34,7 +33,7 @@ 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 context: Context, class VideoContentRenderer @Inject constructor(private val localFilesHelper: LocalFilesHelper,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val errorFormatter: ErrorFormatter) { private val errorFormatter: ErrorFormatter) {
@ -65,7 +64,7 @@ class VideoContentRenderer @Inject constructor(private val context: Context,
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(context) && data.allowNonMxcUrls) { } else if (localFilesHelper.isLocalFile(data.url) && data.allowNonMxcUrls) {
thumbnailView.isVisible = false thumbnailView.isVisible = false
loadingView.isVisible = false loadingView.isVisible = false
videoView.isVisible = true videoView.isVisible = true
@ -100,7 +99,7 @@ class VideoContentRenderer @Inject constructor(private val context: Context,
} }
} else { } else {
val resolvedUrl = contentUrlResolver.resolveFullSize(data.url) val resolvedUrl = contentUrlResolver.resolveFullSize(data.url)
?: data.url?.takeIf { data.url.isLocalFile(context) && data.allowNonMxcUrls } ?: data.url?.takeIf { localFilesHelper.isLocalFile(data.url) && data.allowNonMxcUrls }
if (resolvedUrl == null) { if (resolvedUrl == null) {
thumbnailView.isVisible = false thumbnailView.isVisible = false