mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-26 17:52:29 +01:00
Merge branch 'main' of https://github.com/vitorpamplona/amethyst
This commit is contained in:
commit
3e1184562f
@ -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)
|
||||
}
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user