Skip to content

Commit

Permalink
keep ordering when picking multiple media files at once (#4841)
Browse files Browse the repository at this point in the history
This restructures the code so that all picked media will be added to the
upload queue in the correct order and also does some other code cleanup.

closes #4754
  • Loading branch information
connyduck authored Jan 6, 2025
1 parent 510e093 commit 93fb9c2
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 164 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import com.keylesspalace.tusky.adapter.EmojiAdapter
import com.keylesspalace.tusky.adapter.LocaleAdapter
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
import com.keylesspalace.tusky.components.compose.ComposeViewModel.ConfirmationKind
import com.keylesspalace.tusky.components.compose.ComposeViewModel.QueuedMedia
import com.keylesspalace.tusky.components.compose.dialog.CaptionDialog
import com.keylesspalace.tusky.components.compose.dialog.makeFocusDialog
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
Expand Down Expand Up @@ -102,6 +103,7 @@ import com.keylesspalace.tusky.util.getSerializableCompat
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.highlightSpans
import com.keylesspalace.tusky.util.loadAvatar
import com.keylesspalace.tusky.util.map
import com.keylesspalace.tusky.util.modernLanguageCode
import com.keylesspalace.tusky.util.setDrawableTint
import com.keylesspalace.tusky.util.show
Expand Down Expand Up @@ -162,7 +164,7 @@ class ComposeActivity :
private val takePictureLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
pickMedia(photoUploadUri!!)
viewModel.pickMedia(photoUploadUri!!)
}
}
private val pickMediaFilePermissionLauncher =
Expand Down Expand Up @@ -194,9 +196,11 @@ class ComposeActivity :
Toast.LENGTH_SHORT
).show()
} else {
uris.forEach { uri ->
pickMedia(uri)
}
viewModel.pickMedia(
uris.map { uri ->
ComposeViewModel.MediaData(uri)
}
)
}
}

Expand All @@ -207,17 +211,15 @@ class ComposeActivity :
viewModel.cropImageItemOld?.let { itemOld ->
val size = getMediaSize(contentResolver, uriNew)

lifecycleScope.launch {
viewModel.addMediaToQueue(
itemOld.type,
uriNew,
size,
itemOld.description,
// Intentionally reset focus when cropping
null,
itemOld
)
}
viewModel.addMediaToQueue(
type = itemOld.type,
uri = uriNew,
mediaSize = size,
description = itemOld.description,
// Intentionally reset focus when cropping
focus = null,
replaceItem = itemOld
)
}
} else if (result == CropImage.CancelledResult) {
Log.w(TAG, "Edit image cancelled by user")
Expand Down Expand Up @@ -308,7 +310,7 @@ class ComposeActivity :
}

if (!composeOptions?.scheduledAt.isNullOrEmpty()) {
binding.composeScheduleView.setDateTime(composeOptions?.scheduledAt)
binding.composeScheduleView.setDateTime(composeOptions.scheduledAt)
}

setupLanguageSpinner(getInitialLanguages(composeOptions?.language, activeAccount))
Expand Down Expand Up @@ -347,14 +349,14 @@ class ComposeActivity :
when (intent.action) {
Intent.ACTION_SEND -> {
intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM)?.let { uri ->
pickMedia(uri)
viewModel.pickMedia(uri)
}
}
Intent.ACTION_SEND_MULTIPLE -> {
intent.getParcelableArrayListExtraCompat<Uri>(Intent.EXTRA_STREAM)
?.forEach { uri ->
pickMedia(uri)
}
?.map { uri ->
ComposeViewModel.MediaData(uri)
}?.let(viewModel::pickMedia)
}
}
}
Expand Down Expand Up @@ -557,16 +559,25 @@ class ComposeActivity :

