Skip to content
This repository was archived by the owner on May 23, 2025. It is now read-only.

Commit 93fb9c2

Browse files
authored
keep ordering when picking multiple media files at once (#4841)
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
1 parent 510e093 commit 93fb9c2

File tree

5 files changed

+155
-164
lines changed

5 files changed

+155
-164
lines changed

app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt

Lines changed: 59 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import com.keylesspalace.tusky.adapter.EmojiAdapter
7373
import com.keylesspalace.tusky.adapter.LocaleAdapter
7474
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
7575
import com.keylesspalace.tusky.components.compose.ComposeViewModel.ConfirmationKind
76+
import com.keylesspalace.tusky.components.compose.ComposeViewModel.QueuedMedia
7677
import com.keylesspalace.tusky.components.compose.dialog.CaptionDialog
7778
import com.keylesspalace.tusky.components.compose.dialog.makeFocusDialog
7879
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
@@ -102,6 +103,7 @@ import com.keylesspalace.tusky.util.getSerializableCompat
102103
import com.keylesspalace.tusky.util.hide
103104
import com.keylesspalace.tusky.util.highlightSpans
104105
import com.keylesspalace.tusky.util.loadAvatar
106+
import com.keylesspalace.tusky.util.map
105107
import com.keylesspalace.tusky.util.modernLanguageCode
106108
import com.keylesspalace.tusky.util.setDrawableTint
107109
import com.keylesspalace.tusky.util.show
@@ -162,7 +164,7 @@ class ComposeActivity :
162164
private val takePictureLauncher =
163165
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
164166
if (success) {
165-
pickMedia(photoUploadUri!!)
167+
viewModel.pickMedia(photoUploadUri!!)
166168
}
167169
}
168170
private val pickMediaFilePermissionLauncher =
@@ -194,9 +196,11 @@ class ComposeActivity :
194196
Toast.LENGTH_SHORT
195197
).show()
196198
} else {
197-
uris.forEach { uri ->
198-
pickMedia(uri)
199-
}
199+
viewModel.pickMedia(
200+
uris.map { uri ->
201+
ComposeViewModel.MediaData(uri)
202+
}
203+
)
200204
}
201205
}
202206

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

210-
lifecycleScope.launch {
211-
viewModel.addMediaToQueue(
212-
itemOld.type,
213-
uriNew,
214-
size,
215-
itemOld.description,
216-
// Intentionally reset focus when cropping
217-
null,
218-
itemOld
219-
)
220-
}
214+
viewModel.addMediaToQueue(
215+
type = itemOld.type,
216+
uri = uriNew,
217+
mediaSize = size,
218+
description = itemOld.description,
219+
// Intentionally reset focus when cropping
220+
focus = null,
221+
replaceItem = itemOld
222+
)
221223
}
222224
} else if (result == CropImage.CancelledResult) {
223225
Log.w(TAG, "Edit image cancelled by user")
@@ -308,7 +310,7 @@ class ComposeActivity :
308310
}
309311

310312
if (!composeOptions?.scheduledAt.isNullOrEmpty()) {
311-
binding.composeScheduleView.setDateTime(composeOptions?.scheduledAt)
313+
binding.composeScheduleView.setDateTime(composeOptions.scheduledAt)
312314
}
313315

314316
setupLanguageSpinner(getInitialLanguages(composeOptions?.language, activeAccount))
@@ -347,14 +349,14 @@ class ComposeActivity :
347349
when (intent.action) {
348350
Intent.ACTION_SEND -> {
349351
intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM)?.let { uri ->
350-
pickMedia(uri)
352+
viewModel.pickMedia(uri)
351353
}
352354
}
353355
Intent.ACTION_SEND_MULTIPLE -> {
354356
intent.getParcelableArrayListExtraCompat<Uri>(Intent.EXTRA_STREAM)
355-
?.forEach { uri ->
356-
pickMedia(uri)
357-
}
357+
?.map { uri ->
358+
ComposeViewModel.MediaData(uri)
359+
}?.let(viewModel::pickMedia)
358360
}
359361
}
360362
}
@@ -557,16 +559,25 @@ class ComposeActivity :
557559

