Merge branch 'vitorpamplona:main' into followset-improvements

This commit is contained in:
KotlinGeekDev
2025-10-03 14:39:45 +00:00
committed by GitHub
30 changed files with 335 additions and 73 deletions
+29 -18
View File
@@ -242,16 +242,16 @@ openssl base64 < <my-release-key.keystore> | tr -d '\n' | tee some_signing_key.j
Add the following line to your `commonMain` dependencies: Add the following line to your `commonMain` dependencies:
```gradle ```gradle
implementation('com.github.vitorpamplona.amethyst:quartz:<Amethyst Version>') implementation('com.vitorpamplona.quartz:quartz:<Amethyst Version>')
``` ```
Variations to each platform are also available: Variations to each platform are also available:
```gradle ```gradle
implementation('com.github.vitorpamplona.amethyst:quartz-android:<Amethyst Version>') implementation('com.vitorpamplona.quartz:quartz-android:<Amethyst Version>')
implementation('com.github.vitorpamplona.amethyst:quartz-jvm:<Amethyst Version>') implementation('com.vitorpamplona.quartz:quartz-jvm:<Amethyst Version>')
implementation('com.github.vitorpamplona.amethyst:quartz-iosarm64:<Amethyst Version>') implementation('com.vitorpamplona.quartz:quartz-iosarm64:<Amethyst Version>')
implementation('com.github.vitorpamplona.amethyst:quartz-iossimulatorarm64:<Amethyst Version>') implementation('com.vitorpamplona.quartz:quartz-iossimulatorarm64:<Amethyst Version>')
``` ```
### How to use ### How to use
@@ -259,25 +259,25 @@ implementation('com.github.vitorpamplona.amethyst:quartz-iossimulatorarm64:<Amet
Manage logged in users with the `KeyPair` class Manage logged in users with the `KeyPair` class
```kt ```kt
val keys = KeyPair() // creates a random key val keyPair = KeyPair() // creates a random key
val keys = KeyPair("hex...".hexToByteArray()) val keyPair = KeyPair("hex...".hexToByteArray())
val keys = KeyPair("nsec1...".bechToBytes()) val keyPair = KeyPair("nsec1...".bechToBytes())
val keys = KeyPair(Nip06().privateKeyFromMnemonic("<mnemonic>")) val keyPair = KeyPair(Nip06().privateKeyFromMnemonic("<mnemonic>"))
val readOnly = KeyPair(pubKey = "hex...".hexToByteArray()) val readOnly = KeyPair(pubKey = "hex...".hexToByteArray())
val readOnly = KeyPair(pubKey = "npub1...".bechToBytes()) val readOnly = KeyPair(pubKey = "npub1...".bechToBytes())
``` ```
Create signers that can be internal, when you have the private key or when it is a read-only user Create signers that can be Internal, when you have the private key or a read-only public key,
or external, when it is controlled by Amber in NIP-55 or External, when it is controlled by Amber in NIP-55.
the `NostrSignerInternal` and `NostrSignerExternal` classes. Use either the `NostrSignerInternal` or `NostrSignerExternal` class:
```kt ```kt
val signer = NostrSignerInternal(keyPair) val signer = NostrSignerInternal(keyPair)
val amberSigner = NostrSignerExternal( val amberSigner = NostrSignerExternal(
pubKey = keyPair.pubKey.toHexKey(), pubKey = keyPair.pubKey.toHexKey(),
packageName = signerPackageName, packageName = signerPackageName, // Amber package name
contentResolver = appContext.contentResolver, contentResolver = appContext.contentResolver,
) )
``` ```
@@ -299,16 +299,19 @@ val client = NostrClient(socketBuilder, appScope)
If you want to auth, given a logged-in `signer`: If you want to auth, given a logged-in `signer`:
```kt ```kt
val authCoordinator = RelayAuthenticator(client, applicationIOScope) { challenge, relay -> val authCoordinator = RelayAuthenticator(client, appScope) { challenge, relay ->
val authedEvent = RelayAuthEvent.create(relayUrl, challenge, signer) val authedEvent = RelayAuthEvent.create(relay.url, challenge, signer)
client.sendIfExists(authedEvent, relay.url) client.sendIfExists(authedEvent, relay.url)
} }
``` ```
To manage subscriptions, the suggested approach is to use subscriptions in the Application class. To manage subscriptions, the simplest approach is to build mutable subscriptions in
the Application class. To use the best of the outbox model, this class allows you to
build filters for as many relays as needed. The `NostrClient` will connect to the
complete set of relays for all subscriptions.
```kt ```kt
val metadataSub = RelayClientSubscription( val metadataSub = NostrClientSubscription(
client = client, client = client,
filter = { filter = {
val filters = listOf( val filters = listOf(
@@ -330,6 +333,14 @@ val metadataSub = RelayClientSubscription(
} }
``` ```
In that way, you can simply call `metadataSub.updateFilter()` when you need to update
subscriptions to all relays. Or call `metadataSub.closeSubscription()` to stop the sub
without deleting it.
When your app goes to the background, you can use NostrClient's `connect` and `disconnect`
methods to stop all communication to relays. Add the `connect` to your `onResume` and `disconnect`
to `onPause` methods.
## Contributing ## Contributing
Issues can be logged on: [https://gitworkshop.dev/repo/amethyst](https://gitworkshop.dev/repo/amethyst) Issues can be logged on: [https://gitworkshop.dev/repo/amethyst](https://gitworkshop.dev/repo/amethyst)
+6
View File
@@ -151,6 +151,12 @@ android {
signingConfig = signingConfigs.debug signingConfig = signingConfigs.debug
} }
} }
// TODO: remove this when lightcompressor uses one MP4 parser only
packaging {
resources {
resources.pickFirsts.add('builddef.lst')
}
}
flavorDimensions = ["channel"] flavorDimensions = ["channel"]
@@ -118,7 +118,6 @@ import com.vitorpamplona.quartz.experimental.profileGallery.mimeType
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.value
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
@@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
@@ -45,6 +45,7 @@ class MediaCompressor {
contentType: String?, contentType: String?,
mediaQuality: CompressorQuality, mediaQuality: CompressorQuality,
applicationContext: Context, applicationContext: Context,
useH265: Boolean = false,
): MediaCompressorResult { ): MediaCompressorResult {
// Skip compression if user selected uncompressed // Skip compression if user selected uncompressed
if (mediaQuality == CompressorQuality.UNCOMPRESSED) { if (mediaQuality == CompressorQuality.UNCOMPRESSED) {
@@ -57,7 +58,7 @@ class MediaCompressor {
// branch into compression based on content type // branch into compression based on content type
return when { return when {
contentType?.startsWith("video", ignoreCase = true) == true -> { contentType?.startsWith("video", ignoreCase = true) == true -> {
VideoCompressionHelper.compressVideo(uri, contentType, applicationContext, mediaQuality) VideoCompressionHelper.compressVideo(uri, contentType, applicationContext, mediaQuality, useH265)
} }
contentType?.startsWith("image", ignoreCase = true) == true && contentType?.startsWith("image", ignoreCase = true) == true &&
!contentType.contains("gif") && !contentType.contains("gif") &&
@@ -46,6 +46,8 @@ class MultiOrchestrator(
fun first() = list.first() fun first() = list.first()
fun hasVideo() = list.any { it.media.mimeType?.startsWith("video", ignoreCase = true) == true }
suspend fun upload( suspend fun upload(
alt: String?, alt: String?,
contentWarningReason: String?, contentWarningReason: String?,
@@ -53,6 +55,7 @@ class MultiOrchestrator(
server: ServerName, server: ServerName,
account: Account, account: Account,
context: Context, context: Context,
useH265: Boolean = false,
): Result { ): Result {
coroutineScope { coroutineScope {
val jobs = val jobs =
@@ -67,6 +70,7 @@ class MultiOrchestrator(
server, server,
account, account,
context, context,
useH265,
) )
} }
} }
@@ -85,6 +89,7 @@ class MultiOrchestrator(
server: ServerName, server: ServerName,
account: Account, account: Account,
context: Context, context: Context,
useH265: Boolean = false,
): Result { ): Result {
coroutineScope { coroutineScope {
val jobs = val jobs =
@@ -100,6 +105,7 @@ class MultiOrchestrator(
server, server,
account, account,
context, context,
useH265,
) )
} }
} }
@@ -288,9 +288,10 @@ class UploadOrchestrator {
mimeType: String?, mimeType: String?,
compressionQuality: CompressorQuality, compressionQuality: CompressorQuality,
context: Context, context: Context,
useH265: Boolean = false,
) = if (compressionQuality != CompressorQuality.UNCOMPRESSED) { ) = if (compressionQuality != CompressorQuality.UNCOMPRESSED) {
updateState(0.02, UploadingState.Compressing) updateState(0.02, UploadingState.Compressing)
MediaCompressor().compress(uri, mimeType, compressionQuality, context.applicationContext) MediaCompressor().compress(uri, mimeType, compressionQuality, context.applicationContext, useH265)
} else { } else {
MediaCompressorResult(uri, mimeType, null) MediaCompressorResult(uri, mimeType, null)
} }
@@ -304,8 +305,9 @@ class UploadOrchestrator {
server: ServerName, server: ServerName,
account: Account, account: Account,
context: Context, context: Context,
useH265: Boolean = false,
): UploadingFinalState { ): UploadingFinalState {
val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context) val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265)
return when (server.type) { return when (server.type) {
ServerType.NIP95 -> uploadNIP95(compressed.uri, compressed.contentType, null, null, context) ServerType.NIP95 -> uploadNIP95(compressed.uri, compressed.contentType, null, null, context)
@@ -324,8 +326,9 @@ class UploadOrchestrator {
server: ServerName, server: ServerName,
account: Account, account: Account,
context: Context, context: Context,
useH265: Boolean = false,
): UploadingFinalState { ): UploadingFinalState {
val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context) val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265)
val encrypted = EncryptFiles().encryptFile(context, compressed.uri, encrypt) val encrypted = EncryptFiles().encryptFile(context, compressed.uri, encrypt)
return when (server.type) { return when (server.type) {
@@ -29,6 +29,7 @@ import android.text.format.Formatter.formatFileSize
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import com.abedelazizshe.lightcompressorlibrary.CompressionListener import com.abedelazizshe.lightcompressorlibrary.CompressionListener
import com.abedelazizshe.lightcompressorlibrary.VideoCodec
import com.abedelazizshe.lightcompressorlibrary.VideoCompressor import com.abedelazizshe.lightcompressorlibrary.VideoCompressor
import com.abedelazizshe.lightcompressorlibrary.config.AppSpecificStorageConfiguration import com.abedelazizshe.lightcompressorlibrary.config.AppSpecificStorageConfiguration
import com.abedelazizshe.lightcompressorlibrary.config.Configuration import com.abedelazizshe.lightcompressorlibrary.config.Configuration
@@ -88,11 +89,18 @@ data class CompressionRule(
val bitrateMbps: Float, val bitrateMbps: Float,
val description: String, val description: String,
) { ) {
fun getBitrateBps(framerate: Float): Int { fun getBitrateBps(
// Apply 1.5x multiplier for 60fps+ videos framerate: Float,
val multiplier = if (framerate >= 60f) 1.5f else 1.0f useH265: Boolean,
): Int {
// Apply 1.3x multiplier for 60fps+ videos, 0.7x multiplier for H265
val framerateMultiplier = if (framerate >= 60f) 1.3f else 1.0f
val codecMultiplier = if (useH265) 0.7f else 1.0f
val finalMultiplier = framerateMultiplier * codecMultiplier
return (bitrateMbps * multiplier * MBPS_TO_BPS_MULTIPLIER).toInt() Log.d("VideoCompressionHelper", "framerate: $framerate, useH265: $useH265, Bitrate multiplier: $finalMultiplier")
return (bitrateMbps * finalMultiplier * MBPS_TO_BPS_MULTIPLIER).toInt()
} }
} }
@@ -141,6 +149,7 @@ object VideoCompressionHelper {
contentType: String?, contentType: String?,
applicationContext: Context, applicationContext: Context,
mediaQuality: CompressorQuality, mediaQuality: CompressorQuality,
useH265: Boolean,
timeoutMs: Long = 60_000L, // configurable, default 60s timeoutMs: Long = 60_000L, // configurable, default 60s
): MediaCompressorResult { ): MediaCompressorResult {
val videoInfo = getVideoInfo(uri, applicationContext) val videoInfo = getVideoInfo(uri, applicationContext)
@@ -152,8 +161,8 @@ object VideoCompressionHelper {
.getValue(mediaQuality) .getValue(mediaQuality)
.getValue(info.resolution.getStandard()) .getValue(info.resolution.getStandard())
val bitrateBps = rule.getBitrateBps(info.framerate) val bitrateBps = rule.getBitrateBps(info.framerate, useH265)
Log.d(LOG_TAG, "Bitrate: ${bitrateBps}bps for ${info.resolution.getStandard()} quality=$mediaQuality framerate=${info.framerate}fps.") Log.d(LOG_TAG, "Bitrate: ${bitrateBps}bps for ${info.resolution.getStandard()} quality=$mediaQuality framerate=${info.framerate}fps useH265=$useH265.")
Log.d( Log.d(
LOG_TAG, LOG_TAG,
@@ -186,6 +195,7 @@ object VideoCompressionHelper {
resizer = resizer, resizer = resizer,
videoNames = listOf(UUID.randomUUID().toString()), videoNames = listOf(UUID.randomUUID().toString()),
isMinBitrateCheckEnabled = false, isMinBitrateCheckEnabled = false,
videoCodec = if (useH265) VideoCodec.H265 else VideoCodec.H264,
), ),
listener = listener =
object : CompressionListener { object : CompressionListener {
@@ -54,7 +54,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -255,13 +254,13 @@ fun EditPostView(
postViewModel.multiOrchestrator?.let { postViewModel.multiOrchestrator?.let {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) { ) {
ImageVideoDescription( ImageVideoDescription(
it, it,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel.toastManager::toast, context) postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) { if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server) accountViewModel.account.settings.changeDefaultFileServer(server)
@@ -279,7 +278,7 @@ fun EditPostView(
if (lud16 != null && postViewModel.wantsInvoice) { if (lud16 != null && postViewModel.wantsInvoice) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) { ) {
InvoiceRequest( InvoiceRequest(
@@ -87,6 +87,9 @@ open class EditPostViewModel : ViewModel() {
// Images and Videos // Images and Videos
var multiOrchestrator by mutableStateOf<MultiOrchestrator?>(null) var multiOrchestrator by mutableStateOf<MultiOrchestrator?>(null)
// Codec selection: false = H264, true = H265
var useH265Codec by mutableStateOf(false)
// Invoices // Invoices
var canAddInvoice by mutableStateOf(false) var canAddInvoice by mutableStateOf(false)
var wantsInvoice by mutableStateOf(false) var wantsInvoice by mutableStateOf(false)
@@ -201,6 +204,7 @@ open class EditPostViewModel : ViewModel() {
server, server,
myAccount, myAccount,
context, context,
useH265Codec,
) )
if (results.allGood) { if (results.allGood) {
@@ -62,6 +62,9 @@ open class NewMediaModel : ViewModel() {
// 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED // 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED
var mediaQualitySlider by mutableIntStateOf(1) var mediaQualitySlider by mutableIntStateOf(1)
// Codec selection: false = H264, true = H265
var useH265Codec by mutableStateOf(false)
open fun load( open fun load(
account: Account, account: Account,
uris: ImmutableList<SelectedMedia>, uris: ImmutableList<SelectedMedia>,
@@ -111,6 +114,7 @@ open class NewMediaModel : ViewModel() {
serverToUse, serverToUse,
myAccount, myAccount,
context, context,
useH265Codec,
) )
if (results.allGood) { if (results.allGood) {
@@ -261,4 +261,18 @@ fun ImageVideoPost(
steps = 2, steps = 2,
) )
} }
// Only show H.265 codec option if there are videos in the upload
if (postViewModel.multiOrchestrator?.hasVideo() == true) {
SettingSwitchItem(
title = R.string.video_codec_h265_label,
description = R.string.video_codec_h265_description,
modifier =
Modifier
.fillMaxWidth()
.padding(top = 8.dp),
checked = postViewModel.useH265Codec,
onCheckedChange = { postViewModel.useH265Codec = it },
)
}
} }
@@ -75,7 +75,7 @@ fun SettingSwitchItem(
text = stringRes(id = description), text = stringRes(id = description),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = Color.Gray, color = Color.Gray,
maxLines = 2, maxLines = 3,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
} }
@@ -78,7 +78,7 @@ import kotlinx.collections.immutable.toImmutableList
fun ImageVideoDescription( fun ImageVideoDescription(
uris: MultiOrchestrator, uris: MultiOrchestrator,
defaultServer: ServerName, defaultServer: ServerName,
onAdd: (String, ServerName, Boolean, Int) -> Unit, onAdd: (String, ServerName, Boolean, Int, Boolean) -> Unit,
onDelete: (SelectedMediaProcessing) -> Unit, onDelete: (SelectedMediaProcessing) -> Unit,
onCancel: () -> Unit, onCancel: () -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -91,7 +91,7 @@ fun ImageVideoDescription(
uris: MultiOrchestrator, uris: MultiOrchestrator,
defaultServer: ServerName, defaultServer: ServerName,
includeNIP95: Boolean, includeNIP95: Boolean,
onAdd: (String, ServerName, Boolean, Int) -> Unit, onAdd: (String, ServerName, Boolean, Int, Boolean) -> Unit,
onDelete: (SelectedMediaProcessing) -> Unit, onDelete: (SelectedMediaProcessing) -> Unit,
onCancel: () -> Unit, onCancel: () -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -128,6 +128,9 @@ fun ImageVideoDescription(
// 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(1) }
// Codec selection: false = H264, true = H265
var useH265Codec by remember { mutableStateOf(false) }
Column( Column(
modifier = modifier =
Modifier Modifier
@@ -294,32 +297,40 @@ fun ImageVideoDescription(
} }
} }
Row( Column(horizontalAlignment = Alignment.CenterHorizontally) {
verticalAlignment = Alignment.CenterVertically, Box(modifier = Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(), Text(
) { 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) {
SettingSwitchItem(
title = R.string.video_codec_h265_label,
description = R.string.video_codec_h265_description,
modifier =
Modifier
.fillMaxWidth()
.padding(top = 8.dp),
checked = useH265Codec,
onCheckedChange = { useH265Codec = it },
)
} }
Button( Button(
@@ -327,7 +338,7 @@ fun ImageVideoDescription(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 10.dp), .padding(vertical = 10.dp),
onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider) }, onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider, useH265Codec) },
shape = QuoteBorder, shape = QuoteBorder,
colors = colors =
ButtonDefaults.buttonColors( ButtonDefaults.buttonColors(
@@ -285,7 +285,7 @@ private fun GenericCommentPostBody(
ImageVideoDescription( ImageVideoDescription(
it, it,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) { if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server) accountViewModel.account.settings.changeDefaultFileServer(server)
@@ -278,7 +278,7 @@ fun GroupDMScreenContent(
ImageVideoDescription( ImageVideoDescription(
selectedFiles, selectedFiles,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.uploadAndHold( postViewModel.uploadAndHold(
accountViewModel.toastManager::toast, accountViewModel.toastManager::toast,
context, context,
@@ -266,7 +266,7 @@ private fun NewProductBody(
uris = it, uris = it,
defaultServer = accountViewModel.account.settings.defaultFileServer, defaultServer = accountViewModel.account.settings.defaultFileServer,
includeNIP95 = false, includeNIP95 = false,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) { if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server) accountViewModel.account.settings.changeDefaultFileServer(server)
@@ -326,8 +326,8 @@ private fun NewPostScreenBody(
ImageVideoDescription( ImageVideoDescription(
it, it,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, useH265 ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context, useH265)
if (server.type != ServerType.NIP95) { if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server) accountViewModel.account.settings.changeDefaultFileServer(server)
} }
@@ -624,8 +624,9 @@ open class ShortNotePostViewModel :
server: ServerName, server: ServerName,
onError: (title: String, message: String) -> Unit, onError: (title: String, message: String) -> Unit,
context: Context, context: Context,
useH265: Boolean,
) = try { ) = try {
uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context) uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context, useH265)
} catch (_: SignerExceptions.ReadOnlyException) { } catch (_: SignerExceptions.ReadOnlyException) {
onError( onError(
stringRes(context, R.string.read_only_user), stringRes(context, R.string.read_only_user),
@@ -640,6 +641,7 @@ open class ShortNotePostViewModel :
server: ServerName, server: ServerName,
onError: (title: String, message: String) -> Unit, onError: (title: String, message: String) -> Unit,
context: Context, context: Context,
useH265: Boolean,
) { ) {
viewModelScope.launch(Dispatchers.Default) { viewModelScope.launch(Dispatchers.Default) {
val myMultiOrchestrator = multiOrchestrator ?: return@launch val myMultiOrchestrator = multiOrchestrator ?: return@launch
@@ -654,6 +656,7 @@ open class ShortNotePostViewModel :
server, server,
account, account,
context, context,
useH265,
) )
if (results.allGood) { if (results.allGood) {
@@ -231,7 +231,7 @@ fun PublicMessageScreenContent(
ImageVideoDescription( ImageVideoDescription(
it, it,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) { if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server) accountViewModel.account.settings.changeDefaultFileServer(server)
@@ -952,4 +952,35 @@
<string name="guidelines">Pokyny</string> <string name="guidelines">Pokyny</string>
<string name="moderators">Moderátoři</string> <string name="moderators">Moderátoři</string>
<string name="open_dropdown_menu">Otevřít rozbalovací nabídku</string> <string name="open_dropdown_menu">Otevřít rozbalovací nabídku</string>
<string name="follow_sets">Sady sledování</string>
<string name="labeled_bookmarks">Označené záložky</string>
<string name="general_bookmarks">Obecné záložky</string>
<string name="follow_set_type_public">Veřejné</string>
<string name="follow_set_type_private">Soukromé</string>
<string name="follow_set_type_mixed">Smíšené</string>
<string name="follow_set_empty_feed_msg">Zdá se, že zatím nemáte žádné sady sledování.\nKlepněte níže pro obnovení nebo použijte tlačítko přidat k vytvoření nové.</string>
<string name="follow_set_add_author_from_note_action">Přidat autora do sady sledování</string>
<string name="follow_set_profile_actions_menu_description">Přidat nebo odebrat uživatele ze seznamů, nebo vytvořit nový seznam s tímto uživatelem.</string>
<string name="follow_set_type_description">Ikona pro seznam %1$s</string>
<string name="follow_set_presence_indicator">%1$s je v tomto seznamu</string>
<string name="follow_set_absence_indicator">%1$s není v tomto seznamu</string>
<string name="follow_set_man_dialog_title">Vaše sady sledování</string>
<string name="follow_set_empty_dialog_msg">Nebyly nalezeny žádné sady sledování, nebo žádné nemáte. Klepněte níže pro obnovení nebo použijte menu pro vytvoření nové.</string>
<string name="follow_set_error_dialog_msg">Došlo k problému při načítání: %1$s</string>
<string name="follow_set_creation_menu_title">Vytvořit nový seznam</string>
<string name="follow_set_creation_item_label">Vytvořit nový seznam %1$s s uživatelem</string>
<string name="follow_set_creation_item_description">Vytvoří %1$s sadu sledování a přidá do ní %2$s.</string>
<string name="follow_set_creation_dialog_title">Nový seznam %1$s</string>
<string name="follow_set_creation_name_label">Název sady</string>
<string name="follow_set_creation_desc_label">Popis sady (volitelné)</string>
<string name="follow_set_creation_action_btn_label">Vytvořit sadu</string>
<string name="follow_set_rename_btn_label">Přejmenovat sadu</string>
<string name="follow_set_rename_dialog_indicator_first_part">Přejmenováváte z </string>
<string name="follow_set_rename_dialog_indicator_second_part"> na..</string>
<string name="rename">Přejmenovat</string>
<string name="torrent_no_info">Událost nemá dostatek informací pro vytvoření magnet odkazu</string>
<string name="my_lists_and_sets">Moje seznamy/sady</string>
<string name="select_signer">Vybrat podepisovatele</string>
<string name="video_codec_h265_label">Použít kodek H.265/HEVC</string>
<string name="video_codec_h265_description">Lepší kvalita při menší velikosti souboru, ale ne všechna zařízení podporují přehrávání H.265.</string>
</resources> </resources>
@@ -992,4 +992,35 @@ anz der Bedingungen ist erforderlich</string>
<string name="guidelines">Richtlinien</string> <string name="guidelines">Richtlinien</string>
<string name="moderators">Moderatoren</string> <string name="moderators">Moderatoren</string>
<string name="open_dropdown_menu">Dropdown-Menü öffnen</string> <string name="open_dropdown_menu">Dropdown-Menü öffnen</string>
<string name="follow_sets">Folge-Sets</string>
<string name="labeled_bookmarks">Markierte Lesezeichen</string>
<string name="general_bookmarks">Allgemeine Lesezeichen</string>
<string name="follow_set_type_public">Öffentlich</string>
<string name="follow_set_type_private">Privat</string>
<string name="follow_set_type_mixed">Gemischt</string>
<string name="follow_set_empty_feed_msg">Es scheint, dass du noch keine Folge-Sets hast.\nTippe unten zum Aktualisieren oder verwende die Plus-Taste, um ein neues zu erstellen.</string>
<string name="follow_set_add_author_from_note_action">Autor zum Folge-Set hinzufügen</string>
<string name="follow_set_profile_actions_menu_description">Benutzer zu Listen hinzufügen oder entfernen, oder eine neue Liste mit diesem Benutzer erstellen.</string>
<string name="follow_set_type_description">Symbol für %1$s-Liste</string>
<string name="follow_set_presence_indicator">%1$s ist in dieser Liste</string>
<string name="follow_set_absence_indicator">%1$s ist nicht in dieser Liste</string>
<string name="follow_set_man_dialog_title">Deine Folge-Sets</string>
<string name="follow_set_empty_dialog_msg">Keine Folge-Sets gefunden oder du hast keine. Tippe unten zum Aktualisieren oder verwende das Menü, um eines zu erstellen.</string>
<string name="follow_set_error_dialog_msg">Beim Abrufen ist ein Problem aufgetreten: %1$s</string>
<string name="follow_set_creation_menu_title">Neue Liste erstellen</string>
<string name="follow_set_creation_item_label">Neue %1$s-Liste mit Benutzer erstellen</string>
<string name="follow_set_creation_item_description">Erstellt ein %1$s-Folge-Set und fügt %2$s hinzu.</string>
<string name="follow_set_creation_dialog_title">Neue %1$s-Liste</string>
<string name="follow_set_creation_name_label">Set-Name</string>
<string name="follow_set_creation_desc_label">Set-Beschreibung (optional)</string>
<string name="follow_set_creation_action_btn_label">Set erstellen</string>
<string name="follow_set_rename_btn_label">Set umbenennen</string>
<string name="follow_set_rename_dialog_indicator_first_part">Du benennst um von </string>
<string name="follow_set_rename_dialog_indicator_second_part"> zu..</string>
<string name="rename">Umbenennen</string>
<string name="torrent_no_info">Das Ereignis enthält nicht genügend Informationen, um einen Magnetlink zu erstellen</string>
<string name="my_lists_and_sets">Meine Listen/Sets</string>
<string name="select_signer">Signierer auswählen</string>
<string name="video_codec_h265_label">H.265/HEVC-Codec verwenden</string>
<string name="video_codec_h265_description">Bessere Qualität bei kleinerer Dateigröße, aber nicht alle Geräte unterstützen die H.265-Wiedergabe.</string>
</resources> </resources>
@@ -1065,4 +1065,5 @@
<string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">क्या आप निकटकालिक क्रमदोष सूचनापत्र एक सीधे सन्देश में अमेथिस्ट को भेजना चाहते हैं। कोई व्यक्तिगत जानकारी बाँटी नहीं जाएगी</string> <string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">क्या आप निकटकालिक क्रमदोष सूचनापत्र एक सीधे सन्देश में अमेथिस्ट को भेजना चाहते हैं। कोई व्यक्तिगत जानकारी बाँटी नहीं जाएगी</string>
<string name="crashreport_found_send">भेजें</string> <string name="crashreport_found_send">भेजें</string>
<string name="this_message_will_disappear_in_days">यह सन्देश %1$d दिनों में अदृश्य हो जाएगा</string> <string name="this_message_will_disappear_in_days">यह सन्देश %1$d दिनों में अदृश्य हो जाएगा</string>
<string name="select_signer">हस्ताक्षरकर्ता चुनें</string>
</resources> </resources>
@@ -112,6 +112,7 @@
<string name="post">Közzététel</string> <string name="post">Közzététel</string>
<string name="save">Mentés</string> <string name="save">Mentés</string>
<string name="create">Létrehozás</string> <string name="create">Létrehozás</string>
<string name="rename">Átnevezés</string>
<string name="cancel">Mégse</string> <string name="cancel">Mégse</string>
<string name="failed_to_upload_the_image">Nem sikerült feltölteni a képet</string> <string name="failed_to_upload_the_image">Nem sikerült feltölteni a képet</string>
<string name="relay_address">Atjátszó címe</string> <string name="relay_address">Atjátszó címe</string>
@@ -140,12 +141,12 @@
<string name="ln_address">LN-cím</string> <string name="ln_address">LN-cím</string>
<string name="ln_url_outdated">LN-webcím (elavult)</string> <string name="ln_url_outdated">LN-webcím (elavult)</string>
<string name="save_to_gallery">Mentés a galériába</string> <string name="save_to_gallery">Mentés a galériába</string>
<string name="image_saved_to_the_gallery">Kép elmentve a képgalériába</string> <string name="image_saved_to_the_gallery">Kép mentve a képgalériába</string>
<string name="video_download_has_started_toast">A videó letöltése megkezdődött…</string> <string name="video_download_has_started_toast">A videó letöltése megkezdődött…</string>
<string name="media_download_has_started_toast">A média letöltése megkezdődött…</string> <string name="media_download_has_started_toast">A média letöltése megkezdődött…</string>
<string name="failed_to_save_the_image">Nem sikerült menteni a képet</string> <string name="failed_to_save_the_image">Nem sikerült menteni a képet</string>
<string name="video_saved_to_the_gallery">Videó elmentve a videógalériába</string> <string name="video_saved_to_the_gallery">Videó mentve a videógalériába</string>
<string name="failed_to_save_the_video">Nem sikerült elmenteni a videót</string> <string name="failed_to_save_the_video">Nem sikerült menteni a videót</string>
<string name="upload_image">Kép feltöltése</string> <string name="upload_image">Kép feltöltése</string>
<string name="take_a_picture">Kép készítése</string> <string name="take_a_picture">Kép készítése</string>
<string name="record_a_video">Videó rögzítése</string> <string name="record_a_video">Videó rögzítése</string>
@@ -445,6 +446,33 @@
<string name="follow_list_aroundme">Közelben lévők bejegyzései</string> <string name="follow_list_aroundme">Közelben lévők bejegyzései</string>
<string name="follow_list_global">Globális</string> <string name="follow_list_global">Globális</string>
<string name="follow_list_mute_list">Némítottak bejegyzései</string> <string name="follow_list_mute_list">Némítottak bejegyzései</string>
<string name="follow_sets">Követési gyüjtemények</string>
<string name="labeled_bookmarks">Címkézett könyvjelzők</string>
<string name="general_bookmarks">Általános könyvjelzők</string>
<string name="follow_set_type_public">Nyílvános</string>
<string name="follow_set_type_private">Privát</string>
<string name="follow_set_type_mixed">Kevert</string>
<string name="follow_set_empty_feed_msg"> Úgy tűnik, hogy még egyetlen gyüjteményt sem követ.
\nKoppintson a frissítéshez vagy érintse meg a hozzáadás gombot a gyüjtemény létrehozásához.
</string>
<string name="follow_set_add_author_from_note_action">Szerző hozzáadása a követési gyűjteményhez</string>
<string name="follow_set_profile_actions_menu_description">Felhasználó hozzáadása vagy eltávolítása a listákból, vagy új lista létrehozása ezzel a felhasználóval.</string>
<string name="follow_set_type_description">Ikon a(z) %1$s nevű listához</string>
<string name="follow_set_presence_indicator">"A(z) %1$s már a létezik a listában"</string>
<string name="follow_set_absence_indicator">"A(z) %1$s nincs a listában"</string>
<string name="follow_set_man_dialog_title">Saját követési gyüjtemények</string>
<string name="follow_set_empty_dialog_msg">Nem találhatók követési gyüjtemények, vagy nincs követési gyüjteménye. Érintse meg az alábbi gombot a frissítéshez vagy használja a menüt egy gyüjtemény létrehozásához.</string>
<string name="follow_set_error_dialog_msg">Probléma történt a következő lekérdezésekor: %1$s</string>
<string name="follow_set_creation_menu_title">Új lista létrehozása</string>
<string name="follow_set_creation_item_label">Új %1$s lista létrehozása felhasználóval</string>
<string name="follow_set_creation_item_description">%1$s követési gyüjtemény létrehozása, és hozzáadás a következhöz: %2$s.</string>
<string name="follow_set_creation_dialog_title">Új %1$s lista</string>
<string name="follow_set_creation_name_label">Név megadása</string>
<string name="follow_set_creation_desc_label">Leírás megadása (nem kötelező)</string>
<string name="follow_set_creation_action_btn_label">Gyüjtemény létrehozása</string>
<string name="follow_set_rename_btn_label">Gyüjtemény átnevezése</string>
<string name="follow_set_rename_dialog_indicator_first_part">Ön átnevezi a követési gyüjteményt erről: </string>
<string name="follow_set_rename_dialog_indicator_second_part"> erre:</string>
<string name="connect_through_your_orbot_setup_short">Alapértelmezett port: 9050</string> <string name="connect_through_your_orbot_setup_short">Alapértelmezett port: 9050</string>
<string name="connect_through_your_orbot_setup_markdown"> ## Kapcsolódás a TORon keresztül az Orbot segítségével <string name="connect_through_your_orbot_setup_markdown"> ## Kapcsolódás a TORon keresztül az Orbot segítségével
\n\n1. Telepítse az [Orbotot](https://play.google.com/store/apps/details?id=org.torproject.android) \n\n1. Telepítse az [Orbotot](https://play.google.com/store/apps/details?id=org.torproject.android)
@@ -1014,6 +1042,8 @@
<string name="torrent_download">Letöltés</string> <string name="torrent_download">Letöltés</string>
<string name="torrent_failure">Nem sikerült megnyitni a fájlt</string> <string name="torrent_failure">Nem sikerült megnyitni a fájlt</string>
<string name="torrent_no_apps">A fájl megnyitásához és letöltéséhez nincsenek torrent-alkalmazások telepítve.</string> <string name="torrent_no_apps">A fájl megnyitásához és letöltéséhez nincsenek torrent-alkalmazások telepítve.</string>
<string name="torrent_no_info">Az esemény nem tartalmaz elegendő információt a mágneshivatkozás létrehozásához</string>
<string name="my_lists_and_sets">Saját lista/gyüjtemény</string>
<string name="select_list_to_filter">Lista kiválasztása a hírfolyam szűréséhez</string> <string name="select_list_to_filter">Lista kiválasztása a hírfolyam szűréséhez</string>
<string name="temporary_account">Kijelentkeztetés az eszköz zárolása esetén</string> <string name="temporary_account">Kijelentkeztetés az eszköz zárolása esetén</string>
<string name="private_message">Privát üzenet</string> <string name="private_message">Privát üzenet</string>
@@ -1035,4 +1065,5 @@
<string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">Szeretné elküldeni a legutóbbi összeomlási jelentést az Amethystnek egy közvetlen üzenetben? A személyes adatait nem osztja meg</string> <string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">Szeretné elküldeni a legutóbbi összeomlási jelentést az Amethystnek egy közvetlen üzenetben? A személyes adatait nem osztja meg</string>
<string name="crashreport_found_send">Küldés</string> <string name="crashreport_found_send">Küldés</string>
<string name="this_message_will_disappear_in_days">Ez az üzenet %1$d nap múlva eltűnik</string> <string name="this_message_will_disappear_in_days">Ez az üzenet %1$d nap múlva eltűnik</string>
<string name="select_signer">Aláíró kiválasztása</string>
</resources> </resources>
@@ -112,6 +112,7 @@
<string name="post">Wyślij</string> <string name="post">Wyślij</string>
<string name="save">Zapisz</string> <string name="save">Zapisz</string>
<string name="create">Utwórz</string> <string name="create">Utwórz</string>
<string name="rename">Zmień nazwę</string>
<string name="cancel">Anuluj</string> <string name="cancel">Anuluj</string>
<string name="failed_to_upload_the_image">Nie udało się przesłać obrazu</string> <string name="failed_to_upload_the_image">Nie udało się przesłać obrazu</string>
<string name="relay_address">Adres transmitera</string> <string name="relay_address">Adres transmitera</string>
@@ -442,6 +443,33 @@
<string name="follow_list_aroundme">W pobliżu</string> <string name="follow_list_aroundme">W pobliżu</string>
<string name="follow_list_global">Wszystkie</string> <string name="follow_list_global">Wszystkie</string>
<string name="follow_list_mute_list">Zablokowane</string> <string name="follow_list_mute_list">Zablokowane</string>
<string name="follow_sets">Zbiory obserwowanych</string>
<string name="labeled_bookmarks">Oznaczone zakładki</string>
<string name="general_bookmarks">Ogólne zakładki</string>
<string name="follow_set_type_public">Publiczna</string>
<string name="follow_set_type_private">Prywatna</string>
<string name="follow_set_type_mixed">Mieszana</string>
<string name="follow_set_empty_feed_msg"> Wygląda na to, że nie masz jeszcze żadnych zbiorów obserwowanych.
\nDotknij poniżej, aby odświeżyć, lub naciśnij przycisk Dodaj, aby utworzyć nowy.
</string>
<string name="follow_set_add_author_from_note_action">Dodaj autora do zbioru obserwowanych</string>
<string name="follow_set_profile_actions_menu_description">Dodaj lub usuń użytkownika z list, lub utwórz nową listę z tym użytkownikiem.</string>
<string name="follow_set_type_description">Ikona dla listy %1$s</string>
<string name="follow_set_presence_indicator">"%1$s jest obecny na liście"</string>
<string name="follow_set_absence_indicator">"%1$s nie jest na liście"</string>
<string name="follow_set_man_dialog_title">Twój zbiór obserwowanych</string>
<string name="follow_set_empty_dialog_msg">Nie znaleziono zbiorów obserwowanych lub nie masz żadnych zbiorów obserwowanych. Dotknij poniżej, aby odświeżyć lub użyj menu, aby go utworzyć.</string>
<string name="follow_set_error_dialog_msg">Podczas pobierania wystąpił błąd: %1$s</string>
<string name="follow_set_creation_menu_title">Utwórz nową listę</string>
<string name="follow_set_creation_item_label">\"Utwórz nową listę %1$s z użytkownikiem</string>
<string name="follow_set_creation_item_description">Tworzy zbiór obserwowanych %1$s i dodaje do niego %2$s.</string>
<string name="follow_set_creation_dialog_title">Nowa lista %1$s</string>
<string name="follow_set_creation_name_label">Nazwa zbioru</string>
<string name="follow_set_creation_desc_label">Opis zbioru (opcjonalnie)</string>
<string name="follow_set_creation_action_btn_label">Utwórz zbiór</string>
<string name="follow_set_rename_btn_label">Zmień nazwę zbioru</string>
<string name="follow_set_rename_dialog_indicator_first_part">Zmieniasz nazwę z </string>
<string name="follow_set_rename_dialog_indicator_second_part"> do..</string>
<string name="connect_through_your_orbot_setup_short">Domyślny port to 9050</string> <string name="connect_through_your_orbot_setup_short">Domyślny port to 9050</string>
<string name="connect_through_your_orbot_setup_markdown"> ## Połącz przez Tor z Orbotem <string name="connect_through_your_orbot_setup_markdown"> ## Połącz przez Tor z Orbotem
\n\n1. Zainstaluj [Orbota](https://play.google. om/store/apps/details?id= org.torproject.android) \n\n1. Zainstaluj [Orbota](https://play.google. om/store/apps/details?id= org.torproject.android)
@@ -1011,6 +1039,8 @@
<string name="torrent_download">Pobierz</string> <string name="torrent_download">Pobierz</string>
<string name="torrent_failure">Nie udało się otworzyć pliku</string> <string name="torrent_failure">Nie udało się otworzyć pliku</string>
<string name="torrent_no_apps">Brak zainstalowanych aplikacji torrent do otwarcia i pobrania pliku.</string> <string name="torrent_no_apps">Brak zainstalowanych aplikacji torrent do otwarcia i pobrania pliku.</string>
<string name="torrent_no_info">Zdarzenie nie ma wystarczającej ilości informacji, aby zbudować link magnetyczny</string>
<string name="my_lists_and_sets">Moje Listy/Zbiory</string>
<string name="select_list_to_filter">Wybierz listę, aby filtrować aktualności</string> <string name="select_list_to_filter">Wybierz listę, aby filtrować aktualności</string>
<string name="temporary_account">Wyloguj się przy blokowaniu urządzenia</string> <string name="temporary_account">Wyloguj się przy blokowaniu urządzenia</string>
<string name="private_message">Wiadomość prywatna</string> <string name="private_message">Wiadomość prywatna</string>
@@ -1018,6 +1048,7 @@
<string name="group_relay">Transmiter Czatu</string> <string name="group_relay">Transmiter Czatu</string>
<string name="group_relay_explanation">Transmiter, z którym łączą się wszyscy użytkownicy tego czatu</string> <string name="group_relay_explanation">Transmiter, z którym łączą się wszyscy użytkownicy tego czatu</string>
<string name="share_image">Udostępnij zdjęcie…</string> <string name="share_image">Udostępnij zdjęcie…</string>
<string name="unable_to_share_image">Nie można udostępnić obrazu, spróbuj ponownie później…</string>
<string name="search_by_hashtag">Szukaj tagu: #%1$s</string> <string name="search_by_hashtag">Szukaj tagu: #%1$s</string>
<string name="dont_translate_from">Nie tłumacz z</string> <string name="dont_translate_from">Nie tłumacz z</string>
<string name="dont_translate_from_description">Języki wyświetlane tutaj nie będą tłumaczone. Wybierz język, aby usunąć go z listy języków nietłumaczonych.</string> <string name="dont_translate_from_description">Języki wyświetlane tutaj nie będą tłumaczone. Wybierz język, aby usunąć go z listy języków nietłumaczonych.</string>
@@ -1031,4 +1062,5 @@
<string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">Czy chcesz wysłać ostatni raport o awarii do Amethyst w DM? Żadne dane osobowe nie będą udostępnione</string> <string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">Czy chcesz wysłać ostatni raport o awarii do Amethyst w DM? Żadne dane osobowe nie będą udostępnione</string>
<string name="crashreport_found_send">Prześlij</string> <string name="crashreport_found_send">Prześlij</string>
<string name="this_message_will_disappear_in_days">Ta wiadomość zniknie za %1$d dni</string> <string name="this_message_will_disappear_in_days">Ta wiadomość zniknie za %1$d dni</string>
<string name="select_signer">Wybierz Sygnatariusza</string>
</resources> </resources>
@@ -112,6 +112,7 @@
<string name="post">Salvar</string> <string name="post">Salvar</string>
<string name="save">Salvar</string> <string name="save">Salvar</string>
<string name="create">Criar</string> <string name="create">Criar</string>
<string name="rename">Renomear</string>
<string name="cancel">Cancelar</string> <string name="cancel">Cancelar</string>
<string name="failed_to_upload_the_image">Falha ao enviar imagem</string> <string name="failed_to_upload_the_image">Falha ao enviar imagem</string>
<string name="relay_address">Endereço do Relay</string> <string name="relay_address">Endereço do Relay</string>
@@ -443,6 +444,31 @@
<string name="follow_list_aroundme">Perto de mim</string> <string name="follow_list_aroundme">Perto de mim</string>
<string name="follow_list_global">Global</string> <string name="follow_list_global">Global</string>
<string name="follow_list_mute_list">Lista Silenciada</string> <string name="follow_list_mute_list">Lista Silenciada</string>
<string name="follow_sets">Conjuntos de Seguimento</string>
<string name="labeled_bookmarks">Favoritos com etiqueta</string>
<string name="general_bookmarks">Favoritos gerais</string>
<string name="follow_set_type_public">Público</string>
<string name="follow_set_type_private">Privado</string>
<string name="follow_set_type_mixed">Misto</string>
<string name="follow_set_empty_feed_msg">Parece que você ainda não tem conjuntos de seguimento.\nToque abaixo para atualizar ou use o botão de adicionar para criar um novo.</string>
<string name="follow_set_add_author_from_note_action">Adicionar autor ao conjunto de seguimento</string>
<string name="follow_set_profile_actions_menu_description">Adicionar ou remover usuário de listas, ou criar uma nova lista com este usuário.</string>
<string name="follow_set_type_description">Ícone da lista %1$s</string>
<string name="follow_set_presence_indicator">"%1$s está presente nesta lista"</string>
<string name="follow_set_absence_indicator">"%1$s não está nesta lista"</string>
<string name="follow_set_man_dialog_title">Seus conjuntos de seguimento</string>
<string name="follow_set_empty_dialog_msg">Nenhum conjunto de seguimento foi encontrado ou você não possui nenhum. Toque abaixo para atualizar ou use o menu para criar um.</string>
<string name="follow_set_error_dialog_msg">Houve um problema ao buscar: %1$s</string>
<string name="follow_set_creation_menu_title">Criar nova lista</string>
<string name="follow_set_creation_item_label">Criar nova lista %1$s com usuário</string>
<string name="follow_set_creation_item_description">Cria um conjunto de seguimento %1$s e adiciona %2$s a ele.</string>
<string name="follow_set_creation_dialog_title">Nova lista %1$s</string>
<string name="follow_set_creation_name_label">Nome do conjunto</string>
<string name="follow_set_creation_desc_label">Descrição do conjunto (opcional)</string>
<string name="follow_set_creation_action_btn_label">Criar conjunto</string>
<string name="follow_set_rename_btn_label">Renomear conjunto</string>
<string name="follow_set_rename_dialog_indicator_first_part">Você está renomeando de </string>
<string name="follow_set_rename_dialog_indicator_second_part"> para..</string>
<string name="connect_through_your_orbot_setup_short">Porta padrão é 9050</string> <string name="connect_through_your_orbot_setup_short">Porta padrão é 9050</string>
<string name="connect_through_your_orbot_setup_markdown"> ## Conecte-se através do Tor com o Orbot <string name="connect_through_your_orbot_setup_markdown"> ## Conecte-se através do Tor com o Orbot
\n\n1. Instale o [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) \n\n1. Instale o [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)
@@ -830,6 +856,8 @@
<string name="media_compression_quality_medium">Média</string> <string name="media_compression_quality_medium">Média</string>
<string name="media_compression_quality_high">Alta</string> <string name="media_compression_quality_high">Alta</string>
<string name="media_compression_quality_uncompressed">Sem compressão</string> <string name="media_compression_quality_uncompressed">Sem compressão</string>
<string name="video_codec_h265_label">Usar codec H.265/HEVC</string>
<string name="video_codec_h265_description">Melhor qualidade em arquivos menores, mas nem todos os dispositivos suportam reprodução em H.265.</string>
<string name="edit_draft">Editar rascunho</string> <string name="edit_draft">Editar rascunho</string>
<string name="login_with_qr_code">Entrar com Código QR</string> <string name="login_with_qr_code">Entrar com Código QR</string>
<string name="route">Rota</string> <string name="route">Rota</string>
@@ -1012,6 +1040,8 @@
<string name="torrent_download">Baixar</string> <string name="torrent_download">Baixar</string>
<string name="torrent_failure">Falha ao abrir o arquivo</string> <string name="torrent_failure">Falha ao abrir o arquivo</string>
<string name="torrent_no_apps">Nenhum aplicativo torrent instalado para abrir e baixar o arquivo.</string> <string name="torrent_no_apps">Nenhum aplicativo torrent instalado para abrir e baixar o arquivo.</string>
<string name="torrent_no_info">O evento não tem informações suficientes para criar um link magnético</string>
<string name="my_lists_and_sets">Minhas listas/conjuntos</string>
<string name="select_list_to_filter">Selecione uma lista para filtrar o feed</string> <string name="select_list_to_filter">Selecione uma lista para filtrar o feed</string>
<string name="temporary_account">Terminar sessão no bloqueio do dispositivo</string> <string name="temporary_account">Terminar sessão no bloqueio do dispositivo</string>
<string name="private_message">Mensagem Privada</string> <string name="private_message">Mensagem Privada</string>
@@ -1033,4 +1063,5 @@
<string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">Gostaria de enviar o relatório de falha recente para o Amethyst em uma DM? Nenhuma informação pessoal será compartilhada</string> <string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">Gostaria de enviar o relatório de falha recente para o Amethyst em uma DM? Nenhuma informação pessoal será compartilhada</string>
<string name="crashreport_found_send">Enviar</string> <string name="crashreport_found_send">Enviar</string>
<string name="this_message_will_disappear_in_days">Esta mensagem desaparecerá em %1$d dias</string> <string name="this_message_will_disappear_in_days">Esta mensagem desaparecerá em %1$d dias</string>
<string name="select_signer">Selecionar assinador</string>
</resources> </resources>
@@ -456,6 +456,8 @@ Za podpisovanje se je potrebno prijaviti s privatnim ključem</string>
<string name="follow_list_aroundme">V moji okolici</string> <string name="follow_list_aroundme">V moji okolici</string>
<string name="follow_list_global">Globalno</string> <string name="follow_list_global">Globalno</string>
<string name="follow_list_mute_list">Spisek utišanih</string> <string name="follow_list_mute_list">Spisek utišanih</string>
<string name="follow_set_type_private">Zasebno</string>
<string name="follow_set_type_mixed">Mešano</string>
<string name="connect_through_your_orbot_setup_short">Prevzeta vrata so 9050</string> <string name="connect_through_your_orbot_setup_short">Prevzeta vrata so 9050</string>
<string name="connect_through_your_orbot_setup_markdown"> ## Poveži se preko Tor omrežja z Orbot aplikacijo <string name="connect_through_your_orbot_setup_markdown"> ## Poveži se preko Tor omrežja z Orbot aplikacijo
\n\n1. Namesti [Orbot aplikacijo](https://play.google.com/store/apps/details?id=org.torproject.android) \n\n1. Namesti [Orbot aplikacijo](https://play.google.com/store/apps/details?id=org.torproject.android)
@@ -112,6 +112,7 @@
<string name="post">Dela</string> <string name="post">Dela</string>
<string name="save">Spara</string> <string name="save">Spara</string>
<string name="create">Skapa</string> <string name="create">Skapa</string>
<string name="rename">Byt namn</string>
<string name="cancel">Avbryt</string> <string name="cancel">Avbryt</string>
<string name="failed_to_upload_the_image">Det gick inte att ladda upp bilden</string> <string name="failed_to_upload_the_image">Det gick inte att ladda upp bilden</string>
<string name="relay_address">Relä Adress</string> <string name="relay_address">Relä Adress</string>
@@ -443,6 +444,31 @@
<string name="follow_list_aroundme">Runt mig</string> <string name="follow_list_aroundme">Runt mig</string>
<string name="follow_list_global">Global</string> <string name="follow_list_global">Global</string>
<string name="follow_list_mute_list">Tyst listan</string> <string name="follow_list_mute_list">Tyst listan</string>
<string name="follow_sets">Följ-set</string>
<string name="labeled_bookmarks">Märkta bokmärken</string>
<string name="general_bookmarks">Allmänna bokmärken</string>
<string name="follow_set_type_public">Offentlig</string>
<string name="follow_set_type_private">Privat</string>
<string name="follow_set_type_mixed">Blandad</string>
<string name="follow_set_empty_feed_msg">Det verkar som att du inte har några följ-set ännu.\nTryck nedan för att uppdatera eller använd plusknappen för att skapa ett nytt.</string>
<string name="follow_set_add_author_from_note_action">Lägg till författare i följ-set</string>
<string name="follow_set_profile_actions_menu_description">Lägg till eller ta bort användare från listor, eller skapa en ny lista med denna användare.</string>
<string name="follow_set_type_description">Ikon för %1$s-lista</string>
<string name="follow_set_presence_indicator">"%1$s finns i denna lista"</string>
<string name="follow_set_absence_indicator">"%1$s finns inte i denna lista"</string>
<string name="follow_set_man_dialog_title">Dina följ-set</string>
<string name="follow_set_empty_dialog_msg">Inga följ-set hittades, eller så har du inga. Tryck nedan för att uppdatera eller använd menyn för att skapa ett.</string>
<string name="follow_set_error_dialog_msg">Ett problem uppstod vid hämtning: %1$s</string>
<string name="follow_set_creation_menu_title">Skapa ny lista</string>
<string name="follow_set_creation_item_label">Skapa ny %1$s-lista med användare</string>
<string name="follow_set_creation_item_description">Skapar ett %1$s-följ-set och lägger till %2$s i det.</string>
<string name="follow_set_creation_dialog_title">Ny %1$s-lista</string>
<string name="follow_set_creation_name_label">Set-namn</string>
<string name="follow_set_creation_desc_label">Set-beskrivning (valfritt)</string>
<string name="follow_set_creation_action_btn_label">Skapa set</string>
<string name="follow_set_rename_btn_label">Byt namn på set</string>
<string name="follow_set_rename_dialog_indicator_first_part">Du byter namn från </string>
<string name="follow_set_rename_dialog_indicator_second_part"> till..</string>
<string name="connect_through_your_orbot_setup_short">Standardporten är 9050</string> <string name="connect_through_your_orbot_setup_short">Standardporten är 9050</string>
<string name="connect_through_your_orbot_setup_markdown"> ## Anslut genom Tor med Orbot <string name="connect_through_your_orbot_setup_markdown"> ## Anslut genom Tor med Orbot
\n\n1. Installera [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) \n\n1. Installera [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)
@@ -829,6 +855,8 @@
<string name="media_compression_quality_medium">Medel</string> <string name="media_compression_quality_medium">Medel</string>
<string name="media_compression_quality_high">Hög</string> <string name="media_compression_quality_high">Hög</string>
<string name="media_compression_quality_uncompressed">Okomprimerad</string> <string name="media_compression_quality_uncompressed">Okomprimerad</string>
<string name="video_codec_h265_label">Använd H.265/HEVC-codec</string>
<string name="video_codec_h265_description">Bättre kvalitet med mindre filstorlek, men inte alla enheter stöder H.265-uppspelning.</string>
<string name="edit_draft">Redigera utkast</string> <string name="edit_draft">Redigera utkast</string>
<string name="login_with_qr_code">Logga in med QR-kod</string> <string name="login_with_qr_code">Logga in med QR-kod</string>
<string name="route">Rutt</string> <string name="route">Rutt</string>
@@ -1011,6 +1039,8 @@
<string name="torrent_download">Ladda ner</string> <string name="torrent_download">Ladda ner</string>
<string name="torrent_failure">Det gick inte att öppna filen</string> <string name="torrent_failure">Det gick inte att öppna filen</string>
<string name="torrent_no_apps">Inga torrent-appar installerade för att öppna och ladda ner filen.</string> <string name="torrent_no_apps">Inga torrent-appar installerade för att öppna och ladda ner filen.</string>
<string name="torrent_no_info">Händelsen har inte tillräcklig information för att skapa en magnetlänk</string>
<string name="my_lists_and_sets">Mina listor/set</string>
<string name="select_list_to_filter">Välj en lista för att filtrera flödet</string> <string name="select_list_to_filter">Välj en lista för att filtrera flödet</string>
<string name="temporary_account">Logga ut när enheten låses</string> <string name="temporary_account">Logga ut när enheten låses</string>
<string name="private_message">Privat meddelande</string> <string name="private_message">Privat meddelande</string>
@@ -1032,4 +1062,5 @@
<string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">Vill du skicka den senaste kraschrapporten till Amethyst i ett DM? Ingen personlig information kommer att delas</string> <string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">Vill du skicka den senaste kraschrapporten till Amethyst i ett DM? Ingen personlig information kommer att delas</string>
<string name="crashreport_found_send">Skicka</string> <string name="crashreport_found_send">Skicka</string>
<string name="this_message_will_disappear_in_days">Detta meddelande försvinner om %1$d dagar</string> <string name="this_message_will_disappear_in_days">Detta meddelande försvinner om %1$d dagar</string>
<string name="select_signer">Välj signatör</string>
</resources> </resources>
+2
View File
@@ -1042,6 +1042,8 @@
<string name="media_compression_quality_medium">Medium</string> <string name="media_compression_quality_medium">Medium</string>
<string name="media_compression_quality_high">High</string> <string name="media_compression_quality_high">High</string>
<string name="media_compression_quality_uncompressed">Uncompressed</string> <string name="media_compression_quality_uncompressed">Uncompressed</string>
<string name="video_codec_h265_label">Use H.265/HEVC Codec</string>
<string name="video_codec_h265_description">Better quality at smaller file sizes but not all devices support H.265 playback.</string>
<string name="edit_draft">Edit draft</string> <string name="edit_draft">Edit draft</string>
+1 -1
View File
@@ -33,7 +33,7 @@ languageId = "17.0.6"
lazysodiumAndroid = "5.2.0" lazysodiumAndroid = "5.2.0"
lazysodiumJava = "5.2.0" lazysodiumJava = "5.2.0"
lifecycleRuntimeKtx = "2.9.4" lifecycleRuntimeKtx = "2.9.4"
lightcompressor = "1.4.0" lightcompressor = "1.5.0"
markdown = "e1151c8" markdown = "e1151c8"
media3 = "1.8.0" media3 = "1.8.0"
mockk = "1.14.5" mockk = "1.14.5"