Improves upload of music and documents.

This commit is contained in:
Vitor Pamplona
2026-03-10 19:49:15 -04:00
parent 8a03f0b501
commit 8cd9449ba6
7 changed files with 81 additions and 57 deletions
@@ -79,7 +79,7 @@ class BlossomServerListState(
emptyList(), emptyList(),
) )
fun mergeServerList(blossom: List<String>?): List<ServerName> = blossom?.map { ServerName(host(it), it, ServerType.Blossom) } ?: emptyList() fun mergeServerList(blossom: List<String>?): List<ServerName> = blossom?.map { ServerName(host(it), it, ServerType.Blossom) }?.ifEmpty { DEFAULT_MEDIA_SERVERS } ?: DEFAULT_MEDIA_SERVERS
val hostNameFlow: StateFlow<List<ServerName>> = val hostNameFlow: StateFlow<List<ServerName>> =
flow flow
@@ -49,6 +49,8 @@ class MultiOrchestrator(
fun hasVideo() = list.any { it.media.mimeType?.startsWith("video", ignoreCase = true) == true } fun hasVideo() = list.any { it.media.mimeType?.startsWith("video", ignoreCase = true) == true }
fun hasNonMedia() = list.any { it.media.isNotMedia() }
suspend fun upload( suspend fun upload(
alt: String?, alt: String?,
contentWarningReason: String?, contentWarningReason: String?,
@@ -54,6 +54,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
@@ -204,7 +205,8 @@ fun ImageVideoPost(
fileServers fileServers
.firstOrNull { it == accountViewModel.account.settings.defaultFileServer } .firstOrNull { it == accountViewModel.account.settings.defaultFileServer }
?.name ?.name
?: fileServers[0].name, ?: fileServers.firstOrNull()?.name
?: DEFAULT_MEDIA_SERVERS[0].name,
options = fileServerOptions, options = fileServerOptions,
onSelect = { postViewModel.selectedServer = fileServers[it] }, onSelect = { postViewModel.selectedServer = fileServers[it] },
) )
@@ -58,6 +58,11 @@ class SelectedMedia(
fun isAudio() = mimeType?.startsWith("audio") fun isAudio() = mimeType?.startsWith("audio")
fun isNotMedia() =
mimeType?.let {
!(it.startsWith("image") || it.startsWith("video") || it.startsWith("audio"))
} ?: true
fun isDocument() = mimeType == "application/pdf" fun isDocument() = mimeType == "application/pdf"
} }
@@ -129,7 +129,7 @@ fun ShowImageGallery(
icon = Icons.Default.AudioFile, icon = Icons.Default.AudioFile,
label = media.mimeType ?: "audio/*", label = media.mimeType ?: "audio/*",
) )
} else if (media.isDocument() == true) { } else if (media.isDocument()) {
FilePreviewPlaceholder( FilePreviewPlaceholder(
icon = Icons.Default.PictureAsPdf, icon = Icons.Default.PictureAsPdf,
label = media.mimeType ?: "application/pdf", label = media.mimeType ?: "application/pdf",
@@ -236,39 +236,44 @@ fun UploadingState(
progress: Double, progress: Double,
progressState: UploadingState, progressState: UploadingState,
) { ) {
Box(Modifier.size(55.dp), contentAlignment = Alignment.Center) { Box(
val animatedProgress by animateFloatAsState( contentAlignment = Alignment.Center,
targetValue = progress.toFloat(), modifier = Modifier.fillMaxSize(),
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, ) {
) Box(Modifier.size(55.dp), contentAlignment = Alignment.Center) {
val animatedProgress by animateFloatAsState(
targetValue = progress.toFloat(),
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
)
CircularProgressIndicator( CircularProgressIndicator(
progress = { animatedProgress }, progress = { animatedProgress },
modifier = modifier =
Size55Modifier Size55Modifier
.clip(CircleShape) .clip(CircleShape)
.background(MaterialTheme.colorScheme.background), .background(MaterialTheme.colorScheme.background),
strokeWidth = 5.dp, strokeWidth = 5.dp,
) )
val txt = val txt =
when (progressState) { when (progressState) {
is UploadingState.Ready -> stringRes(R.string.uploading_state_ready) is UploadingState.Ready -> stringRes(R.string.uploading_state_ready)
is UploadingState.Compressing -> stringRes(R.string.uploading_state_compressing) is UploadingState.Compressing -> stringRes(R.string.uploading_state_compressing)
is UploadingState.Uploading -> stringRes(R.string.uploading_state_uploading) is UploadingState.Uploading -> stringRes(R.string.uploading_state_uploading)
is UploadingState.ServerProcessing -> stringRes(R.string.uploading_state_server_processing) is UploadingState.ServerProcessing -> stringRes(R.string.uploading_state_server_processing)
is UploadingState.Downloading -> stringRes(R.string.uploading_state_downloading) is UploadingState.Downloading -> stringRes(R.string.uploading_state_downloading)
is UploadingState.Hashing -> stringRes(R.string.uploading_state_hashing) is UploadingState.Hashing -> stringRes(R.string.uploading_state_hashing)
is UploadingState.Finished -> stringRes(R.string.uploading_state_finished) is UploadingState.Finished -> stringRes(R.string.uploading_state_finished)
is UploadingState.Error -> stringRes(R.string.uploading_state_error) is UploadingState.Error -> stringRes(R.string.uploading_state_error)
} }
Text( Text(
txt, txt,
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
fontSize = 10.sp, fontSize = 10.sp,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )
}
} }
} }
@@ -280,7 +285,7 @@ fun FilePreviewPlaceholder(
Box( Box(
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxSize()
.background(MaterialTheme.colorScheme.surfaceVariant), .background(MaterialTheme.colorScheme.surfaceVariant),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
@@ -57,6 +57,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing
import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery
@@ -92,14 +93,16 @@ fun ImageVideoDescription(
var selectedServer by remember { var selectedServer by remember {
mutableStateOf( mutableStateOf(
fileServers.firstOrNull { it == defaultServer } ?: fileServers[0], fileServers.firstOrNull { it == defaultServer } ?: fileServers.firstOrNull() ?: DEFAULT_MEDIA_SERVERS[0],
) )
} }
var message by remember { mutableStateOf("") } var message by remember { mutableStateOf("") }
var sensitiveContent by remember { mutableStateOf(false) } var sensitiveContent by remember { mutableStateOf(false) }
// 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED // 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED
var mediaQualitySlider by remember { mutableIntStateOf(1) } var mediaQualitySlider by remember {
mutableIntStateOf(if (uris.hasNonMedia()) 3 else 1)
}
// Codec selection: false = H264, true = H265 // Codec selection: false = H264, true = H265
var useH265Codec by remember { mutableStateOf(false) } var useH265Codec by remember { mutableStateOf(false) }
@@ -189,7 +192,8 @@ fun ImageVideoDescription(
fileServers fileServers
.firstOrNull { it == defaultServer } .firstOrNull { it == defaultServer }
?.name ?.name
?: fileServers[0].name, ?: fileServers.firstOrNull()?.name
?: DEFAULT_MEDIA_SERVERS[0].name,
options = fileServerOptions, options = fileServerOptions,
onSelect = { selectedServer = fileServers[it] }, onSelect = { selectedServer = fileServers[it] },
modifier = modifier =
@@ -270,27 +274,31 @@ fun ImageVideoDescription(
} }
} }
Column(horizontalAlignment = Alignment.CenterHorizontally) { val firstMedia = uris.first().media
Box(modifier = Modifier.fillMaxWidth()) {
Text( if (firstMedia.isVideo() == true || firstMedia.isImage() == true || firstMedia.isAudio() == true) {
text = Column(horizontalAlignment = Alignment.CenterHorizontally) {
when (mediaQualitySlider) { Box(modifier = Modifier.fillMaxWidth()) {
0 -> stringRes(R.string.media_compression_quality_low) Text(
1 -> stringRes(R.string.media_compression_quality_medium) text =
2 -> stringRes(R.string.media_compression_quality_high) when (mediaQualitySlider) {
3 -> stringRes(R.string.media_compression_quality_uncompressed) 0 -> stringRes(R.string.media_compression_quality_low)
else -> stringRes(R.string.media_compression_quality_medium) 1 -> stringRes(R.string.media_compression_quality_medium)
}, 2 -> stringRes(R.string.media_compression_quality_high)
modifier = Modifier.align(Alignment.Center), 3 -> stringRes(R.string.media_compression_quality_uncompressed)
else -> stringRes(R.string.media_compression_quality_medium)
},
modifier = Modifier.align(Alignment.Center),
)
}
Slider(
value = mediaQualitySlider.toFloat(),
onValueChange = { mediaQualitySlider = it.toInt() },
valueRange = 0f..3f,
steps = 2,
) )
} }
Slider(
value = mediaQualitySlider.toFloat(),
onValueChange = { mediaQualitySlider = it.toInt() },
valueRange = 0f..3f,
steps = 2,
)
} }
if (uris.first().media.isVideo() == true) { if (uris.first().media.isVideo() == true) {
@@ -55,6 +55,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
import com.vitorpamplona.amethyst.ui.components.TextSpinner import com.vitorpamplona.amethyst.ui.components.TextSpinner
@@ -196,7 +197,8 @@ private fun ImageVideoPostChat(
fileServers fileServers
.firstOrNull { it == accountViewModel.account.settings.defaultFileServer } .firstOrNull { it == accountViewModel.account.settings.defaultFileServer }
?.name ?.name
?: fileServers[0].name, ?: fileServers.firstOrNull()?.name
?: DEFAULT_MEDIA_SERVERS[0].name,
options = fileServerOptions, options = fileServerOptions,
onSelect = { fileUploadState.selectedServer = fileServers[it] }, onSelect = { fileUploadState.selectedServer = fileServers[it] },
) )