1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 00:02:48 +02:00

fix permanent loader

This commit is contained in:
Valere
2021-05-31 15:06:01 +02:00
parent e26cbf24dd
commit 259f4de37f
4 changed files with 114 additions and 37 deletions

View File

@@ -27,6 +27,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenScope
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.TimelineMediaStateView
import im.vector.app.core.utils.TextUtils
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
@@ -42,10 +43,11 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess
fun bind(eventId: String,
isLocalFile: Boolean,
progressLayout: ViewGroup) {
progressLayout: ViewGroup,
mediaStateView: TimelineMediaStateView?) {
activeSessionHolder.getSafeActiveSession()?.also { session ->
val uploadStateTracker = session.contentUploadProgressTracker()
val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, messageColorProvider, errorFormatter)
val updateListener = ContentMediaProgressUpdater(progressLayout, mediaStateView, isLocalFile, messageColorProvider, errorFormatter)
updateListeners[eventId] = updateListener
uploadStateTracker.track(eventId, updateListener)
}
@@ -68,6 +70,7 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess
}
private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
private val mediaStateView: TimelineMediaStateView?,
private val isLocalFile: Boolean,
private val messageColorProvider: MessageColorProvider,
private val errorFormatter: ErrorFormatter) : ContentUploadStateTracker.UpdateListener {
@@ -91,14 +94,20 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
private fun handleIdle() {
if (isLocalFile) {
if (mediaStateView != null) {
progressBar.isVisible = false
mediaStateView.render(TimelineMediaStateView.State.Downloading(0, true))
} else {
progressBar.isVisible = true
progressBar.isIndeterminate = true
progressBar.progress = 0
}
progressLayout.isVisible = true
progressBar.isVisible = true
progressBar.isIndeterminate = true
progressBar.progress = 0
progressTextView.text = progressLayout.context.getString(R.string.send_file_step_idle)
progressTextView.setTextColor(messageColorProvider.getMessageTextColor(SendState.UNSENT))
} else {
progressLayout.isVisible = false
mediaStateView?.render(TimelineMediaStateView.State.Downloading(0, true))
}
}
@@ -120,8 +129,13 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
private fun handleCompressingImage() {
progressLayout.visibility = View.VISIBLE
progressBar.isVisible = true
progressBar.isIndeterminate = true
if (mediaStateView != null) {
progressBar.isVisible = false
mediaStateView.render(TimelineMediaStateView.State.Downloading(10, true))
} else {
progressBar.isVisible = true
progressBar.isIndeterminate = true
}
progressTextView.isVisible = true
progressTextView.text = progressLayout.context.getString(R.string.send_file_step_compressing_image)
progressTextView.setTextColor(messageColorProvider.getMessageTextColor(SendState.SENDING))
@@ -131,9 +145,14 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
@SuppressLint("StringFormatMatches")
private fun handleCompressingVideo(state: ContentUploadStateTracker.State.CompressingVideo) {
progressLayout.visibility = View.VISIBLE
progressBar.isVisible = true
progressBar.isIndeterminate = false
progressBar.progress = state.percent.toInt()
if (mediaStateView != null) {
progressBar.isVisible = false
mediaStateView.render(TimelineMediaStateView.State.Downloading(state.percent.toInt(), false))
} else {
progressBar.isVisible = true
progressBar.isIndeterminate = false
progressBar.progress = state.percent.toInt()
}
progressTextView.isVisible = true
// False positive is here...
progressTextView.text = progressLayout.context.getString(R.string.send_file_step_compressing_video, state.percent.toInt())
@@ -141,21 +160,33 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
}
private fun doHandleEncrypting(resId: Int, current: Long, total: Long) {
progressLayout.visibility = View.VISIBLE
val percent = if (total > 0) (100L * (current.toFloat() / total.toFloat())) else 0f
progressBar.isIndeterminate = false
progressBar.progress = percent.toInt()
progressLayout.visibility = View.VISIBLE
if (mediaStateView != null) {
progressBar.isVisible = false
mediaStateView.render(TimelineMediaStateView.State.Downloading(percent.toInt(), false))
} else {
progressBar.isVisible = true
progressBar.isIndeterminate = false
progressBar.progress = percent.toInt()
}
progressTextView.isVisible = true
progressTextView.text = progressLayout.context.getString(resId)
progressTextView.setTextColor(messageColorProvider.getMessageTextColor(SendState.ENCRYPTING))
}
private fun doHandleProgress(resId: Int, current: Long, total: Long) {
progressLayout.visibility = View.VISIBLE
val percent = 100L * (current.toFloat() / total.toFloat())
progressBar.isVisible = true
progressBar.isIndeterminate = false
progressBar.progress = percent.toInt()
progressLayout.visibility = View.VISIBLE
if (mediaStateView != null) {
progressBar.isVisible = false
mediaStateView.render(TimelineMediaStateView.State.Downloading(percent.toInt(), false))
} else {
progressBar.isVisible = true
progressBar.isIndeterminate = false
progressBar.progress = percent.toInt()
}
progressTextView.isVisible = true
progressTextView.text = progressLayout.context.getString(resId,
TextUtils.formatFileSize(progressLayout.context, current, true),
@@ -164,6 +195,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
}
private fun handleFailure(/*state: ContentUploadStateTracker.State.Failure*/) {
mediaStateView?.render(TimelineMediaStateView.State.PermanentError)
progressLayout.visibility = View.VISIBLE
progressBar.isVisible = false
// Do not show the message it's too technical for users, and unfortunate when upload is cancelled

View File

@@ -61,7 +61,7 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
super.bind(holder)
renderSendState(holder.fileLayout, holder.filenameView)
if (!attributes.informationData.sendState.hasFailed()) {
contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout)
contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout, null)
} else {
holder.fileImageView.setImageResource(R.drawable.ic_cross)
holder.progressLayout.isVisible = false

View File

@@ -71,7 +71,8 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
contentUploadStateTrackerBinder.bind(
attributes.informationData.eventId,
LocalFilesHelper(holder.view.context).isLocalFile(mediaData.url),
holder.progressLayout
holder.progressLayout,
holder.mediaStateView
)
} else {
holder.progressLayout.isVisible = false
@@ -86,19 +87,35 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
holder.mediaStateView.render(if (playable && !autoPlayGifs) TimelineMediaStateView.State.ReadyToPlay else TimelineMediaStateView.State.None)
}
holder.mediaStateView.setTag(R.id.messageMediaStateView, mediaData.url)
imageContentRenderer.render(mediaData, mode, holder.imageView, autoPlayGifs) {
// if a server thumbnail was used the download tracker won't be called
mediaData.url?.let { mxcUrl ->
if (mxcUrl == holder.mediaStateView.getTag(R.id.messageMediaStateView)) {
contentDownloadStateTrackerBinder.unbind(mxcUrl)
// mmm annoying but have to post if not the previous contentDownloadStateTrackerBinder.bind call we render
// an IDLE state (i.e a loading wheel...)
holder.mediaStateView.post {
holder.mediaStateView.render(if (it) TimelineMediaStateView.State.None else TimelineMediaStateView.State.PermanentError)
imageContentRenderer.render(mediaData, mode, holder.imageView, autoPlayGifs, rendererCallbacks = object: ImageContentRenderer.ContentRendererCallbacks {
override fun onThumbnailModeFinish(success: Boolean) {
// if a server thumbnail was used the download tracker won't be called
mediaData.url?.let { mxcUrl ->
if (mxcUrl == holder.mediaStateView.getTag(R.id.messageMediaStateView)) {
contentDownloadStateTrackerBinder.unbind(mxcUrl)
// mmm annoying but have to post if not the previous contentDownloadStateTrackerBinder.bind call we render
// an IDLE state (i.e a loading wheel...)
holder.mediaStateView.post {
holder.mediaStateView.render(if (success) TimelineMediaStateView.State.None else TimelineMediaStateView.State.PermanentError)
}
}
}
}
}
override fun onLoadModeFinish(success: Boolean) {
// if a server thumbnail was used the download tracker won't be called
mediaData.url?.let { mxcUrl ->
if (mxcUrl == holder.mediaStateView.getTag(R.id.messageMediaStateView)) {
contentDownloadStateTrackerBinder.unbind(mxcUrl)
// mmm annoying but have to post if not the previous contentDownloadStateTrackerBinder.bind call we render
// an IDLE state (i.e a loading wheel...)
holder.mediaStateView.post {
holder.mediaStateView.render(if (success) TimelineMediaStateView.State.None else TimelineMediaStateView.State.PermanentError)
}
}
}
}
})
holder.mediaStateView.callback = object : TimelineMediaStateView.Callback {
override fun onButtonClicked() {

View File

@@ -119,13 +119,18 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
.into(imageView)
}
interface ContentRendererCallbacks {
fun onThumbnailModeFinish(success: Boolean)
fun onLoadModeFinish(success: Boolean)
}
/**
* In timeline
* All encrypted media will be downloaded by the SDK's FileService, so caller could follow progress using download tracker,
* but for clear media a server thumbnail will be requested and in this case it will be invisible to download tracker that's why there is the
* `mxcThumbnailCallback` callback. Caller can use it to know when media is loaded.
*/
fun render(data: Data, mode: Mode, imageView: ImageView, animate: Boolean = false, mxcThumbnailCallback: ((Boolean) -> Unit)? = null) {
fun render(data: Data, mode: Mode, imageView: ImageView, animate: Boolean = false, rendererCallbacks: ContentRendererCallbacks? = null) {
val size = processSize(data, mode)
// This size will be used by glide for bitmap size
imageView.updateLayoutParams {
@@ -135,7 +140,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
// a11y
imageView.contentDescription = data.filename
createGlideRequest(data, mode, imageView, size, animate, mxcThumbnailCallback)
createGlideRequest(data, mode, imageView, size, animate, rendererCallbacks)
.apply {
if (!animate) {
dontAnimate()
@@ -289,11 +294,11 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
.into(imageView)
}
private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size, autoplay: Boolean = false, mxcThumbnailCallback: ((Boolean) -> Unit)? = null): GlideRequest<Drawable> {
return createGlideRequest(data, mode, GlideApp.with(imageView), size, autoplay, mxcThumbnailCallback)
private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size, autoplay: Boolean = false, rendererCallbacks: ContentRendererCallbacks? = null): GlideRequest<Drawable> {
return createGlideRequest(data, mode, GlideApp.with(imageView), size, autoplay, rendererCallbacks)
}
fun createGlideRequest(data: Data, mode: Mode, glideRequests: GlideRequests, size: Size = processSize(data, mode), autoplay: Boolean = false, mxcThumbnailCallback: ((Boolean) -> Unit)? = null): GlideRequest<Drawable> {
fun createGlideRequest(data: Data, mode: Mode, glideRequests: GlideRequests, size: Size = processSize(data, mode), autoplay: Boolean = false, rendererCallbacks: ContentRendererCallbacks? = null): GlideRequest<Drawable> {
return if (data.elementToDecrypt != null) {
// Encrypted image
glideRequests
@@ -305,6 +310,17 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
}
}
.load(data)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
rendererCallbacks?.onLoadModeFinish(false)
return false
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
rendererCallbacks?.onLoadModeFinish(true)
return false
}
})
.diskCacheStrategy(DiskCacheStrategy.NONE)
} else {
// Clear image
@@ -328,12 +344,12 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
?: data.url.takeIf { it?.startsWith("content://") == true }
).listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
mxcThumbnailCallback?.invoke(false)
rendererCallbacks?.onThumbnailModeFinish(false)
return false
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
mxcThumbnailCallback?.invoke(true)
rendererCallbacks?.onThumbnailModeFinish(true)
return false
}
})
@@ -346,7 +362,19 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
asBitmap()
}
}
.load(data).diskCacheStrategy(DiskCacheStrategy.NONE)
.load(data)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
rendererCallbacks?.onLoadModeFinish(false)
return false
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
rendererCallbacks?.onLoadModeFinish(true)
return false
}
})
.diskCacheStrategy(DiskCacheStrategy.NONE)
}
// glideRequests