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
+28 -17
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:
```gradle
implementation('com.github.vitorpamplona.amethyst:quartz:<Amethyst Version>')
implementation('com.vitorpamplona.quartz:quartz:<Amethyst Version>')
```
Variations to each platform are also available:
```gradle
implementation('com.github.vitorpamplona.amethyst:quartz-android:<Amethyst Version>')
implementation('com.github.vitorpamplona.amethyst:quartz-jvm:<Amethyst Version>')
implementation('com.github.vitorpamplona.amethyst:quartz-iosarm64:<Amethyst Version>')
implementation('com.github.vitorpamplona.amethyst:quartz-iossimulatorarm64:<Amethyst Version>')
implementation('com.vitorpamplona.quartz:quartz-android:<Amethyst Version>')
implementation('com.vitorpamplona.quartz:quartz-jvm:<Amethyst Version>')
implementation('com.vitorpamplona.quartz:quartz-iosarm64:<Amethyst Version>')
implementation('com.vitorpamplona.quartz:quartz-iossimulatorarm64:<Amethyst Version>')
```
### How to use
@@ -259,24 +259,24 @@ implementation('com.github.vitorpamplona.amethyst:quartz-iossimulatorarm64:<Amet
Manage logged in users with the `KeyPair` class
```kt
val keys = KeyPair() // creates a random key
val keys = KeyPair("hex...".hexToByteArray())
val keys = KeyPair("nsec1...".bechToBytes())
val keys = KeyPair(Nip06().privateKeyFromMnemonic("<mnemonic>"))
val keyPair = KeyPair() // creates a random key
val keyPair = KeyPair("hex...".hexToByteArray())
val keyPair = KeyPair("nsec1...".bechToBytes())
val keyPair = KeyPair(Nip06().privateKeyFromMnemonic("<mnemonic>"))
val readOnly = KeyPair(pubKey = "hex...".hexToByteArray())
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
or external, when it is controlled by Amber in NIP-55
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.
the `NostrSignerInternal` and `NostrSignerExternal` classes.
Use either the `NostrSignerInternal` or `NostrSignerExternal` class:
```kt
val signer = NostrSignerInternal(keyPair)
val amberSigner = NostrSignerExternal(
pubKey = keyPair.pubKey.toHexKey(),
packageName = signerPackageName,
packageName = signerPackageName, // Amber package name
contentResolver = appContext.contentResolver,
)
```
@@ -299,16 +299,19 @@ val client = NostrClient(socketBuilder, appScope)
If you want to auth, given a logged-in `signer`:
```kt
val authCoordinator = RelayAuthenticator(client, applicationIOScope) { challenge, relay ->
val authedEvent = RelayAuthEvent.create(relayUrl, challenge, signer)
val authCoordinator = RelayAuthenticator(client, appScope) { challenge, relay ->
val authedEvent = RelayAuthEvent.create(relay.url, challenge, signer)
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
val metadataSub = RelayClientSubscription(
val metadataSub = NostrClientSubscription(
client = client,
filter = {
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
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
}
}
// TODO: remove this when lightcompressor uses one MP4 parser only
packaging {
resources {
resources.pickFirsts.add('builddef.lst')
}
}
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.Event
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.hints.AddressHintProvider
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
@@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user
import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.model.Account
@@ -45,6 +45,7 @@ class MediaCompressor {
contentType: String?,
mediaQuality: CompressorQuality,
applicationContext: Context,
useH265: Boolean = false,
): MediaCompressorResult {
// Skip compression if user selected uncompressed
if (mediaQuality == CompressorQuality.UNCOMPRESSED) {
@@ -57,7 +58,7 @@ class MediaCompressor {
// branch into compression based on content type
return when {
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.contains("gif") &&
@@ -46,6 +46,8 @@ class MultiOrchestrator(
fun first() = list.first()
fun hasVideo() = list.any { it.media.mimeType?.startsWith("video", ignoreCase = true) == true }
suspend fun upload(
alt: String?,
contentWarningReason: String?,
@@ -53,6 +55,7 @@ class MultiOrchestrator(
server: ServerName,
account: Account,
context: Context,
useH265: Boolean = false,
): Result {
coroutineScope {
val jobs =
@@ -67,6 +70,7 @@ class MultiOrchestrator(
server,
account,
context,
useH265,
)
}
}
@@ -85,6 +89,7 @@ class MultiOrchestrator(
server: ServerName,
account: Account,
context: Context,
useH265: Boolean = false,
): Result {
coroutineScope {
val jobs =
@@ -100,6 +105,7 @@ class MultiOrchestrator(
server,
account,
context,
useH265,
)
}
}
@@ -288,9 +288,10 @@ class UploadOrchestrator {
mimeType: String?,
compressionQuality: CompressorQuality,
context: Context,
useH265: Boolean = false,
) = if (compressionQuality != CompressorQuality.UNCOMPRESSED) {
updateState(0.02, UploadingState.Compressing)
MediaCompressor().compress(uri, mimeType, compressionQuality, context.applicationContext)
MediaCompressor().compress(uri, mimeType, compressionQuality, context.applicationContext, useH265)
} else {
MediaCompressorResult(uri, mimeType, null)
}
@@ -304,8 +305,9 @@ class UploadOrchestrator {
server: ServerName,
account: Account,
context: Context,
useH265: Boolean = false,
): UploadingFinalState {
val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context)
val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265)
return when (server.type) {
ServerType.NIP95 -> uploadNIP95(compressed.uri, compressed.contentType, null, null, context)
@@ -324,8 +326,9 @@ class UploadOrchestrator {
server: ServerName,
account: Account,
context: Context,
useH265: Boolean = false,
): UploadingFinalState {
val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context)
val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265)
val encrypted = EncryptFiles().encryptFile(context, compressed.uri, encrypt)
return when (server.type) {
@@ -29,6 +29,7 @@ import android.text.format.Formatter.formatFileSize
import android.util.Log
import android.widget.Toast
import com.abedelazizshe.lightcompressorlibrary.CompressionListener
import com.abedelazizshe.lightcompressorlibrary.VideoCodec
import com.abedelazizshe.lightcompressorlibrary.VideoCompressor
import com.abedelazizshe.lightcompressorlibrary.config.AppSpecificStorageConfiguration
import com.abedelazizshe.lightcompressorlibrary.config.Configuration
@@ -88,11 +89,18 @@ data class CompressionRule(
val bitrateMbps: Float,
val description: String,
) {
fun getBitrateBps(framerate: Float): Int {
// Apply 1.5x multiplier for 60fps+ videos
val multiplier = if (framerate >= 60f) 1.5f else 1.0f
fun getBitrateBps(
framerate: Float,
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?,
applicationContext: Context,
mediaQuality: CompressorQuality,
useH265: Boolean,
timeoutMs: Long = 60_000L, // configurable, default 60s
): MediaCompressorResult {
val videoInfo = getVideoInfo(uri, applicationContext)
@@ -152,8 +161,8 @@ object VideoCompressionHelper {
.getValue(mediaQuality)
.getValue(info.resolution.getStandard())
val bitrateBps = rule.getBitrateBps(info.framerate)
Log.d(LOG_TAG, "Bitrate: ${bitrateBps}bps for ${info.resolution.getStandard()} quality=$mediaQuality framerate=${info.framerate}fps.")
val bitrateBps = rule.getBitrateBps(info.framerate, useH265)
Log.d(LOG_TAG, "Bitrate: ${bitrateBps}bps for ${info.resolution.getStandard()} quality=$mediaQuality framerate=${info.framerate}fps useH265=$useH265.")
Log.d(
LOG_TAG,
@@ -186,6 +195,7 @@ object VideoCompressionHelper {
resizer = resizer,
videoNames = listOf(UUID.randomUUID().toString()),
isMinBitrateCheckEnabled = false,
videoCodec = if (useH265) VideoCodec.H265 else VideoCodec.H264,
),
listener =
object : CompressionListener {
@@ -54,7 +54,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -255,13 +254,13 @@ fun EditPostView(
postViewModel.multiOrchestrator?.let {
Row(
verticalAlignment = Alignment.CenterVertically,
verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
ImageVideoDescription(
it,
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)
if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server)
@@ -279,7 +278,7 @@ fun EditPostView(
if (lud16 != null && postViewModel.wantsInvoice) {
Row(
verticalAlignment = Alignment.CenterVertically,
verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
InvoiceRequest(
@@ -87,6 +87,9 @@ open class EditPostViewModel : ViewModel() {
// Images and Videos
var multiOrchestrator by mutableStateOf<MultiOrchestrator?>(null)
// Codec selection: false = H264, true = H265
var useH265Codec by mutableStateOf(false)
// Invoices
var canAddInvoice by mutableStateOf(false)
var wantsInvoice by mutableStateOf(false)
@@ -201,6 +204,7 @@ open class EditPostViewModel : ViewModel() {
server,
myAccount,
context,
useH265Codec,
)
if (results.allGood) {
@@ -62,6 +62,9 @@ open class NewMediaModel : ViewModel() {
// 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED
var mediaQualitySlider by mutableIntStateOf(1)
// Codec selection: false = H264, true = H265
var useH265Codec by mutableStateOf(false)
open fun load(
account: Account,
uris: ImmutableList<SelectedMedia>,
@@ -111,6 +114,7 @@ open class NewMediaModel : ViewModel() {
serverToUse,
myAccount,
context,
useH265Codec,
)
if (results.allGood) {
@@ -261,4 +261,18 @@ fun ImageVideoPost(
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),
style = MaterialTheme.typography.bodySmall,
color = Color.Gray,
maxLines = 2,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
}
@@ -78,7 +78,7 @@ import kotlinx.collections.immutable.toImmutableList
fun ImageVideoDescription(
uris: MultiOrchestrator,
defaultServer: ServerName,
onAdd: (String, ServerName, Boolean, Int) -> Unit,
onAdd: (String, ServerName, Boolean, Int, Boolean) -> Unit,
onDelete: (SelectedMediaProcessing) -> Unit,
onCancel: () -> Unit,
accountViewModel: AccountViewModel,
@@ -91,7 +91,7 @@ fun ImageVideoDescription(
uris: MultiOrchestrator,
defaultServer: ServerName,
includeNIP95: Boolean,
onAdd: (String, ServerName, Boolean, Int) -> Unit,
onAdd: (String, ServerName, Boolean, Int, Boolean) -> Unit,
onDelete: (SelectedMediaProcessing) -> Unit,
onCancel: () -> Unit,
accountViewModel: AccountViewModel,
@@ -128,6 +128,9 @@ fun ImageVideoDescription(
// 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED
var mediaQualitySlider by remember { mutableIntStateOf(1) }
// Codec selection: false = H264, true = H265
var useH265Codec by remember { mutableStateOf(false) }
Column(
modifier =
Modifier
@@ -294,10 +297,6 @@ fun ImageVideoDescription(
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.fillMaxWidth()) {
Text(
@@ -320,6 +319,18 @@ fun ImageVideoDescription(
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(
@@ -327,7 +338,7 @@ fun ImageVideoDescription(
Modifier
.fillMaxWidth()
.padding(vertical = 10.dp),
onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider) },
onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider, useH265Codec) },
shape = QuoteBorder,
colors =
ButtonDefaults.buttonColors(
@@ -285,7 +285,7 @@ private fun GenericCommentPostBody(
ImageVideoDescription(
it,
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)
if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server)
@@ -278,7 +278,7 @@ fun GroupDMScreenContent(
ImageVideoDescription(
selectedFiles,
accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality ->
onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.uploadAndHold(
accountViewModel.toastManager::toast,
context,
@@ -266,7 +266,7 @@ private fun NewProductBody(
uris = it,
defaultServer = accountViewModel.account.settings.defaultFileServer,
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)
if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server)
@@ -326,8 +326,8 @@ private fun NewPostScreenBody(
ImageVideoDescription(
it,
accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context)
onAdd = { alt, server, sensitiveContent, mediaQuality, useH265 ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context, useH265)
if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server)
}
@@ -624,8 +624,9 @@ open class ShortNotePostViewModel :
server: ServerName,
onError: (title: String, message: String) -> Unit,
context: Context,
useH265: Boolean,
) = try {
uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context)
uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context, useH265)
} catch (_: SignerExceptions.ReadOnlyException) {
onError(
stringRes(context, R.string.read_only_user),
@@ -640,6 +641,7 @@ open class ShortNotePostViewModel :
server: ServerName,
onError: (title: String, message: String) -> Unit,
context: Context,
useH265: Boolean,
) {
viewModelScope.launch(Dispatchers.Default) {
val myMultiOrchestrator = multiOrchestrator ?: return@launch
@@ -654,6 +656,7 @@ open class ShortNotePostViewModel :
server,
account,
context,
useH265,
)
if (results.allGood) {
@@ -231,7 +231,7 @@ fun PublicMessageScreenContent(
ImageVideoDescription(
it,
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)
if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server)
@@ -952,4 +952,35 @@
<string name="guidelines">Pokyny</string>
<string name="moderators">Moderátoři</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>
@@ -992,4 +992,35 @@ anz der Bedingungen ist erforderlich</string>
<string name="guidelines">Richtlinien</string>
<string name="moderators">Moderatoren</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>
@@ -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="crashreport_found_send">भेजें</string>
<string name="this_message_will_disappear_in_days">यह सन्देश %1$d दिनों में अदृश्य हो जाएगा</string>
<string name="select_signer">हस्ताक्षरकर्ता चुनें</string>
</resources>
@@ -112,6 +112,7 @@
<string name="post">Közzététel</string>
<string name="save">Mentés</string>
<string name="create">Létrehozás</string>
<string name="rename">Átnevezés</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="relay_address">Atjátszó címe</string>
@@ -140,12 +141,12 @@
<string name="ln_address">LN-cím</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="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="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="video_saved_to_the_gallery">Videó elmentve a videógalériába</string>
<string name="failed_to_save_the_video">Nem sikerült elmenteni a videót</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 menteni a videót</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="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_global">Globális</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_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)
@@ -1014,6 +1042,8 @@
<string name="torrent_download">Letöltés</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_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="temporary_account">Kijelentkeztetés az eszköz zárolása esetén</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="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="select_signer">Aláíró kiválasztása</string>
</resources>
@@ -112,6 +112,7 @@
<string name="post">Wyślij</string>
<string name="save">Zapisz</string>
<string name="create">Utwórz</string>
<string name="rename">Zmień nazwę</string>
<string name="cancel">Anuluj</string>
<string name="failed_to_upload_the_image">Nie udało się przesłać obrazu</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_global">Wszystkie</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_markdown"> ## Połącz przez Tor z Orbotem
\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_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_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="temporary_account">Wyloguj się przy blokowaniu urządzenia</string>
<string name="private_message">Wiadomość prywatna</string>
@@ -1018,6 +1048,7 @@
<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="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="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>
@@ -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="crashreport_found_send">Prześlij</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>
@@ -112,6 +112,7 @@
<string name="post">Salvar</string>
<string name="save">Salvar</string>
<string name="create">Criar</string>
<string name="rename">Renomear</string>
<string name="cancel">Cancelar</string>
<string name="failed_to_upload_the_image">Falha ao enviar imagem</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_global">Global</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_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)
@@ -830,6 +856,8 @@
<string name="media_compression_quality_medium">Média</string>
<string name="media_compression_quality_high">Alta</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="login_with_qr_code">Entrar com Código QR</string>
<string name="route">Rota</string>
@@ -1012,6 +1040,8 @@
<string name="torrent_download">Baixar</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_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="temporary_account">Terminar sessão no bloqueio do dispositivo</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="crashreport_found_send">Enviar</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>
@@ -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_global">Globalno</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_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)
@@ -112,6 +112,7 @@
<string name="post">Dela</string>
<string name="save">Spara</string>
<string name="create">Skapa</string>
<string name="rename">Byt namn</string>
<string name="cancel">Avbryt</string>
<string name="failed_to_upload_the_image">Det gick inte att ladda upp bilden</string>
<string name="relay_address">Relä Adress</string>
@@ -443,6 +444,31 @@
<string name="follow_list_aroundme">Runt mig</string>
<string name="follow_list_global">Global</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_markdown"> ## Anslut genom Tor med Orbot
\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_high">Hög</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="login_with_qr_code">Logga in med QR-kod</string>
<string name="route">Rutt</string>
@@ -1011,6 +1039,8 @@
<string name="torrent_download">Ladda ner</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_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="temporary_account">Logga ut när enheten låses</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="crashreport_found_send">Skicka</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>
+2
View File
@@ -1042,6 +1042,8 @@
<string name="media_compression_quality_medium">Medium</string>
<string name="media_compression_quality_high">High</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>
+1 -1
View File
@@ -33,7 +33,7 @@ languageId = "17.0.6"
lazysodiumAndroid = "5.2.0"
lazysodiumJava = "5.2.0"
lifecycleRuntimeKtx = "2.9.4"
lightcompressor = "1.4.0"
lightcompressor = "1.5.0"
markdown = "e1151c8"
media3 = "1.8.0"
mockk = "1.14.5"