Use dynamic/rule-based size calculations for transfer control view.

This commit is contained in:
Cody Henthorne 2025-01-17 13:29:26 -05:00 committed by Greyson Parrelli
parent e945efac8b
commit d7c2e6844b
3 changed files with 37 additions and 27 deletions

View file

@ -17,6 +17,8 @@ import androidx.core.view.updateLayoutParams
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.signal.core.util.ByteSize
import org.signal.core.util.bytes
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.attachments.Attachment import org.thoughtcrime.securesms.attachments.Attachment
@ -529,7 +531,7 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att
val existingEvent = mutableMap[attachment] val existingEvent = mutableMap[attachment]
if (existingEvent == null || updateEvent.completed > existingEvent.completed) { if (existingEvent == null || updateEvent.completed > existingEvent.completed) {
mutableMap[attachment] = updateEvent mutableMap[attachment] = updateEvent
} else if (updateEvent.completed < 0) { } else if (updateEvent.completed < 0.bytes) {
mutableMap.remove(attachment) mutableMap.remove(attachment)
} }
verboseLog("onEventAsync compression update") verboseLog("onEventAsync compression update")
@ -540,7 +542,7 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att
val existingEvent = mutableMap[attachment] val existingEvent = mutableMap[attachment]
if (existingEvent == null || updateEvent.completed > existingEvent.completed) { if (existingEvent == null || updateEvent.completed > existingEvent.completed) {
mutableMap[attachment] = updateEvent mutableMap[attachment] = updateEvent
} else if (updateEvent.completed < 0) { } else if (updateEvent.completed < 0.bytes) {
mutableMap.remove(attachment) mutableMap.remove(attachment)
} }
verboseLog("onEventAsync network update") verboseLog("onEventAsync network update")
@ -556,14 +558,14 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att
val isNewSlideSet = !isUpdateToExistingSet(state, slides) val isNewSlideSet = !isUpdateToExistingSet(state, slides)
val networkProgress: MutableMap<Attachment, Progress> = if (isNewSlideSet) HashMap() else state.networkProgress.toMutableMap() val networkProgress: MutableMap<Attachment, Progress> = if (isNewSlideSet) HashMap() else state.networkProgress.toMutableMap()
if (isNewSlideSet) { if (isNewSlideSet) {
slides.forEach { networkProgress[it.asAttachment()] = Progress(0L, it.fileSize) } slides.forEach { networkProgress[it.asAttachment()] = Progress(0.bytes, it.fileSize.bytes) }
} }
val compressionProgress: MutableMap<Attachment, Progress> = if (isNewSlideSet) HashMap() else state.compressionProgress.toMutableMap() val compressionProgress: MutableMap<Attachment, Progress> = if (isNewSlideSet) HashMap() else state.compressionProgress.toMutableMap()
var allStreamableOrDone = true var allStreamableOrDone = true
for (slide in slides) { for (slide in slides) {
val attachment = slide.asAttachment() val attachment = slide.asAttachment()
if (attachment.transferState == AttachmentTable.TRANSFER_PROGRESS_DONE) { if (attachment.transferState == AttachmentTable.TRANSFER_PROGRESS_DONE) {
networkProgress[attachment] = Progress(attachment.size, attachment.size) networkProgress[attachment] = Progress(attachment.size.bytes, attachment.size.bytes)
} else if (!MediaUtil.isInstantVideoSupported(slide)) { } else if (!MediaUtil.isInstantVideoSupported(slide)) {
allStreamableOrDone = false allStreamableOrDone = false
} }
@ -657,12 +659,12 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att
private fun isCompressing(state: TransferControlViewState): Boolean { private fun isCompressing(state: TransferControlViewState): Boolean {
val total = state.compressionProgress.sumTotal() val total = state.compressionProgress.sumTotal()
return total > 0 && state.compressionProgress.sumCompleted() / total < 0.99f return total > 0.bytes && state.compressionProgress.sumCompleted().percentageOf(total) < 0.99f
} }
private fun calculateProgress(state: TransferControlViewState): Float { private fun calculateProgress(state: TransferControlViewState): Float {
val totalCompressionProgress: Float = state.compressionProgress.values.map { it.completed.toFloat() / it.total }.sum() val totalCompressionProgress: Float = state.compressionProgress.values.map { it.completed.percentageOf(it.total) }.sum()
val totalDownloadProgress: Float = state.networkProgress.values.map { it.completed.toFloat() / it.total }.sum() val totalDownloadProgress: Float = state.networkProgress.values.map { it.completed.percentageOf(it.total) }.sum()
val weightedProgress = UPLOAD_TASK_WEIGHT * totalDownloadProgress + COMPRESSION_TASK_WEIGHT * totalCompressionProgress val weightedProgress = UPLOAD_TASK_WEIGHT * totalDownloadProgress + COMPRESSION_TASK_WEIGHT * totalCompressionProgress
val weightedTotal = (UPLOAD_TASK_WEIGHT * state.networkProgress.size + COMPRESSION_TASK_WEIGHT * state.compressionProgress.size).toFloat() val weightedTotal = (UPLOAD_TASK_WEIGHT * state.networkProgress.size + COMPRESSION_TASK_WEIGHT * state.compressionProgress.size).toFloat()
return weightedProgress / weightedTotal return weightedProgress / weightedTotal
@ -677,35 +679,35 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att
val remainingSlides = currentState.slides.filterNot { it.transferState == AttachmentTable.TRANSFER_PROGRESS_DONE } val remainingSlides = currentState.slides.filterNot { it.transferState == AttachmentTable.TRANSFER_PROGRESS_DONE }
val downloadCount = remainingSlides.size val downloadCount = remainingSlides.size
binding.primaryDetailsText.text = context.resources.getQuantityString(R.plurals.TransferControlView_n_items, downloadCount, downloadCount) binding.primaryDetailsText.text = context.resources.getQuantityString(R.plurals.TransferControlView_n_items, downloadCount, downloadCount)
val mebibyteCount = (currentState.networkProgress.sumTotal() - currentState.networkProgress.sumCompleted()) / MEBIBYTE val size = currentState.networkProgress.sumTotal() - currentState.networkProgress.sumCompleted()
binding.secondaryDetailsText.text = context.getString(R.string.TransferControlView__filesize, mebibyteCount) binding.secondaryDetailsText.text = size.toUnitString()
} }
Mode.PENDING_GALLERY_CONTAINS_PLAYABLE -> { Mode.PENDING_GALLERY_CONTAINS_PLAYABLE -> {
binding.secondaryDetailsText.updateLayoutParams { binding.secondaryDetailsText.updateLayoutParams {
width = ViewGroup.LayoutParams.WRAP_CONTENT width = ViewGroup.LayoutParams.WRAP_CONTENT
} }
val mebibyteCount = (currentState.networkProgress.sumTotal() - currentState.networkProgress.sumCompleted()) / MEBIBYTE val size = currentState.networkProgress.sumTotal() - currentState.networkProgress.sumCompleted()
binding.secondaryDetailsText.text = context.getString(R.string.TransferControlView__filesize, mebibyteCount) binding.secondaryDetailsText.text = size.toUnitString()
} }
Mode.PENDING_SINGLE_ITEM, Mode.PENDING_VIDEO_PLAYABLE -> { Mode.PENDING_SINGLE_ITEM, Mode.PENDING_VIDEO_PLAYABLE -> {
binding.secondaryDetailsText.updateLayoutParams { binding.secondaryDetailsText.updateLayoutParams {
width = ViewGroup.LayoutParams.WRAP_CONTENT width = ViewGroup.LayoutParams.WRAP_CONTENT
} }
val mebibyteCount = (currentState.slides.sumOf { it.asAttachment().size }) / MEBIBYTE val size: ByteSize = (currentState.slides.sumOf { it.asAttachment().size }).bytes
binding.secondaryDetailsText.text = context.getString(R.string.TransferControlView__filesize, mebibyteCount) binding.secondaryDetailsText.text = size.toUnitString()
} }
Mode.DOWNLOADING_GALLERY, Mode.DOWNLOADING_SINGLE_ITEM, Mode.DOWNLOADING_VIDEO_PLAYABLE, Mode.UPLOADING_GALLERY, Mode.UPLOADING_SINGLE_ITEM -> { Mode.DOWNLOADING_GALLERY, Mode.DOWNLOADING_SINGLE_ITEM, Mode.DOWNLOADING_VIDEO_PLAYABLE, Mode.UPLOADING_GALLERY, Mode.UPLOADING_SINGLE_ITEM -> {
if (currentState.isUpload && (currentState.networkProgress.sumCompleted() == 0L || isCompressing(currentState))) { if (currentState.isUpload && (currentState.networkProgress.sumCompleted() == 0.bytes || isCompressing(currentState))) {
binding.secondaryDetailsText.updateLayoutParams { binding.secondaryDetailsText.updateLayoutParams {
width = ViewGroup.LayoutParams.WRAP_CONTENT width = ViewGroup.LayoutParams.WRAP_CONTENT
} }
binding.secondaryDetailsText.text = context.getString(R.string.TransferControlView__processing) binding.secondaryDetailsText.text = context.getString(R.string.TransferControlView__processing)
} else { } else {
val progressMiB = currentState.networkProgress.sumCompleted() / MEBIBYTE val progressMiB = currentState.networkProgress.sumCompleted().toUnitString()
val totalMiB = currentState.networkProgress.sumTotal() / MEBIBYTE val totalMiB = currentState.networkProgress.sumTotal().toUnitString()
val completedLabel = context.resources.getString(R.string.TransferControlView__download_progress, totalMiB, totalMiB) val completedLabel = context.resources.getString(R.string.TransferControlView__download_progress, totalMiB, totalMiB)
val desiredWidth = StaticLayout.getDesiredWidth(completedLabel, binding.secondaryDetailsText.paint) val desiredWidth = StaticLayout.getDesiredWidth(completedLabel, binding.secondaryDetailsText.paint)
binding.secondaryDetailsText.text = context.resources.getString(R.string.TransferControlView__download_progress, progressMiB, totalMiB) binding.secondaryDetailsText.text = context.resources.getString(R.string.TransferControlView__download_progress, progressMiB, totalMiB)
@ -743,7 +745,6 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att
private const val SECONDARY_TEXT_OFFSET_DP = 6 private const val SECONDARY_TEXT_OFFSET_DP = 6
private const val RETRY_SECONDARY_TEXT_OFFSET_DP = 6 private const val RETRY_SECONDARY_TEXT_OFFSET_DP = 6
private const val PRIMARY_TEXT_OFFSET_DP = 4 private const val PRIMARY_TEXT_OFFSET_DP = 4
private const val MEBIBYTE = 1048576f
/** /**
* A weighting compared to [UPLOAD_TASK_WEIGHT] * A weighting compared to [UPLOAD_TASK_WEIGHT]
@ -773,20 +774,20 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att
} }
} }
data class Progress(val completed: Long, val total: Long) { data class Progress(val completed: ByteSize, val total: ByteSize) {
companion object { companion object {
fun fromEvent(event: PartProgressEvent): Progress { fun fromEvent(event: PartProgressEvent): Progress {
return Progress(event.progress, event.total) return Progress(event.progress.bytes, event.total.bytes)
} }
} }
} }
private fun Map<Attachment, Progress>.sumCompleted(): Long { private fun Map<Attachment, Progress>.sumCompleted(): ByteSize {
return this.values.sumOf { it.completed } return this.values.sumOf { it.completed.inWholeBytes }.bytes
} }
private fun Map<Attachment, Progress>.sumTotal(): Long { private fun Map<Attachment, Progress>.sumTotal(): ByteSize {
return this.values.sumOf { it.total } return this.values.sumOf { it.total.inWholeBytes }.bytes
} }
enum class Mode { enum class Mode {

View file

@ -3033,10 +3033,7 @@
<!-- Status update label used while the device is transcoding video as a prerequisite to uploading --> <!-- Status update label used while the device is transcoding video as a prerequisite to uploading -->
<string name="TransferControlView__processing">Processing…</string> <string name="TransferControlView__processing">Processing…</string>
<!-- Status update label used while the device is transmitting data over the network. Will take the form of "1.0 MB/2.0 MB" --> <!-- Status update label used while the device is transmitting data over the network. Will take the form of "1.0 MB/2.0 MB" -->
<string name="TransferControlView__download_progress">%1.1f MB/%2.1f MB</string> <string name="TransferControlView__download_progress">%1$s/%2$s</string>
<!-- Attachment file size label for not-yet-downloaded images and video. Will take the form of "1.0 MB" -->
<string name="TransferControlView__filesize">%1.1f MB</string>
<!-- UnauthorizedReminder --> <!-- UnauthorizedReminder -->
<!-- Message shown in a reminder banner when the user\'s device is no longer registered --> <!-- Message shown in a reminder banner when the user\'s device is no longer registered -->

View file

@ -99,6 +99,18 @@ class ByteSize(val bytes: Long) {
return bytes.compareTo(other.bytes) return bytes.compareTo(other.bytes)
} }
operator fun plus(other: ByteSize): ByteSize {
return ByteSize(this.inWholeBytes + other.inWholeBytes)
}
fun percentageOf(other: ByteSize): Float {
return this.inWholeBytes.toFloat() / other.inWholeBytes.toFloat()
}
operator fun minus(other: ByteSize): ByteSize {
return ByteSize(this.inWholeBytes - other.inWholeBytes)
}
enum class Size(val label: String) { enum class Size(val label: String) {
BYTE("B"), BYTE("B"),
KIBIBYTE("KB"), KIBIBYTE("KB"),