From 396831456c591a39b4fcd487545e049c02e477a2 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 31 Mar 2025 11:09:04 -0400 Subject: [PATCH 1/3] - Tor Cleanup: - Only starts Tor when the "Internal" option is selected on the privacy settings - Adds a mutex to make sure tor only starts once. - Fixes bug when setting port before the service is running - Moves tor start and stop to Compose actions, instead of activity - Adds a 5 second delay before disconnecting Tor on the App's onPause --- .../vitorpamplona/amethyst/ServiceManager.kt | 17 +++++- .../vitorpamplona/amethyst/model/Account.kt | 7 --- .../service/okhttp/HttpClientManager.kt | 12 +++- .../vitorpamplona/amethyst/ui/MainActivity.kt | 5 -- .../ui/components/ZoomableContentDialog.kt | 9 ++- .../amethyst/ui/screen/AccountScreen.kt | 61 ++++++++++++++++++- .../amethyst/ui/tor/TorManager.kt | 35 ++++++++--- 7 files changed, 116 insertions(+), 30 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt index 8f028fb72..e1bcb40c9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt @@ -103,10 +103,21 @@ class ServiceManager( if (myAccount != null) { when (myAccount.settings.torSettings.torType.value) { TorType.INTERNAL -> { - Log.d("TorManager", "Relays Service Connected ${TorManager.socksPort()}") - HttpClientManager.setDefaultProxyOnPort(TorManager.socksPort()) + // Tor's lib will automatically set this port. + if (TorManager.isSocksReady()) { + HttpClientManager.setDefaultProxyOnPort(TorManager.socksPort()) + } else { + HttpClientManager.setProxyNotReady() + } + } + TorType.EXTERNAL -> { + val port = myAccount.settings.torSettings.externalSocksPort.value + if (port > 0) { + HttpClientManager.setDefaultProxyOnPort(port) + } else { + HttpClientManager.setProxyNotReady() + } } - TorType.EXTERNAL -> HttpClientManager.setDefaultProxyOnPort(myAccount.settings.torSettings.externalSocksPort.value) else -> HttpClientManager.setDefaultProxy(null) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 65f697d0c..9de50211c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -36,7 +36,6 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.location.LocationState -import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.service.uploads.FileHeader import com.vitorpamplona.amethyst.tryAndWait import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS @@ -3648,12 +3647,6 @@ class Account( fun markDonatedInThisVersion() = settings.markDonatedInThisVersion(BuildConfig.VERSION_NAME) - fun httpClientForCoil() = HttpClientManager.getHttpClient(shouldUseTorForImageDownload()) - - fun httpClientForRelay(dirtyUrl: String) = HttpClientManager.getHttpClient(shouldUseTorForDirty(dirtyUrl)) - - fun httpClientForPreviewUrl(url: String) = HttpClientManager.getHttpClient(shouldUseTorForPreviewUrl(url)) - fun shouldUseTorForImageDownload() = when (settings.torSettings.torType.value) { TorType.OFF -> false diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/HttpClientManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/HttpClientManager.kt index f1ffc9174..2cb3ee404 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/HttpClientManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/HttpClientManager.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.service.okhttp +import android.R.attr.port import android.util.Log import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher import okhttp3.OkHttpClient @@ -35,6 +36,8 @@ object HttpClientManager { .followSslRedirects(true) .build() + val DEFAULT_TOR_PROXY = Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", 9050)) + val DEFAULT_TIMEOUT_ON_WIFI: Duration = Duration.ofSeconds(10L) val DEFAULT_TIMEOUT_ON_MOBILE: Duration = Duration.ofSeconds(30L) @@ -43,13 +46,13 @@ object HttpClientManager { private var defaultHttpClientWithoutProxy: OkHttpClient? = null private var userAgent: String = "Amethyst" - private var currentProxy: Proxy? = null + private var currentProxy: Proxy? = DEFAULT_TOR_PROXY private val cache = EncryptionKeyCache() fun setDefaultProxy(proxy: Proxy?) { if (currentProxy != proxy) { - Log.d("HttpClient", "Changing proxy to: ${proxy != null}") + Log.d("HttpClient", "Changing proxy to: $proxy") currentProxy = proxy // recreates singleton @@ -117,6 +120,11 @@ object HttpClientManager { defaultHttpClientWithoutProxy!! } + fun setProxyNotReady() { + // this blocks all connections unless Orbot is live. + setDefaultProxy(DEFAULT_TOR_PROXY) + } + fun setDefaultProxyOnPort(port: Int) { setDefaultProxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", port))) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index 78398f156..b2c77c96c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -46,7 +46,6 @@ import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.screen.AccountScreen import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.theme.AmethystTheme -import com.vitorpamplona.amethyst.ui.tor.TorManager import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress @@ -112,8 +111,6 @@ class MainActivity : AppCompatActivity() { checkLanguage(locales.get(0).language) } - TorManager.startTor(this) - Log.d("Lifetime Event", "MainActivity.onResume") // starts muted every time @@ -160,8 +157,6 @@ class MainActivity : AppCompatActivity() { (getSystemService(ConnectivityManager::class.java) as ConnectivityManager) .unregisterNetworkCallback(networkCallback) - TorManager.stopTor(this) - super.onPause() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt index 13ecd28de..ea1c383f7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt @@ -50,6 +50,7 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextFieldDefaults.contentPadding import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -246,7 +247,9 @@ private fun DialogContent( val writeStoragePermissionState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE) { isGranted -> if (isGranted) { - saveMediaToGallery(myContent, localContext, accountViewModel) + scope.launch { + saveMediaToGallery(myContent, localContext, accountViewModel) + } scope.launch { Toast .makeText( @@ -264,7 +267,9 @@ private fun DialogContent( Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q || writeStoragePermissionState.status.isGranted ) { - saveMediaToGallery(myContent, localContext, accountViewModel) + scope.launch { + saveMediaToGallery(myContent, localContext, accountViewModel) + } scope.launch { Toast .makeText( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt index 473796764..a69208190 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt @@ -39,13 +39,16 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.lifecycle.viewmodel.compose.viewModel @@ -58,8 +61,13 @@ import com.vitorpamplona.amethyst.ui.navigation.AppNavigation import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginOrSignupScreen import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.tor.TorManager +import com.vitorpamplona.amethyst.ui.tor.TorType import com.vitorpamplona.quartz.nip55AndroidSigner.NostrSignerExternal import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch @Composable fun AccountScreen( @@ -136,6 +144,8 @@ fun LoggedInPage( accountViewModel.restartServices() } + ManageTorInstance(accountViewModel) + ListenToExternalSignerIfNeeded(accountViewModel) AppNavigation( @@ -145,6 +155,55 @@ fun LoggedInPage( ) } +@Composable +fun ManageTorInstance(accountViewModel: AccountViewModel) { + val torSettings by accountViewModel.account.settings.torSettings.torType + .collectAsStateWithLifecycle() + if (torSettings == TorType.INTERNAL) { + ManageTorInstanceInner(accountViewModel) + } +} + +@Composable +fun ManageTorInstanceInner(accountViewModel: AccountViewModel) { + val context = LocalContext.current.applicationContext + val lifeCycleOwner = LocalLifecycleOwner.current + + val scope = rememberCoroutineScope() + var job = remember { null } + + DisposableEffect(key1 = accountViewModel) { + job?.cancel() + job = null + TorManager.startTorIfNotAlreadyOn(context) + + val observer = + LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> { + job?.cancel() + job = null + TorManager.startTorIfNotAlreadyOn(context) + } + Lifecycle.Event.ON_PAUSE -> { + job = + scope.launch { + delay(5000) // 5 seconds + TorManager.stopTor(context) + } + } + else -> {} + } + } + + lifeCycleOwner.lifecycle.addObserver(observer) + onDispose { + lifeCycleOwner.lifecycle.removeObserver(observer) + TorManager.stopTor(context) + } + } +} + @Composable private fun ListenToExternalSignerIfNeeded(accountViewModel: AccountViewModel) { if (accountViewModel.account.signer is NostrSignerExternal) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorManager.kt index 446476e24..a25fc1340 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorManager.kt @@ -30,20 +30,40 @@ import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import org.torproject.jni.TorService import org.torproject.jni.TorService.LocalBinder +import java.util.concurrent.atomic.AtomicBoolean object TorManager { + var runningIntent: Intent? = null var torService: TorService? = null + // To make sure we don't start two services. + var isConnectingMutex = AtomicBoolean(false) + fun startTorIfNotAlreadyOn(ctx: Context) { - if (torService == null) { - startTor(ctx) + if (runningIntent == null && isConnectingMutex.compareAndSet(false, true)) { + try { + startTor(ctx) + } finally { + isConnectingMutex.set(false) + } } } - fun startTor(ctx: Context) { + fun stopTor(ctx: Context) { + Log.d("TorManager", "Stopping Tor Service") + runningIntent?.let { + ctx.stopService(runningIntent) + } + runningIntent = null + torService = null + } + + private fun startTor(ctx: Context) { Log.d("TorManager", "Binding Tor Service") + val currentIntent = Intent(ctx, TorService::class.java) + runningIntent = currentIntent ctx.bindService( - Intent(ctx, TorService::class.java), + currentIntent, object : ServiceConnection { override fun onServiceConnected( name: ComponentName, @@ -66,6 +86,7 @@ object TorManager { } override fun onServiceDisconnected(name: ComponentName) { + runningIntent = null torService = null Log.d("TorManager", "Tor Service Disconected") } @@ -74,12 +95,6 @@ object TorManager { ) } - fun stopTor(ctx: Context) { - Log.d("TorManager", "Stopping Tor Service") - torService = null - ctx.stopService(Intent(ctx, TorService::class.java)) - } - fun isSocksReady() = torService?.let { it.socksPort > 0 From b762907d69beee6daaab922a698ed2b93a3cafcc Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 31 Mar 2025 11:09:23 -0400 Subject: [PATCH 2/3] adds a mutex to make sure memory clearing only happens once at a time. --- .../vitorpamplona/amethyst/ServiceManager.kt | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt index e1bcb40c9..6dbdc7564 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt @@ -71,6 +71,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import java.util.concurrent.atomic.AtomicBoolean @Stable class ServiceManager( @@ -84,6 +85,8 @@ class ServiceManager( private var collectorJob: Job? = null + var isTrimmingMemoryMutex = AtomicBoolean(false) + private fun start(account: Account) { this.account = account start() @@ -259,20 +262,26 @@ class ServiceManager( } suspend fun trimMemory() { - LocalCache.cleanObservers() + if (isTrimmingMemoryMutex.compareAndSet(false, true)) { + try { + LocalCache.cleanObservers() - val accounts = - LocalPreferences.allSavedAccounts().mapNotNull { decodePublicKeyAsHexOrNull(it.npub) }.toSet() + val accounts = + LocalPreferences.allSavedAccounts().mapNotNull { decodePublicKeyAsHexOrNull(it.npub) }.toSet() - account?.let { - LocalCache.pruneOldAndHiddenMessages(it) - NostrChatroomDataSource.clearEOSEs(it) + account?.let { + LocalCache.pruneOldAndHiddenMessages(it) + NostrChatroomDataSource.clearEOSEs(it) - LocalCache.pruneHiddenMessages(it) - LocalCache.pruneContactLists(accounts) - LocalCache.pruneRepliesAndReactions(accounts) - LocalCache.prunePastVersionsOfReplaceables() - LocalCache.pruneExpiredEvents() + LocalCache.pruneHiddenMessages(it) + LocalCache.pruneContactLists(accounts) + LocalCache.pruneRepliesAndReactions(accounts) + LocalCache.prunePastVersionsOfReplaceables() + LocalCache.pruneExpiredEvents() + } + } finally { + isTrimmingMemoryMutex.getAndSet(false) + } } } From c79243a9123290fe533f9f6db20ff9cd56888057 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 31 Mar 2025 15:11:49 +0000 Subject: [PATCH 3/3] New Crowdin translations by GitHub Action --- .../src/main/res/values-cs-rCZ/strings.xml | 6 ++++ .../src/main/res/values-de-rDE/strings.xml | 6 ++++ .../src/main/res/values-es-rES/strings.xml | 28 +++++++++++++++++++ .../src/main/res/values-es-rMX/strings.xml | 28 +++++++++++++++++++ .../src/main/res/values-es-rUS/strings.xml | 28 +++++++++++++++++++ .../src/main/res/values-pt-rBR/strings.xml | 6 ++++ .../src/main/res/values-sv-rSE/strings.xml | 6 ++++ 7 files changed, 108 insertions(+) diff --git a/amethyst/src/main/res/values-cs-rCZ/strings.xml b/amethyst/src/main/res/values-cs-rCZ/strings.xml index 1da6feca0..f9d54bc87 100644 --- a/amethyst/src/main/res/values-cs-rCZ/strings.xml +++ b/amethyst/src/main/res/values-cs-rCZ/strings.xml @@ -98,6 +98,7 @@ Popis "O nás.. " Co vás napadá? + Napište zprávu... Příspěvek Uložit Vytvořit @@ -205,6 +206,7 @@ s popisem a obrázkem změnil název chatu na + Nový chatový profil: popis na a obrázek na Opustit @@ -524,6 +526,7 @@ Poslat do Zap peněženky Otevřít v Cashu Wallet Kopírovat token + Otevřít v jiné aplikaci Není nastavena žádná Lightning adresa Token zkopírován do schránky ŽIVĚ @@ -603,6 +606,8 @@ Předmět Téma konverzace "\@Uživatel1, @Uživatel2, @Uživatel3" + Nelze nahrát + Nejdříve vložte cíl zprávy Členové této skupiny Vysvětlení členům Změna názvu pro nové cíle. @@ -936,4 +941,5 @@ Pro otevření a stažení souboru nejsou nainstalovány žádné torrent aplikace. Vyberte seznam pro filtrování kanálu Odhlásit se na zámek zařízení + Soukromá zpráva diff --git a/amethyst/src/main/res/values-de-rDE/strings.xml b/amethyst/src/main/res/values-de-rDE/strings.xml index 5e2dee8be..cca2c8538 100644 --- a/amethyst/src/main/res/values-de-rDE/strings.xml +++ b/amethyst/src/main/res/values-de-rDE/strings.xml @@ -98,6 +98,7 @@ Beschreibung "Über uns.. " Was beschäftigt dich? + Eine Nachricht schreiben... Beitrag Speichern Erstellen @@ -209,6 +210,7 @@ anz der Bedingungen ist erforderlich mit Beschreibung von und Bild Chat-Namen geändert zu + Neues Chat-Profil: Beschreibung zu und Bild zu Verlassen @@ -529,6 +531,7 @@ anz der Bedingungen ist erforderlich An Zap Wallet senden In Cashu Wallet öffnen Token kopieren + In anderer App öffnen Keine Lightning-Adresse festgelegt Token in die Zwischenablage kopiert LIVE @@ -608,6 +611,8 @@ anz der Bedingungen ist erforderlich Betreff Gesprächsthema "\@Benutzer1, @Benutzer2, @Benutzer3" + Kann nicht hochladen + Füge das Ziel der Nachricht zuerst ein Mitglieder dieser Gruppe Erklärung an Mitglieder Ändern des Namens für die neuen Ziele. @@ -941,4 +946,5 @@ anz der Bedingungen ist erforderlich Keine Torrent-Apps installiert, um die Datei zu öffnen und herunterzuladen. Liste zum Filtern des Feeds auswählen Beim Sperren des Geräts abmelden + Private Nachricht diff --git a/amethyst/src/main/res/values-es-rES/strings.xml b/amethyst/src/main/res/values-es-rES/strings.xml index 93de12a44..67d37df3c 100644 --- a/amethyst/src/main/res/values-es-rES/strings.xml +++ b/amethyst/src/main/res/values-es-rES/strings.xml @@ -20,6 +20,7 @@ Suplantación de identidad Comportamiento ilegal Otro + Acoso Desconocido Icono del transmisor Autor desconocido @@ -81,6 +82,13 @@ ¡Muchas gracias! Cantidad en sats Enviar sats + Creador de emojis secretos + Agrega un emoji con mensaje oculto para publicar. + Nota secreta al destinatario + Mi mensaje oculto + Prefijo visible + 😎 + Agregar a publicación "Error al analizar la vista previa de %1$s : %2$s" "Imagen de vista previa para %1$s" Nuevo canal @@ -90,6 +98,7 @@ Descripción "Sobre nosotros… " ¿Qué tienes en mente? + Escribir un mensaje... Publicar Guardar Crear @@ -106,6 +115,7 @@ Fuente global Fuente de búsqueda Añadir transmisor + Nombre Nombre para mostrar Mi nombre para mostrar Avestruz Maravillosa @@ -141,6 +151,7 @@ Conversaciones Notas Respuestas + Tuyo Galería "Siguiendo" "Reportes" @@ -195,6 +206,7 @@ con descripción de y foto cambió el nombre del chat a + Nuevo perfil de chat: descripción a e imagen a Salir @@ -202,6 +214,13 @@ Canal creado "La información del canal cambió a" Chat público + Metadatos públicos del chat + Los chats públicos son visibles para todos en Nostr y cualquiera + puede participar en ellos. Son ideales para comunidades abiertas alrededor de temas específicos. + La moderación puede controlarse eliminando publicaciones en sus relés. + Relés + Inserta entre 1 y 3 relés que alojan este grupo. + Los clientes de Nostr utilizan esta configuración para saber de dónde descargar y adónde enviar los mensajes. publicaciones recibidas Eliminar Automático @@ -509,6 +528,7 @@ Enviar a monedero de zaps Abrir en monedero de Cashu Copiar token + Abrir en otra app La dirección de Lightning no se ha configurado Token copiado al portapapeles EN VIVO @@ -539,6 +559,8 @@ Completo Simplificado Rendimiento + Clásico + Moderno Sistema Claro Oscuro @@ -553,6 +575,8 @@ Ocultar barras de navegación al desplazarse Modo de interfaz Elegir el estilo de publicación + Estilo de galería de perfil + Elige el estilo de galería Cargar imagen Spammers Silenciado. Hacer clic para reactivar el sonido. @@ -584,6 +608,8 @@ Asunto Tema de la conversación "\@Usuario1, @Usuario2, @Usuario3" + No se puede subir + Ingresa primero el destino del mensaje Miembros de este grupo Explicación para los miembros Cambio de nombre para los nuevos objetivos. @@ -851,6 +877,7 @@ Resumen de cambios Correcciones rápidas… Aceptar la sugerencia + Iniciar video en una ventana emergente Descargar Letra activada Letra desactivada @@ -916,4 +943,5 @@ No hay aplicaciones torrent instaladas para abrir y descargar el archivo. Selecciona una lista para filtrar el tablón Cerrar sesión al bloquear dispositivo + Mensaje privado diff --git a/amethyst/src/main/res/values-es-rMX/strings.xml b/amethyst/src/main/res/values-es-rMX/strings.xml index ae995f7a9..cb49d6cf2 100644 --- a/amethyst/src/main/res/values-es-rMX/strings.xml +++ b/amethyst/src/main/res/values-es-rMX/strings.xml @@ -20,6 +20,7 @@ Suplantación de identidad Comportamiento ilegal Otro + Acoso Desconocido Ícono de relé Autor desconocido @@ -81,6 +82,13 @@ ¡Muchas gracias! Cantidad en sats Enviar sats + Creador de emojis secretos + Agrega un emoji con mensaje oculto para publicar. + Nota secreta al destinatario + Mi mensaje oculto + Prefijo visible + 😎 + Agregar a publicación "Error al analizar la vista previa de %1$s : %2$s" "Vista previa de imagen de tarjeta para %1$s" Nuevo canal @@ -90,6 +98,7 @@ Descripción "Quiénes somos..." ¿Qué estás pensando? + Escribir un mensaje... Publicar Guardar Crear @@ -106,6 +115,7 @@ Feed global Feed de búsqueda Agregar un relé + Nombre Nombre para mostrar Mi nombre para mostrar Avestruz Maravillosa @@ -141,6 +151,7 @@ Conversaciones Notas Respuestas + Tuyo Galería "Seguidos" "Reportes" @@ -195,6 +206,7 @@ con la descripción de e imagen cambió el nombre del chat a + Nuevo perfil de chat: descripción a e imagen a Salir @@ -202,6 +214,13 @@ Canal creado "La información del canal cambió a" Chat público + Metadatos públicos del chat + Los chats públicos son visibles para todos en Nostr y cualquiera + puede participar en ellos. Son ideales para comunidades abiertas alrededor de temas específicos. + La moderación puede controlarse eliminando publicaciones en sus relés. + Relés + Inserta entre 1 y 3 relés que alojan este grupo. + Los clientes de Nostr utilizan esta configuración para saber de dónde descargar y adónde enviar los mensajes. publicaciones recibidas Eliminar Automático @@ -509,6 +528,7 @@ Enviar a billetera de zaps Abrir en billetera de Cashu Copiar token + Abrir en otra app La dirección de Lightning no está configurada Token copiado al portapapeles EN VIVO @@ -539,6 +559,8 @@ Completo Simplificado Rendimiento + Clásico + Moderno Sistema Claro Oscuro @@ -553,6 +575,8 @@ Ocultar barras de navegación al desplazarse Modo de interfaz Elegir el estilo de publicación + Estilo de galería de perfil + Elige el estilo de galería Cargar imagen Spammers Silenciado. Hacer clic para reactivar el sonido. @@ -584,6 +608,8 @@ Asunto Tema de la conversación "\@Usuario1, @Usuario2, @Usuario3" + No se puede subir + Ingresa primero el destino del mensaje Miembros de este grupo Explicación para los miembros Cambio de nombre para los nuevos objetivos. @@ -851,6 +877,7 @@ Resumen de cambios Correcciones rápidas… Aceptar la sugerencia + Iniciar video en una ventana emergente Descargar Letra activada Letra desactivada @@ -916,4 +943,5 @@ No hay aplicaciones torrent instaladas para abrir y descargar el archivo. Selecciona una lista para filtrar el feed Cerrar sesión al bloquear dispositivo + Mensaje privado diff --git a/amethyst/src/main/res/values-es-rUS/strings.xml b/amethyst/src/main/res/values-es-rUS/strings.xml index b06dc6020..3ad324eec 100644 --- a/amethyst/src/main/res/values-es-rUS/strings.xml +++ b/amethyst/src/main/res/values-es-rUS/strings.xml @@ -20,6 +20,7 @@ Suplantación de identidad Comportamiento ilegal Otro + Acoso Desconocido Ícono de relé Autor desconocido @@ -81,6 +82,13 @@ ¡Muchas gracias! Cantidad en sats Enviar sats + Creador de emojis secretos + Agrega un emoji con mensaje oculto para publicar. + Nota secreta al destinatario + Mi mensaje oculto + Prefijo visible + 😎 + Agregar a publicación "Error al analizar la vista previa de %1$s : %2$s" "Vista previa de imagen de tarjeta para %1$s" Nuevo canal @@ -90,6 +98,7 @@ Descripción "Quiénes somos..." ¿Qué estás pensando? + Escribir un mensaje... Publicar Guardar Crear @@ -106,6 +115,7 @@ Feed global Feed de búsqueda Agregar un relé + Nombre Nombre para mostrar Mi nombre para mostrar Avestruz Maravillosa @@ -141,6 +151,7 @@ Conversaciones Notas Respuestas + Tuyo Galería "Seguidos" "Reportes" @@ -195,6 +206,7 @@ con la descripción de e imagen cambió el nombre del chat a + Nuevo perfil de chat: descripción a e imagen a Salir @@ -202,6 +214,13 @@ Canal creado "La información del canal cambió a" Chat público + Metadatos públicos del chat + Los chats públicos son visibles para todos en Nostr y cualquiera + puede participar en ellos. Son ideales para comunidades abiertas alrededor de temas específicos. + La moderación puede controlarse eliminando mensajes en sus relés. + Relés + Inserta entre 1 y 3 relés que alojan este grupo. + Los clientes de Nostr utilizan esta configuración para saber de dónde descargar y adónde enviar los mensajes. publicaciones recibidas Eliminar Automático @@ -509,6 +528,7 @@ Enviar a billetera de zaps Abrir en billetera de Cashu Copiar token + Abrir en otra app La dirección de Lightning no está configurada Token copiado al portapapeles EN VIVO @@ -539,6 +559,8 @@ Completo Simplificado Rendimiento + Clásico + Moderno Sistema Claro Oscuro @@ -553,6 +575,8 @@ Ocultar barras de navegación al desplazarse Modo de interfaz Elegir el estilo de publicación + Estilo de galería de perfil + Elige el estilo de galería Cargar imagen Spammers Silenciado. Hacer clic para reactivar el sonido. @@ -584,6 +608,8 @@ Asunto Tema de la conversación "\@Usuario1, @Usuario2, @Usuario3" + No se puede subir + Ingresa primero el destino del mensaje Miembros de este grupo Explicación para los miembros Cambio de nombre para los nuevos objetivos. @@ -851,6 +877,7 @@ Resumen de cambios Correcciones rápidas… Aceptar la sugerencia + Iniciar video en una ventana emergente Descargar Letra activada Letra desactivada @@ -916,4 +943,5 @@ No hay aplicaciones torrent instaladas para abrir y descargar el archivo. Selecciona una lista para filtrar el feed Cerrar sesión al bloquear dispositivo + Mensaje privado diff --git a/amethyst/src/main/res/values-pt-rBR/strings.xml b/amethyst/src/main/res/values-pt-rBR/strings.xml index 3c46fdfa8..3c683f12a 100644 --- a/amethyst/src/main/res/values-pt-rBR/strings.xml +++ b/amethyst/src/main/res/values-pt-rBR/strings.xml @@ -98,6 +98,7 @@ Descrição "Sobre nós.. " O que você está pensando? + Escreva uma mensagem… Salvar Salvar Criar @@ -205,6 +206,7 @@ com descrição de e foto mudou nome do canal para + Novo perfil de chat: descrição para e foto para Sair @@ -524,6 +526,7 @@ Enviar para Zap Wallet Abrir na Carteira Cashu Copiar Token + Abrir em outro aplicativo Nenhum endereço Lightning definido Token copiado para a área de transferência AO VIVO @@ -603,6 +606,8 @@ Assunto Tema da conversa "\@Usuário1, @Usuário2, @Usuário3" + Não pode enviar + Insira o destino da mensagem primeiro Membros deste grupo Explicação aos membros Mudando o nome dos novos objetivos. @@ -936,4 +941,5 @@ Nenhum aplicativo torrent instalado para abrir e baixar o arquivo. Selecione uma lista para filtrar o feed Terminar sessão no bloqueio do dispositivo + Mensagem Privada diff --git a/amethyst/src/main/res/values-sv-rSE/strings.xml b/amethyst/src/main/res/values-sv-rSE/strings.xml index cf72cefd6..43efc6398 100644 --- a/amethyst/src/main/res/values-sv-rSE/strings.xml +++ b/amethyst/src/main/res/values-sv-rSE/strings.xml @@ -98,6 +98,7 @@ Beskrivning "Om oss.. " Vad tänker du på? + Skriv ett meddelande... Dela Spara Skapa @@ -205,6 +206,7 @@ med beskrivning av och bild ändra chat namn till + Ny chattprofil: beskrivning till och bild till Lämna @@ -523,6 +525,7 @@ Skicka till Zap Plånbok Öppna i Cashu Plånbok Kopiera Token + Öppna i annan app Ingen Lightning-adress angiven Kopierade token till urklipp LIVE @@ -602,6 +605,8 @@ Ämne Samtalsämne "\@Användare1, @Användare2, @Användare3" + Kan inte ladda upp + Infoga destinationen för meddelandet först Medlemmar i denna grupp Förklaring till medlemmar Ändra namnet för de nya målen. @@ -935,4 +940,5 @@ Inga torrent-appar installerade för att öppna och ladda ner filen. Välj en lista för att filtrera flödet Logga ut när enheten låses + Privat meddelande