lifecycleScope.launch {
viewModel.uploadError.collect { throwable ->
if (throwable is UploadServerError) {
displayTransientMessage(throwable.errorMessage)
} else {
displayTransientMessage(
getString(
R.string.error_media_upload_sending_fmt,
throwable.message
)
val errorString = when (throwable) {
is UploadServerError -> throwable.errorMessage
is FileSizeException -> {
val decimalFormat = DecimalFormat("0.##")
val allowedSizeInMb = throwable.allowedSizeInBytes.toDouble() / (1024 * 1024)
val formattedSize = decimalFormat.format(allowedSizeInMb)
getString(R.string.error_multimedia_size_limit, formattedSize)
}
is VideoOrImageException -> getString(
R.string.error_media_upload_image_or_video
)
is CouldNotOpenFileException -> getString(R.string.error_media_upload_opening)
is MediaTypeException -> getString(R.string.error_media_upload_opening)
else -> getString(
R.string.error_media_upload_sending_fmt,
throwable.message
)
}
displayTransientMessage(errorString)
}
}

Expand Down Expand Up @@ -1090,12 +1101,27 @@ class ComposeActivity :
if (contentInfo.clip.description.hasMimeType("image/*")) {
val split = contentInfo.partition { item: ClipData.Item -> item.uri != null }
split.first?.let { content ->
for (i in 0 until content.clip.itemCount) {
pickMedia(
content.clip.getItemAt(i).uri,
contentInfo.clip.description.label as String?
)
val description = (contentInfo.clip.description.label as String?)?.let {
// The Gboard android keyboard attaches this text whenever the user
// pastes something from the keyboard's suggestion bar.
// Due to different end user locales, the exact text may vary, but at
// least in version 13.4.08, all of the translations contained the
// string "Gboard".
if ("Gboard" in it) {
null
} else {
it
}
}

viewModel.pickMedia(
content.clip.map { clipItem ->
ComposeViewModel.MediaData(
uri = clipItem.uri,
description = description
)
}
)
}
return split.second
}
Expand Down Expand Up @@ -1199,45 +1225,6 @@ class ComposeActivity :
viewModel.removeMediaFromQueue(item)
}

private fun sanitizePickMediaDescription(description: String?): String? {
if (description == null) {
return null
}

// The Gboard android keyboard attaches this text whenever the user
// pastes something from the keyboard's suggestion bar.
// Due to different end user locales, the exact text may vary, but at
// least in version 13.4.08, all of the translations contained the
// string "Gboard".
if ("Gboard" in description) {
return null
}

return description
}

private fun pickMedia(uri: Uri, description: String? = null) {
val sanitizedDescription = sanitizePickMediaDescription(description)

lifecycleScope.launch {
viewModel.pickMedia(uri, sanitizedDescription).onFailure { throwable ->
val errorString = when (throwable) {
is FileSizeException -> {
val decimalFormat = DecimalFormat("0.##")
val allowedSizeInMb = throwable.allowedSizeInBytes.toDouble() / (1024 * 1024)
val formattedSize = decimalFormat.format(allowedSizeInMb)
getString(R.string.error_multimedia_size_limit, formattedSize)
}
is VideoOrImageException -> getString(
R.string.error_media_upload_image_or_video
)
else -> getString(R.string.error_media_upload_opening)
}
displayTransientMessage(errorString)
}
}
}

private fun showContentWarning(show: Boolean) {
TransitionManager.beginDelayedTransition(
binding.composeContentWarningBar.parent as ViewGroup
Expand Down Expand Up @@ -1420,30 +1407,6 @@ class ComposeActivity :
}
}

data class QueuedMedia(
val localId: Int,
val uri: Uri,
val type: Type,
val mediaSize: Long,
val uploadPercent: Int = 0,
val id: String? = null,
val description: String? = null,
val focus: Attachment.Focus? = null,
val state: State
) {
enum class Type {
IMAGE,
VIDEO,
AUDIO
}
enum class State {
UPLOADING,
UNPROCESSED,
PROCESSED,
PUBLISHED
}
}

override fun onTimeSet(time: String?) {
viewModel.updateScheduledAt(time)
if (verifyScheduledTime()) {
Expand Down
Loading

0 comments on commit 93fb9c2

Please sign in to comment.