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:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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() {
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user