Send file: improve UI feedback

This commit is contained in:
Benoit Marty 2019-07-08 14:31:19 +02:00
parent d24ce27903
commit 8a5612be3d
5 changed files with 90 additions and 22 deletions

View File

@ -28,10 +28,11 @@ interface ContentUploadStateTracker {
sealed class State { sealed class State {
object Idle : State() object Idle : State()
object EncryptingThumbnail : State()
data class ProgressThumbnailData(val current: Long, val total: Long) : State()
object Encrypting : State()
data class ProgressData(val current: Long, val total: Long) : State() data class ProgressData(val current: Long, val total: Long) : State()
object Success : State() object Success : State()
object Failure : State() data class Failure(val throwable: Throwable) : State()
} }
} }

View File

@ -43,8 +43,8 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
} }
} }
internal fun setFailure(key: String) { internal fun setFailure(key: String, throwable: Throwable) {
val failure = ContentUploadStateTracker.State.Failure val failure = ContentUploadStateTracker.State.Failure(throwable)
updateState(key, failure) updateState(key, failure)
} }
@ -53,6 +53,21 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
updateState(key, success) updateState(key, success)
} }
internal fun setEncryptingThumbnail(key: String) {
val progressData = ContentUploadStateTracker.State.EncryptingThumbnail
updateState(key, progressData)
}
internal fun setProgressThumbnail(key: String, current: Long, total: Long) {
val progressData = ContentUploadStateTracker.State.ProgressThumbnailData(current, total)
updateState(key, progressData)
}
internal fun setEncrypting(key: String) {
val progressData = ContentUploadStateTracker.State.Encrypting
updateState(key, progressData)
}
internal fun setProgress(key: String, current: Long, total: Long) { internal fun setProgress(key: String, current: Long, total: Long) {
val progressData = ContentUploadStateTracker.State.ProgressData(current, total) val progressData = ContentUploadStateTracker.State.ProgressData(current, total)
updateState(key, progressData) updateState(key, progressData)

View File

@ -73,7 +73,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
File(attachment.path) File(attachment.path)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
contentUploadStateTracker.setFailure(params.event.eventId) contentUploadStateTracker.setFailure(params.event.eventId, e)
return Result.success( return Result.success(
WorkerParamsFactory.toData(params.copy( WorkerParamsFactory.toData(params.copy(
lastFailureMessage = e.localizedMessage lastFailureMessage = e.localizedMessage
@ -85,18 +85,31 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null
ThumbnailExtractor.extractThumbnail(params.attachment)?.let { thumbnailData -> ThumbnailExtractor.extractThumbnail(params.attachment)?.let { thumbnailData ->
val thumbnailProgressListener = object : ProgressRequestBody.Listener {
override fun onProgress(current: Long, total: Long) {
contentUploadStateTracker.setProgressThumbnail(eventId, current, total)
}
}
val contentUploadResponse = if (params.isRoomEncrypted) { val contentUploadResponse = if (params.isRoomEncrypted) {
Timber.v("Encrypt thumbnail") Timber.v("Encrypt thumbnail")
contentUploadStateTracker.setEncryptingThumbnail(eventId)
MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType) MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
.flatMap { encryptionResult -> .flatMap { encryptionResult ->
uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo
fileUploader fileUploader
.uploadByteArray(encryptionResult.encryptedByteArray, "thumb_${attachment.name}", "application/octet-stream") .uploadByteArray(encryptionResult.encryptedByteArray,
"thumb_${attachment.name}",
"application/octet-stream",
thumbnailProgressListener)
} }
} else { } else {
fileUploader fileUploader
.uploadByteArray(thumbnailData.bytes, "thumb_${attachment.name}", thumbnailData.mimeType) .uploadByteArray(thumbnailData.bytes,
"thumb_${attachment.name}",
thumbnailData.mimeType,
thumbnailProgressListener)
} }
contentUploadResponse contentUploadResponse
@ -116,6 +129,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
val contentUploadResponse = if (params.isRoomEncrypted) { val contentUploadResponse = if (params.isRoomEncrypted) {
Timber.v("Encrypt file") Timber.v("Encrypt file")
contentUploadStateTracker.setEncrypting(eventId)
MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType) MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType)
.flatMap { encryptionResult -> .flatMap { encryptionResult ->
@ -137,7 +151,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
} }
private fun handleFailure(params: Params, failure: Throwable): Result { private fun handleFailure(params: Params, failure: Throwable): Result {
contentUploadStateTracker.setFailure(params.event.eventId!!) contentUploadStateTracker.setFailure(params.event.eventId!!, failure)
return Result.success( return Result.success(
WorkerParamsFactory.toData( WorkerParamsFactory.toData(
params.copy( params.copy(

View File

@ -16,12 +16,12 @@
package im.vector.riotx.features.home.room.detail.timeline.helper package im.vector.riotx.features.home.room.detail.timeline.helper
import android.content.Context
import android.text.format.Formatter import android.text.format.Formatter
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
@ -62,9 +62,12 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
override fun onUpdate(state: ContentUploadStateTracker.State) { override fun onUpdate(state: ContentUploadStateTracker.State) {
when (state) { when (state) {
is ContentUploadStateTracker.State.Idle -> handleIdle(state) is ContentUploadStateTracker.State.Idle -> handleIdle(state)
is ContentUploadStateTracker.State.EncryptingThumbnail -> handleEncryptingThumbnail(state)
is ContentUploadStateTracker.State.ProgressThumbnailData -> handleProgressThumbnail(state)
is ContentUploadStateTracker.State.Encrypting -> handleEncrypting(state)
is ContentUploadStateTracker.State.ProgressData -> handleProgress(state)
is ContentUploadStateTracker.State.Failure -> handleFailure(state) is ContentUploadStateTracker.State.Failure -> handleFailure(state)
is ContentUploadStateTracker.State.Success -> handleSuccess(state) is ContentUploadStateTracker.State.Success -> handleSuccess(state)
is ContentUploadStateTracker.State.ProgressData -> handleProgress(state)
} }
} }
@ -74,32 +77,61 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
progressLayout.visibility = View.VISIBLE progressLayout.visibility = View.VISIBLE
val progressBar = progressLayout.findViewById<ProgressBar>(R.id.mediaProgressBar) val progressBar = progressLayout.findViewById<ProgressBar>(R.id.mediaProgressBar)
val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView) val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView)
progressBar?.isVisible = true
progressBar?.isIndeterminate = true
progressBar?.progress = 0 progressBar?.progress = 0
progressTextView?.text = formatStats(progressLayout.context, 0L, file.length()) progressTextView?.text = progressLayout.context.getString(R.string.send_file_step_idle)
} else { } else {
progressLayout.visibility = View.GONE progressLayout.visibility = View.GONE
} }
} }
private fun handleFailure(state: ContentUploadStateTracker.State.Failure) { private fun handleEncryptingThumbnail(state: ContentUploadStateTracker.State.EncryptingThumbnail) {
_handleEncrypting(R.string.send_file_step_encrypting_thumbnail)
} }
private fun handleSuccess(state: ContentUploadStateTracker.State.Success) { private fun handleProgressThumbnail(state: ContentUploadStateTracker.State.ProgressThumbnailData) {
_handleProgress(R.string.send_file_step_sending_thumbnail, state.current, state.total)
}
private fun handleEncrypting(state: ContentUploadStateTracker.State.Encrypting) {
_handleEncrypting(R.string.send_file_step_encrypting_file)
} }
private fun handleProgress(state: ContentUploadStateTracker.State.ProgressData) { private fun handleProgress(state: ContentUploadStateTracker.State.ProgressData) {
_handleProgress(R.string.send_file_step_sending_file, state.current, state.total)
}
private fun _handleEncrypting(resId: Int) {
progressLayout.visibility = View.VISIBLE progressLayout.visibility = View.VISIBLE
val percent = 100L * (state.current.toFloat() / state.total.toFloat())
val progressBar = progressLayout.findViewById<ProgressBar>(R.id.mediaProgressBar) val progressBar = progressLayout.findViewById<ProgressBar>(R.id.mediaProgressBar)
val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView) val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView)
progressBar?.isIndeterminate = true
progressTextView?.text = progressLayout.context.getString(resId)
}
private fun _handleProgress(resId: Int, current: Long, total: Long) {
progressLayout.visibility = View.VISIBLE
val percent = 100L * (current.toFloat() / total.toFloat())
val progressBar = progressLayout.findViewById<ProgressBar>(R.id.mediaProgressBar)
val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView)
progressBar?.isVisible = true
progressBar?.isIndeterminate = false
progressBar?.progress = percent.toInt() progressBar?.progress = percent.toInt()
progressTextView?.text = formatStats(progressLayout.context, state.current, state.total) progressTextView?.text = progressLayout.context.getString(resId,
Formatter.formatShortFileSize(progressLayout.context, current),
Formatter.formatShortFileSize(progressLayout.context, total))
} }
private fun formatStats(context: Context, current: Long, total: Long): String { private fun handleFailure(state: ContentUploadStateTracker.State.Failure) {
return "${Formatter.formatShortFileSize(context, current)} / ${Formatter.formatShortFileSize(context, total)}" progressLayout.visibility = View.VISIBLE
val progressBar = progressLayout.findViewById<ProgressBar>(R.id.mediaProgressBar)
val progressTextView = progressLayout.findViewById<TextView>(R.id.mediaProgressTextView)
progressBar?.isVisible = false
progressTextView?.text = state.throwable.localizedMessage
} }
private fun handleSuccess(state: ContentUploadStateTracker.State.Success) {
// Nothing to do
}
} }

View File

@ -5,5 +5,11 @@
<string name="bottom_action_people_x">Direct Messages</string> <string name="bottom_action_people_x">Direct Messages</string>
<string name="send_file_step_idle">Waiting…</string>
<string name="send_file_step_encrypting_thumbnail">Encrypting thumbnail…</string>
<string name="send_file_step_sending_thumbnail">Sending thumbnail (%1$s / %2$s)</string>
<string name="send_file_step_encrypting_file">Encrypting file…</string>
<string name="send_file_step_sending_file">Sending file (%1$s / %2$s)</string>
</resources> </resources>