diff --git a/app/build.gradle b/app/build.gradle index bb50960e2..8fc23db2b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 34 - versionCode 291 - versionName "0.75.12" + versionCode 293 + versionName "0.75.14" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/androidTest/java/com/vitorpamplona/amethyst/ThreadAssemblerTest.kt b/app/src/androidTest/java/com/vitorpamplona/amethyst/ThreadAssemblerTest.kt index 1bbf7caeb..f70ced309 100644 --- a/app/src/androidTest/java/com/vitorpamplona/amethyst/ThreadAssemblerTest.kt +++ b/app/src/androidTest/java/com/vitorpamplona/amethyst/ThreadAssemblerTest.kt @@ -1,8 +1,10 @@ package com.vitorpamplona.amethyst import com.fasterxml.jackson.module.kotlin.readValue +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter +import com.vitorpamplona.quartz.crypto.KeyPair import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.events.Event import junit.framework.TestCase @@ -95,7 +97,7 @@ class ThreadAssemblerTest { val naddr = ATag(30023, "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93", "1680612926599", null) - val filter = ThreadFeedFilter(naddr.toTag()) + val filter = ThreadFeedFilter(Account(KeyPair()), naddr.toTag()) val calculatedFeed = filter.feed() val expecteedOrder = listOf( @@ -126,14 +128,14 @@ class ThreadAssemblerTest { "6a58f8315af5badb1bdaeb5489417b94621a4d8e192ae2fedcca0c5dcf0c9cd4", "6e9bb03c7c40d67fec0d0bb872548ec207ba0ac4533efa137d7bcaca9fb4b191", "e2ae784b239cac4bad38136e4bd758b87dd261b659ef460450064bf9073edcb3", - "45d4fc726f2cc5b524be862c14fdadc1a24b25b8c6c011eedf2d2909589263e7", - "7a4a2419824669f07081abe2132f8cc0027efbce066ccdf187c897bb7ffa5dc3", "674c62f84afdc045bc3623ea132d90afdfe4b64249807f65302231115af5406d", "d54761f672669ea4f4b7592f3b0a30ee28de340b0a7e46b91af94e66905171c9", "00813a18ac9084cd0948c27027a980e34039a3011f30279a8b52ad87da5a3031", "87a5bd25aa084cefb3357fc9c2a5b327254fab35fdd7b2d4bd0acddc63d0abe8", "512962dbada5fd5015fc727a107d5c3f569662de67eab8e5da5a8065012cf11e", "ce6e32e3e17b6901d2cc70b60f3743e24f885bb6e9da6d88cff516079eac1883", + "7a4a2419824669f07081abe2132f8cc0027efbce066ccdf187c897bb7ffa5dc3", + "45d4fc726f2cc5b524be862c14fdadc1a24b25b8c6c011eedf2d2909589263e7", "d4a0b4f08d98d82a04292654ec132723cc2cf3fa24ffb6c0833426cb9372f4d5", "8cdc4676aca93bbafcfbe6784f9b2df54e8ca20fbe69ba55fda487736bfdb7f6", "7a18dda355525d468b31bba4fa947cba98cc19048d4a3099d5e9ba045d878c26" diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index ad0daf0fe..63788310a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -142,7 +142,7 @@ open class Note(val idHex: String) { * This method caches signatures during each execution to avoid recalculation in longer threads */ fun replyLevelSignature( - eventsToConsider: Set, + eventsToConsider: Set, cachedSignatures: MutableMap, account: User, accountFollowingSet: Set, @@ -151,7 +151,7 @@ open class Note(val idHex: String) { val replyTo = replyTo if (event is RepostEvent || event is GenericRepostEvent || replyTo == null || replyTo.isEmpty()) { return LevelSignature( - signature = "/" + formattedDateTime(createdAt() ?: 0) + ";", + signature = "/" + formattedDateTime(createdAt() ?: 0) + idHex.substring(0, 8) + ";", createdAt = createdAt(), author = author ) @@ -159,7 +159,7 @@ open class Note(val idHex: String) { val parent = ( replyTo - .filter { it in eventsToConsider } // This forces the signature to be based on a branch, avoiding two roots + .filter { it.idHex in eventsToConsider } // This forces the signature to be based on a branch, avoiding two roots .map { cachedSignatures[it] ?: it.replyLevelSignature( eventsToConsider, @@ -176,13 +176,13 @@ open class Note(val idHex: String) { val threadOrder = if (parent?.author == author && createdAt() != null) { // author of the thread first, in **ascending** order - "9" + formattedDateTime((parent?.createdAt ?: 0) + (now - (createdAt() ?: 0))) + "9" + formattedDateTime((parent?.createdAt ?: 0) + (now - (createdAt() ?: 0))) + idHex.substring(0, 8) } else if (author?.pubkeyHex == account.pubkeyHex) { - "8" + formattedDateTime(createdAt() ?: 0) // my replies + "8" + formattedDateTime(createdAt() ?: 0) + idHex.substring(0, 8) // my replies } else if (author?.pubkeyHex in accountFollowingSet) { - "7" + formattedDateTime(createdAt() ?: 0) // my follows replies. + "7" + formattedDateTime(createdAt() ?: 0) + idHex.substring(0, 8) // my follows replies. } else { - "0" + formattedDateTime(createdAt() ?: 0) // everyone else. + "0" + formattedDateTime(createdAt() ?: 0) + idHex.substring(0, 8) // everyone else. } val mySignature = LevelSignature( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt index 6d67e3ddb..eded63d1d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt @@ -164,9 +164,16 @@ class LightningAddressResolver() { onProgress(0.7f) onSuccess(pr) } else { + onProgress(0.0f) onError("Incorrect invoice amount (${invoiceAmount.toLong()} sats) from server") } - } ?: onError("Invoice Not Created (element pr not found in the resulting JSON)") + } ?: lnInvoice?.get("reason")?.asText()?.ifBlank { null }?.let { reason -> + onProgress(0.0f) + onError("Unable to create a lightning invoice before sending the zap. The receiver's lightning wallet sent the following error: $reason") + } ?: run { + onProgress(0.0f) + onError("nable to create a lightning invoice before sending the zap. Element pr not found in the resulting JSON.") + } }, onError = onError ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index dd8e104a0..9b740b2f6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -19,6 +19,7 @@ import com.vitorpamplona.quartz.events.LnZapEvent import com.vitorpamplona.quartz.events.LnZapRequestEvent import com.vitorpamplona.quartz.events.PrivateDmEvent import com.vitorpamplona.quartz.events.SealedGossipEvent +import com.vitorpamplona.quartz.utils.TimeUtils import kotlinx.collections.immutable.persistentSetOf import java.math.BigDecimal @@ -75,7 +76,11 @@ class EventNotificationConsumer(private val applicationContext: Context) { if (acc != null && acc.userProfile().pubkeyHex == giftWrap.recipientPubKey()) { val chatEvent = unwrapAndConsume(giftWrap, account = acc) - if (chatEvent is ChatMessageEvent && acc.keyPair.privKey != null) { + if (chatEvent is ChatMessageEvent && // must be messages, not any other event + acc.keyPair.privKey != null && // can't decrypt + chatEvent.createdAt > TimeUtils.fiveMinutesAgo() && // old event being re-broadcasted + chatEvent.pubKey != acc.userProfile().pubkeyHex // from the user + ) { val chatNote = LocalCache.notes[chatEvent.id] ?: return val chatRoom = chatEvent.chatroomKey(acc.keyPair.pubKey.toHexKey()) @@ -101,6 +106,9 @@ class EventNotificationConsumer(private val applicationContext: Context) { private fun notify(event: PrivateDmEvent) { val note = LocalCache.notes[event.id] ?: return + // old event being re-broadcast + if (event.createdAt < TimeUtils.fiveMinutesAgo()) return + LocalPreferences.allSavedAccounts().forEach { val acc = LocalPreferences.loadFromEncryptedStorage(it.npub) @@ -130,6 +138,9 @@ class EventNotificationConsumer(private val applicationContext: Context) { private fun notify(event: LnZapEvent) { val noteZapEvent = LocalCache.notes[event.id] ?: return + // old event being re-broadcast + if (event.createdAt < TimeUtils.fiveMinutesAgo()) return + val noteZapRequest = event.zapRequest?.id?.let { LocalCache.checkGetOrCreateNote(it) } ?: return val noteZapped = event.zappedPost().firstOrNull()?.let { LocalCache.checkGetOrCreateNote(it) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt index 21ea17847..4a95c0bed 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt @@ -47,6 +47,7 @@ class Relay( private var listeners = setOf() private var socket: WebSocket? = null private var isReady: Boolean = false + private var usingCompression: Boolean = false var eventDownloadCounterInBytes = 0 var eventUploadCounterInBytes = 0 @@ -85,6 +86,9 @@ class Relay( private var connectingBlock = AtomicBoolean() fun connectAndRun(onConnected: (Relay) -> Unit) { + // BRB is crashing OkHttp Deflater object :( + if (url.contains("brb.io")) return + // If there is a connection, don't wait. if (connectingBlock.getAndSet(true)) { return @@ -115,7 +119,10 @@ class Relay( override fun onOpen(webSocket: WebSocket, response: Response) { checkNotInMainThread() - markConnectionAsReady(response.receivedResponseAtMillis - response.sentRequestAtMillis) + markConnectionAsReady( + pingInMs = response.receivedResponseAtMillis - response.sentRequestAtMillis, + usingCompression = response.headers.get("Sec-WebSocket-Extensions")?.contains("permessage-deflate") ?: false + ) // Log.w("Relay", "Relay OnOpen, Loading All subscriptions $url") onConnected(this@Relay) @@ -175,15 +182,17 @@ class Relay( } } - fun markConnectionAsReady(pingInMs: Long) { + fun markConnectionAsReady(pingInMs: Long, usingCompression: Boolean) { this.afterEOSE = false this.isReady = true this.pingInMs = pingInMs + this.usingCompression = usingCompression } fun markConnectionAsClosed() { this.socket = null this.isReady = false + this.usingCompression = false this.afterEOSE = false this.closingTimeInSeconds = TimeUtils.now() } @@ -239,6 +248,7 @@ class Relay( socket?.close(1000, "Normal close") socket = null isReady = false + usingCompression = false afterEOSE = false } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ThreadFeedFilter.kt index 0f028d4bf..a216db8a4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ThreadFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ThreadFeedFilter.kt @@ -18,11 +18,12 @@ class ThreadFeedFilter(val account: Account, val noteId: String) : FeedFilter = mutableMapOf() val followingSet = account.selectedUsersFollowList(KIND3_FOLLOWS) ?: emptySet() val eventsToWatch = ThreadAssembler().findThreadFor(noteId) + val eventsInHex = eventsToWatch.map { it.idHex }.toSet() val now = TimeUtils.now() // Currently orders by date of each event, descending, at each level of the reply stack val order = compareByDescending { - it.replyLevelSignature(eventsToWatch, cachedSignatures, account.userProfile(), followingSet, now).signature + it.replyLevelSignature(eventsInHex, cachedSignatures, account.userProfile(), followingSet, now).signature } return eventsToWatch.sortedWith(order) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index afdb46afa..04f3eb872 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -2700,14 +2700,14 @@ private fun BadgeBox( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - val isRepost by remember { + val isRepost by remember(baseNote) { derivedStateOf { baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent } } if (isRepost) { - val baseReply by remember { + val baseReply by remember(baseNote) { derivedStateOf { baseNote.replyTo?.lastOrNull() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index afa21a7e1..4a2647314 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -1025,11 +1025,29 @@ fun ZapReaction( } if (wantsToChangeZapAmount) { - UpdateZapAmountDialog({ wantsToChangeZapAmount = false }, accountViewModel = accountViewModel) + UpdateZapAmountDialog( + onClose = { wantsToChangeZapAmount = false }, + accountViewModel = accountViewModel + ) } if (wantsToSetCustomZap) { - ZapCustomDialog({ wantsToSetCustomZap = false }, accountViewModel, baseNote) + ZapCustomDialog( + onClose = { wantsToSetCustomZap = false }, + onError = { + scope.launch { + zappingProgress = 0f + showErrorMessageDialog = it + } + }, + onProgress = { + scope.launch(Dispatchers.Main) { + zappingProgress = it + } + }, + accountViewModel = accountViewModel, + baseNote = baseNote + ) } if (zappingProgress > 0.00001 && zappingProgress < 0.99999) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt index e988207a4..d267dcf33 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt @@ -197,7 +197,11 @@ class UpdateZapAmountViewModel(val account: Account) : ViewModel() { @OptIn(ExperimentalLayoutApi::class) @Composable -fun UpdateZapAmountDialog(onClose: () -> Unit, nip47uri: String? = null, accountViewModel: AccountViewModel) { +fun UpdateZapAmountDialog( + onClose: () -> Unit, + nip47uri: String? = null, + accountViewModel: AccountViewModel +) { val context = LocalContext.current val scope = rememberCoroutineScope() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index 591553867..828182dac 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -1,6 +1,5 @@ package com.vitorpamplona.amethyst.ui.note -import android.widget.Toast import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* @@ -31,8 +30,6 @@ import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.LnZapEvent import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch class ZapOptionstViewModel : ViewModel() { private var account: Account? = null @@ -61,17 +58,20 @@ class ZapOptionstViewModel : ViewModel() { } @Composable -fun ZapCustomDialog(onClose: () -> Unit, accountViewModel: AccountViewModel, baseNote: Note) { +fun ZapCustomDialog( + onClose: () -> Unit, + onError: (text: String) -> Unit, + onProgress: (percent: Float) -> Unit, + accountViewModel: AccountViewModel, + baseNote: Note +) { val context = LocalContext.current - val scope = rememberCoroutineScope() val postViewModel: ZapOptionstViewModel = viewModel() LaunchedEffect(accountViewModel) { postViewModel.load(accountViewModel.account) } - var zappingProgress by remember { mutableStateOf(0f) } - val zapTypes = listOf( Triple(LnZapEvent.ZapType.PUBLIC, stringResource(id = R.string.zap_type_public), stringResource(id = R.string.zap_type_public_explainer)), Triple(LnZapEvent.ZapType.PRIVATE, stringResource(id = R.string.zap_type_private), stringResource(id = R.string.zap_type_private_explainer)), @@ -111,17 +111,8 @@ fun ZapCustomDialog(onClose: () -> Unit, accountViewModel: AccountViewModel, bas null, postViewModel.customMessage.text, context, - onError = { - zappingProgress = 0f - scope.launch { - Toast.makeText(context, it, Toast.LENGTH_SHORT).show() - } - }, - onProgress = { - scope.launch(Dispatchers.Main) { - zappingProgress = it - } - }, + onError = onError, + onProgress = onProgress, zapType = selectedZapType ) onClose() diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index d18cf5312..033b5af98 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -113,7 +113,7 @@ Konversacioj Notoj Respondoj - "Sekvoj" + "Sekvitaj" "Raportoj" Pli da Opcioj " Plusendiloj" diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 66a570486..7e927f205 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -1,2 +1,477 @@ - + + Osoita QR-koodiin + Näytä QR + Profiilikuva + Skannaa QR + Näytä silti + Viesti raportoitiin käyttäjän toimesta + Tapahtumaa ladataan tai sitä ei löydy relelistalta + Kanavan kuva + Viitattua tapahtumaa ei löytynyt + Viestin purku epäonnistui + Ryhmäkuva + Julkeaa sisältöä + Roskaposti + Väärennös + Laiton toiminta + Tuntematon + Relekuvake + Tuntematon tekijä + Kopioi teksti + Kopioi tekijän tunniste + Kopioi viestin tunniste + Lähetä + Pyydä poistoa + Estä / Ilmoita + + Ilmoita roskapostista / huijauksesta + Ilmoita väärennöksestä + Ilmoita julkeasta sisällöstä + Ilmoita laittomasta toiminnasta + Kirjaudu sisään yksityisellä avaimella viestehin vastaamiseksi + Kirjaudu sisään yksityisellä avaimella tehostaaksesi viestejä + Kirjaudu sisään yksityisellä avaimella tykätäksesi viesteistä + Zap-määrää ei ole asetettu. Pidä painiketta pohjassa vaihtaaksesi sitä + Kirjaudu sisään yksityisellä avaimella Zapien lähettämiseksi + Kirjaudu sisään yksityisellä avaimella voidaksesi seurata + Kirjaudu sisään yksityisellä avaimella seuraamisen lopettamiseksi + Zapit + Katselukerrat + Tehosta + tehostettu + Lainaa + Uusi määrä satseissa + Lisää + "vastaus kohteeseen " + " ja " + "kanavassa " + Profiilin banneri + Maksu onnistui + Virhe virhesanomaa tulkittaessa + " Seuratut" + " Seuraajat" + Profiili + Turvallisuussuodattimet + Kirjaudu ulos + Näytä lisää + Lightning lasku + Maksa + Lightning tipit + Viesti vastaanottajalle + Kiitos paljon! + Määrä satseissa + Lähetä satseja + "Virhe tulkittaessa esikatselua kohteelle %1$s : %2$s" + "Esikatsele kortin kuvaa kohteelle %1$s" + Uusi kanava + Kanavan nimi + Oma upea ryhmäni + Kuvan URL + Kuvaus + "Tietoa meistä.. " + Mitä mielessä? + Lähetä + Tallenna + Luo + Peruuta + Kuvan lataus epäonnistui + Releosoite + Viestit + Tavua + Virheet + Kotisyöte + Yksityinen viestisyöte + Julkinen chat-syöte + Maailmanlaajuinen syöte + Hae syötettä + Lisää rele + Näyttönimi + Oma näyttönimeni + Käyttäjänimi + Oma käyttäjänimeni + Tietoja minusta + Avatarin URL + Bannerin URL + Verkkosivuston URL + LN-osoite + LN-URL (vanhentunut) + Kuva tallennettu galleriaan + Kuvan tallennus epäonnistui + Lataa kuva + Ladataan… + Käyttäjällä ei ole asetettuna Lightning-osoitetta satsien vastaanottamiseksi + "vastaa tähän.. " + Kopioi viestin tunniste leikepöydälle Nostressa jakamista varten + Kopioi kanavan tunniste (viesti) leikepöydälle + Muokkaa kanavan metatietoja + Liity + Tunnetut + Uudet pyynnöt + Estetyt käyttäjät + Uudet keskustelut + Keskustelut + Viestit + Vastaukset + "Seuratut" + "Ilmiannetut" + Lisää vaihtoehtoja + " Releet" + Verkkosivusto + Lightning-osoite + Kopioi Nsec-tunniste (salasanasi) leikepöydälle varmuuskopiointia varten + Kopioi yksityinen avain leikepöydälle + Kopioi julkinen avain leikepöydälle jakamista varten + Kopioi julkinen avain (NPub) leikepöydälle + Lähetä suora viesti + Muokkaa käyttäjän metatietoja + Seuraa + Seuraa takaisin + Poista esto + Kopioi käyttäjän tunniste + Poista käyttäjän esto + "npub, käyttäjänimi, teksti" + Tyhjennä + Sovelluksen logo + nsec.. tai npub.. + Näytä salasana + Piilota salasana + Virheellinen avain + "Hyväksyn " + käyttöehdot + Käyttöehtojen hyväksyminen on pakollista + Avain on pakollinen + Kirjaudu sisään + Luo uusi avain + Ladataan syöte + "Virhe ladattaessa vastauksia: " + Yritä uudelleen + Syöte on tyhjä. + Päivitä + luotu + kuvaus + ja kuva + muutti keskustelun nimen kohteeksi + kuvaus kohteeksi + ja kuva kohteeksi + Poistu + Lopeta seuraaminen + Kanava luotu + "Kanavan tiedot muutettu kohteeksi" + Julkinen keskustelu + vastaanotetut viestit + Poista + Automaattinen + käännös kohteesta + kohteeseen + Näytä ensin kielellä %1$s + Aina käännä kielelle %1$s + Älä koskaan käännä kielestä %1$s + Nostr-osoite + ei koskaan + nyt + t + m + pv + Alastomuus + Loukkaava kielenkäyttö / vihapuhe + Ilmoita vihapuheesta + Ilmoita alastomuudesta / pornosta + muut + Merkitse kaikki Tunnetut luetuiksi + Merkitse kaikki Uudet luetuiksi + Merkitse kaikki luetuiksi + Varmuuskopioi Avaimet + ## Avaimen varmuuskopiointi ja turvallisuusvinkit + \n\nTiliäsi suojaa salainen avain. Avain on pitkä satunnainen merkkijono, joka alkaa **nsec1**. Kaikki, joilla on pääsy salaiseen avaimeesi, voivat julkaista sisältöä käyttäen identiteettiäsi. + \n\n- Älä **koskaan** laita salaista avaintasi mihinkään verkkosivustoon tai ohjelmistoon, johon et luota. + \n- Amethyst-kehittäjät eivät **koskaan** kysy sinulta salaista avaintasi. + \n- **Säilytä** salaisen avaimen varmuuskopio turvallisesti tilin palauttamista varten. Suosittelemme salasanahallintaohjelman käyttämistä. + + Salainen avain (nsec) kopioitu leikepöydälle + Kopioi salainen avain + Tunnistautuminen epäonnistui + Virhe + "Luonut %1$s" + "Merkki kohteelle %1$s" + Olet saanut uuden merkin + Merkki myönnetty kohteelle + Viestin teksti kopioitu leikepöydälle + Käyttäjän @npub kopioitu leikepöydälle + Viestin tunniste (@note1) kopioitu leikepöydälle + Valitse teksti + "<Ei voitu purkaa yksityisviestiä>\n\nSinut mainittiin yksityisessä/salatussa keskustelussa kohteiden %1$s ja %2$s välillä." + Lisää uusi tili + Tilit + Valitse tili + Lisää uusi tili + Aktiivinen tili + Sisältää salaisen avaimen + Vain julkisavain, ei salaista avainta + Takaisin + Valitse + Jaa selaimen linkki + Jaa + Kirjoittajan tunniste + Viestin tunniste + Kopioi teksti + Poista + Lopeta seuraaminen + Seuraa + Pyydä poistoa + Amethyst pyytää, että viestisi poistetaan releistä, joihin olet tällä hetkellä yhteydessä. Emme voi taata, että viestisi poistetaan pysyvästi niistä releistä tai muista releistä, joissa se voi olla tallennettuna. + Estä + Poista + Estä + Ilmoita + Poista + Älä näytä uudelleen + Roskaposti tai huijaus + Loukkaava kielenkäyttö tai vihamielinen käytös + Haitallinen henkilöllisyyden omiminen + Alastomuus tai graafinen sisältö + Laiton käytös + Käyttäjän estäminen piilottaa hänen sisältönsä sovelluksessasi. Merkintäsi ovat silti julkisesti nähtävillä, myös niille, jotka estät. Estetyt käyttäjät on lueteltu turvallisuussuodattimien näytössä. + + Ilmoita väärinkäytöstä + Kaikki ilmoitukset ovat julkisesti nähtävillä. + Voit antaa lisäkontekstia ilmoituksellesi valinnaisesti... + Lisäkonteksti + Syy + Valitse syy... + Lähetä ilmoitus + Estä ja ilmoita + Estä + Kirjanmerkit + Yksityiset kirjanmerkit + Julkiset kirjanmerkit + Lisää yksityisiin kirjanmerkkeihin + Lisää julkisiin kirjanmerkkeihin + Poista yksityisistä kirjanmerkeistä + Poista julkisista kirjanmerkeistä + Lompakkoyhteyspalvelu + Valtuuttaa Nostr-salaisuuden maksamaan zapeja ilman sovelluksesta poistumista. Pidä salaisuutesi turvassa ja käytä yksityistä relettä, jos mahdollista. + Lompakkoyhteyden julkinen avain + Lompakkoyhteyden rele + Lompakkoyhteyden salainen avain + Näytä salainen avain + nsec / heksadesimaalinen salainen avain + Luvattu määrä satseissa + Luo äänestys + Vaaditut kohdat: + Zap-vastaanottajat + Pääasiallinen äänestyksen kuvaus... + Vaihtoehto %s + Vaihtoehdon kuvaus + Valinnaiset kohdat: + Vähimmäiszap + Enimmäiszap + Konsensus + (0–100)% + Sulje äänestys + päivän kuluttua + Äänestys on suljettu uusilta ääniltä + Zap-määrä + Vain yksi ääni käyttäjää kohti tällä äänestystyypillä + "Etsitään tapahtumaa %1$s" + Lisää julkinen viesti + Lisää yksityinen viesti + Lisää laskuviesti + Kiitos tehdystä työstä! + Luo ja lisää + Äänestyksen luojat eivät voi äänestää omissa äänestyksissään. + Tämä sisältö on sama kuin viestin julkaisuhetkellä + Tämä sisältö on muuttunut. Kirjoittaja ei ehkä ole nähnyt tai hyväksynyt muutosta + Lisää kuva + Lisää video + Lisää dokumentti + Lisää viestiin + Sisällön kuvaus + Sininen vene valkoisella hiekkarannalla auringonlaskussa + Zap-tyyppi + Zap-tyyppi kaikille vaihtoehdoille + Julkinen + Kaikki voivat nähdä tapahtuman ja viestin + Yksityinen + Lähettäjä ja vastaanottaja voivat nähdä toisensa ja lukea viestin + Anonyymi + Vastaanottaja ja yleisö eivät tiedä, kuka lähetti maksun + Ei-Zap + Ei jälkiä Nostrissa, vain Lightningissa + Tiedostopalvelin + LnAddress tai @Käyttäjä + imgur.com - luotettava + Imgur voi muokata tiedostoa + nostrimg.com - luotettava + NostrImg voi muokata tiedostoa + nostr.build - luotettava + Nostr.build voi muokata tiedostoa + nostrfiles.dev - luotettava + Nostrfiles.dev voi muokata tiedostoa + nostrcheck.me - luotettava + Nostrcheck.me voi muokata tiedostoa + Todennettava Imgur (NIP-94) + Tarkistaa, onko Imgur muokannut tiedostoa. Uusi NIP: muut asiakkaat eivät ehkä näe sitä + Todennettava NostrImg (NIP-94) + Tarkistaa, onko NostrImg muokannut tiedostoa. Uusi NIP: muut asiakkaat eivät ehkä näe sitä + Todennettava Nostr.build (NIP-94) + Tarkistaa, onko Nostr.build muokannut tiedostoa. Uusi NIP: muut asiakkaat eivät ehkä näe sitä + Todennettava Nostrfiles.dev (NIP-94) + Tarkistaa, onko Nostrfiles.dev muokannut tiedostoa. Uusi NIP: muut asiakkaat eivät ehkä näe sitä + Todennettava Nostrcheck.me (NIP-94) + Tarkistaa, onko Nostrcheck.me muokannut tiedostoa. Uusi NIP: muut asiakkaat eivät ehkä näe sitä + Omat releet (NIP-95) + Tiedostot ovat isännöityinä omilla releilläsi. Uusi NIP: tarkista, tukevatko ne sitä + Tor/Orbot-asetus + Yhdistä Orbot-asetuksesi kautta + Haluatko todella katkaista Orbot/Tor-yhteytesi? + Tietosi siirtyvät välittömästi tavalliseen verkkoon + Kyllä + Ei + Seuraa-lista + Kaikki seuraajat + Maailmanlaajuinen + ## Yhdistä Torin kautta Orbotin avulla + \n\n1. Asenna [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) + \n2. Käynnistä Orbot + \n3. Orbotissa tarkista Socks-portti. Oletusarvo on 9050 + \n4. Tarvittaessa vaihda porttia Orbotissa + \n5. Aseta Socks-portti tällä näytöllä + \n6. Paina Aktivoi-painiketta käyttääksesi Orbotia välityspalvelimena + + Orbot Socks-portti + Virheellinen porttinumero + Käytä Orbotia + Katkaise Tor/Orbot-yhteys + Yksityisviestit + Ilmoittaa sinulle, kun yksityinen viesti saapuu + Saapuneet Zapit + Ilmoittaa sinulle, kun joku zappaa sinua + %1$s sats + Lähettäjä: %1$s + saaja: %1$s + Ilmoitus: + Liity keskusteluun + Käyttäjän tai ryhmän tunniste + npub, nevent tai heksadesimaali + Luo + Liity + Tänään + Sisältövaroitus + Tämä viesti sisältää herkkää sisältöä, joka voi olla loukkaavaa tai häiritsevää joillekin ihmisille + Piilota aina herkkä sisältö + Näytä aina herkkä sisältö + Näytä aina sisältövaroitukset + Suosittelee: + Suodata roskapostia tuntemattomilta + Varoita, kun seurattavasi ovat raportoineet viestejä + Uusi reaktiosymboli + Ei valittu reaktiotyyppejä. Pidä pitkään painettuna vaihtaaksesi. + Zapkerääjä + Lisää tavoitesumma satseina tämän viestin tukemiseksi. Tuettavat asiakkaat voivat näyttää tämän edistymispalkkina tuen kannustamiseksi + Tavoitesumma satseina + Zapraiser %1$s. %2$s satsiin tavoitteesta + Lue releestä + Kirjoita releeseen + Virhe yritettäessä hakea rele-tietoja %1$s:stä + Omistaja + Versio + Ohjelmisto + Yhteystiedot + Tuetut NIPit + Pääsymaksut + Maksut URL + Rajoitukset + Maat + Kielet + Tagit + Julkaisupolitiikka + Viestin pituus + Tilaukset + Suodattimet + Tilaustunnuksen pituus + Vähimmäisliite + Maksimi tapahtumien tagit + Sisällön pituus + Vähimmäis PoW + Autentikointi + Maksu + Cashu-tunniste + Lunasta + Ei Lightning-osoitetta asetettuna + Kopioitu tunniste leikepöydälle + LIVE + POISSA + PÄÄTTYNYT + AJOITETTU + Livestream on offline-tilassa + Livestream on päättynyt + Uloskirjautuminen poistaa kaikki paikalliset tiedot. Varmista, että sinulla on varmuuskopio yksityisavaimistasi välttääksesi tilisi menetyksen. Haluatko jatkaa? + Seuratut tagit + Releet + Live + Yhteisö + Keskustelut + Hyväksytyt viestit + Tällä ryhmällä ei ole kuvausta tai sääntöjä. Keskustele omistajan kanssa sen lisäämiseksi + Tällä yhteisöllä ei ole kuvausta. Keskustele omistajan kanssa sen lisäämiseksi + Herkkä sisältö + Lisää herkän sisällön varoitus ennen tämän sisällön näyttämistä + Asetukset + Aina + Vain WiFi + Ei koskaan + Järjestelmä + Vaalea + Tumma + Sovelluksen asetukset + Kieli + Teema + Kuvan esikatselu + Videon toisto + URL-esikatselu + Lataa kuva + Roskapostittajat + Ääni pois päältä. Napsauta palauttaaksesi äänet + Ääni päällä. Napsauta vaimentaaksesi + Hae paikallisia ja etämerkintöjä + Nostr-osoite varmennettiin + Nostr-osoite ei läpäissyt varmennusta + Tarkistetaan Nostr-osoite + Valitse/Poista valinta kaikista + Oletus + Valitse rele jatkaaksesi + Ohjaa zappit kohteeseen: + Sovelluksien tukeminen ohjaa zappit alla olevaan LN-osoitteeseen tai käyttäjäprofiiliin sen sijaan, että ne menisivät sinulle + Näytä sijaintina + Lisää sijaintisi Geohashinä viestiin. On julkisesti nähtävänä, että olet 5 km (3 mailin) säteellä nykyisestä sijainnista + Lisää herkkä sisältö -varoitus ennen sisällön näyttämistä. Tämä on suositeltua kaikelle aikuissisällölle tai sisällölle, jota jotkut saattavat pitää loukkaavana tai häiritsevänä + Uusi ominaisuus + Tämän tilan aktivoiminen vaatii Amethystin lähettämään NIP-24-viestin (Lahjapakatut, Suljetut suorat ja ryhmäviestit). NIP-24 on uusi, ja useimmat asiakkaat eivät ole vielä toteuttaneet sitä. Varmista, että vastaanottaja käyttää yhteensopivaa asiakasta. + Aktivoi + Julkinen + Yksityinen + Vastaanottaja + Aihe + Keskustelun aihe + "\@Käyttäjä1, @Käyttäjä2, @Käyttäjä3" + Tämän ryhmän jäsenet + Selitys jäsenille + Muuta nimi uusille tavoitteille. + Sovelluksen käyttöliittymälle + Tumma, vaalea tai järjestelmäteema + Lataa kuvat ja GIF-animaatiot automaattisesti + Toista videot ja GIF-animaatiot automaattisesti + Näytä URL-esikatselut + Koska kuvat ladataan + Kopioi URL leikepöydälle + Kopioi viestin tunniste leikepöydälle + Luotu + Säännöt + Päivitä tilaasi + Virhe tulkittaessa virhesanomaa + Äänet painotetaan zappin määrän perusteella. Voit asettaa minimimäärän roskapostin välttämiseksi ja maksimimäärän suurten zappereiden estämiseksi. Käytä samaa määrää molemmissa kentissä, jotta jokainen ääni arvostetaan samalla määrällä. Jätä se tyhjäksi hyväksyäksesi minkä tahansa määrän. + Zapin lähettäminen ei onnistunut + Viesti käyttäjälle + Ok + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1303a71dc..200383078 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -101,7 +101,7 @@ Failed to save the image Upload Image Uploading… - User does not have a lightning address setup to receive sats + User does not have a lightning address set up to receive sats "reply here.. " Copies the Note ID to the clipboard for sharing in Nostr Copy Channel ID (Note) to the Clipboard