diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt index 869d94426..58a0d574f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt @@ -331,8 +331,8 @@ fun EditPostView( ImageVideoDescription( url, accountViewModel.account.settings.defaultFileServer, - onAdd = { alt, server, sensitiveContent -> - postViewModel.upload(url, alt, sensitiveContent, false, server, accountViewModel::toast, context) + onAdd = { alt, server, sensitiveContent, mediaQuality -> + postViewModel.upload(url, alt, sensitiveContent, mediaQuality, false, server, accountViewModel::toast, context) if (!server.isNip95) { accountViewModel.account.settings.changeDefaultFileServer(server.server) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt index 9f0c7d45f..b0d3826a5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt @@ -148,6 +148,7 @@ open class EditPostViewModel : ViewModel() { galleryUri: Uri, alt: String?, sensitiveContent: Boolean, + mediaQuality: Int, isPrivate: Boolean = false, server: ServerOption, onError: (String, String) -> Unit, @@ -223,6 +224,7 @@ open class EditPostViewModel : ViewModel() { isUploadingImage = false onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, it)) }, + mediaQuality = MediaCompressor().intToCompressorQuality(mediaQuality), ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt index 818463a26..0c8ac3137 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt @@ -79,6 +79,7 @@ open class NewMediaModel : ViewModel() { fun upload( context: Context, relayList: List? = null, + mediaQuality: Int, onError: (String) -> Unit = {}, ) { isUploadingImage = true @@ -166,6 +167,7 @@ open class NewMediaModel : ViewModel() { uploadingDescription.value = null onError(stringRes(context, R.string.error_when_compressing_media, it)) }, + mediaQuality = MediaCompressor().intToCompressorQuality(mediaQuality), ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index 9766d25eb..cdce79368 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -44,22 +44,26 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Slider import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties @@ -72,6 +76,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.FileServersEvent import kotlinx.collections.immutable.toImmutableList @@ -100,6 +105,7 @@ fun NewMediaView( var showRelaysDialog by remember { mutableStateOf(false) } var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() } + var mediaQualitySlider by remember { mutableIntStateOf(1) } // 0 = Low, 1 = Medium, 2 = High Dialog( onDismissRequest = { onClose() }, @@ -160,7 +166,7 @@ fun NewMediaView( PostButton( onPost = { onClose() - postViewModel.upload(context, relayList) { + postViewModel.upload(context, relayList, mediaQualitySlider) { accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it) } postViewModel.selectedServer?.let { @@ -180,6 +186,59 @@ fun NewMediaView( modifier = Modifier.fillMaxWidth().verticalScroll(scrollState), ) { ImageVideoPost(postViewModel, accountViewModel) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) + .padding(vertical = 8.dp), + ) { + Column( + modifier = Modifier.weight(1.0f), + verticalArrangement = Arrangement.spacedBy(Size5dp), + ) { + Text( + text = stringRes(context, R.string.media_compression_quality_label), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = stringRes(context, R.string.media_compression_quality_explainer), + style = MaterialTheme.typography.bodySmall, + color = Color.Gray, + maxLines = 4, + overflow = TextOverflow.Ellipsis, + ) + } + } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = + when (mediaQualitySlider) { + 0 -> stringRes(R.string.media_compression_quality_low) + 1 -> stringRes(R.string.media_compression_quality_medium) + 2 -> stringRes(R.string.media_compression_quality_high) + else -> stringRes(R.string.media_compression_quality_medium) + }, + modifier = Modifier.align(Alignment.Center), + ) + } + + Slider( + value = mediaQualitySlider.toFloat(), + onValueChange = { mediaQualitySlider = it.toInt() }, + valueRange = 0f..2f, + steps = 1, + ) + } + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index da3b73200..c55c867c9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -88,6 +88,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -493,8 +494,8 @@ fun NewPostView( ImageVideoDescription( url, accountViewModel.account.settings.defaultFileServer, - onAdd = { alt, server, sensitiveContent -> - postViewModel.upload(url, alt, sensitiveContent, false, server, accountViewModel::toast, context) + onAdd = { alt, server, sensitiveContent, mediaQuality -> + postViewModel.upload(url, alt, sensitiveContent, mediaQuality, false, server, accountViewModel::toast, context) if (!server.isNip95) { accountViewModel.account.settings.changeDefaultFileServer(server.server) } @@ -1610,7 +1611,7 @@ fun CreateButton( fun ImageVideoDescription( uri: Uri, defaultServer: Nip96MediaServers.ServerName, - onAdd: (String, ServerOption, Boolean) -> Unit, + onAdd: (String, ServerOption, Boolean, Int) -> Unit, onCancel: () -> Unit, onError: (Int) -> Unit, accountViewModel: AccountViewModel, @@ -1668,6 +1669,7 @@ fun ImageVideoDescription( } var message by remember { mutableStateOf("") } var sensitiveContent by remember { mutableStateOf(false) } + var mediaQualitySlider by remember { mutableIntStateOf(1) } // 0 = Low, 1 = Medium, 2 = High Column( modifier = @@ -1846,12 +1848,66 @@ fun ImageVideoDescription( ) } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) + .padding(vertical = 8.dp), + ) { + Column( + modifier = Modifier.weight(1.0f), + verticalArrangement = Arrangement.spacedBy(Size5dp), + ) { + Text( + text = stringRes(R.string.media_compression_quality_label), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = stringRes(R.string.media_compression_quality_explainer), + style = MaterialTheme.typography.bodySmall, + color = Color.Gray, + maxLines = 4, + overflow = TextOverflow.Ellipsis, + ) + } + } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = + when (mediaQualitySlider) { + 0 -> stringRes(R.string.media_compression_quality_low) + 1 -> stringRes(R.string.media_compression_quality_medium) + 2 -> stringRes(R.string.media_compression_quality_high) + else -> stringRes(R.string.media_compression_quality_medium) + }, + modifier = Modifier.align(Alignment.Center), + ) + } + + Slider( + value = mediaQualitySlider.toFloat(), + onValueChange = { mediaQualitySlider = it.toInt() }, + valueRange = 0f..2f, + steps = 1, + ) + } + } + Button( modifier = Modifier .fillMaxWidth() .padding(vertical = 10.dp), - onClick = { onAdd(message, selectedServer, sensitiveContent) }, + onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider) }, shape = QuoteBorder, colors = ButtonDefaults.buttonColors( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index 638e0a78b..ad7402f66 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -834,6 +834,7 @@ open class NewPostViewModel : ViewModel() { galleryUri: Uri, alt: String?, sensitiveContent: Boolean, + mediaQuality: Int, isPrivate: Boolean = false, server: ServerOption, onError: (title: String, message: String) -> Unit, @@ -909,6 +910,7 @@ open class NewPostViewModel : ViewModel() { isUploadingImage = false onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, it)) }, + mediaQuality = MediaCompressor().intToCompressorQuality(mediaQuality), ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt index af776e756..055e24eb5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt @@ -30,6 +30,7 @@ import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.Nip96Uploader +import com.vitorpamplona.amethyst.ui.components.CompressorQuality import com.vitorpamplona.amethyst.ui.components.MediaCompressor import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.events.GitHubIdentity @@ -211,6 +212,8 @@ class NewUserMetadataViewModel : ViewModel() { onError(stringRes(context, R.string.error_when_compressing_media), stringRes(context, it)) }, + // Use MEDIUM quality as default + mediaQuality = CompressorQuality.MEDIUM, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/MediaCompressor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/MediaCompressor.kt index 526fd4730..bf53e718d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/MediaCompressor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/MediaCompressor.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.components import android.content.Context import android.graphics.Bitmap import android.net.Uri +import android.util.Log import android.webkit.MimeTypeMap import androidx.core.net.toUri import com.abedelazizshe.lightcompressorlibrary.CompressionListener @@ -46,10 +47,20 @@ class MediaCompressor { applicationContext: Context, onReady: (Uri, String?, Long?) -> Unit, onError: (Int) -> Unit, + mediaQuality: CompressorQuality, ) { checkNotInMainThread() if (contentType?.startsWith("video", true) == true) { + val videoQuality = + when (mediaQuality) { + CompressorQuality.VERY_LOW -> VideoQuality.VERY_LOW + CompressorQuality.LOW -> VideoQuality.LOW + CompressorQuality.MEDIUM -> VideoQuality.MEDIUM + CompressorQuality.HIGH -> VideoQuality.HIGH + CompressorQuality.VERY_HIGH -> VideoQuality.VERY_HIGH + } + Log.d("MediaCompressor", "Using video compression $mediaQuality") VideoCompressor.start( // => This is required context = applicationContext, @@ -65,7 +76,7 @@ class MediaCompressor { appSpecificStorageConfiguration = AppSpecificStorageConfiguration(), configureWith = Configuration( - quality = VideoQuality.MEDIUM, + quality = videoQuality, // => required name videoNames = listOf(UUID.randomUUID().toString()), ), @@ -110,10 +121,19 @@ class MediaCompressor { !contentType.contains("gif") && !contentType.contains("svg") ) { + val imageQuality = + when (mediaQuality) { + CompressorQuality.VERY_LOW -> 40 + CompressorQuality.LOW -> 50 + CompressorQuality.MEDIUM -> 60 + CompressorQuality.HIGH -> 80 + CompressorQuality.VERY_HIGH -> 90 + } try { + Log.d("MediaCompressor", "Using image compression $mediaQuality") val compressedImageFile = Compressor.compress(applicationContext, from(uri, contentType, applicationContext)) { - default(width = 640, format = Bitmap.CompressFormat.JPEG) + default(width = 640, format = Bitmap.CompressFormat.JPEG, quality = imageQuality) } onReady(compressedImageFile.toUri(), contentType, compressedImageFile.length()) } catch (e: Exception) { @@ -162,4 +182,28 @@ class MediaCompressor { } return arrayOf(name, extension) } + + fun intToCompressorQuality(mediaQualityFloat: Int): CompressorQuality = + when (mediaQualityFloat) { + 0 -> CompressorQuality.LOW + 1 -> CompressorQuality.MEDIUM + 2 -> CompressorQuality.HIGH + else -> CompressorQuality.MEDIUM + } + + fun compressorQualityToInt(compressorQuality: CompressorQuality): Int = + when (compressorQuality) { + CompressorQuality.LOW -> 0 + CompressorQuality.MEDIUM -> 1 + CompressorQuality.HIGH -> 2 + else -> 1 + } +} + +enum class CompressorQuality { + VERY_LOW, + LOW, + MEDIUM, + HIGH, + VERY_HIGH, } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt index a466c3bcd..f8a7a2337 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt @@ -116,7 +116,9 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel import com.vitorpamplona.amethyst.ui.actions.ServerOption import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation +import com.vitorpamplona.amethyst.ui.components.CompressorQuality import com.vitorpamplona.amethyst.ui.components.LoadNote +import com.vitorpamplona.amethyst.ui.components.MediaCompressor import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer @@ -514,6 +516,8 @@ fun EditFieldRow( galleryUri = it, alt = null, sensitiveContent = false, + // Use MEDIUM quality + mediaQuality = MediaCompressor().compressorQualityToInt(CompressorQuality.MEDIUM), server = ServerOption(accountViewModel.account.settings.defaultFileServer, false), onError = accountViewModel::toast, context = context, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt index 67c24a611..21d028a95 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt @@ -92,6 +92,8 @@ import com.vitorpamplona.amethyst.ui.actions.PostButton import com.vitorpamplona.amethyst.ui.actions.ServerOption import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation +import com.vitorpamplona.amethyst.ui.components.CompressorQuality +import com.vitorpamplona.amethyst.ui.components.MediaCompressor import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.TopBarExtensibleWithBackButton import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture @@ -584,6 +586,8 @@ fun PrivateMessageEditFieldRow( galleryUri = it, alt = null, sensitiveContent = false, + // use MEDIUM quality + mediaQuality = MediaCompressor().compressorQualityToInt(CompressorQuality.MEDIUM), isPrivate = isPrivate, server = ServerOption(accountViewModel.account.settings.defaultFileServer, false), onError = accountViewModel::toast, diff --git a/amethyst/src/main/res/values-cs/strings.xml b/amethyst/src/main/res/values-cs/strings.xml index bc8cc775d..b70271893 100644 --- a/amethyst/src/main/res/values-cs/strings.xml +++ b/amethyst/src/main/res/values-cs/strings.xml @@ -817,4 +817,10 @@ Stáhnout Nepodařilo se otevřít soubor Pro otevření a stažení souboru nejsou nainstalovány žádné torrent aplikace. + Kvalita médií + Vyberte Nízkou kvalitu pro kompresi médií na menší soubor s nižší kvalitou, nebo vyberte Vysokou kvalitu pro kompresi na větší soubor s vyšší kvalitou. + Nízká + Střední + Vysoká + diff --git a/amethyst/src/main/res/values-de/strings.xml b/amethyst/src/main/res/values-de/strings.xml index 318e70dae..32ccc2151 100644 --- a/amethyst/src/main/res/values-de/strings.xml +++ b/amethyst/src/main/res/values-de/strings.xml @@ -822,4 +822,9 @@ anz der Bedingungen ist erforderlich Herunterladen Fehler beim Öffnen der Datei Keine Torrent-Apps installiert, um die Datei zu öffnen und herunterzuladen. + Medienqualität + Wählen Sie Niedrige Qualität, um Ihre Medien in eine kleinere Datei mit geringerer Qualität zu komprimieren, oder wählen Sie Hohe Qualität, um sie in eine größere Datei mit höherer Qualität zu komprimieren. + Niedrig + Mittel + Hoch diff --git a/amethyst/src/main/res/values-pt-rBR/strings.xml b/amethyst/src/main/res/values-pt-rBR/strings.xml index 17899b311..4363caeb7 100644 --- a/amethyst/src/main/res/values-pt-rBR/strings.xml +++ b/amethyst/src/main/res/values-pt-rBR/strings.xml @@ -817,4 +817,9 @@ Baixar Falha ao abrir o arquivo Nenhum aplicativo torrent instalado para abrir e baixar o arquivo. + Qualidade de Mídia + Selecione Baixa qualidade para comprimir sua mídia para um arquivo menor com menor qualidade ou selecione Alta qualidade para comprimir para um arquivo maior com maior qualidade. + Baixa + Média + Alta diff --git a/amethyst/src/main/res/values-sv-rSE/strings.xml b/amethyst/src/main/res/values-sv-rSE/strings.xml index ae3fc86a4..66d7d2b51 100644 --- a/amethyst/src/main/res/values-sv-rSE/strings.xml +++ b/amethyst/src/main/res/values-sv-rSE/strings.xml @@ -816,4 +816,10 @@ Ladda ner Det gick inte att öppna filen Inga torrent-appar installerade för att öppna och ladda ner filen. + + Mediakvalitet + Välj Låg kvalitet för att komprimera ditt media till en mindre fil med lägre kvalitet, eller välj Hög kvalitet för att komprimera till en större fil med högre kvalitet. + Låg + Medel + Hög diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index deb2b0a4f..588ae1a44 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -791,6 +791,12 @@ Compression Cancelled Compression failed to return a file + Media Quality + Select Low quality to compress your media to a smaller file with less quality or select High quality to compress to a larger file with higher quality. + Low + Medium + High + Edit draft Login with QR Code