558560
lifecycleScope.launch {
559561
viewModel.uploadError.collect { throwable ->
560-
if (throwable is UploadServerError) {
561-
displayTransientMessage(throwable.errorMessage)
562-
} else {
563-
displayTransientMessage(
564-
getString(
565-
R.string.error_media_upload_sending_fmt,
566-
throwable.message
567-
)
562+
val errorString = when (throwable) {
563+
is UploadServerError -> throwable.errorMessage
564+
is FileSizeException -> {
565+
val decimalFormat = DecimalFormat("0.##")
566+
val allowedSizeInMb = throwable.allowedSizeInBytes.toDouble() / (1024 * 1024)
567+
val formattedSize = decimalFormat.format(allowedSizeInMb)
568+
getString(R.string.error_multimedia_size_limit, formattedSize)
569+
}
570+
is VideoOrImageException -> getString(
571+
R.string.error_media_upload_image_or_video
572+
)
573+
is CouldNotOpenFileException -> getString(R.string.error_media_upload_opening)
574+
is MediaTypeException -> getString(R.string.error_media_upload_opening)
575+
else -> getString(
576+
R.string.error_media_upload_sending_fmt,
577+
throwable.message
568578
)
569579
}
580+
displayTransientMessage(errorString)
570581
}
571582
}
572583

@@ -1090,12 +1101,27 @@ class ComposeActivity :
10901101
if (contentInfo.clip.description.hasMimeType("image/*")) {
10911102
val split = contentInfo.partition { item: ClipData.Item -> item.uri != null }
10921103
split.first?.let { content ->
1093-
for (i in 0 until content.clip.itemCount) {
1094-
pickMedia(
1095-
content.clip.getItemAt(i).uri,
1096-
contentInfo.clip.description.label as String?
1097-
)
1104+
val description = (contentInfo.clip.description.label as String?)?.let {
1105+
// The Gboard android keyboard attaches this text whenever the user
1106+
// pastes something from the keyboard's suggestion bar.
1107+
// Due to different end user locales, the exact text may vary, but at
1108+
// least in version 13.4.08, all of the translations contained the
1109+
// string "Gboard".
1110+
if ("Gboard" in it) {
1111+
null
1112+
} else {
1113+
it
1114+
}
10981115
}
1116+
1117+
viewModel.pickMedia(
1118+
content.clip.map { clipItem ->
1119+
ComposeViewModel.MediaData(
1120+
uri = clipItem.uri,
1121+
description = description
1122+
)
1123+
}
1124+
)
10991125
}
11001126
return split.second
11011127
}
@@ -1199,45 +1225,6 @@ class ComposeActivity :
11991225
viewModel.removeMediaFromQueue(item)
12001226
}
12011227

1202-
private fun sanitizePickMediaDescription(description: String?): String? {
1203-
if (description == null) {
1204-
return null
1205-
}
1206-
1207-
// The Gboard android keyboard attaches this text whenever the user
1208-
// pastes something from the keyboard's suggestion bar.
1209-
// Due to different end user locales, the exact text may vary, but at
1210-
// least in version 13.4.08, all of the translations contained the
1211-
// string "Gboard".
1212-
if ("Gboard" in description) {
1213-
return null
1214-
}
1215-
1216-
return description
1217-
}
1218-
1219-
private fun pickMedia(uri: Uri, description: String? = null) {
1220-
val sanitizedDescription = sanitizePickMediaDescription(description)
1221-
1222-
lifecycleScope.launch {
1223-
viewModel.pickMedia(uri, sanitizedDescription).onFailure { throwable ->
1224-
val errorString = when (throwable) {
1225-
is FileSizeException -> {
1226-
val decimalFormat = DecimalFormat("0.##")
1227-
val allowedSizeInMb = throwable.allowedSizeInBytes.toDouble() / (1024 * 1024)
1228-
val formattedSize = decimalFormat.format(allowedSizeInMb)
1229-
getString(R.string.error_multimedia_size_limit, formattedSize)
1230-
}
1231-
is VideoOrImageException -> getString(
1232-
R.string.error_media_upload_image_or_video
1233-
)
1234-
else -> getString(R.string.error_media_upload_opening)
1235-
}
1236-
displayTransientMessage(errorString)
1237-
}
1238-
}
1239-
}
1240-
12411228
private fun showContentWarning(show: Boolean) {
12421229
TransitionManager.beginDelayedTransition(
12431230
binding.composeContentWarningBar.parent as ViewGroup
@@ -1420,30 +1407,6 @@ class ComposeActivity :
14201407
}
14211408
}
14221409

1423-
data class QueuedMedia(
1424-
val localId: Int,
1425-
val uri: Uri,
1426-
val type: Type,
1427-
val mediaSize: Long,
1428-
val uploadPercent: Int = 0,
1429-
val id: String? = null,
1430-
val description: String? = null,
1431-
val focus: Attachment.Focus? = null,
1432-
val state: State
1433-
) {
1434-
enum class Type {
1435-
IMAGE,
1436-
VIDEO,
1437-
AUDIO
1438-
}
1439-
enum class State {
1440-
UPLOADING,
1441-
UNPROCESSED,
1442-
PROCESSED,
1443-
PUBLISHED
1444-
}
1445-
}
1446-
14471410
override fun onTimeSet(time: String?) {
14481411
viewModel.updateScheduledAt(time)
14491412
if (verifyScheduledTime()) {

0 commit comments

Comments
 (0)