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 869d944265..58a0d574f2 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 9f0c7d45fe..b0d3826a5f 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 818463a26d..0c8ac31372 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 9766d25eb6..cdce793689 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 da3b732004..c55c867c91 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 638e0a78b2..ad7402f663 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 af776e7564..055e24eb55 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 526fd47304..bf53e718d2 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 a466c3bcdd..f8a7a23371 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 67c24a6113..21d028a95d 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 bc8cc775d6..b702718936 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 318e70daea..32ccc21514 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 17899b3119..4363caeb76 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 ae3fc86a42..66d7d2b51b 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 deb2b0a4f6..588ae1a445 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