Send file: improve UI feedback
This commit is contained in:
parent
d24ce27903
commit
8a5612be3d
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue