This commit is contained in:
Vitor Pamplona 2024-09-05 13:47:33 -04:00
commit 3e1184562f
15 changed files with 211 additions and 9 deletions

View File

@ -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)
}

View File

@ -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),
)
}
}

View File

@ -79,6 +79,7 @@ open class NewMediaModel : ViewModel() {
fun upload(
context: Context,
relayList: List<RelaySetupInfo>? = 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),
)
}
}

View File

@ -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,
)
}
}
}
}
}

View File

@ -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(

View File

@ -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),
)
}
}

View File

@ -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,
)
}
}

View File

@ -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,
}

View File

@ -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,

View File

@ -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,

View File

@ -665,6 +665,11 @@
<string name="could_not_prepare_header">Nelze připravit informace v záhlaví: %1$s</string>
<string name="compression_cancelled">Komprese zrušena</string>
<string name="compression_returned_null">Komprese se nepodařilo vrátit soubor</string>
<string name="media_compression_quality_label">Kvalita médií</string>
<string name="media_compression_quality_explainer">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.</string>
<string name="media_compression_quality_low">Nízká</string>
<string name="media_compression_quality_medium">Střední</string>
<string name="media_compression_quality_high">Vysoká</string>
<string name="edit_draft">Upravit koncept</string>
<string name="login_with_qr_code">Přihlášení pomocí QR kódu</string>
<string name="route">Trasa</string>

View File

@ -670,6 +670,11 @@ anz der Bedingungen ist erforderlich</string>
<string name="could_not_prepare_header">Konnte Kopfzeileninformationen nicht vorbereiten: %1$s</string>
<string name="compression_cancelled">Komprimierung abgebrochen</string>
<string name="compression_returned_null">Komprimierung fehlgeschlagen eine Datei zurückzugeben</string>
<string name="media_compression_quality_label">Medienqualität</string>
<string name="media_compression_quality_explainer">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.</string>
<string name="media_compression_quality_low">Niedrig</string>
<string name="media_compression_quality_medium">Mittel</string>
<string name="media_compression_quality_high">Hoch</string>
<string name="edit_draft">Entwurf bearbeiten</string>
<string name="login_with_qr_code">Einloggen mit QR-Code</string>
<string name="route">Route</string>

View File

@ -665,6 +665,11 @@
<string name="could_not_prepare_header">Não foi possível preparar informações do cabeçalho: %1$s</string>
<string name="compression_cancelled">Compressão cancelada</string>
<string name="compression_returned_null">Compressão falhou ao retornar um arquivo</string>
<string name="media_compression_quality_label">Qualidade de Mídia</string>
<string name="media_compression_quality_explainer">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.</string>
<string name="media_compression_quality_low">Baixa</string>
<string name="media_compression_quality_medium">Média</string>
<string name="media_compression_quality_high">Alta</string>
<string name="edit_draft">Editar rascunho</string>
<string name="login_with_qr_code">Entrar com Código QR</string>
<string name="route">Rota</string>

View File

@ -664,6 +664,11 @@
<string name="could_not_prepare_header">Kunde inte förbereda header information: %1$s</string>
<string name="compression_cancelled">Komprimering avbruten</string>
<string name="compression_returned_null">Komprimering misslyckades att returnera en fil</string>
<string name="media_compression_quality_label">Mediakvalitet</string>
<string name="media_compression_quality_explainer">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.</string>
<string name="media_compression_quality_low">Låg</string>
<string name="media_compression_quality_medium">Medel</string>
<string name="media_compression_quality_high">Hög</string>
<string name="edit_draft">Redigera utkast</string>
<string name="login_with_qr_code">Logga in med QR-kod</string>
<string name="route">Rutt</string>

View File

@ -791,6 +791,12 @@
<string name="compression_cancelled">Compression Cancelled</string>
<string name="compression_returned_null">Compression failed to return a file</string>
<string name="media_compression_quality_label">Media Quality</string>
<string name="media_compression_quality_explainer">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.</string>
<string name="media_compression_quality_low">Low</string>
<string name="media_compression_quality_medium">Medium</string>
<string name="media_compression_quality_high">High</string>
<string name="edit_draft">Edit draft</string>
<string name="login_with_qr_code">Login with QR Code</string>