mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-07-22 15:52:22 +00:00
Compare commits
118 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d0c356aa5 | ||
|
|
0a92c253b8 | ||
|
|
92347863bc | ||
|
|
a146ec5e1a | ||
|
|
83be43e94e | ||
|
|
c845aac8e5 | ||
|
|
1cdf114fff | ||
|
|
4d1a99d076 | ||
|
|
b1debd9879 | ||
|
|
3843917bd1 | ||
|
|
536fb49bc3 | ||
|
|
edd93141bc | ||
|
|
20c2d19a9c | ||
|
|
17b2fe9cd8 | ||
|
|
134e7cee0c | ||
|
|
108ddffcc7 | ||
|
|
20e76daff8 | ||
|
|
7bee44c5ad | ||
|
|
a32e90aed0 | ||
|
|
310db1008e | ||
|
|
cb69d056b8 | ||
|
|
e7f950cd32 | ||
|
|
3b2686ffeb | ||
|
|
2387a609df | ||
|
|
6457a84f4b | ||
|
|
dee231d43a | ||
|
|
88d2ce3ad0 | ||
|
|
484f3a6842 | ||
|
|
866caf8bc5 | ||
|
|
be9e3c3bf7 | ||
|
|
49a3cb1978 | ||
|
|
d9053f580f | ||
|
|
fee2764338 | ||
|
|
61cb4fdd74 | ||
|
|
d89869ebff | ||
|
|
8518819dfa | ||
|
|
0a8923be6b | ||
|
|
cda821e5af | ||
|
|
be2402702f | ||
|
|
d723a8ab5c | ||
|
|
5c882c81c8 | ||
|
|
24d2709564 | ||
|
|
ce15bc46a7 | ||
|
|
de4bc30730 | ||
|
|
64f6811c00 | ||
|
|
e05ab104b9 | ||
|
|
b4f1c4d13e | ||
|
|
939eb1bd8d | ||
|
|
854469cc7d | ||
|
|
338ae41baa | ||
|
|
6cae89d276 | ||
|
|
15c1e25122 | ||
|
|
c5716416b6 | ||
|
|
65e4a766ab | ||
|
|
c2a4c9bbb3 | ||
|
|
b042110154 | ||
|
|
da97b5593b | ||
|
|
44b4b0cdb9 | ||
|
|
190b7fe819 | ||
|
|
8eb89f0fe9 | ||
|
|
3085353729 | ||
|
|
1465ce112c | ||
|
|
5c797858ba | ||
|
|
8c51c4be0c | ||
|
|
4e8ecff67b | ||
|
|
83f8c51aca | ||
|
|
e675348461 | ||
|
|
9026f5bf07 | ||
|
|
02f18b5b35 | ||
|
|
db740f4747 | ||
|
|
6a3206b938 | ||
|
|
23414b0bc6 | ||
|
|
0234cfca30 | ||
|
|
f707714611 | ||
|
|
7b90230c45 | ||
|
|
d3468f3eba | ||
|
|
fc9500b45c | ||
|
|
df0b5fcd19 | ||
|
|
40e2dba490 | ||
|
|
d13e8bacec | ||
|
|
d3fa05a4df | ||
|
|
734dd2e119 | ||
|
|
a9ea9ea2ae | ||
|
|
1b521bc140 | ||
|
|
364e3c5f5f | ||
|
|
647cda3f90 | ||
|
|
bcca4d4bcc | ||
|
|
0b038dca17 | ||
|
|
ad6f63df56 | ||
|
|
b5b8b62a16 | ||
|
|
8d3066b377 | ||
|
|
18933b4326 | ||
|
|
672841c0ed | ||
|
|
872c7cb257 | ||
|
|
bfae5871c8 | ||
|
|
451c5d1648 | ||
|
|
3423626c60 | ||
|
|
8982f02a4a | ||
|
|
f06b5fdacf | ||
|
|
8ea8cf2985 | ||
|
|
2771d34951 | ||
|
|
669b33a3e1 | ||
|
|
c934a1cdfe | ||
|
|
0ccd41f5e8 | ||
|
|
1af3bb7dfc | ||
|
|
f0fcf1ea8a | ||
|
|
5325542c81 | ||
|
|
3d9675841b | ||
|
|
0d475fba0e | ||
|
|
05714de945 | ||
|
|
89c1b53c95 | ||
|
|
1c9041f12b | ||
|
|
d3de5e0a42 | ||
|
|
b1bd21533d | ||
|
|
51f997a34a | ||
|
|
d5607faadd | ||
|
|
1a9c3ccb79 | ||
|
|
053de4d06f |
44
.github/workflows/create-release.yml
vendored
44
.github/workflows/create-release.yml
vendored
@@ -112,6 +112,28 @@ jobs:
|
||||
asset_name: amethyst-googleplay-x86_64-${{ github.ref_name }}.apk
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Play APK arm64-v8a Asset
|
||||
id: upload-release-asset-play-arm64-v8a-apk
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: app/build/outputs/apk/play/release/app-play-arm64-v8a-release-unsigned-signed.apk
|
||||
asset_name: amethyst-googleplay-arm64-v8a-${{ github.ref_name }}.apk
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Play APK armeabi-v7a Asset
|
||||
id: upload-release-asset-play-armeabi-v7a-apk
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: app/build/outputs/apk/play/release/app-play-armeabi-v7a-release-unsigned-signed.apk
|
||||
asset_name: amethyst-googleplay-armeabi-v7a-${{ github.ref_name }}.apk
|
||||
asset_content_type: application/zip
|
||||
|
||||
# F-Droid APK
|
||||
- name: Upload F-Droid APK Universal Asset
|
||||
id: upload-release-asset-fdroid-universal-apk
|
||||
@@ -146,6 +168,28 @@ jobs:
|
||||
asset_name: amethyst-fdroid-x86_64-${{ github.ref_name }}.apk
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload F-Droid APK arm64-v8a Asset
|
||||
id: upload-release-asset-fdroid-arm64-v8a-apk
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-arm64-v8a-release-unsigned-signed.apk
|
||||
asset_name: amethyst-fdroid-arm64-v8a-${{ github.ref_name }}.apk
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload F-Droid APK armeabi-v7a Asset
|
||||
id: upload-release-asset-fdroid-armeabi-v7a-apk
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-armeabi-v7a-release-unsigned-signed.apk
|
||||
asset_name: amethyst-fdroid-armeabi-v7a-${{ github.ref_name }}.apk
|
||||
asset_content_type: application/zip
|
||||
|
||||
|
||||
|
||||
# Google Play AAB
|
||||
|
||||
10
README.md
10
README.md
@@ -106,7 +106,7 @@ height="80">](https://github.com/vitorpamplona/amethyst/releases)
|
||||
|
||||
This is a native Android app made with Kotlin and Jetpack Compose.
|
||||
The app uses a modified version of the [nostrpostrlib](https://github.com/Giszmo/NostrPostr/tree/master/nostrpostrlib) to talk to Nostr relays.
|
||||
The overall architecture consists in the UI, which uses the usual State/ViewModel/Composition, the service layer that connects with Nostr relays,
|
||||
The overall architecture consists of the UI, which uses the usual State/ViewModel/Composition, the service layer that connects with Nostr relays,
|
||||
and the model/repository layer, which keeps all Nostr objects in memory, in a full OO graph.
|
||||
|
||||
The repository layer stores Nostr Events as Notes and Users separately. Those classes use LiveData objects to
|
||||
@@ -172,7 +172,7 @@ For the Play build:
|
||||
keytool -genkey -v -keystore <my-release-key.keystore> -alias <alias_name> -keyalg RSA -keysize 2048 -validity 10000
|
||||
openssl base64 < <my-release-key.keystore> | tr -d '\n' | tee some_signing_key.jks.base64.txt
|
||||
```
|
||||
2. Create 4 Secret Key variables on your GitHub repository and fill in with the signing key information
|
||||
2. Create four Secret Key variables on your GitHub repository and fill in the signing key information
|
||||
- `KEY_ALIAS` <- `<alias_name>`
|
||||
- `KEY_PASSWORD` <- `<your password>`
|
||||
- `KEY_STORE_PASSWORD` <- `<your key store password>`
|
||||
@@ -192,15 +192,15 @@ The relay also learns which public keys you are requesting, meaning your public
|
||||
Relays have all your data in raw text. They know your IP, your name, your location (guessed from IP), your pub key, all your contacts, and other relays, and can read every action you do (post, like, boost, quote, report, etc) with the exception of Private Zaps and Private DMs.
|
||||
|
||||
# DM Privacy #
|
||||
While the content of direct messages (DMs) is only visible to you, and your DM nostr counterparty, everyone can see that and when you and your counterparty are DM-ing each other.
|
||||
While the content of direct messages (DMs) is only visible to you and your DM counterparty, everyone can see when you and your counterparty DM each other.
|
||||
|
||||
# Visibility & Permanence of Your Content on nostr
|
||||
## Information Visibility ##
|
||||
Content that you share can be shared to other relays.
|
||||
Information that you share is publicly visible to anyone reading from relays that have your information. Your information may also be visible to nostr users who do not share relays with you.
|
||||
Information that you share publicly is visible to anyone reading from relays that have your information. Your information may also be visible to nostr users who do not share relays with you.
|
||||
|
||||
## Information Permanence ##
|
||||
Information shared on nostr should be assumed permanent for privacy purposes. There is no way to guarantee deleting or editing any content once posted.
|
||||
Information shared on nostr should be assumed permanent for privacy purposes. There is no way to guarantee edit or deletion of any content once posted.
|
||||
|
||||
# Screenshots
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ android {
|
||||
applicationId "com.vitorpamplona.amethyst"
|
||||
minSdk 26
|
||||
targetSdk 34
|
||||
versionCode 289
|
||||
versionName "0.75.10"
|
||||
versionCode 296
|
||||
versionName "0.76.2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
}
|
||||
resourceConfigurations += ['ar', 'cs', 'de', 'eo', 'es', 'fa', 'fr', 'hu', 'ja', 'night', 'nl', 'pt-rBR', 'ru', 'sv-rSE', 'ta', 'th', 'tr', 'uk', 'zh', 'sh-rHK', 'zh-rTW']
|
||||
resourceConfigurations += ['ar', 'cs', 'de', 'eo', 'es', 'fa', 'fr', 'hu', 'in', 'ja', 'night', 'nl', 'pt-rBR', 'ru', 'sv-rSE', 'ta', 'th', 'tr', 'uk', 'zh', 'sh-rHK', 'zh-rTW']
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -54,7 +54,7 @@ android {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include "x86", "x86_64"
|
||||
include "x86", "x86_64", "arm64-v8a", "armeabi-v7a"
|
||||
universalApk true
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ dependencies {
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
||||
// Zoomable images
|
||||
implementation "net.engawapg.lib:zoomable:1.5.0"
|
||||
implementation 'net.engawapg.lib:zoomable:1.5.1'
|
||||
|
||||
// Biometrics
|
||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -92,9 +92,7 @@ class MultiPlayerPlaybackManager(
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
when (playbackState) {
|
||||
STATE_IDLE -> {
|
||||
if (player.currentPosition > 5 * 60) { // 5 seconds
|
||||
cachedPositions.add(uri, player.currentPosition)
|
||||
}
|
||||
cachedPositions.add(uri, player.currentPosition)
|
||||
}
|
||||
STATE_READY -> {
|
||||
cachedPositions.get(uri)?.let { lastPosition ->
|
||||
@@ -104,9 +102,7 @@ class MultiPlayerPlaybackManager(
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (player.currentPosition > 5 * 60) { // 5 seconds
|
||||
cachedPositions.add(uri, player.currentPosition)
|
||||
}
|
||||
cachedPositions.add(uri, player.currentPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
fun createZapRequestFor(note: Note, pollOption: Int?, message: String = "", zapType: LnZapEvent.ZapType): LnZapRequestEvent? {
|
||||
fun createZapRequestFor(note: Note, pollOption: Int?, message: String = "", zapType: LnZapEvent.ZapType, toUser: User?): LnZapRequestEvent? {
|
||||
if (!isWriteable()) return null
|
||||
|
||||
note.event?.let { event ->
|
||||
@@ -291,7 +291,8 @@ class Account(
|
||||
keyPair.privKey!!,
|
||||
pollOption,
|
||||
message,
|
||||
zapType
|
||||
zapType,
|
||||
toUser?.pubkeyHex
|
||||
)
|
||||
}
|
||||
return null
|
||||
@@ -759,7 +760,7 @@ class Account(
|
||||
replyTo: List<Note>?,
|
||||
mentions: List<User>?,
|
||||
tags: List<String>? = null,
|
||||
zapReceiver: String? = null,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long? = null,
|
||||
replyingTo: String?,
|
||||
@@ -820,7 +821,7 @@ class Account(
|
||||
valueMinimum: Int?,
|
||||
consensusThreshold: Int?,
|
||||
closedAt: Int?,
|
||||
zapReceiver: String? = null,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long? = null,
|
||||
relayList: List<Relay>? = null,
|
||||
@@ -864,7 +865,7 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
fun sendChannelMessage(message: String, toChannel: String, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
fun sendChannelMessage(message: String, toChannel: String, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: List<ZapSplitSetup>? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||
@@ -886,7 +887,7 @@ class Account(
|
||||
LocalCache.consume(signedEvent, null)
|
||||
}
|
||||
|
||||
fun sendLiveMessage(message: String, toChannel: ATag, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
fun sendLiveMessage(message: String, toChannel: ATag, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: List<ZapSplitSetup>? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||
@@ -908,11 +909,11 @@ class Account(
|
||||
LocalCache.consume(signedEvent, null)
|
||||
}
|
||||
|
||||
fun sendPrivateMessage(message: String, toUser: User, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
fun sendPrivateMessage(message: String, toUser: User, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: List<ZapSplitSetup>? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
sendPrivateMessage(message, toUser.pubkeyHex, replyingTo, mentions, zapReceiver, wantsToMarkAsSensitive, zapRaiserAmount, geohash)
|
||||
}
|
||||
|
||||
fun sendPrivateMessage(message: String, toUser: HexKey, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
fun sendPrivateMessage(message: String, toUser: HexKey, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: List<ZapSplitSetup>? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||
@@ -941,7 +942,7 @@ class Account(
|
||||
subject: String? = null,
|
||||
replyingTo: Note? = null,
|
||||
mentions: List<User>?,
|
||||
zapReceiver: String? = null,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long? = null,
|
||||
geohash: String? = null
|
||||
@@ -1214,7 +1215,7 @@ class Account(
|
||||
val event = if (note is AddressableNote) {
|
||||
BookmarkListEvent.create(
|
||||
"bookmark",
|
||||
bookmarks?.taggedEvents() ?: emptyList(),
|
||||
bookmarks?.taggedEvents()?.minus(note.address.toTag()) ?: emptyList(),
|
||||
bookmarks?.taggedUsers() ?: emptyList(),
|
||||
bookmarks?.taggedAddresses()?.minus(note.address),
|
||||
|
||||
@@ -1661,14 +1662,18 @@ class Account(
|
||||
|
||||
// Ugly, but forces nostr.band as the only search-supporting relay today.
|
||||
// TODO: Remove when search becomes more available.
|
||||
if (usersRelayList.none { it.activeTypes.contains(FeedType.SEARCH) }) {
|
||||
usersRelayList = usersRelayList + Relay(
|
||||
Constants.forcedRelayForSearch.url,
|
||||
Constants.forcedRelayForSearch.read,
|
||||
Constants.forcedRelayForSearch.write,
|
||||
Constants.forcedRelayForSearch.feedTypes,
|
||||
proxy
|
||||
)
|
||||
val searchRelays = usersRelayList.filter { it.url.removeSuffix("/") in Constants.forcedRelaysForSearchSet }
|
||||
val hasSearchRelay = usersRelayList.any { it.activeTypes.contains(FeedType.SEARCH) }
|
||||
if (!hasSearchRelay && searchRelays.isEmpty()) {
|
||||
usersRelayList = usersRelayList + Constants.forcedRelayForSearch.map {
|
||||
Relay(
|
||||
it.url,
|
||||
it.read,
|
||||
it.write,
|
||||
it.feedTypes,
|
||||
proxy
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return usersRelayList.toTypedArray()
|
||||
@@ -1686,6 +1691,10 @@ class Account(
|
||||
.toTypedArray()
|
||||
}
|
||||
|
||||
fun activeWriteRelays(): List<Relay> {
|
||||
return (activeRelays() ?: convertLocalRelays()).filter { it.write }
|
||||
}
|
||||
|
||||
fun reconnectIfRelaysHaveChanged() {
|
||||
val newRelaySet = activeRelays() ?: convertLocalRelays()
|
||||
if (!Client.isSameRelaySetConfig(newRelaySet)) {
|
||||
|
||||
@@ -53,6 +53,10 @@ object LocalCache {
|
||||
// checkNotInMainThread()
|
||||
|
||||
return users[key] ?: run {
|
||||
require(isValidHex(key = key)) {
|
||||
"$key is not a valid hex"
|
||||
}
|
||||
|
||||
val newObject = User(key)
|
||||
users.putIfAbsent(key, newObject) ?: newObject
|
||||
}
|
||||
@@ -98,6 +102,10 @@ object LocalCache {
|
||||
checkNotInMainThread()
|
||||
|
||||
return notes.get(idHex) ?: run {
|
||||
require(isValidHex(idHex)) {
|
||||
"$idHex is not a valid hex"
|
||||
}
|
||||
|
||||
val newObject = Note(idHex)
|
||||
notes.putIfAbsent(idHex, newObject) ?: newObject
|
||||
}
|
||||
@@ -807,23 +815,23 @@ object LocalCache {
|
||||
// Already processed this event.
|
||||
if (note.event != null) return
|
||||
|
||||
val zapRequest = event.zapRequest?.id?.let { getOrCreateNote(it) }
|
||||
val zapRequest = event.zapRequest?.id?.let { getNoteIfExists(it) }
|
||||
|
||||
if (zapRequest == null || zapRequest.event !is LnZapRequestEvent) {
|
||||
Log.e("ZP", "Zap Request not found. Unable to process Zap {${event.toJson()}}")
|
||||
return
|
||||
}
|
||||
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
val mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
||||
val repliesTo = event.zappedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||
event.taggedAddresses().map { getOrCreateAddressableNote(it) } +
|
||||
(
|
||||
(zapRequest?.event as? LnZapRequestEvent)?.taggedAddresses()?.map { getOrCreateAddressableNote(it) } ?: emptySet<Note>()
|
||||
(zapRequest.event as? LnZapRequestEvent)?.taggedAddresses()?.map { getOrCreateAddressableNote(it) } ?: emptySet<Note>()
|
||||
)
|
||||
|
||||
note.loadEvent(event, author, repliesTo)
|
||||
|
||||
if (zapRequest == null) {
|
||||
Log.e("ZP", "Zap Request not found. Unable to process Zap {${event.toJson()}}")
|
||||
return
|
||||
}
|
||||
|
||||
// Log.d("ZP", "New ZapEvent ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||
|
||||
repliesTo.forEach {
|
||||
@@ -1183,13 +1191,7 @@ object LocalCache {
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
notes.remove(it.idHex)
|
||||
// Doesn't need to clean up the replies and mentions.. Too small to matter.
|
||||
|
||||
// Counts the replies
|
||||
it.replyTo?.forEach { parent ->
|
||||
parent.removeReply(it)
|
||||
}
|
||||
removeFromCache(it)
|
||||
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
@@ -1208,12 +1210,7 @@ object LocalCache {
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
notes.remove(it.idHex)
|
||||
|
||||
// Counts the replies
|
||||
it.replyTo?.forEach { parent ->
|
||||
parent.removeReply(it)
|
||||
}
|
||||
removeFromCache(it)
|
||||
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
@@ -1240,17 +1237,12 @@ object LocalCache {
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
notes.remove(it.idHex)
|
||||
|
||||
val newerVersion = addressables[(it.event as? AddressableEvent)?.address()?.toTag()]
|
||||
if (newerVersion != null) {
|
||||
it.moveAllReferencesTo(newerVersion)
|
||||
}
|
||||
|
||||
it.replyTo?.forEach { masterNote ->
|
||||
removeLinkFromParentNote(it)
|
||||
}
|
||||
|
||||
removeFromCache(it)
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
|
||||
@@ -1279,11 +1271,7 @@ object LocalCache {
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
notes.remove(it.idHex)
|
||||
|
||||
removeLinkFromParentNote(it)
|
||||
removeAuthorLinkTo(it)
|
||||
|
||||
removeFromCache(it)
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
|
||||
@@ -1300,73 +1288,39 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
|
||||
fun removeLinkFromParentNote(it: Note) {
|
||||
it.replyTo?.forEach { masterNote ->
|
||||
masterNote.removeReply(it)
|
||||
masterNote.removeBoost(it)
|
||||
masterNote.removeReaction(it)
|
||||
masterNote.removeZap(it)
|
||||
masterNote.removeReport(it)
|
||||
private fun removeFromCache(note: Note) {
|
||||
note.replyTo?.forEach { masterNote ->
|
||||
masterNote.removeReply(note)
|
||||
masterNote.removeBoost(note)
|
||||
masterNote.removeReaction(note)
|
||||
masterNote.removeZap(note)
|
||||
masterNote.removeReport(note)
|
||||
masterNote.clearEOSE() // allows reloading of these events if needed
|
||||
}
|
||||
}
|
||||
|
||||
fun pruneExpiredEvents() {
|
||||
checkNotInMainThread()
|
||||
|
||||
val toBeRemoved = notes.filter {
|
||||
it.value.event?.isExpired() == true
|
||||
}.values
|
||||
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
notes.remove(it.idHex)
|
||||
|
||||
removeLinkFromParentNote(it)
|
||||
removeAuthorLinkTo(it)
|
||||
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
|
||||
removeChildrenOf(childrenToBeRemoved)
|
||||
|
||||
if (toBeRemoved.size > 1) {
|
||||
println("PRUNE: ${toBeRemoved.size} thread replies removed.")
|
||||
}
|
||||
}
|
||||
|
||||
fun pruneHiddenMessages(account: Account) {
|
||||
checkNotInMainThread()
|
||||
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
val toBeRemoved = account.hiddenUsers.map { userHex ->
|
||||
(
|
||||
notes.values.filter {
|
||||
it.event?.pubKey() == userHex
|
||||
} + addressables.values.filter {
|
||||
it.event?.pubKey() == userHex
|
||||
}
|
||||
).toSet()
|
||||
}.flatten()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
// Counts the replies
|
||||
it.replyTo?.forEach { masterNote ->
|
||||
removeLinkFromParentNote(it)
|
||||
if (note.event is LnZapEvent) {
|
||||
(note.event as LnZapEvent).zappedAuthor().mapNotNull {
|
||||
val author = getUserIfExists(it)
|
||||
author?.removeZap(note)
|
||||
author?.clearEOSE()
|
||||
}
|
||||
}
|
||||
if (note.event is LnZapRequestEvent) {
|
||||
(note.event as LnZapRequestEvent).zappedAuthor().mapNotNull {
|
||||
val author = getUserIfExists(it)
|
||||
author?.removeZap(note)
|
||||
author?.clearEOSE()
|
||||
}
|
||||
}
|
||||
if (note.event is ReportEvent) {
|
||||
(note.event as ReportEvent).reportedAuthor().mapNotNull {
|
||||
val author = getUserIfExists(it.key)
|
||||
author?.removeReport(note)
|
||||
author?.clearEOSE()
|
||||
}
|
||||
|
||||
notes.remove(it.idHex)
|
||||
|
||||
removeAuthorLinkTo(it)
|
||||
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
|
||||
removeChildrenOf(childrenToBeRemoved)
|
||||
|
||||
println("PRUNE: ${toBeRemoved.size} messages removed because they were Hidden")
|
||||
notes.remove(note.idHex)
|
||||
}
|
||||
|
||||
fun removeAuthorLinkTo(note: Note) {
|
||||
@@ -1395,11 +1349,56 @@ object LocalCache {
|
||||
|
||||
fun removeChildrenOf(nextToBeRemoved: List<Note>) {
|
||||
nextToBeRemoved.forEach { note ->
|
||||
removeAuthorLinkTo(note)
|
||||
notes.remove(note.idHex)
|
||||
removeFromCache(note)
|
||||
}
|
||||
}
|
||||
|
||||
fun pruneExpiredEvents() {
|
||||
checkNotInMainThread()
|
||||
|
||||
val toBeRemoved = notes.filter {
|
||||
it.value.event?.isExpired() == true
|
||||
}.values
|
||||
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
removeFromCache(it)
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
|
||||
removeChildrenOf(childrenToBeRemoved)
|
||||
|
||||
if (toBeRemoved.size > 1) {
|
||||
println("PRUNE: ${toBeRemoved.size} thread replies removed.")
|
||||
}
|
||||
}
|
||||
|
||||
fun pruneHiddenMessages(account: Account) {
|
||||
checkNotInMainThread()
|
||||
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
val toBeRemoved = account.hiddenUsers.map { userHex ->
|
||||
(
|
||||
notes.values.filter {
|
||||
it.event?.pubKey() == userHex
|
||||
} + addressables.values.filter {
|
||||
it.event?.pubKey() == userHex
|
||||
}
|
||||
).toSet()
|
||||
}.flatten()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
removeFromCache(it)
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
|
||||
removeChildrenOf(childrenToBeRemoved)
|
||||
|
||||
println("PRUNE: ${toBeRemoved.size} messages removed because they were Hidden")
|
||||
}
|
||||
|
||||
fun pruneContactLists(loggedIn: Set<HexKey>) {
|
||||
checkNotInMainThread()
|
||||
|
||||
@@ -1492,9 +1491,10 @@ object LocalCache {
|
||||
is LiveActivitiesChatMessageEvent -> consume(event, relay)
|
||||
is LnZapEvent -> {
|
||||
event.zapRequest?.let {
|
||||
// must have a valid request
|
||||
verifyAndConsume(it, relay)
|
||||
consume(event)
|
||||
}
|
||||
consume(event)
|
||||
}
|
||||
is LnZapRequestEvent -> consume(event)
|
||||
is LnZapPaymentRequestEvent -> consume(event)
|
||||
|
||||
@@ -136,26 +136,60 @@ open class Note(val idHex: String) {
|
||||
.format(DateTimeFormatter.ofPattern("uuuu-MM-dd-HH:mm:ss"))
|
||||
}
|
||||
|
||||
data class LevelSignature(val signature: String, val createdAt: Long?, val author: User?)
|
||||
|
||||
/**
|
||||
* This method caches signatures during each execution to avoid recalculation in longer threads
|
||||
*/
|
||||
fun replyLevelSignature(
|
||||
eventsToConsider: Set<Note>,
|
||||
cachedSignatures: MutableMap<Note, String>
|
||||
): String {
|
||||
eventsToConsider: Set<HexKey>,
|
||||
cachedSignatures: MutableMap<Note, LevelSignature>,
|
||||
account: User,
|
||||
accountFollowingSet: Set<String>,
|
||||
now: Long
|
||||
): LevelSignature {
|
||||
val replyTo = replyTo
|
||||
if (event is RepostEvent || event is GenericRepostEvent || replyTo == null || replyTo.isEmpty()) {
|
||||
return "/" + formattedDateTime(createdAt() ?: 0) + ";"
|
||||
return LevelSignature(
|
||||
signature = "/" + formattedDateTime(createdAt() ?: 0) + idHex.substring(0, 8) + ";",
|
||||
createdAt = createdAt(),
|
||||
author = author
|
||||
)
|
||||
}
|
||||
|
||||
val mySignature = (
|
||||
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, cachedSignatures).apply { cachedSignatures.put(it, this) }
|
||||
cachedSignatures[it] ?: it.replyLevelSignature(
|
||||
eventsToConsider,
|
||||
cachedSignatures,
|
||||
account,
|
||||
accountFollowingSet,
|
||||
now
|
||||
).apply { cachedSignatures.put(it, this) }
|
||||
}
|
||||
.maxByOrNull { it.length }?.removeSuffix(";") ?: ""
|
||||
) + "/" + formattedDateTime(createdAt() ?: 0) + ";"
|
||||
.maxByOrNull { it.signature.length }
|
||||
)
|
||||
|
||||
val parentSignature = parent?.signature?.removeSuffix(";") ?: ""
|
||||
|
||||
val threadOrder = if (parent?.author == author && createdAt() != null) {
|
||||
// author of the thread first, in **ascending** order
|
||||
"9" + formattedDateTime((parent?.createdAt ?: 0) + (now - (createdAt() ?: 0))) + idHex.substring(0, 8)
|
||||
} else if (author?.pubkeyHex == account.pubkeyHex) {
|
||||
"8" + formattedDateTime(createdAt() ?: 0) + idHex.substring(0, 8) // my replies
|
||||
} else if (author?.pubkeyHex in accountFollowingSet) {
|
||||
"7" + formattedDateTime(createdAt() ?: 0) + idHex.substring(0, 8) // my follows replies.
|
||||
} else {
|
||||
"0" + formattedDateTime(createdAt() ?: 0) + idHex.substring(0, 8) // everyone else.
|
||||
}
|
||||
|
||||
val mySignature = LevelSignature(
|
||||
signature = parentSignature + "/" + threadOrder + ";",
|
||||
createdAt = createdAt(),
|
||||
author = author
|
||||
)
|
||||
|
||||
cachedSignatures[this] = mySignature
|
||||
return mySignature
|
||||
@@ -700,6 +734,12 @@ class NoteLiveSet(u: Note) {
|
||||
it.note.boosts.toImmutableList()
|
||||
}.distinctUntilChanged()
|
||||
|
||||
val relayInfo = innerRelays.map {
|
||||
it.note.relays.map {
|
||||
RelayBriefInfo(it)
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
fun isInUse(): Boolean {
|
||||
return metadata.hasObservers() ||
|
||||
reactions.hasObservers() ||
|
||||
@@ -737,16 +777,12 @@ class NoteBundledRefresherLiveData(val note: Note) : LiveData<NoteState>(NoteSta
|
||||
}
|
||||
|
||||
fun invalidateData() {
|
||||
if (!hasObservers()) return
|
||||
|
||||
checkNotInMainThread()
|
||||
|
||||
bundler.invalidate() {
|
||||
checkNotInMainThread()
|
||||
|
||||
if (hasActiveObservers()) {
|
||||
postValue(NoteState(note))
|
||||
}
|
||||
postValue(NoteState(note))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,3 +818,10 @@ class NoteLoadingLiveData<Y>(val note: Note, initialValue: Y?) : MediatorLiveDat
|
||||
|
||||
@Immutable
|
||||
class NoteState(val note: Note)
|
||||
|
||||
@Immutable
|
||||
data class RelayBriefInfo(
|
||||
val url: String,
|
||||
val displayUrl: String = url.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/").intern(),
|
||||
val favIcon: String = "https://$displayUrl/favicon.ico".intern()
|
||||
)
|
||||
|
||||
@@ -14,4 +14,6 @@ data class RelaySetupInfo(
|
||||
val spamCount: Int = 0,
|
||||
val feedTypes: Set<FeedType>,
|
||||
val paidRelay: Boolean = false
|
||||
)
|
||||
) {
|
||||
val briefInfo: RelayBriefInfo = RelayBriefInfo(url)
|
||||
}
|
||||
|
||||
@@ -5,16 +5,15 @@ import androidx.compose.runtime.Stable
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.distinctUntilChanged
|
||||
import androidx.lifecycle.map
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSETime
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.quartz.encoders.Bech32
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.Lud06
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import com.vitorpamplona.quartz.events.BookmarkListEvent
|
||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||
@@ -27,9 +26,6 @@ import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import java.math.BigDecimal
|
||||
import java.util.regex.Pattern
|
||||
|
||||
val lnurlpPattern = Pattern.compile("(?i:http|https):\\/\\/((.+)\\/)*\\.well-known\\/lnurlp\\/(.*)")
|
||||
|
||||
@Stable
|
||||
class User(val pubkeyHex: String) {
|
||||
@@ -268,19 +264,11 @@ class User(val pubkeyHex: String) {
|
||||
info?.updatedMetadataAt = latestMetadata.createdAt
|
||||
info?.tags = latestMetadata.tags.toImmutableListOfLists()
|
||||
|
||||
if (newUserInfo.lud16.isNullOrBlank() && newUserInfo.lud06?.lowercase()?.startsWith("lnurl") == true) {
|
||||
try {
|
||||
val url = String(Bech32.decodeBytes(newUserInfo.lud06!!, false).second)
|
||||
|
||||
val matcher = lnurlpPattern.matcher(url)
|
||||
while (matcher.find()) {
|
||||
val domain = matcher.group(2)
|
||||
val username = matcher.group(3)
|
||||
|
||||
info?.lud16 = "$username@$domain"
|
||||
if (newUserInfo.lud16.isNullOrBlank()) {
|
||||
info?.lud06?.let {
|
||||
if (it.lowercase().startsWith("lnurl")) {
|
||||
info?.lud16 = Lud06().toLud16(it)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
// Doesn't create errors.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,15 +457,12 @@ class UserBundledRefresherLiveData(val user: User) : LiveData<UserState>(UserSta
|
||||
}
|
||||
|
||||
fun invalidateData() {
|
||||
if (!hasObservers()) return
|
||||
checkNotInMainThread()
|
||||
|
||||
bundler.invalidate() {
|
||||
checkNotInMainThread()
|
||||
|
||||
if (hasActiveObservers()) {
|
||||
postValue(UserState(user))
|
||||
}
|
||||
postValue(UserState(user))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,13 +47,13 @@ class Nip05Verifier() {
|
||||
if (it.isSuccessful) {
|
||||
onSuccess(it.body.string())
|
||||
} else {
|
||||
onError("Could not resolve $nip05. Error: ${it.code}. Check if the server up and if the address $nip05 is correct")
|
||||
onError("Could not resolve $nip05. Error: ${it.code}. Check if the server is up and if the address $nip05 is correct")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: java.io.IOException) {
|
||||
onError("Could not resolve $url. Check if the server up and if the address $nip05 is correct")
|
||||
onError("Could not resolve $url. Check if the server is up and if the address $nip05 is correct")
|
||||
e.printStackTrace()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.vitorpamplona.amethyst.service
|
||||
|
||||
import android.util.Log
|
||||
import android.util.LruCache
|
||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.io.IOException
|
||||
|
||||
object Nip11CachedRetriever {
|
||||
val relayInformationDocumentCache = LruCache<String, RelayInformation>(100)
|
||||
val retriever = Nip11Retriever()
|
||||
|
||||
suspend fun loadRelayInfo(
|
||||
dirtyUrl: String,
|
||||
onInfo: (RelayInformation) -> Unit,
|
||||
onError: (String, Nip11Retriever.ErrorCode, String?) -> Unit
|
||||
) {
|
||||
val url = retriever.cleanUrl(dirtyUrl)
|
||||
val doc = relayInformationDocumentCache.get(url)
|
||||
|
||||
if (doc != null) {
|
||||
onInfo(doc)
|
||||
} else {
|
||||
Nip11Retriever().loadRelayInfo(
|
||||
url,
|
||||
dirtyUrl,
|
||||
onInfo = {
|
||||
relayInformationDocumentCache.put(url, it)
|
||||
onInfo(it)
|
||||
},
|
||||
onError
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Nip11Retriever {
|
||||
enum class ErrorCode {
|
||||
FAIL_TO_ASSEMBLE_URL,
|
||||
FAIL_TO_REACH_SERVER,
|
||||
FAIL_TO_PARSE_RESULT,
|
||||
FAIL_WITH_HTTP_STATUS
|
||||
}
|
||||
|
||||
fun cleanUrl(dirtyUrl: String): String {
|
||||
return if (dirtyUrl.contains("://")) {
|
||||
dirtyUrl
|
||||
.replace("wss://", "https://")
|
||||
.replace("ws://", "http://")
|
||||
} else {
|
||||
"https://$dirtyUrl"
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadRelayInfo(
|
||||
url: String,
|
||||
dirtyUrl: String,
|
||||
onInfo: (RelayInformation) -> Unit,
|
||||
onError: (String, ErrorCode, String?) -> Unit
|
||||
) {
|
||||
try {
|
||||
val request: Request = Request
|
||||
.Builder()
|
||||
.header("Accept", "application/nostr+json")
|
||||
.url(url)
|
||||
.build()
|
||||
|
||||
HttpClient.getHttpClient()
|
||||
.newCall(request)
|
||||
.enqueue(
|
||||
object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
checkNotInMainThread()
|
||||
response.use {
|
||||
val body = it.body.string()
|
||||
try {
|
||||
if (it.isSuccessful) {
|
||||
onInfo(RelayInformation.fromJson(body))
|
||||
} else {
|
||||
onError(dirtyUrl, ErrorCode.FAIL_WITH_HTTP_STATUS, it.code.toString())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("RelayInfoFail", "Resulting Message from Relay $dirtyUrl in not parseable: $body", e)
|
||||
onError(dirtyUrl, ErrorCode.FAIL_TO_PARSE_RESULT, e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
Log.e("RelayInfoFail", "$dirtyUrl unavailable", e)
|
||||
onError(dirtyUrl, ErrorCode.FAIL_TO_REACH_SERVER, e.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("RelayInfoFail", "Invalid URL $dirtyUrl", e)
|
||||
onError(dirtyUrl, ErrorCode.FAIL_TO_ASSEMBLE_URL, e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.Error
|
||||
|
||||
abstract class NostrDataSource(val debugName: String) {
|
||||
@@ -23,6 +24,7 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
data class Counter(var counter: Int)
|
||||
|
||||
private var eventCounter = mapOf<String, Counter>()
|
||||
var changingFilters = AtomicBoolean()
|
||||
|
||||
fun printCounter() {
|
||||
eventCounter.forEach {
|
||||
@@ -49,7 +51,7 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
// Log.e("ERROR", "Relay ${relay.url}: ${error.message}")
|
||||
}
|
||||
|
||||
override fun onRelayStateChange(type: Relay.Type, relay: Relay, channel: String?) {
|
||||
override fun onRelayStateChange(type: Relay.Type, relay: Relay, subscriptionId: String?) {
|
||||
// Log.d("RELAY", "Relay ${relay.url} ${when (type) {
|
||||
// Relay.Type.CONNECT -> "connected."
|
||||
// Relay.Type.DISCONNECT -> "disconnected."
|
||||
@@ -57,13 +59,19 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
// Relay.Type.EOSE -> "sent all events it had stored."
|
||||
// }}")
|
||||
|
||||
if (type == Relay.Type.EOSE && channel != null) {
|
||||
if (type == Relay.Type.EOSE && subscriptionId != null && subscriptionId in subscriptions.keys) {
|
||||
// updates a per subscripton since date
|
||||
subscriptions[channel]?.updateEOSE(TimeUtils.now(), relay.url)
|
||||
subscriptions[subscriptionId]?.updateEOSE(
|
||||
TimeUtils.fiveMinutesAgo(), // in case people's clock is slighly off.
|
||||
relay.url
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) {
|
||||
if (success) {
|
||||
markAsSeenOnRelay(eventId, relay)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAuth(relay: Relay, challenge: String) {
|
||||
@@ -136,6 +144,8 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
// saves the current content to only update if it changes
|
||||
val currentFilters = activeSubscriptions.associate { it.id to it.toJson() }
|
||||
|
||||
changingFilters.getAndSet(true)
|
||||
|
||||
updateChannelFilters()
|
||||
|
||||
// Makes sure to only send an updated filter when it actually changes.
|
||||
@@ -167,12 +177,18 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changingFilters.getAndSet(false)
|
||||
}
|
||||
|
||||
open fun consume(event: Event, relay: Relay) {
|
||||
LocalCache.verifyAndConsume(event, relay)
|
||||
}
|
||||
|
||||
open fun markAsSeenOnRelay(eventId: String, relay: Relay) {
|
||||
LocalCache.getNoteIfExists(eventId)?.addRelay(relay)
|
||||
}
|
||||
|
||||
abstract fun updateChannelFilters()
|
||||
open fun auth(relay: Relay, challenge: String) = Unit
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
||||
}
|
||||
|
||||
fun createFollowAccountsFilter(): TypedFilter {
|
||||
val follows = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||
val followSet = follows.plus(account.userProfile().pubkeyHex).toList()
|
||||
val follows = account.selectedUsersFollowList(account.defaultHomeFollowList)
|
||||
val followSet = follows?.plus(account.userProfile().pubkeyHex)?.toList()?.ifEmpty { null }
|
||||
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
|
||||
@@ -134,6 +134,9 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
||||
}
|
||||
|
||||
val singleEventChannel = requestNewChannel { time, relayUrl ->
|
||||
// Ignores EOSE if it is in the middle of a filter change.
|
||||
if (changingFilters.get()) return@requestNewChannel
|
||||
|
||||
checkNotInMainThread()
|
||||
|
||||
eventsToWatch.forEach {
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
package com.vitorpamplona.amethyst.service
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||
import com.vitorpamplona.quartz.events.PayInvoiceErrorResponse
|
||||
import com.vitorpamplona.quartz.events.ZapSplitSetup
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.round
|
||||
|
||||
class ZapPaymentHandler(val account: Account) {
|
||||
|
||||
@Immutable
|
||||
data class Payable(
|
||||
val info: ZapSplitSetup,
|
||||
val user: User?,
|
||||
val amountMilliSats: Long,
|
||||
val invoice: String
|
||||
)
|
||||
|
||||
suspend fun zap(
|
||||
note: Note,
|
||||
amountMilliSats: Long,
|
||||
pollOption: Int?,
|
||||
message: String,
|
||||
context: Context,
|
||||
onError: (String) -> Unit,
|
||||
onProgress: (percent: Float) -> Unit,
|
||||
onPayViaIntent: (ImmutableList<Payable>) -> Unit,
|
||||
zapType: LnZapEvent.ZapType
|
||||
) = withContext(Dispatchers.IO) {
|
||||
val zapSplitSetup = note.event?.zapSplitSetup()
|
||||
|
||||
val zapsToSend = if (!zapSplitSetup.isNullOrEmpty()) {
|
||||
zapSplitSetup
|
||||
} else {
|
||||
val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
|
||||
|
||||
if (lud16.isNullOrBlank()) {
|
||||
onError(context.getString(R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats))
|
||||
return@withContext
|
||||
}
|
||||
|
||||
listOf(ZapSplitSetup(lud16, null, weight = 1.0, true))
|
||||
}
|
||||
|
||||
val totalWeight = zapsToSend.sumOf { it.weight }
|
||||
|
||||
val invoicesToPayOnIntent = mutableListOf<Payable>()
|
||||
|
||||
zapsToSend.forEachIndexed { index, value ->
|
||||
val outerProgressMin = index / zapsToSend.size.toFloat()
|
||||
val outerProgressMax = (index + 1) / zapsToSend.size.toFloat()
|
||||
|
||||
val zapValue =
|
||||
round((amountMilliSats * value.weight / totalWeight) / 1000f).toLong() * 1000
|
||||
|
||||
if (value.isLnAddress) {
|
||||
innerZap(
|
||||
lud16 = value.lnAddressOrPubKeyHex,
|
||||
note = note,
|
||||
amount = zapValue,
|
||||
pollOption = pollOption,
|
||||
message = message,
|
||||
context = context,
|
||||
onError = onError,
|
||||
onProgress = {
|
||||
onProgress((it * (outerProgressMax - outerProgressMin)) + outerProgressMin)
|
||||
},
|
||||
zapType = zapType,
|
||||
onPayInvoiceThroughIntent = {
|
||||
invoicesToPayOnIntent.add(
|
||||
Payable(
|
||||
info = value,
|
||||
user = null,
|
||||
amountMilliSats = zapValue,
|
||||
invoice = it
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
val user = LocalCache.getUserIfExists(value.lnAddressOrPubKeyHex)
|
||||
val lud16 = user?.info?.lnAddress()
|
||||
|
||||
if (lud16 != null) {
|
||||
innerZap(
|
||||
lud16 = lud16,
|
||||
note = note,
|
||||
amount = zapValue,
|
||||
pollOption = pollOption,
|
||||
message = message,
|
||||
context = context,
|
||||
onError = onError,
|
||||
onProgress = {
|
||||
onProgress((it * (outerProgressMax - outerProgressMin)) + outerProgressMin)
|
||||
},
|
||||
zapType = zapType,
|
||||
overrideUser = user,
|
||||
onPayInvoiceThroughIntent = {
|
||||
invoicesToPayOnIntent.add(
|
||||
Payable(
|
||||
info = value,
|
||||
user = user,
|
||||
amountMilliSats = zapValue,
|
||||
invoice = it
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
onError(
|
||||
context.getString(
|
||||
R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats,
|
||||
user?.toBestDisplayName() ?: value.lnAddressOrPubKeyHex
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (invoicesToPayOnIntent.isNotEmpty()) {
|
||||
onPayViaIntent(invoicesToPayOnIntent.toImmutableList())
|
||||
onProgress(1f)
|
||||
} else {
|
||||
launch(Dispatchers.IO) {
|
||||
// Awaits for the event to come back to LocalCache.
|
||||
delay(5000)
|
||||
onProgress(1f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun innerZap(
|
||||
lud16: String,
|
||||
note: Note,
|
||||
amount: Long,
|
||||
pollOption: Int?,
|
||||
message: String,
|
||||
context: Context,
|
||||
onError: (String) -> Unit,
|
||||
onProgress: (percent: Float) -> Unit,
|
||||
onPayInvoiceThroughIntent: (String) -> Unit,
|
||||
zapType: LnZapEvent.ZapType,
|
||||
overrideUser: User? = null
|
||||
) {
|
||||
var zapRequestJson = ""
|
||||
|
||||
if (zapType != LnZapEvent.ZapType.NONZAP) {
|
||||
val zapRequest = account.createZapRequestFor(note, pollOption, message, zapType, overrideUser)
|
||||
if (zapRequest != null) {
|
||||
zapRequestJson = zapRequest.toJson()
|
||||
}
|
||||
}
|
||||
|
||||
onProgress(0.10f)
|
||||
|
||||
LightningAddressResolver().lnAddressInvoice(
|
||||
lud16,
|
||||
amount,
|
||||
message,
|
||||
zapRequestJson,
|
||||
onSuccess = {
|
||||
onProgress(0.7f)
|
||||
if (account.hasWalletConnectSetup()) {
|
||||
account.sendZapPaymentRequestFor(
|
||||
bolt11 = it,
|
||||
note,
|
||||
onResponse = { response ->
|
||||
if (response is PayInvoiceErrorResponse) {
|
||||
onProgress(0.0f)
|
||||
onError(
|
||||
response.error?.message
|
||||
?: response.error?.code?.toString()
|
||||
?: "Error parsing error message"
|
||||
)
|
||||
} else {
|
||||
onProgress(1f)
|
||||
}
|
||||
}
|
||||
)
|
||||
onProgress(0.8f)
|
||||
} else {
|
||||
try {
|
||||
onPayInvoiceThroughIntent(it)
|
||||
} catch (e: Exception) {
|
||||
onError(context.getString(R.string.lightning_wallets_not_found2))
|
||||
}
|
||||
onProgress(0f)
|
||||
}
|
||||
},
|
||||
onError = onError,
|
||||
onProgress = onProgress
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,14 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.service.HttpClient
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.quartz.encoders.Bech32
|
||||
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
|
||||
import com.vitorpamplona.quartz.encoders.Lud06
|
||||
import com.vitorpamplona.quartz.encoders.toLnUrl
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.net.URLEncoder
|
||||
|
||||
class LightningAddressResolver() {
|
||||
@@ -27,11 +25,7 @@ class LightningAddressResolver() {
|
||||
}
|
||||
|
||||
if (lnaddress.lowercase().startsWith("lnurl")) {
|
||||
return try {
|
||||
String(Bech32.decodeBytes(lnaddress, false).second)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
return Lud06().toLnUrlp(lnaddress)
|
||||
}
|
||||
|
||||
return null
|
||||
@@ -57,16 +51,16 @@ class LightningAddressResolver() {
|
||||
if (it.isSuccessful) {
|
||||
onSuccess(it.body.string())
|
||||
} else {
|
||||
onError("The receiver's lightning service at $url is not available. It was calculated from the lightning address \"${lnaddress}\". Error: ${it.code}. Check if the server up and if the lightning address is correct")
|
||||
onError("The receiver's lightning service at $url is not available. It was calculated from the lightning address \"${lnaddress}\". Error: ${it.code}. Check if the server is up and if the lightning address is correct")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
onError("Could not resolve $url. Check if the server up and if the lightning address $lnaddress is correct")
|
||||
onError("Could not resolve $url. Check if the server is up and if the lightning address $lnaddress is correct")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchLightningInvoice(lnCallback: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) {
|
||||
suspend fun fetchLightningInvoice(lnCallback: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: suspend (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) {
|
||||
val encodedMessage = URLEncoder.encode(message, "utf-8")
|
||||
|
||||
val urlBinder = if (lnCallback.contains("?")) "&" else "?"
|
||||
@@ -82,22 +76,13 @@ class LightningAddressResolver() {
|
||||
.url(url)
|
||||
.build()
|
||||
|
||||
client.newCall(request).enqueue(object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
response.use {
|
||||
if (it.isSuccessful) {
|
||||
onSuccess(response.body.string())
|
||||
} else {
|
||||
onError("Could not fetch invoice from $lnCallback")
|
||||
}
|
||||
}
|
||||
client.newCall(request).execute().use {
|
||||
if (it.isSuccessful) {
|
||||
onSuccess(it.body.string())
|
||||
} else {
|
||||
onError("Could not fetch invoice from $lnCallback")
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: java.io.IOException) {
|
||||
onError("Could not fetch an invoice from $lnCallback. Message ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun lnAddressToLnUrl(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
|
||||
@@ -115,7 +100,7 @@ class LightningAddressResolver() {
|
||||
milliSats: Long,
|
||||
message: String,
|
||||
nostrRequest: String? = null,
|
||||
onSuccess: (String) -> Unit,
|
||||
onSuccess: suspend (String) -> Unit,
|
||||
onError: (String) -> Unit,
|
||||
onProgress: (percent: Float) -> Unit
|
||||
) {
|
||||
@@ -159,14 +144,22 @@ class LightningAddressResolver() {
|
||||
|
||||
lnInvoice?.get("pr")?.asText()?.ifBlank { null }?.let { pr ->
|
||||
// Forces LN Invoice amount to be the requested amount.
|
||||
val expectedAmountInSats = BigDecimal(milliSats).divide(BigDecimal(1000), RoundingMode.HALF_UP).toLong()
|
||||
val invoiceAmount = LnInvoiceUtil.getAmountInSats(pr)
|
||||
if (invoiceAmount.multiply(BigDecimal(1000)).toLong() == BigDecimal(milliSats).toLong()) {
|
||||
if (invoiceAmount.toLong() == expectedAmountInSats) {
|
||||
onProgress(0.7f)
|
||||
onSuccess(pr)
|
||||
} else {
|
||||
onError("Incorrect invoice amount (${invoiceAmount.toLong()} sats) from server")
|
||||
onProgress(0.0f)
|
||||
onError("Incorrect invoice amount (${invoiceAmount.toLong()} sats) from $lnaddress. It should have been $expectedAmountInSats")
|
||||
}
|
||||
} ?: 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
|
||||
)
|
||||
|
||||
@@ -19,28 +19,46 @@ 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
|
||||
|
||||
class EventNotificationConsumer(private val applicationContext: Context) {
|
||||
suspend fun consume(event: GiftWrapEvent) {
|
||||
if (!LocalCache.justVerify(event)) return
|
||||
if (!notificationManager().areNotificationsEnabled()) return
|
||||
|
||||
suspend fun consume(event: Event) {
|
||||
if (LocalCache.notes[event.id] == null) {
|
||||
if (LocalCache.justVerify(event)) {
|
||||
LocalCache.justConsume(event, null)
|
||||
// PushNotification Wraps don't include a receiver.
|
||||
// Test with all logged in accounts
|
||||
LocalPreferences.allSavedAccounts().forEach {
|
||||
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
|
||||
if (acc != null && acc.keyPair.privKey != null) {
|
||||
consumeIfMatchesAccount(event, acc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val manager = notificationManager()
|
||||
if (manager.areNotificationsEnabled()) {
|
||||
when (event) {
|
||||
is PrivateDmEvent -> notify(event)
|
||||
is LnZapEvent -> notify(event)
|
||||
is GiftWrapEvent -> unwrapAndNotify(event)
|
||||
}
|
||||
private suspend fun consumeIfMatchesAccount(pushWrappedEvent: GiftWrapEvent, account: Account) {
|
||||
val key = account.keyPair.privKey ?: return
|
||||
pushWrappedEvent.unwrap(key)?.let { notificationEvent ->
|
||||
if (!LocalCache.justVerify(notificationEvent)) return // invalid event
|
||||
if (LocalCache.notes[notificationEvent.id] != null) return // already processed
|
||||
|
||||
LocalCache.justConsume(notificationEvent, null)
|
||||
|
||||
unwrapAndConsume(notificationEvent, account)?.let { innerEvent ->
|
||||
if (innerEvent is PrivateDmEvent) {
|
||||
notify(innerEvent, account)
|
||||
} else if (innerEvent is LnZapEvent) {
|
||||
notify(innerEvent, account)
|
||||
} else if (innerEvent is ChatMessageEvent) {
|
||||
notify(innerEvent, account)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unwrapAndConsume(event: Event, account: Account): Event? {
|
||||
private fun unwrapAndConsume(event: Event, account: Account): Event? {
|
||||
if (!LocalCache.justVerify(event)) return null
|
||||
|
||||
return when (event) {
|
||||
@@ -65,106 +83,106 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun unwrapAndNotify(giftWrap: GiftWrapEvent) {
|
||||
val giftWrapNote = LocalCache.notes[giftWrap.id] ?: return
|
||||
private fun notify(event: ChatMessageEvent, acc: Account) {
|
||||
if (event.createdAt > TimeUtils.fiveMinutesAgo() && // old event being re-broadcasted
|
||||
event.pubKey != acc.userProfile().pubkeyHex
|
||||
) { // from the user
|
||||
|
||||
LocalPreferences.allSavedAccounts().forEach {
|
||||
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
|
||||
val chatNote = LocalCache.notes[event.id] ?: return
|
||||
val chatRoom = event.chatroomKey(acc.keyPair.pubKey.toHexKey())
|
||||
|
||||
if (acc != null && acc.userProfile().pubkeyHex == giftWrap.recipientPubKey()) {
|
||||
val chatEvent = unwrapAndConsume(giftWrap, account = acc)
|
||||
val followingKeySet = acc.followingKeySet()
|
||||
|
||||
if (chatEvent is ChatMessageEvent && acc.keyPair.privKey != null) {
|
||||
val chatNote = LocalCache.notes[chatEvent.id] ?: return
|
||||
val chatRoom = chatEvent.chatroomKey(acc.keyPair.pubKey.toHexKey())
|
||||
val isKnownRoom = (
|
||||
acc.userProfile().privateChatrooms[chatRoom]?.senderIntersects(followingKeySet) == true ||
|
||||
acc.userProfile().hasSentMessagesTo(chatRoom)
|
||||
) && !acc.isAllHidden(chatRoom.users)
|
||||
|
||||
val followingKeySet = acc.followingKeySet()
|
||||
|
||||
val isKnownRoom = (
|
||||
acc.userProfile().privateChatrooms[chatRoom]?.senderIntersects(followingKeySet) == true ||
|
||||
acc.userProfile().hasSentMessagesTo(chatRoom)
|
||||
) && !acc.isAllHidden(chatRoom.users)
|
||||
|
||||
if (isKnownRoom) {
|
||||
val content = chatNote.event?.content() ?: ""
|
||||
val user = chatNote.author?.toBestDisplayName() ?: ""
|
||||
val userPicture = chatNote.author?.profilePicture()
|
||||
val noteUri = chatNote.toNEvent()
|
||||
notificationManager().sendDMNotification(chatEvent.id, content, user, userPicture, noteUri, applicationContext)
|
||||
}
|
||||
}
|
||||
if (isKnownRoom) {
|
||||
val content = chatNote.event?.content() ?: ""
|
||||
val user = chatNote.author?.toBestDisplayName() ?: ""
|
||||
val userPicture = chatNote.author?.profilePicture()
|
||||
val noteUri = chatNote.toNEvent()
|
||||
notificationManager().sendDMNotification(
|
||||
event.id,
|
||||
content,
|
||||
user,
|
||||
userPicture,
|
||||
noteUri,
|
||||
applicationContext
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notify(event: PrivateDmEvent) {
|
||||
private fun notify(event: PrivateDmEvent, acc: Account) {
|
||||
val note = LocalCache.notes[event.id] ?: return
|
||||
|
||||
LocalPreferences.allSavedAccounts().forEach {
|
||||
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
|
||||
// old event being re-broadcast
|
||||
if (event.createdAt < TimeUtils.fiveMinutesAgo()) return
|
||||
|
||||
if (acc != null && acc.userProfile().pubkeyHex == event.verifiedRecipientPubKey()) {
|
||||
val followingKeySet = acc.followingKeySet()
|
||||
if (acc != null && acc.userProfile().pubkeyHex == event.verifiedRecipientPubKey()) {
|
||||
val followingKeySet = acc.followingKeySet()
|
||||
|
||||
val knownChatrooms = acc.userProfile().privateChatrooms.keys.filter {
|
||||
(
|
||||
acc.userProfile().privateChatrooms[it]?.senderIntersects(followingKeySet) == true ||
|
||||
acc.userProfile().hasSentMessagesTo(it)
|
||||
) && !acc.isAllHidden(it.users)
|
||||
}.toSet()
|
||||
val knownChatrooms = acc.userProfile().privateChatrooms.keys.filter {
|
||||
(
|
||||
acc.userProfile().privateChatrooms[it]?.senderIntersects(followingKeySet) == true ||
|
||||
acc.userProfile().hasSentMessagesTo(it)
|
||||
) && !acc.isAllHidden(it.users)
|
||||
}.toSet()
|
||||
|
||||
note.author?.let {
|
||||
if (ChatroomKey(persistentSetOf(it.pubkeyHex)) in knownChatrooms) {
|
||||
val content = acc.decryptContent(note) ?: ""
|
||||
val user = note.author?.toBestDisplayName() ?: ""
|
||||
val userPicture = note.author?.profilePicture()
|
||||
val noteUri = note.toNEvent()
|
||||
notificationManager().sendDMNotification(event.id, content, user, userPicture, noteUri, applicationContext)
|
||||
}
|
||||
note.author?.let {
|
||||
if (ChatroomKey(persistentSetOf(it.pubkeyHex)) in knownChatrooms) {
|
||||
val content = acc.decryptContent(note) ?: ""
|
||||
val user = note.author?.toBestDisplayName() ?: ""
|
||||
val userPicture = note.author?.profilePicture()
|
||||
val noteUri = note.toNEvent()
|
||||
notificationManager().sendDMNotification(event.id, content, user, userPicture, noteUri, applicationContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notify(event: LnZapEvent) {
|
||||
private fun notify(event: LnZapEvent, acc: Account) {
|
||||
val noteZapEvent = LocalCache.notes[event.id] ?: return
|
||||
|
||||
val noteZapRequest = event.zapRequest?.id?.let { LocalCache.checkGetOrCreateNote(it) }
|
||||
// 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) }
|
||||
|
||||
LocalPreferences.allSavedAccounts().forEach {
|
||||
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
|
||||
if ((event.amount ?: BigDecimal.ZERO) < BigDecimal.TEN) return
|
||||
|
||||
if (acc != null && acc.userProfile().pubkeyHex == event.zappedAuthor().firstOrNull()) {
|
||||
val amount = showAmount(event.amount)
|
||||
val senderInfo = (noteZapRequest?.event as? LnZapRequestEvent)?.let {
|
||||
val decryptedContent = acc.decryptZapContentAuthor(noteZapRequest)
|
||||
if (decryptedContent != null) {
|
||||
val author = LocalCache.getOrCreateUser(decryptedContent.pubKey)
|
||||
Pair(author, decryptedContent.content)
|
||||
} else if (!noteZapRequest.event?.content().isNullOrBlank()) {
|
||||
Pair(noteZapRequest.author, noteZapRequest.event?.content())
|
||||
} else {
|
||||
Pair(noteZapRequest.author, null)
|
||||
}
|
||||
if (acc.userProfile().pubkeyHex == event.zappedAuthor().firstOrNull()) {
|
||||
val amount = showAmount(event.amount)
|
||||
val senderInfo = (noteZapRequest.event as? LnZapRequestEvent)?.let {
|
||||
val decryptedContent = acc.decryptZapContentAuthor(noteZapRequest)
|
||||
if (decryptedContent != null) {
|
||||
val author = LocalCache.getOrCreateUser(decryptedContent.pubKey)
|
||||
Pair(author, decryptedContent.content)
|
||||
} else if (!noteZapRequest.event?.content().isNullOrBlank()) {
|
||||
Pair(noteZapRequest.author, noteZapRequest.event?.content())
|
||||
} else {
|
||||
Pair(noteZapRequest.author, null)
|
||||
}
|
||||
|
||||
val zappedContent =
|
||||
noteZapped?.let { it1 -> acc.decryptContent(it1)?.split("\n")?.get(0) }
|
||||
|
||||
val user = senderInfo?.first?.toBestDisplayName() ?: ""
|
||||
var title = applicationContext.getString(R.string.app_notification_zaps_channel_message, amount)
|
||||
senderInfo?.second?.ifBlank { null }?.let {
|
||||
title += " ($it)"
|
||||
}
|
||||
var content = applicationContext.getString(R.string.app_notification_zaps_channel_message_from, user)
|
||||
zappedContent?.let {
|
||||
content += " " + applicationContext.getString(R.string.app_notification_zaps_channel_message_for, zappedContent)
|
||||
}
|
||||
val userPicture = senderInfo?.first?.profilePicture()
|
||||
val noteUri = "nostr:Notifications"
|
||||
notificationManager().sendZapNotification(event.id, content, title, userPicture, noteUri, applicationContext)
|
||||
}
|
||||
|
||||
val zappedContent =
|
||||
noteZapped?.let { it1 -> acc.decryptContent(it1)?.split("\n")?.get(0) }
|
||||
|
||||
val user = senderInfo?.first?.toBestDisplayName() ?: ""
|
||||
var title = applicationContext.getString(R.string.app_notification_zaps_channel_message, amount)
|
||||
senderInfo?.second?.ifBlank { null }?.let {
|
||||
title += " ($it)"
|
||||
}
|
||||
var content = applicationContext.getString(R.string.app_notification_zaps_channel_message_from, user)
|
||||
zappedContent?.let {
|
||||
content += " " + applicationContext.getString(R.string.app_notification_zaps_channel_message_for, zappedContent)
|
||||
}
|
||||
val userPicture = senderInfo?.first?.profilePicture()
|
||||
val noteUri = "nostr:Notifications"
|
||||
notificationManager().sendZapNotification(event.id, content, title, userPicture, noteUri, applicationContext)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class RegisterAccounts(
|
||||
): List<RelayAuthEvent> {
|
||||
return accounts.mapNotNull {
|
||||
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
|
||||
if (acc != null) {
|
||||
if (acc != null && acc.isWriteable()) {
|
||||
val relayToUse = acc.activeRelays()?.firstOrNull { it.read }
|
||||
if (relayToUse != null) {
|
||||
acc.createAuthEvent(relayToUse, notificationToken)
|
||||
|
||||
@@ -16,9 +16,7 @@ object Constants {
|
||||
}
|
||||
|
||||
val defaultRelays = arrayOf(
|
||||
// Free relays for DMs and Follows
|
||||
RelaySetupInfo("wss://no.str.cr", read = true, write = true, feedTypes = activeTypes),
|
||||
RelaySetupInfo("wss://relay.snort.social", read = true, write = true, feedTypes = activeTypes),
|
||||
// Free relays for only DMs and Follows due to the amount of spam
|
||||
RelaySetupInfo("wss://relay.damus.io", read = true, write = true, feedTypes = activeTypes),
|
||||
|
||||
// Chats
|
||||
@@ -40,6 +38,7 @@ object Constants {
|
||||
// NewRelayListViewModel.Relay("wss://brb.io", read = true, write = true, feedTypes = activeTypes),
|
||||
|
||||
// Paid relays
|
||||
RelaySetupInfo("wss://relay.snort.social", read = true, write = false, feedTypes = activeTypesGlobalChats),
|
||||
RelaySetupInfo("wss://relay.nostr.com.au", read = true, write = false, feedTypes = activeTypesGlobalChats),
|
||||
RelaySetupInfo("wss://eden.nostr.land", read = true, write = false, feedTypes = activeTypesGlobalChats),
|
||||
RelaySetupInfo("wss://nostr.milou.lol", read = true, write = false, feedTypes = activeTypesGlobalChats),
|
||||
@@ -51,8 +50,15 @@ object Constants {
|
||||
RelaySetupInfo("wss://relay.nostrati.com", read = true, write = false, feedTypes = activeTypesGlobalChats),
|
||||
|
||||
// Supporting NIP-50
|
||||
RelaySetupInfo("wss://relay.nostr.band", read = true, write = false, feedTypes = activeTypesSearch)
|
||||
RelaySetupInfo("wss://relay.nostr.band", read = true, write = false, feedTypes = activeTypesSearch),
|
||||
RelaySetupInfo("wss://filter.nostr.wine", read = true, write = false, feedTypes = activeTypesSearch),
|
||||
RelaySetupInfo("wss://relay.noswhere.com", read = true, write = false, feedTypes = activeTypesSearch)
|
||||
)
|
||||
|
||||
val forcedRelayForSearch = RelaySetupInfo("wss://relay.nostr.band", read = true, write = false, feedTypes = activeTypesSearch)
|
||||
val forcedRelayForSearch = arrayOf(
|
||||
RelaySetupInfo("wss://relay.nostr.band", read = true, write = false, feedTypes = activeTypesSearch),
|
||||
RelaySetupInfo("wss://filter.nostr.wine", read = true, write = false, feedTypes = activeTypesSearch),
|
||||
RelaySetupInfo("wss://relay.noswhere.com", read = true, write = false, feedTypes = activeTypesSearch)
|
||||
)
|
||||
val forcedRelaysForSearchSet = forcedRelayForSearch.map { it.url }
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ class Relay(
|
||||
private var listeners = setOf<Listener>()
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.vitorpamplona.amethyst.service.relays
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.EventInterface
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
|
||||
/**
|
||||
* RelayPool manages the connection to multiple Relays and lets consumers deal with simple events.
|
||||
@@ -12,6 +16,11 @@ object RelayPool : Relay.Listener {
|
||||
private var relays = listOf<Relay>()
|
||||
private var listeners = setOf<Listener>()
|
||||
|
||||
// Backing property to avoid flow emissions from other classes
|
||||
private var _lastStatus = RelayPoolStatus(0, 0)
|
||||
private val _statusFlow = MutableSharedFlow<RelayPoolStatus>(1, 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
val statusFlow: SharedFlow<RelayPoolStatus> = _statusFlow.asSharedFlow()
|
||||
|
||||
fun availableRelays(): Int {
|
||||
return relays.size
|
||||
}
|
||||
@@ -76,11 +85,13 @@ object RelayPool : Relay.Listener {
|
||||
fun addRelay(relay: Relay) {
|
||||
relay.register(this)
|
||||
relays += relay
|
||||
updateStatus()
|
||||
}
|
||||
|
||||
fun removeRelay(relay: Relay) {
|
||||
relay.unregister(this)
|
||||
relays = relays.minus(relay)
|
||||
updateStatus()
|
||||
}
|
||||
|
||||
fun register(listener: Listener) {
|
||||
@@ -109,12 +120,14 @@ object RelayPool : Relay.Listener {
|
||||
|
||||
override fun onError(relay: Relay, subscriptionId: String, error: Error) {
|
||||
listeners.forEach { it.onError(error, subscriptionId, relay) }
|
||||
refreshObservers()
|
||||
updateStatus()
|
||||
}
|
||||
|
||||
override fun onRelayStateChange(relay: Relay, type: Relay.Type, channel: String?) {
|
||||
listeners.forEach { it.onRelayStateChange(type, relay, channel) }
|
||||
refreshObservers()
|
||||
if (type != Relay.Type.EOSE) {
|
||||
updateStatus()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSendResponse(relay: Relay, eventId: String, success: Boolean, message: String) {
|
||||
@@ -125,18 +138,15 @@ object RelayPool : Relay.Listener {
|
||||
listeners.forEach { it.onAuth(relay, challenge) }
|
||||
}
|
||||
|
||||
// Observers line up here.
|
||||
val live: RelayPoolLiveData = RelayPoolLiveData(this)
|
||||
|
||||
private fun refreshObservers() {
|
||||
live.refresh()
|
||||
private fun updateStatus() {
|
||||
val connected = connectedRelays()
|
||||
val available = availableRelays()
|
||||
if (_lastStatus.connected != connected || _lastStatus.available != available) {
|
||||
_lastStatus = RelayPoolStatus(connected, available)
|
||||
_statusFlow.tryEmit(_lastStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RelayPoolLiveData(val relays: RelayPool) : LiveData<RelayPoolState>(RelayPoolState(relays)) {
|
||||
fun refresh() {
|
||||
postValue(RelayPoolState(relays))
|
||||
}
|
||||
}
|
||||
|
||||
class RelayPoolState(val relays: RelayPool)
|
||||
@Immutable
|
||||
data class RelayPoolStatus(val connected: Int, val available: Int, val isConnected: Boolean = connected > 0)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.vitorpamplona.amethyst.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
@@ -65,7 +66,10 @@ class MainActivity : AppCompatActivity() {
|
||||
themeViewModel.onChange(LocalPreferences.getTheme())
|
||||
AmethystTheme(themeViewModel) {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colors.background
|
||||
) {
|
||||
val accountStateViewModel: AccountStateViewModel = viewModel {
|
||||
AccountStateViewModel(this@MainActivity)
|
||||
}
|
||||
@@ -81,7 +85,8 @@ class MainActivity : AppCompatActivity() {
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.build()
|
||||
|
||||
val connectivityManager = getSystemService(ConnectivityManager::class.java) as ConnectivityManager
|
||||
val connectivityManager =
|
||||
getSystemService(ConnectivityManager::class.java) as ConnectivityManager
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
||||
}
|
||||
|
||||
@@ -153,7 +158,8 @@ class MainActivity : AppCompatActivity() {
|
||||
super.onCapabilitiesChanged(network, networkCapabilities)
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val hasMobileData = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
val hasMobileData =
|
||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
val hasWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
Log.d("NETWORKCALLBACK", "onCapabilitiesChanged: hasMobileData $hasMobileData")
|
||||
Log.d("NETWORKCALLBACK", "onCapabilitiesChanged: hasWifi $hasWifi")
|
||||
@@ -178,9 +184,15 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
class GetMediaActivityResultContract : ActivityResultContracts.GetContent() {
|
||||
|
||||
@SuppressLint("MissingSuperCall")
|
||||
override fun createIntent(context: Context, input: String): Intent {
|
||||
return super.createIntent(context, input).apply {
|
||||
// Force only images and videos to be selectable
|
||||
// Force OPEN Document because of the resulting URI must be passed to the
|
||||
// Playback service and the picker's permissions only allow the activity to read the URI
|
||||
return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
// Force only images and videos to be selectable
|
||||
type = "*/*"
|
||||
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.actions
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.util.Size
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
@@ -79,13 +80,7 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
|
||||
var showRelaysDialog by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
var relayList = account.activeRelays()?.filter {
|
||||
it.write
|
||||
}?.map {
|
||||
it
|
||||
} ?: account.convertLocalRelays().filter {
|
||||
it.write
|
||||
}
|
||||
var relayList = account.activeWriteRelays()
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = { onClose() },
|
||||
@@ -101,7 +96,7 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
|
||||
) {
|
||||
if (showRelaysDialog) {
|
||||
RelaySelectionDialog(
|
||||
list = relayList,
|
||||
preSelectedList = relayList,
|
||||
onClose = {
|
||||
showRelaysDialog = false
|
||||
},
|
||||
@@ -221,7 +216,8 @@ fun ImageVideoPost(postViewModel: NewMediaModel, accountViewModel: AccountViewMo
|
||||
try {
|
||||
bitmap = resolver.loadThumbnail(it, Size(1200, 1000), null)
|
||||
} catch (e: Exception) {
|
||||
postViewModel.imageUploadingError.emit("Unable to load file")
|
||||
postViewModel.imageUploadingError.emit("Unable to load thumbnail, but the video can be uploaded")
|
||||
Log.w("NewPostView", "Couldn't create thumbnail, but the video can be uploaded", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,37 +101,50 @@ class NewMessageTagger(
|
||||
|
||||
key = key.removePrefix("@")
|
||||
|
||||
if (key.length < 63) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
val keyB32 = key.substring(0, 63)
|
||||
val restOfWord = key.substring(63)
|
||||
|
||||
if (key.startsWith("nsec1", true)) {
|
||||
if (key.length < 63) {
|
||||
return null
|
||||
}
|
||||
|
||||
val keyB32 = key.substring(0, 63)
|
||||
val restOfWord = key.substring(63)
|
||||
// Converts to npub
|
||||
val pubkey = Nip19.uriToRoute(KeyPair(privKey = keyB32.bechToBytes()).pubKey.toNpub()) ?: return null
|
||||
|
||||
return DirtyKeyInfo(pubkey, restOfWord)
|
||||
} else if (key.startsWith("npub1", true)) {
|
||||
if (key.length < 63) {
|
||||
return null
|
||||
}
|
||||
|
||||
val keyB32 = key.substring(0, 63)
|
||||
val restOfWord = key.substring(63)
|
||||
|
||||
val pubkey = Nip19.uriToRoute(keyB32) ?: return null
|
||||
|
||||
return DirtyKeyInfo(pubkey, restOfWord)
|
||||
} else if (key.startsWith("note1", true)) {
|
||||
if (key.length < 63) {
|
||||
return null
|
||||
}
|
||||
|
||||
val keyB32 = key.substring(0, 63)
|
||||
val restOfWord = key.substring(63)
|
||||
|
||||
val noteId = Nip19.uriToRoute(keyB32) ?: return null
|
||||
|
||||
return DirtyKeyInfo(noteId, restOfWord)
|
||||
} else if (key.startsWith("nprofile", true)) {
|
||||
val pubkeyRelay = Nip19.uriToRoute(keyB32 + restOfWord) ?: return null
|
||||
val pubkeyRelay = Nip19.uriToRoute(key) ?: return null
|
||||
|
||||
return DirtyKeyInfo(pubkeyRelay, pubkeyRelay.additionalChars)
|
||||
} else if (key.startsWith("nevent1", true)) {
|
||||
val noteRelayId = Nip19.uriToRoute(keyB32 + restOfWord) ?: return null
|
||||
val noteRelayId = Nip19.uriToRoute(key) ?: return null
|
||||
|
||||
return DirtyKeyInfo(noteRelayId, noteRelayId.additionalChars)
|
||||
} else if (key.startsWith("naddr1", true)) {
|
||||
val address = Nip19.uriToRoute(keyB32 + restOfWord) ?: return null
|
||||
val address = Nip19.uriToRoute(key) ?: return null
|
||||
|
||||
return DirtyKeyInfo(address, address.additionalChars) // no way to know when they address ends and dirt begins
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.util.Size
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -82,20 +83,24 @@ import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
||||
import com.vitorpamplona.amethyst.ui.components.*
|
||||
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.CancelIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.PollIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.RegularPostIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.MyTextField
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
@@ -111,6 +116,7 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.Math.round
|
||||
|
||||
@Composable
|
||||
fun NewPostView(
|
||||
@@ -133,13 +139,7 @@ fun NewPostView(
|
||||
var showRelaysDialog by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
var relayList = account.activeRelays()?.filter {
|
||||
it.write
|
||||
}?.map {
|
||||
it
|
||||
} ?: account.convertLocalRelays().filter {
|
||||
it.write
|
||||
}
|
||||
var relayList = account.activeWriteRelays()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
postViewModel.load(account, baseReplyTo, quote)
|
||||
@@ -169,7 +169,7 @@ fun NewPostView(
|
||||
) {
|
||||
if (showRelaysDialog) {
|
||||
RelaySelectionDialog(
|
||||
list = relayList,
|
||||
preSelectedList = relayList,
|
||||
onClose = {
|
||||
showRelaysDialog = false
|
||||
},
|
||||
@@ -327,9 +327,9 @@ fun NewPostView(
|
||||
if (postViewModel.wantsForwardZapTo) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)
|
||||
modifier = Modifier.padding(top = Size5dp, bottom = Size5dp, start = Size10dp)
|
||||
) {
|
||||
FowardZapTo(postViewModel)
|
||||
FowardZapTo(postViewModel, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -727,7 +727,7 @@ fun SendDirectMessageTo(postViewModel: NewPostViewModel) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FowardZapTo(postViewModel: NewPostViewModel) {
|
||||
fun FowardZapTo(postViewModel: NewPostViewModel, accountViewModel: AccountViewModel) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
@@ -761,7 +761,7 @@ fun FowardZapTo(postViewModel: NewPostViewModel) {
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.zap_forward_title),
|
||||
text = stringResource(R.string.zap_split_title),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier = Modifier.padding(start = 10.dp)
|
||||
@@ -771,22 +771,52 @@ fun FowardZapTo(postViewModel: NewPostViewModel) {
|
||||
Divider()
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.zap_forward_explainer),
|
||||
text = stringResource(R.string.zap_split_explainer),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
modifier = Modifier.padding(vertical = 10.dp)
|
||||
)
|
||||
|
||||
postViewModel.forwardZapTo.items.forEachIndexed { index, splitItem ->
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = Size10dp)) {
|
||||
BaseUserPicture(splitItem.key, Size55dp, accountViewModel = accountViewModel)
|
||||
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
UsernameDisplay(splitItem.key, showPlayButton = false)
|
||||
Text(
|
||||
text = String.format("%.0f%%", splitItem.percentage * 100),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
|
||||
Slider(
|
||||
value = splitItem.percentage,
|
||||
onValueChange = { sliderValue ->
|
||||
val rounded = (round(sliderValue * 20)) / 20.0f
|
||||
postViewModel.updateZapPercentage(index, rounded)
|
||||
},
|
||||
modifier = Modifier
|
||||
.weight(1.5f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = postViewModel.forwardZapToEditting,
|
||||
onValueChange = {
|
||||
postViewModel.updateZapForwardTo(it)
|
||||
},
|
||||
|
||||
label = { Text(text = stringResource(R.string.zap_forward_lnAddress)) },
|
||||
label = { Text(text = stringResource(R.string.zap_split_serarch_and_add_user)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.zap_forward_lnAddress),
|
||||
text = stringResource(R.string.zap_split_serarch_and_add_user_placeholder),
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
@@ -1376,8 +1406,8 @@ fun ImageVideoDescription(
|
||||
try {
|
||||
bitmap = resolver.loadThumbnail(uri, Size(1200, 1000), null)
|
||||
} catch (e: Exception) {
|
||||
onError("Unable to load file")
|
||||
Log.e("NewPostView", "Couldn't create thumbnail for $uri")
|
||||
onError("Unable to load thumbnail")
|
||||
Log.w("NewPostView", "Couldn't create thumbnail, but the video can be uploaded", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
|
||||
import com.vitorpamplona.amethyst.ui.components.Split
|
||||
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.events.AddressableEvent
|
||||
@@ -29,6 +30,7 @@ import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.events.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.events.ZapSplitSetup
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@@ -88,9 +90,13 @@ open class NewPostViewModel() : ViewModel() {
|
||||
var canAddInvoice by mutableStateOf(false)
|
||||
var wantsInvoice by mutableStateOf(false)
|
||||
|
||||
data class ForwardZapSetup(val user: User) {
|
||||
var percentage by mutableStateOf(100)
|
||||
}
|
||||
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
var forwardZapTo by mutableStateOf<User?>(null)
|
||||
var forwardZapTo by mutableStateOf<Split<User>>(Split())
|
||||
var forwardZapToEditting by mutableStateOf(TextFieldValue(""))
|
||||
|
||||
// NSFW, Sensitive
|
||||
@@ -151,7 +157,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||
wantsToAddGeoHash = false
|
||||
wantsZapraiser = false
|
||||
zapRaiserAmount = null
|
||||
forwardZapTo = null
|
||||
forwardZapTo = Split()
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
|
||||
this.account = account
|
||||
@@ -166,10 +172,13 @@ open class NewPostViewModel() : ViewModel() {
|
||||
val dmUsers = toUsersTagger.mentions
|
||||
|
||||
val zapReceiver = if (wantsForwardZapTo) {
|
||||
if (forwardZapTo != null) {
|
||||
forwardZapTo?.info?.lud16 ?: forwardZapTo?.info?.lud06
|
||||
} else {
|
||||
forwardZapToEditting.text
|
||||
forwardZapTo?.items?.map {
|
||||
ZapSplitSetup(
|
||||
lnAddressOrPubKeyHex = it.key.pubkeyHex,
|
||||
relay = it.key.relaysBeingUsed.keys.firstOrNull(),
|
||||
weight = it.percentage.toDouble(),
|
||||
isLnAddress = false
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
@@ -355,7 +364,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||
wantsForwardZapTo = false
|
||||
wantsToMarkAsSensitive = false
|
||||
wantsToAddGeoHash = false
|
||||
forwardZapTo = null
|
||||
forwardZapTo = Split()
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
|
||||
userSuggestions = emptyList()
|
||||
@@ -427,10 +436,10 @@ open class NewPostViewModel() : ViewModel() {
|
||||
open fun updateZapForwardTo(it: TextFieldValue) {
|
||||
forwardZapToEditting = it
|
||||
if (it.selection.collapsed) {
|
||||
val lastWord = it.text.substring(0, it.selection.end).substringAfterLast("\n").substringAfterLast(" ")
|
||||
val lastWord = it.text
|
||||
userSuggestionAnchor = it.selection
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.FORWARD_ZAPS
|
||||
if (lastWord.startsWith("@") && lastWord.length > 2) {
|
||||
if (lastWord.length > 2) {
|
||||
NostrSearchEventOrUserDataSource.search(lastWord.removePrefix("@"))
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
userSuggestions = LocalCache.findUsersStartingWith(lastWord.removePrefix("@"))
|
||||
@@ -460,6 +469,9 @@ open class NewPostViewModel() : ViewModel() {
|
||||
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length)
|
||||
)
|
||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||
forwardZapTo?.addItem(item)
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
/*
|
||||
val lastWord = forwardZapToEditting.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
||||
val lastWordStart = it.end - lastWord.length
|
||||
val wordToInsert = "@${item.pubkeyNpub()}"
|
||||
@@ -468,7 +480,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||
forwardZapToEditting = TextFieldValue(
|
||||
forwardZapToEditting.text.replaceRange(lastWordStart, it.end, wordToInsert),
|
||||
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length)
|
||||
)
|
||||
)*/
|
||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.TO_USERS) {
|
||||
val lastWord = toUsers.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
||||
val lastWordStart = it.end - lastWord.length
|
||||
@@ -656,6 +668,10 @@ open class NewPostViewModel() : ViewModel() {
|
||||
isValidvalueMaximum.value = true
|
||||
}
|
||||
}
|
||||
|
||||
fun updateZapPercentage(index: Int, sliderValue: Float) {
|
||||
forwardZapTo?.updatePercentage(index, sliderValue)
|
||||
}
|
||||
}
|
||||
|
||||
enum class GeohashPrecision(val digits: Int) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||
import com.vitorpamplona.amethyst.service.relays.Constants
|
||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||
@@ -24,6 +25,7 @@ class NewRelayListViewModel : ViewModel() {
|
||||
fun load(account: Account) {
|
||||
this.account = account
|
||||
clear()
|
||||
loadRelayDocuments()
|
||||
}
|
||||
|
||||
fun create() {
|
||||
@@ -36,17 +38,34 @@ class NewRelayListViewModel : ViewModel() {
|
||||
clear()
|
||||
}
|
||||
|
||||
fun loadRelayDocuments() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_relays.value.forEach { item ->
|
||||
Nip11CachedRetriever.loadRelayInfo(
|
||||
dirtyUrl = item.url,
|
||||
onInfo = {
|
||||
togglePaidRelay(item, it.limitation?.payment_required ?: false)
|
||||
},
|
||||
onError = { url, errorCode, exceptionMessage ->
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
_relays.update {
|
||||
var relayFile = account.userProfile().latestContactList?.relays()
|
||||
|
||||
// Ugly, but forces nostr.band as the only search-supporting relay today.
|
||||
// TODO: Remove when search becomes more available.
|
||||
if (relayFile?.none { it.key == Constants.forcedRelayForSearch.url } == true) {
|
||||
relayFile = relayFile + Pair(
|
||||
Constants.forcedRelayForSearch.url,
|
||||
ContactListEvent.ReadWrite(Constants.forcedRelayForSearch.read, Constants.forcedRelayForSearch.write)
|
||||
)
|
||||
if (relayFile?.none { it.key.removeSuffix("/") in Constants.forcedRelaysForSearchSet } == true) {
|
||||
relayFile = relayFile + Constants.forcedRelayForSearch.map {
|
||||
Pair(
|
||||
it.url,
|
||||
ContactListEvent.ReadWrite(it.read, it.write)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (relayFile != null) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.vitorpamplona.amethyst.ui.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -28,27 +25,24 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||
import com.vitorpamplona.amethyst.service.HttpClient
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.io.IOException
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun RelayInformationDialog(
|
||||
onClose: () -> Unit,
|
||||
relayBriefInfo: RelayBriefInfo,
|
||||
relayInfo: RelayInformation,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
@@ -79,18 +73,25 @@ fun RelayInformationDialog(
|
||||
})
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Title(relayInfo.name ?: "")
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = StdPadding.fillMaxWidth()) {
|
||||
Column() {
|
||||
RenderRelayIcon(
|
||||
relayBriefInfo.favIcon,
|
||||
Size55dp
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
SectionContent(relayInfo.description ?: "")
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Row() {
|
||||
Title(relayInfo.name?.trim() ?: "")
|
||||
}
|
||||
|
||||
Row() {
|
||||
SubtitleContent(relayInfo.description?.trim() ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(stringResource(R.string.owner))
|
||||
@@ -212,7 +213,7 @@ private fun DisplaySupportedNips(relayInfo: RelayInformation) {
|
||||
val text = item.toString().padStart(2, '0')
|
||||
Box(Modifier.padding(10.dp)) {
|
||||
ClickableUrl(
|
||||
urlText = "$text",
|
||||
urlText = text,
|
||||
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
|
||||
)
|
||||
}
|
||||
@@ -222,7 +223,7 @@ private fun DisplaySupportedNips(relayInfo: RelayInformation) {
|
||||
val text = item.padStart(2, '0')
|
||||
Box(Modifier.padding(10.dp)) {
|
||||
ClickableUrl(
|
||||
urlText = "$text",
|
||||
urlText = text,
|
||||
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
|
||||
)
|
||||
}
|
||||
@@ -258,13 +259,18 @@ private fun DisplayOwnerInformation(
|
||||
|
||||
@Composable
|
||||
fun Title(text: String) {
|
||||
Spacer(modifier = DoubleVertSpacer)
|
||||
Text(
|
||||
text = text,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 24.sp
|
||||
)
|
||||
Spacer(modifier = DoubleVertSpacer)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SubtitleContent(text: String) {
|
||||
Text(
|
||||
text = text
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -285,85 +291,3 @@ fun SectionContent(text: String) {
|
||||
text = text
|
||||
)
|
||||
}
|
||||
|
||||
fun loadRelayInfo(
|
||||
dirtyUrl: String,
|
||||
context: Context,
|
||||
scope: CoroutineScope,
|
||||
onInfo: (RelayInformation) -> Unit
|
||||
) {
|
||||
try {
|
||||
val url = if (dirtyUrl.contains("://")) {
|
||||
dirtyUrl
|
||||
.replace("wss://", "https://")
|
||||
.replace("ws://", "http://")
|
||||
} else {
|
||||
"https://$dirtyUrl"
|
||||
}
|
||||
|
||||
val request: Request = Request
|
||||
.Builder()
|
||||
.header("Accept", "application/nostr+json")
|
||||
.url(url)
|
||||
.build()
|
||||
|
||||
HttpClient.getHttpClient()
|
||||
.newCall(request)
|
||||
.enqueue(
|
||||
object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
checkNotInMainThread()
|
||||
response.use {
|
||||
val body = it.body.string()
|
||||
try {
|
||||
if (it.isSuccessful) {
|
||||
onInfo(RelayInformation.fromJson(body))
|
||||
} else {
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("RelayInfoFail", "Resulting Message from Relay $dirtyUrl in not parseable: $body", e)
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
Log.e("RelayInfoFail", "$dirtyUrl unavailable", e)
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("RelayInfoFail", "Invalid URL $dirtyUrl", e)
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,19 +29,27 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class RelayList(
|
||||
val relay: Relay,
|
||||
val relayInfo: RelayBriefInfo,
|
||||
val isSelected: Boolean
|
||||
)
|
||||
|
||||
data class RelayInfoDialog(
|
||||
val relayBriefInfo: RelayBriefInfo,
|
||||
val relayInfo: RelayInformation
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RelaySelectionDialog(
|
||||
list: List<Relay>,
|
||||
preSelectedList: List<Relay>,
|
||||
onClose: () -> Unit,
|
||||
onPost: (list: List<Relay>) -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
@@ -49,34 +57,29 @@ fun RelaySelectionDialog(
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val relayList = accountViewModel.account.activeRelays()?.filter {
|
||||
it.write
|
||||
}?.map {
|
||||
it
|
||||
} ?: accountViewModel.account.convertLocalRelays().filter {
|
||||
it.write
|
||||
}
|
||||
|
||||
var relays by remember {
|
||||
mutableStateOf(
|
||||
relayList.map {
|
||||
accountViewModel.account.activeWriteRelays().map {
|
||||
RelayList(
|
||||
it,
|
||||
list.any { relay -> it.url == relay.url }
|
||||
relay = it,
|
||||
relayInfo = RelayBriefInfo(it.url),
|
||||
isSelected = preSelectedList.any { relay -> it.url == relay.url }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
|
||||
var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) }
|
||||
|
||||
if (relayInfo != null) {
|
||||
relayInfo?.let {
|
||||
RelayInformationDialog(
|
||||
onClose = {
|
||||
relayInfo = null
|
||||
},
|
||||
relayInfo = relayInfo!!,
|
||||
accountViewModel,
|
||||
nav
|
||||
relayInfo = it.relayInfo,
|
||||
relayBriefInfo = it.relayBriefInfo,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
|
||||
@@ -115,14 +118,14 @@ fun RelaySelectionDialog(
|
||||
}
|
||||
)
|
||||
|
||||
PostButton(
|
||||
SaveButton(
|
||||
onPost = {
|
||||
val selectedRelays = relays.filter { it.isSelected }
|
||||
if (selectedRelays.isEmpty()) {
|
||||
scope.launch {
|
||||
Toast.makeText(context, context.getString(R.string.select_a_relay_to_continue), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
return@PostButton
|
||||
return@SaveButton
|
||||
}
|
||||
onPost(selectedRelays.map { it.relay })
|
||||
onClose()
|
||||
@@ -153,10 +156,7 @@ fun RelaySelectionDialog(
|
||||
key = { _, item -> item.relay.url }
|
||||
) { index, item ->
|
||||
RelaySwitch(
|
||||
text = item.relay.url
|
||||
.removePrefix("ws://")
|
||||
.removePrefix("wss://")
|
||||
.removeSuffix("/"),
|
||||
text = item.relayInfo.displayUrl,
|
||||
checked = item.isSelected,
|
||||
onClick = {
|
||||
relays = relays.mapIndexed { j, item ->
|
||||
@@ -168,9 +168,30 @@ fun RelaySelectionDialog(
|
||||
}
|
||||
},
|
||||
onLongPress = {
|
||||
loadRelayInfo(item.relay.url, context, scope) {
|
||||
relayInfo = it
|
||||
}
|
||||
accountViewModel.retrieveRelayDocument(
|
||||
item.relay.url,
|
||||
onInfo = {
|
||||
relayInfo = RelayInfoDialog(RelayBriefInfo(item.relay.url), it)
|
||||
},
|
||||
onError = { url, errorCode, exceptionMessage ->
|
||||
val msg = when (errorCode) {
|
||||
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
msg,
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import kotlin.math.abs
|
||||
|
||||
class SplitItem<T>(val key: T) {
|
||||
var percentage by mutableStateOf(0f)
|
||||
}
|
||||
|
||||
class Split<T>() {
|
||||
var items: List<SplitItem<T>> by mutableStateOf(emptyList())
|
||||
|
||||
fun addItem(key: T): Int {
|
||||
val wasEqualSplit = isEqualSplit()
|
||||
val newItem = SplitItem(key)
|
||||
items = items.plus(newItem)
|
||||
|
||||
if (wasEqualSplit) {
|
||||
forceEqualSplit()
|
||||
} else {
|
||||
updatePercentage(items.lastIndex, equalSplit())
|
||||
}
|
||||
|
||||
return items.lastIndex
|
||||
}
|
||||
|
||||
fun equalSplit() = 1f / items.size
|
||||
|
||||
fun isEqualSplit(): Boolean {
|
||||
val expectedPercentage = equalSplit()
|
||||
return items.all { (it.percentage - expectedPercentage) < 0.01 }
|
||||
}
|
||||
|
||||
fun forceEqualSplit() {
|
||||
val correctPercentage = equalSplit()
|
||||
items.forEach {
|
||||
it.percentage = correctPercentage
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePercentage(index: Int, percentage: Float) {
|
||||
if (items.isEmpty()) return
|
||||
|
||||
val splitItem = items.getOrNull(index) ?: return
|
||||
|
||||
if (items.size == 1) {
|
||||
splitItem.percentage = 1f
|
||||
} else {
|
||||
splitItem.percentage = percentage
|
||||
|
||||
println("Update ${items[index].key} to $percentage")
|
||||
|
||||
val othersMustShare = 1.0f - splitItem.percentage
|
||||
|
||||
val othersHave = items.sumOf {
|
||||
if (it == splitItem) 0.0 else it.percentage.toDouble()
|
||||
}.toFloat()
|
||||
|
||||
if (abs(othersHave - othersMustShare) < 0.01) return // nothing to do
|
||||
|
||||
println("Others Must Share $othersMustShare but have $othersHave")
|
||||
|
||||
bottomUpAdjustment(othersMustShare, othersHave, index)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bottomUpAdjustment(othersMustShare: Float, othersHave: Float, exceptForIndex: Int) {
|
||||
var needToRemove = othersHave - othersMustShare
|
||||
if (needToRemove > 0) {
|
||||
for (i in items.indices.reversed()) {
|
||||
if (i == exceptForIndex) continue // do not update the current item
|
||||
|
||||
if (needToRemove < items[i].percentage) {
|
||||
val oldValue = items[i].percentage
|
||||
items[i].percentage -= needToRemove
|
||||
needToRemove = 0f
|
||||
println("- Updating ${items[i].key} from $oldValue to ${items[i].percentage - needToRemove}. $needToRemove left")
|
||||
} else {
|
||||
val oldValue = items[i].percentage
|
||||
needToRemove -= items[i].percentage
|
||||
items[i].percentage = 0f
|
||||
println("- Updating ${items[i].key} from $oldValue to ${0}. $needToRemove left")
|
||||
}
|
||||
|
||||
if (needToRemove < 0.01) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (needToRemove < 0) {
|
||||
if (items.lastIndex == exceptForIndex) {
|
||||
items[items.lastIndex - 1].percentage += -needToRemove
|
||||
} else {
|
||||
items.last().percentage += -needToRemove
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -704,6 +704,7 @@ fun ControlWhenPlayerIsActive(
|
||||
|
||||
controller.addListener(listener)
|
||||
onDispose {
|
||||
view.keepScreenOn = false
|
||||
controller.removeListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
package com.vitorpamplona.amethyst.ui.dal
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.ThreadAssembler
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
class ThreadFeedFilter(val noteId: String) : FeedFilter<Note>() {
|
||||
class ThreadFeedFilter(val account: Account, val noteId: String) : FeedFilter<Note>() {
|
||||
|
||||
override fun feedKey(): String {
|
||||
return noteId
|
||||
}
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
val cachedSignatures: MutableMap<Note, String> = mutableMapOf()
|
||||
val eventsToWatch = ThreadAssembler().findThreadFor(noteId) ?: emptySet()
|
||||
val cachedSignatures: MutableMap<Note, Note.LevelSignature> = 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<Note> { it.replyLevelSignature(eventsToWatch, cachedSignatures) }
|
||||
val order = compareByDescending<Note> {
|
||||
it.replyLevelSignature(eventsInHex, cachedSignatures, account.userProfile(), followingSet, now).signature
|
||||
}
|
||||
|
||||
return eventsToWatch.sortedWith(order)
|
||||
}
|
||||
|
||||
@@ -451,8 +451,6 @@ fun GenericMainTopBar(
|
||||
nav: (String) -> Unit,
|
||||
content: @Composable (AccountViewModel) -> Unit
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
Column(modifier = BottomTopHeight) {
|
||||
MyTopAppBar(
|
||||
elevation = 0.dp,
|
||||
@@ -476,6 +474,7 @@ fun GenericMainTopBar(
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
LoggedInUserPictureDrawer(accountViewModel) {
|
||||
coroutineScope.launch {
|
||||
scaffoldState.drawerState.open()
|
||||
@@ -506,10 +505,9 @@ private fun LoggedInUserPictureDrawer(
|
||||
accountViewModel: AccountViewModel,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val accountUserState by accountViewModel.account.userProfile().live().metadata.observeAsState()
|
||||
val profilePicture by accountViewModel.account.userProfile().live().profilePictureChanges.observeAsState()
|
||||
|
||||
val pubkeyHex = remember { accountUserState?.user?.pubkeyHex ?: "" }
|
||||
val profilePicture = remember(accountUserState) { accountUserState?.user?.profilePicture() }
|
||||
val pubkeyHex = remember { accountViewModel.userProfile().pubkeyHex }
|
||||
|
||||
IconButton(
|
||||
onClick = onClick
|
||||
|
||||
@@ -40,6 +40,7 @@ import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
@@ -63,7 +64,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
@@ -72,11 +72,12 @@ import com.vitorpamplona.amethyst.ServiceManager
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.HttpClient
|
||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||
import com.vitorpamplona.amethyst.service.relays.RelayPoolStatus
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadStatuses
|
||||
import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
||||
@@ -109,7 +110,7 @@ fun DrawerContent(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 25.dp)
|
||||
.padding(top = 100.dp),
|
||||
.padding(top = 70.dp),
|
||||
scaffoldState,
|
||||
accountViewModel,
|
||||
nav
|
||||
@@ -162,7 +163,7 @@ fun ProfileContent(
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(150.dp)
|
||||
.height(120.dp)
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
@@ -171,7 +172,7 @@ fun ProfileContent(
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(150.dp)
|
||||
.height(120.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -220,7 +221,6 @@ fun ProfileContent(
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.padding(top = 15.dp)
|
||||
.clickable(
|
||||
onClick = {
|
||||
nav(route)
|
||||
@@ -455,7 +455,6 @@ fun ListContent(
|
||||
}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() }
|
||||
var wantsToEditRelays by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
@@ -491,7 +490,7 @@ fun ListContent(
|
||||
)
|
||||
|
||||
IconRowRelays(
|
||||
relayViewModel = relayViewModel,
|
||||
accountViewModel = accountViewModel,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
scaffoldState.drawerState.close()
|
||||
@@ -634,27 +633,37 @@ private fun enableTor(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RelayStatus(
|
||||
relayViewModel: RelayPoolViewModel
|
||||
) {
|
||||
val connectedRelaysText by relayViewModel.connectionStatus.observeAsState("--/--")
|
||||
val isConnected by relayViewModel.isConnected.observeAsState(false)
|
||||
private fun RelayStatus(accountViewModel: AccountViewModel) {
|
||||
val connectedRelaysText by RelayPool.statusFlow.collectAsState(initial = RelayPoolStatus(0, 0))
|
||||
|
||||
RenderRelayStatus(connectedRelaysText, isConnected)
|
||||
RenderRelayStatus(connectedRelaysText)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderRelayStatus(
|
||||
connectedRelaysText: String,
|
||||
isConnected: Boolean
|
||||
relayPool: RelayPoolStatus
|
||||
) {
|
||||
val text by remember(relayPool) {
|
||||
derivedStateOf {
|
||||
"${relayPool.connected}/${relayPool.available}"
|
||||
}
|
||||
}
|
||||
|
||||
val placeHolder = MaterialTheme.colors.placeholderText
|
||||
|
||||
val color by remember(relayPool) {
|
||||
derivedStateOf {
|
||||
if (relayPool.isConnected) {
|
||||
placeHolder
|
||||
} else {
|
||||
Color.Red
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = connectedRelaysText,
|
||||
color = if (isConnected) {
|
||||
MaterialTheme.colors.placeholderText
|
||||
} else {
|
||||
Color.Red
|
||||
},
|
||||
text = text,
|
||||
color = color,
|
||||
style = MaterialTheme.typography.subtitle1
|
||||
)
|
||||
}
|
||||
@@ -710,7 +719,7 @@ fun IconRow(title: String, icon: Int, tint: Color, onClick: () -> Unit, onLongCl
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun IconRowRelays(relayViewModel: RelayPoolViewModel, onClick: () -> Unit) {
|
||||
fun IconRowRelays(accountViewModel: AccountViewModel, onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -737,7 +746,7 @@ fun IconRowRelays(relayViewModel: RelayPoolViewModel, onClick: () -> Unit) {
|
||||
|
||||
Spacer(modifier = Modifier.width(Size16dp))
|
||||
|
||||
RelayStatus(relayViewModel = relayViewModel)
|
||||
RelayStatus(accountViewModel = accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,57 +62,63 @@ fun BlankNote(modifier: Modifier = Modifier, showDivider: Boolean = false, idHex
|
||||
@Composable
|
||||
fun HiddenNote(
|
||||
reports: ImmutableSet<Note>,
|
||||
isHiddenAuthor: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
isQuote: Boolean = false,
|
||||
nav: (String) -> Unit,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Row(modifier = Modifier.padding(horizontal = if (!isQuote) 12.dp else 6.dp)) {
|
||||
Column(modifier = Modifier.padding(start = if (!isQuote) 10.dp else 5.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.padding(
|
||||
start = 20.dp,
|
||||
end = 20.dp
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(30.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.post_was_flagged_as_inappropriate_by),
|
||||
color = Color.Gray
|
||||
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Row(
|
||||
modifier = Modifier.padding(start = if (!isQuote) 30.dp else 25.dp, end = 20.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(30.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.post_was_flagged_as_inappropriate_by),
|
||||
color = Color.Gray
|
||||
)
|
||||
FlowRow(modifier = Modifier.padding(top = 10.dp)) {
|
||||
if (isHiddenAuthor) {
|
||||
UserPicture(
|
||||
user = accountViewModel.userProfile(),
|
||||
size = Size35dp,
|
||||
nav = nav,
|
||||
accountViewModel = accountViewModel
|
||||
)
|
||||
}
|
||||
reports.forEach {
|
||||
NoteAuthorPicture(
|
||||
baseNote = it,
|
||||
size = Size35dp,
|
||||
nav = nav,
|
||||
accountViewModel = accountViewModel
|
||||
)
|
||||
FlowRow(modifier = Modifier.padding(top = 10.dp)) {
|
||||
reports.forEach {
|
||||
NoteAuthorPicture(
|
||||
baseNote = it,
|
||||
nav = nav,
|
||||
accountViewModel = accountViewModel,
|
||||
size = Size35dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
onClick = onClick,
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
),
|
||||
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
|
||||
) {
|
||||
Text(text = stringResource(R.string.show_anyway), color = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider(
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
Button(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
onClick = onClick,
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
),
|
||||
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
|
||||
) {
|
||||
Text(text = stringResource(R.string.show_anyway), color = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider(
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,6 @@ import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_LIVE
|
||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_PLANNED
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -135,13 +134,9 @@ fun CheckHiddenChannelCardCompose(
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
if (showHidden) {
|
||||
var state by remember {
|
||||
val state by remember {
|
||||
mutableStateOf(
|
||||
NoteComposeReportState(
|
||||
isAcceptable = true,
|
||||
canPreview = true,
|
||||
relevantReports = persistentSetOf()
|
||||
)
|
||||
AccountViewModel.NoteComposeReportState()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -185,19 +180,14 @@ fun LoadedChannelCardCompose(
|
||||
) {
|
||||
var state by remember {
|
||||
mutableStateOf(
|
||||
NoteComposeReportState(
|
||||
isAcceptable = true,
|
||||
canPreview = true,
|
||||
relevantReports = persistentSetOf()
|
||||
)
|
||||
AccountViewModel.NoteComposeReportState()
|
||||
)
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
|
||||
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
|
||||
val newState = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
|
||||
WatchForReports(note, accountViewModel) { newState ->
|
||||
if (state != newState) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
state = newState
|
||||
}
|
||||
@@ -219,7 +209,7 @@ fun LoadedChannelCardCompose(
|
||||
|
||||
@Composable
|
||||
fun RenderChannelCardReportState(
|
||||
state: NoteComposeReportState,
|
||||
state: AccountViewModel.NoteComposeReportState,
|
||||
note: Note,
|
||||
routeForLastRead: String? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -233,6 +223,7 @@ fun RenderChannelCardReportState(
|
||||
if (showHiddenNote) {
|
||||
HiddenNote(
|
||||
state.relevantReports,
|
||||
state.isHiddenAuthor,
|
||||
accountViewModel,
|
||||
modifier,
|
||||
false,
|
||||
|
||||
@@ -69,8 +69,6 @@ import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.events.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -152,17 +150,13 @@ fun LoadedChatMessageCompose(
|
||||
) {
|
||||
var state by remember {
|
||||
mutableStateOf(
|
||||
NoteComposeReportState(
|
||||
isAcceptable = true,
|
||||
canPreview = true,
|
||||
relevantReports = persistentSetOf()
|
||||
)
|
||||
AccountViewModel.NoteComposeReportState()
|
||||
)
|
||||
}
|
||||
|
||||
WatchForReports(baseNote, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
|
||||
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
|
||||
state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports.toImmutableSet())
|
||||
WatchForReports(baseNote, accountViewModel) { newState ->
|
||||
if (state != newState) {
|
||||
state = newState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +172,7 @@ fun LoadedChatMessageCompose(
|
||||
if (it) {
|
||||
HiddenNote(
|
||||
state.relevantReports,
|
||||
state.isHiddenAuthor,
|
||||
accountViewModel,
|
||||
Modifier,
|
||||
innerQuote,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -358,6 +361,12 @@ private fun ParseAuthorCommentAndAmount(
|
||||
onReady(content)
|
||||
}
|
||||
|
||||
fun click(content: MutableState<ZapAmountCommentNotification>, nav: (String) -> Unit) {
|
||||
content.value.user?.let {
|
||||
nav(routeFor(it))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderState(
|
||||
content: MutableState<ZapAmountCommentNotification>,
|
||||
@@ -366,11 +375,7 @@ private fun RenderState(
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.clickable {
|
||||
content.value.user?.let {
|
||||
nav(routeFor(it))
|
||||
}
|
||||
},
|
||||
modifier = Modifier.clickable { click(content, nav) },
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
DisplayAuthorCommentAndAmount(
|
||||
@@ -418,8 +423,19 @@ fun CrossfadeToDisplayPicture(authorComment: MutableState<ZapAmountCommentNotifi
|
||||
|
||||
@Composable
|
||||
fun CrossfadeToDisplayAmount(authorComment: MutableState<ZapAmountCommentNotification>) {
|
||||
Crossfade(authorComment.value, modifier = amountBoxModifier) {
|
||||
it.amount?.let {
|
||||
val visible by remember(authorComment) {
|
||||
derivedStateOf {
|
||||
authorComment.value.amount != null
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
modifier = amountBoxModifier,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
authorComment.value.amount?.let {
|
||||
Box(
|
||||
modifier = amountBoxModifier,
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
@@ -453,8 +469,18 @@ fun CrossfadeToDisplayComment(
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
Crossfade(authorComment.value) {
|
||||
it.comment?.let {
|
||||
val visible by remember(authorComment) {
|
||||
derivedStateOf {
|
||||
authorComment.value.comment != null
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
authorComment.value.comment?.let {
|
||||
TranslatableRichTextViewer(
|
||||
content = it,
|
||||
canPreview = true,
|
||||
|
||||
@@ -31,14 +31,17 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ArrowForwardIos
|
||||
import androidx.compose.material.icons.outlined.Bolt
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
@@ -83,6 +86,7 @@ import com.vitorpamplona.amethyst.model.Channel
|
||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.OnlineChecker
|
||||
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||
@@ -117,11 +121,13 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.LeaveCommunityButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NormalTimeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
||||
@@ -173,6 +179,7 @@ import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.events.EmojiPackEvent
|
||||
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
||||
import com.vitorpamplona.quartz.events.EmojiUrl
|
||||
import com.vitorpamplona.quartz.events.EventInterface
|
||||
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
||||
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||
@@ -196,7 +203,6 @@ import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.events.UserMetadata
|
||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -277,11 +283,7 @@ fun CheckHiddenNoteCompose(
|
||||
// Ignores reports as well
|
||||
val state by remember {
|
||||
mutableStateOf(
|
||||
NoteComposeReportState(
|
||||
isAcceptable = true,
|
||||
canPreview = true,
|
||||
relevantReports = persistentSetOf()
|
||||
)
|
||||
AccountViewModel.NoteComposeReportState()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -324,13 +326,6 @@ fun CheckHiddenNoteCompose(
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class NoteComposeReportState(
|
||||
val isAcceptable: Boolean,
|
||||
val canPreview: Boolean,
|
||||
val relevantReports: ImmutableSet<Note>
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LoadedNoteCompose(
|
||||
note: Note,
|
||||
@@ -347,19 +342,14 @@ fun LoadedNoteCompose(
|
||||
) {
|
||||
var state by remember {
|
||||
mutableStateOf(
|
||||
NoteComposeReportState(
|
||||
isAcceptable = true,
|
||||
canPreview = true,
|
||||
relevantReports = persistentSetOf()
|
||||
)
|
||||
AccountViewModel.NoteComposeReportState()
|
||||
)
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
|
||||
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
|
||||
val newState = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
|
||||
WatchForReports(note, accountViewModel) { newState ->
|
||||
if (state != newState) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
state = newState
|
||||
}
|
||||
@@ -386,7 +376,7 @@ fun LoadedNoteCompose(
|
||||
|
||||
@Composable
|
||||
fun RenderReportState(
|
||||
state: NoteComposeReportState,
|
||||
state: AccountViewModel.NoteComposeReportState,
|
||||
note: Note,
|
||||
routeForLastRead: String? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -405,6 +395,7 @@ fun RenderReportState(
|
||||
if (showHiddenNote) {
|
||||
HiddenNote(
|
||||
state.relevantReports,
|
||||
state.isHiddenAuthor,
|
||||
accountViewModel,
|
||||
modifier,
|
||||
isBoostedNote,
|
||||
@@ -436,7 +427,7 @@ fun RenderReportState(
|
||||
fun WatchForReports(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
onChange: (Boolean, Boolean, ImmutableSet<Note>) -> Unit
|
||||
onChange: (AccountViewModel.NoteComposeReportState) -> Unit
|
||||
) {
|
||||
val userFollowsState by accountViewModel.userFollows.observeAsState()
|
||||
val noteReportsState by note.live().reports.observeAsState()
|
||||
@@ -1071,6 +1062,62 @@ private fun NoteBody(
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
|
||||
val noteEvent = baseNote.event
|
||||
val zapSplits = remember(noteEvent) { noteEvent?.hasZapSplitSetup() ?: false }
|
||||
if (zapSplits && noteEvent != null) {
|
||||
Spacer(modifier = HalfDoubleVertSpacer)
|
||||
DisplayZapSplits(noteEvent, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun DisplayZapSplits(noteEvent: EventInterface, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val list = remember(noteEvent) { noteEvent.zapSplitSetup() }
|
||||
|
||||
if (list.isEmpty()) return
|
||||
|
||||
Row(verticalAlignment = CenterVertically) {
|
||||
Box(
|
||||
Modifier
|
||||
.height(20.dp)
|
||||
.width(25.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Bolt,
|
||||
contentDescription = stringResource(id = R.string.zaps),
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.align(Alignment.CenterStart),
|
||||
tint = BitcoinOrange
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.ArrowForwardIos,
|
||||
contentDescription = stringResource(id = R.string.zaps),
|
||||
modifier = Modifier
|
||||
.size(13.dp)
|
||||
.align(Alignment.CenterEnd),
|
||||
tint = BitcoinOrange
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
|
||||
FlowRow {
|
||||
list.forEach {
|
||||
if (it.isLnAddress) {
|
||||
ClickableText(
|
||||
text = AnnotatedString(it.lnAddressOrPubKeyHex),
|
||||
onClick = { },
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
||||
)
|
||||
} else {
|
||||
UserPicture(userHex = it.lnAddressOrPubKeyHex, size = 25.dp, accountViewModel = accountViewModel, nav = nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -1641,9 +1688,9 @@ fun DisplayRelaySet(
|
||||
) {
|
||||
val noteEvent = baseNote.event as? RelaySetEvent ?: return
|
||||
|
||||
val relays by remember {
|
||||
mutableStateOf<ImmutableList<String>>(
|
||||
noteEvent.relays().toImmutableList()
|
||||
val relays by remember(baseNote) {
|
||||
mutableStateOf(
|
||||
noteEvent.relays().map { RelayBriefInfo(it) }.toImmutableList()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1698,7 +1745,7 @@ fun DisplayRelaySet(
|
||||
toMembersShow.forEach { relay ->
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = CenterVertically) {
|
||||
Text(
|
||||
relay.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/"),
|
||||
text = relay.displayUrl,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
@@ -1708,7 +1755,7 @@ fun DisplayRelaySet(
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
RelayOptionsAction(relay, accountViewModel, nav)
|
||||
RelayOptionsAction(relay.url, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2414,40 +2461,41 @@ private fun ReplyRow(
|
||||
}
|
||||
}
|
||||
|
||||
val showChannelInfo by remember(note) {
|
||||
derivedStateOf {
|
||||
if (noteEvent is ChannelMessageEvent || noteEvent is LiveActivitiesChatMessageEvent) {
|
||||
note.channelHex()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showChannelInfo?.let {
|
||||
ChannelHeader(
|
||||
channelHex = it,
|
||||
showVideo = false,
|
||||
showBottomDiviser = false,
|
||||
sendToChannel = true,
|
||||
modifier = MaterialTheme.colors.replyModifier.padding(10.dp),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
|
||||
if (showReply) {
|
||||
val replyingDirectlyTo = remember { note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.kind } }
|
||||
if (replyingDirectlyTo != null && unPackReply) {
|
||||
ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav)
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
} else {
|
||||
// ReplyInformation(note.replyTo, noteEvent.mentions(), accountViewModel, nav)
|
||||
}
|
||||
} else {
|
||||
val showChannelReply by remember {
|
||||
derivedStateOf {
|
||||
(noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) ||
|
||||
(noteEvent is LiveActivitiesChatMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser()))
|
||||
} else if (showChannelInfo != null) {
|
||||
val replies = remember { note.replyTo?.toImmutableList() }
|
||||
val mentions = remember {
|
||||
(note.event as? BaseTextNoteEvent)?.mentions()?.toImmutableList()
|
||||
?: persistentListOf()
|
||||
}
|
||||
}
|
||||
|
||||
if (showChannelReply) {
|
||||
val channelHex = note.channelHex()
|
||||
channelHex?.let {
|
||||
ChannelHeader(
|
||||
channelHex = channelHex,
|
||||
showVideo = false,
|
||||
showBottomDiviser = false,
|
||||
sendToChannel = true,
|
||||
modifier = remember { Modifier.padding(vertical = 5.dp) },
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
|
||||
val replies = remember { note.replyTo?.toImmutableList() }
|
||||
val mentions = remember { (note.event as? BaseTextNoteEvent)?.mentions()?.toImmutableList() ?: persistentListOf() }
|
||||
|
||||
ReplyInformationChannel(replies, mentions, accountViewModel, nav)
|
||||
}
|
||||
ReplyInformationChannel(replies, mentions, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2680,14 +2728,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()
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.ui.window.Popup
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
@@ -35,6 +36,8 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
@@ -283,6 +286,12 @@ fun ZapVote(
|
||||
}
|
||||
|
||||
var wantsToZap by remember { mutableStateOf(false) }
|
||||
var wantsToPay by remember {
|
||||
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
||||
persistentListOf()
|
||||
)
|
||||
}
|
||||
|
||||
var zappingProgress by remember { mutableStateOf(0f) }
|
||||
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
@@ -362,6 +371,8 @@ fun ZapVote(
|
||||
zappingProgress = it
|
||||
}
|
||||
},
|
||||
onPayViaIntent = {
|
||||
},
|
||||
zapType = accountViewModel.account.defaultZapType
|
||||
)
|
||||
} else {
|
||||
@@ -393,10 +404,19 @@ fun ZapVote(
|
||||
scope.launch(Dispatchers.Main) {
|
||||
zappingProgress = it
|
||||
}
|
||||
},
|
||||
onPayViaIntent = {
|
||||
wantsToPay = it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (wantsToPay.isNotEmpty()) {
|
||||
PayViaIntentDialog(payingInvoices = wantsToPay, accountViewModel = accountViewModel) {
|
||||
wantsToPay = persistentListOf()
|
||||
}
|
||||
}
|
||||
|
||||
if (showErrorMessageDialog != null) {
|
||||
ErrorMessageDialog(
|
||||
title = stringResource(id = R.string.error_dialog_zap_error),
|
||||
@@ -463,7 +483,8 @@ fun FilteredZapAmountChoicePopup(
|
||||
onDismiss: () -> Unit,
|
||||
onChangeAmount: () -> Unit,
|
||||
onError: (text: String) -> Unit,
|
||||
onProgress: (percent: Float) -> Unit
|
||||
onProgress: (percent: Float) -> Unit,
|
||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -502,6 +523,7 @@ fun FilteredZapAmountChoicePopup(
|
||||
context,
|
||||
onError,
|
||||
onProgress,
|
||||
onPayViaIntent,
|
||||
defaultZapType
|
||||
)
|
||||
onDismiss()
|
||||
@@ -526,6 +548,7 @@ fun FilteredZapAmountChoicePopup(
|
||||
context,
|
||||
onError,
|
||||
onProgress,
|
||||
onPayViaIntent,
|
||||
defaultZapType
|
||||
)
|
||||
onDismiss()
|
||||
|
||||
@@ -78,6 +78,7 @@ import coil.request.CachePolicy
|
||||
import coil.request.ImageRequest
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.components.ImageUrlType
|
||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||
@@ -107,6 +108,8 @@ import com.vitorpamplona.amethyst.ui.theme.TinyBorders
|
||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderTextColorFilter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -907,6 +910,11 @@ fun ZapReaction(
|
||||
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
|
||||
var wantsToSetCustomZap by remember { mutableStateOf(false) }
|
||||
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
||||
var wantsToPay by remember(baseNote) {
|
||||
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
||||
persistentListOf()
|
||||
)
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -938,6 +946,9 @@ fun ZapReaction(
|
||||
zappingProgress = 0f
|
||||
showErrorMessageDialog = it
|
||||
}
|
||||
},
|
||||
onPayViaIntent = {
|
||||
wantsToPay = it
|
||||
}
|
||||
)
|
||||
},
|
||||
@@ -971,6 +982,9 @@ fun ZapReaction(
|
||||
scope.launch(Dispatchers.Main) {
|
||||
zappingProgress = it
|
||||
}
|
||||
},
|
||||
onPayViaIntent = {
|
||||
wantsToPay = it
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -981,7 +995,10 @@ fun ZapReaction(
|
||||
textContent = showErrorMessageDialog ?: "",
|
||||
onClickStartMessage = {
|
||||
baseNote.author?.let {
|
||||
nav(routeToMessage(it, showErrorMessageDialog, accountViewModel))
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val route = routeToMessage(it, showErrorMessageDialog, accountViewModel)
|
||||
nav(route)
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismiss = { showErrorMessageDialog = null }
|
||||
@@ -989,11 +1006,38 @@ fun ZapReaction(
|
||||
}
|
||||
|
||||
if (wantsToChangeZapAmount) {
|
||||
UpdateZapAmountDialog({ wantsToChangeZapAmount = false }, accountViewModel = accountViewModel)
|
||||
UpdateZapAmountDialog(
|
||||
onClose = { wantsToChangeZapAmount = false },
|
||||
accountViewModel = accountViewModel
|
||||
)
|
||||
}
|
||||
|
||||
if (wantsToPay.isNotEmpty()) {
|
||||
PayViaIntentDialog(payingInvoices = wantsToPay, accountViewModel = accountViewModel) {
|
||||
wantsToPay = persistentListOf()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
onPayViaIntent = {
|
||||
wantsToPay = it
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
baseNote = baseNote
|
||||
)
|
||||
}
|
||||
|
||||
if (zappingProgress > 0.00001 && zappingProgress < 0.99999) {
|
||||
@@ -1027,7 +1071,8 @@ private fun zapClick(
|
||||
context: Context,
|
||||
onZappingProgress: (Float) -> Unit,
|
||||
onMultipleChoices: () -> Unit,
|
||||
onError: (String) -> Unit
|
||||
onError: (String) -> Unit,
|
||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit
|
||||
) {
|
||||
if (accountViewModel.account.zapAmountChoices.isEmpty()) {
|
||||
scope.launch {
|
||||
@@ -1062,7 +1107,8 @@ private fun zapClick(
|
||||
onZappingProgress(it)
|
||||
}
|
||||
},
|
||||
zapType = accountViewModel.account.defaultZapType
|
||||
zapType = accountViewModel.account.defaultZapType,
|
||||
onPayViaIntent = onPayViaIntent
|
||||
)
|
||||
} else if (accountViewModel.account.zapAmountChoices.size > 1) {
|
||||
onMultipleChoices()
|
||||
@@ -1373,7 +1419,8 @@ fun ZapAmountChoicePopup(
|
||||
onDismiss: () -> Unit,
|
||||
onChangeAmount: () -> Unit,
|
||||
onError: (text: String) -> Unit,
|
||||
onProgress: (percent: Float) -> Unit
|
||||
onProgress: (percent: Float) -> Unit,
|
||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -1399,6 +1446,7 @@ fun ZapAmountChoicePopup(
|
||||
context,
|
||||
onError,
|
||||
onProgress,
|
||||
onPayViaIntent,
|
||||
account.defaultZapType
|
||||
)
|
||||
onDismiss()
|
||||
@@ -1423,6 +1471,7 @@ fun ZapAmountChoicePopup(
|
||||
context,
|
||||
onError,
|
||||
onProgress,
|
||||
onPayViaIntent,
|
||||
account.defaultZapType
|
||||
)
|
||||
onDismiss()
|
||||
|
||||
@@ -11,17 +11,16 @@ import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
|
||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer
|
||||
@@ -29,80 +28,40 @@ import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconButtonModifie
|
||||
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
public fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var showShowMore by remember { mutableStateOf(false) }
|
||||
|
||||
var lazyRelayList by remember {
|
||||
val baseNumber = baseNote.relays.map {
|
||||
it.removePrefix("wss://").removePrefix("ws://")
|
||||
}.toImmutableList()
|
||||
|
||||
mutableStateOf(baseNumber)
|
||||
}
|
||||
var shortRelayList by remember {
|
||||
mutableStateOf(lazyRelayList.take(3).toImmutableList())
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
WatchRelayLists(baseNote) { relayList ->
|
||||
if (!equalImmutableLists(relayList, lazyRelayList)) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
lazyRelayList = relayList
|
||||
shortRelayList = relayList.take(3).toImmutableList()
|
||||
}
|
||||
}
|
||||
|
||||
val nextShowMore = relayList.size > 3
|
||||
if (nextShowMore != showShowMore) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
// only triggers recomposition when actually different
|
||||
showShowMore = nextShowMore
|
||||
}
|
||||
val relayList by baseNote.live().relayInfo.observeAsState(persistentListOf())
|
||||
val shortRelayList by remember {
|
||||
derivedStateOf {
|
||||
relayList.take(3).toImmutableList()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(DoubleVertSpacer)
|
||||
|
||||
if (expanded) {
|
||||
VerticalRelayPanelWithFlow(lazyRelayList, accountViewModel, nav)
|
||||
VerticalRelayPanelWithFlow(relayList, accountViewModel, nav)
|
||||
} else {
|
||||
VerticalRelayPanelWithFlow(shortRelayList, accountViewModel, nav)
|
||||
}
|
||||
|
||||
if (showShowMore && !expanded) {
|
||||
if (relayList.size > 3 && !expanded) {
|
||||
ShowMoreRelaysButton {
|
||||
expanded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WatchRelayLists(baseNote: Note, onListChanges: (ImmutableList<String>) -> Unit) {
|
||||
val noteRelaysState by baseNote.live().relays.observeAsState()
|
||||
|
||||
LaunchedEffect(key1 = noteRelaysState) {
|
||||
launch(Dispatchers.IO) {
|
||||
val relayList = noteRelaysState?.note?.relays?.map {
|
||||
it.removePrefix("wss://").removePrefix("ws://")
|
||||
} ?: emptyList()
|
||||
|
||||
onListChanges(relayList.toImmutableList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
@Stable
|
||||
private fun VerticalRelayPanelWithFlow(
|
||||
relays: ImmutableList<String>,
|
||||
relays: ImmutableList<RelayBriefInfo>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -17,7 +18,6 @@ import androidx.compose.material.icons.filled.ChevronRight
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -29,13 +29,15 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.map
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
|
||||
import com.vitorpamplona.amethyst.ui.actions.loadRelayInfo
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
||||
@@ -44,6 +46,8 @@ import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size15dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
@@ -59,15 +63,13 @@ public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewMo
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun RenderRelayList(baseNote: Note, expanded: MutableState<Boolean>, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val noteRelays by baseNote.live().relays.map {
|
||||
it.note.relays
|
||||
}.observeAsState(baseNote.relays)
|
||||
val noteRelays by baseNote.live().relayInfo.observeAsState()
|
||||
|
||||
FlowRow(StdStartPadding) {
|
||||
val relaysToDisplay = remember(noteRelays, expanded.value) {
|
||||
if (expanded.value) noteRelays else noteRelays.take(3)
|
||||
if (expanded.value) noteRelays else noteRelays?.take(3)?.toImmutableList()
|
||||
}
|
||||
relaysToDisplay.forEach {
|
||||
relaysToDisplay?.forEach {
|
||||
RenderRelay(it, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
@@ -104,14 +106,7 @@ fun ChatRelayExpandButton(onClick: () -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val iconUrl by remember(dirtyUrl) {
|
||||
derivedStateOf {
|
||||
val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/")
|
||||
"https://$cleanUrl/favicon.ico"
|
||||
}
|
||||
}
|
||||
|
||||
fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
|
||||
|
||||
if (relayInfo != null) {
|
||||
@@ -120,8 +115,9 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri
|
||||
relayInfo = null
|
||||
},
|
||||
relayInfo = relayInfo!!,
|
||||
accountViewModel,
|
||||
nav
|
||||
relayBriefInfo = relay,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
|
||||
@@ -130,7 +126,7 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val ripple = rememberRipple(bounded = false, radius = Size15dp)
|
||||
|
||||
val clickableModifier = remember(dirtyUrl) {
|
||||
val clickableModifier = remember(relay) {
|
||||
Modifier
|
||||
.padding(1.dp)
|
||||
.size(Size15dp)
|
||||
@@ -139,9 +135,30 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri
|
||||
interactionSource = interactionSource,
|
||||
indication = ripple,
|
||||
onClick = {
|
||||
loadRelayInfo(dirtyUrl, context, scope) {
|
||||
relayInfo = it
|
||||
}
|
||||
accountViewModel.retrieveRelayDocument(
|
||||
relay.url,
|
||||
onInfo = {
|
||||
relayInfo = it
|
||||
},
|
||||
onError = { url, errorCode, exceptionMessage ->
|
||||
val msg = when (errorCode) {
|
||||
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
msg,
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -149,17 +166,17 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri
|
||||
Box(
|
||||
modifier = clickableModifier
|
||||
) {
|
||||
RenderRelayIcon(iconUrl)
|
||||
RenderRelayIcon(relay.favIcon)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderRelayIcon(iconUrl: String) {
|
||||
fun RenderRelayIcon(iconUrl: String, size: Dp = Size13dp) {
|
||||
val backgroundColor = MaterialTheme.colors.background
|
||||
|
||||
val iconModifier = remember {
|
||||
Modifier
|
||||
.size(Size13dp)
|
||||
.size(size)
|
||||
.clip(shape = CircleShape)
|
||||
.background(backgroundColor)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -73,9 +73,7 @@ fun NoteAuthorPicture(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: ((User) -> Unit)? = null
|
||||
) {
|
||||
val author by baseNote.live().metadata.map {
|
||||
it.note.author
|
||||
}.distinctUntilChanged().observeAsState(baseNote.author)
|
||||
val author by baseNote.live().authorChanges.observeAsState(baseNote.author)
|
||||
|
||||
Crossfade(targetState = author) {
|
||||
if (it == null) {
|
||||
@@ -104,6 +102,32 @@ fun DisplayBlankAuthor(size: Dp, modifier: Modifier = Modifier) {
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserPicture(
|
||||
userHex: String,
|
||||
size: Dp,
|
||||
pictureModifier: Modifier = remember { Modifier },
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
LoadUser(baseUserHex = userHex) {
|
||||
if (it != null) {
|
||||
UserPicture(
|
||||
user = it,
|
||||
size = size,
|
||||
pictureModifier = pictureModifier,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
} else {
|
||||
DisplayBlankAuthor(
|
||||
size,
|
||||
pictureModifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserPicture(
|
||||
user: User,
|
||||
@@ -112,23 +136,19 @@ fun UserPicture(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
val route by remember {
|
||||
val route by remember(user) {
|
||||
derivedStateOf {
|
||||
"User/${user.pubkeyHex}"
|
||||
}
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
ClickableUserPicture(
|
||||
baseUser = user,
|
||||
size = size,
|
||||
accountViewModel = accountViewModel,
|
||||
modifier = pictureModifier,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
nav(route)
|
||||
}
|
||||
nav(route)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -274,9 +294,7 @@ fun InnerBaseUserPicture(
|
||||
accountViewModel: AccountViewModel,
|
||||
modifier: Modifier
|
||||
) {
|
||||
val userProfile by baseUser.live().metadata.map {
|
||||
it.user.profilePicture()
|
||||
}.distinctUntilChanged().observeAsState(baseUser.profilePicture())
|
||||
val userProfile by baseUser.live().profilePictureChanges.observeAsState(baseUser.profilePicture())
|
||||
|
||||
PictureAndFollowingMark(
|
||||
userHex = baseUser.pubkeyHex,
|
||||
@@ -446,6 +464,10 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
|
||||
Text(stringResource(R.string.quick_action_share))
|
||||
}
|
||||
Divider()
|
||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.broadcast(note); onDismiss() } }) {
|
||||
Text(stringResource(R.string.broadcast))
|
||||
}
|
||||
Divider()
|
||||
if (state.isPrivateBookmarkNote) {
|
||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.removePrivateBookmark(note); onDismiss() } }) {
|
||||
Text(stringResource(R.string.remove_from_private_bookmarks))
|
||||
@@ -465,20 +487,6 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.broadcast(note); onDismiss() } }) {
|
||||
Text(stringResource(R.string.broadcast))
|
||||
}
|
||||
Divider()
|
||||
if (state.isLoggedUser) {
|
||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.delete(note); onDismiss() } }) {
|
||||
Text(stringResource(R.string.request_deletion))
|
||||
}
|
||||
} else {
|
||||
DropdownMenuItem(onClick = { reportDialogShowing = true }) {
|
||||
Text(stringResource(R.string.block_report))
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
if (state.showSensitiveContent == null || state.showSensitiveContent == true) {
|
||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.hideSensitiveContent(); onDismiss() } }) {
|
||||
Text(stringResource(R.string.content_warning_hide_all_sensitive_content))
|
||||
@@ -494,6 +502,16 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
|
||||
Text(stringResource(R.string.content_warning_see_warnings))
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
if (state.isLoggedUser) {
|
||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.delete(note); onDismiss() } }) {
|
||||
Text(stringResource(R.string.request_deletion))
|
||||
}
|
||||
} else {
|
||||
DropdownMenuItem(onClick = { reportDialogShowing = true }) {
|
||||
Text(stringResource(R.string.block_report))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reportDialogShowing) {
|
||||
@@ -508,9 +526,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
|
||||
fun WatchBookmarksFollowsAndAccount(note: Note, accountViewModel: AccountViewModel, onNew: (DropDownParams) -> Unit) {
|
||||
val followState by accountViewModel.userProfile().live().follows.observeAsState()
|
||||
val bookmarkState by accountViewModel.userProfile().live().bookmarks.observeAsState()
|
||||
val showSensitiveContent by accountViewModel.accountLiveData.map {
|
||||
it.account.showSensitiveContent
|
||||
}.distinctUntilChanged().observeAsState(accountViewModel.account.showSensitiveContent)
|
||||
val showSensitiveContent by accountViewModel.showSensitiveContentChanges.observeAsState(accountViewModel.account.showSensitiveContent)
|
||||
|
||||
LaunchedEffect(key1 = followState, key2 = bookmarkState, key3 = showSensitiveContent) {
|
||||
launch(Dispatchers.IO) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import android.widget.Toast
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
@@ -13,26 +14,35 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ZapOptionstViewModel : ViewModel() {
|
||||
private var account: Account? = null
|
||||
@@ -61,17 +71,21 @@ class ZapOptionstViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ZapCustomDialog(onClose: () -> Unit, accountViewModel: AccountViewModel, baseNote: Note) {
|
||||
fun ZapCustomDialog(
|
||||
onClose: () -> Unit,
|
||||
onError: (text: String) -> Unit,
|
||||
onProgress: (percent: Float) -> Unit,
|
||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> 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 +125,9 @@ 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,
|
||||
onPayViaIntent = onPayViaIntent,
|
||||
zapType = selectedZapType
|
||||
)
|
||||
onClose()
|
||||
@@ -275,3 +281,115 @@ fun ErrorMessageDialog(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PayViaIntentDialog(
|
||||
payingInvoices: ImmutableList<ZapPaymentHandler.Payable>,
|
||||
accountViewModel: AccountViewModel,
|
||||
onClose: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onClose,
|
||||
properties = DialogProperties(
|
||||
dismissOnClickOutside = false,
|
||||
usePlatformDefaultWidth = false
|
||||
)
|
||||
) {
|
||||
Surface() {
|
||||
Column(modifier = Modifier.padding(10.dp)) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
CloseButton(onPress = onClose)
|
||||
}
|
||||
|
||||
Spacer(modifier = DoubleVertSpacer)
|
||||
|
||||
payingInvoices.forEachIndexed { index, it ->
|
||||
val paid = remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = Size10dp)) {
|
||||
if (it.user != null) {
|
||||
BaseUserPicture(it.user, Size55dp, accountViewModel = accountViewModel)
|
||||
} else {
|
||||
DisplayBlankAuthor(size = Size55dp)
|
||||
}
|
||||
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
if (it.user != null) {
|
||||
UsernameDisplay(it.user, showPlayButton = false)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(id = R.string.wallet_number, index + 1),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
}
|
||||
Row() {
|
||||
Text(
|
||||
text = showAmount((it.amountMilliSats / 1000.0f).toBigDecimal()),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
Text(
|
||||
text = stringResource(id = R.string.sats),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
|
||||
PayButton(isActive = !paid.value) {
|
||||
paid.value = true
|
||||
|
||||
val uri = "lightning:" + it.invoice
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
|
||||
ContextCompat.startActivity(context, intent, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PayButton(isActive: Boolean, modifier: Modifier = Modifier, onPost: () -> Unit = {}) {
|
||||
Button(
|
||||
modifier = modifier,
|
||||
onClick = {
|
||||
onPost()
|
||||
},
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
|
||||
),
|
||||
contentPadding = PaddingValues(0.dp)
|
||||
) {
|
||||
if (isActive) {
|
||||
Text(text = stringResource(R.string.pay), color = Color.White)
|
||||
} else {
|
||||
Text(text = stringResource(R.string.paid), color = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +95,10 @@ class NostrDiscoverChatFeedViewModel(val account: Account) : FeedViewModel(Disco
|
||||
}
|
||||
}
|
||||
|
||||
class NostrThreadFeedViewModel(val noteId: String) : FeedViewModel(ThreadFeedFilter(noteId)) {
|
||||
class Factory(val noteId: String) : ViewModelProvider.Factory {
|
||||
class NostrThreadFeedViewModel(account: Account, noteId: String) : FeedViewModel(ThreadFeedFilter(account, noteId)) {
|
||||
class Factory(val account: Account, val noteId: String) : ViewModelProvider.Factory {
|
||||
override fun <NostrThreadFeedViewModel : ViewModel> create(modelClass: Class<NostrThreadFeedViewModel>): NostrThreadFeedViewModel {
|
||||
return NostrThreadFeedViewModel(noteId) as NostrThreadFeedViewModel
|
||||
return NostrThreadFeedViewModel(account, noteId) as NostrThreadFeedViewModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.distinctUntilChanged
|
||||
import androidx.lifecycle.map
|
||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||
|
||||
@Stable
|
||||
class RelayPoolViewModel : ViewModel() {
|
||||
val connectionStatus = RelayPool.live.map {
|
||||
val connectedRelays = it.relays.connectedRelays()
|
||||
val availableRelays = it.relays.availableRelays()
|
||||
"$connectedRelays/$availableRelays"
|
||||
}.distinctUntilChanged()
|
||||
|
||||
val isConnected = RelayPool.live.map {
|
||||
it.relays.connectedRelays() > 0
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
@@ -262,6 +262,7 @@ fun NoteMaster(
|
||||
|
||||
HiddenNote(
|
||||
reports,
|
||||
note.author?.let { account.isHidden(it) } ?: false,
|
||||
accountViewModel,
|
||||
Modifier,
|
||||
false,
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.distinctUntilChanged
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AccountState
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.model.UserState
|
||||
import com.vitorpamplona.amethyst.service.Nip05Verifier
|
||||
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||
import com.vitorpamplona.amethyst.service.OnlineChecker
|
||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
|
||||
@@ -33,16 +33,15 @@ import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.events.PayInvoiceErrorResponse
|
||||
import com.vitorpamplona.quartz.events.ReportEvent
|
||||
import com.vitorpamplona.quartz.events.SealedGossipEvent
|
||||
import com.vitorpamplona.quartz.events.UserMetadata
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
import java.util.Locale
|
||||
@@ -56,22 +55,26 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
||||
val userFollows: LiveData<UserState> = account.userProfile().live().follows.map { it }
|
||||
val userRelays: LiveData<UserState> = account.userProfile().live().relays.map { it }
|
||||
|
||||
val discoveryListLiveData = accountLiveData.map {
|
||||
val discoveryListLiveData = account.live.map {
|
||||
it.account.defaultDiscoveryFollowList
|
||||
}.distinctUntilChanged()
|
||||
|
||||
val homeListLiveData = accountLiveData.map {
|
||||
val homeListLiveData = account.live.map {
|
||||
it.account.defaultHomeFollowList
|
||||
}.distinctUntilChanged()
|
||||
|
||||
val notificationListLiveData = accountLiveData.map {
|
||||
val notificationListLiveData = account.live.map {
|
||||
it.account.defaultNotificationFollowList
|
||||
}.distinctUntilChanged()
|
||||
|
||||
val storiesListLiveData = accountLiveData.map {
|
||||
val storiesListLiveData = account.live.map {
|
||||
it.account.defaultStoriesFollowList
|
||||
}.distinctUntilChanged()
|
||||
|
||||
val showSensitiveContentChanges = account.live.map {
|
||||
it.account.showSensitiveContent
|
||||
}.distinctUntilChanged()
|
||||
|
||||
fun updateAutomaticallyStartPlayback(
|
||||
automaticallyStartPlayback: ConnectivityType
|
||||
) {
|
||||
@@ -205,78 +208,22 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
||||
return null
|
||||
}
|
||||
|
||||
fun zap(note: Note, amount: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) {
|
||||
fun zap(
|
||||
note: Note,
|
||||
amount: Long,
|
||||
pollOption: Int?,
|
||||
message: String,
|
||||
context: Context,
|
||||
onError: (String) -> Unit,
|
||||
onProgress: (percent: Float) -> Unit,
|
||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||
zapType: LnZapEvent.ZapType
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
innerZap(note, amount, pollOption, message, context, onError, onProgress, zapType)
|
||||
ZapPaymentHandler(account).zap(note, amount, pollOption, message, context, onError, onProgress, onPayViaIntent, zapType)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun innerZap(note: Note, amount: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) {
|
||||
val lud16 = note.event?.zapAddress() ?: note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
|
||||
|
||||
if (lud16.isNullOrBlank()) {
|
||||
onError(context.getString(R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats))
|
||||
return
|
||||
}
|
||||
|
||||
var zapRequestJson = ""
|
||||
|
||||
if (zapType != LnZapEvent.ZapType.NONZAP) {
|
||||
val zapRequest = account.createZapRequestFor(note, pollOption, message, zapType)
|
||||
if (zapRequest != null) {
|
||||
zapRequestJson = zapRequest.toJson()
|
||||
}
|
||||
}
|
||||
|
||||
onProgress(0.10f)
|
||||
|
||||
LightningAddressResolver().lnAddressInvoice(
|
||||
lud16,
|
||||
amount,
|
||||
message,
|
||||
zapRequestJson,
|
||||
onSuccess = {
|
||||
onProgress(0.7f)
|
||||
if (account.hasWalletConnectSetup()) {
|
||||
account.sendZapPaymentRequestFor(
|
||||
bolt11 = it,
|
||||
note,
|
||||
onResponse = { response ->
|
||||
if (response is PayInvoiceErrorResponse) {
|
||||
onProgress(0.0f)
|
||||
onError(
|
||||
response.error?.message
|
||||
?: response.error?.code?.toString()
|
||||
?: "Error parsing error message"
|
||||
)
|
||||
} else {
|
||||
onProgress(1f)
|
||||
}
|
||||
}
|
||||
)
|
||||
onProgress(0.8f)
|
||||
|
||||
// Awaits for the event to come back to LocalCache.
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
delay(5000)
|
||||
onProgress(0f)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$it"))
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
ContextCompat.startActivity(context, intent, null)
|
||||
} catch (e: Exception) {
|
||||
onError(context.getString(R.string.lightning_wallets_not_found))
|
||||
}
|
||||
onProgress(0f)
|
||||
}
|
||||
},
|
||||
onError = onError,
|
||||
onProgress = onProgress
|
||||
)
|
||||
}
|
||||
|
||||
fun report(note: Note, type: ReportEvent.ReportType, content: String = "") {
|
||||
account.report(note, type, content)
|
||||
}
|
||||
@@ -410,14 +357,24 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
||||
return account.defaultZapType
|
||||
}
|
||||
|
||||
fun isNoteAcceptable(note: Note, onReady: (Boolean, Boolean, ImmutableSet<Note>) -> Unit) {
|
||||
@Immutable
|
||||
data class NoteComposeReportState(
|
||||
val isAcceptable: Boolean = true,
|
||||
val canPreview: Boolean = true,
|
||||
val isHiddenAuthor: Boolean = false,
|
||||
val relevantReports: ImmutableSet<Note> = persistentSetOf()
|
||||
)
|
||||
|
||||
fun isNoteAcceptable(note: Note, onReady: (NoteComposeReportState) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val isFromLoggedIn = note.author?.pubkeyHex == userProfile().pubkeyHex
|
||||
val isFromLoggedInFollow = note.author?.let { userProfile().isFollowingCached(it) } ?: true
|
||||
|
||||
if (isFromLoggedIn || isFromLoggedInFollow) {
|
||||
// No need to process if from trusted people
|
||||
onReady(true, true, persistentSetOf())
|
||||
onReady(NoteComposeReportState(true, true, false, persistentSetOf()))
|
||||
} else if (note.author?.let { account.isHidden(it) } == true) {
|
||||
onReady(NoteComposeReportState(false, false, true, persistentSetOf()))
|
||||
} else {
|
||||
val newCanPreview = !note.hasAnyReports()
|
||||
|
||||
@@ -425,11 +382,18 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
||||
|
||||
if (newCanPreview && newIsAcceptable) {
|
||||
// No need to process reports if nothing is wrong
|
||||
onReady(true, true, persistentSetOf())
|
||||
onReady(NoteComposeReportState(true, true, false, persistentSetOf()))
|
||||
} else {
|
||||
val newRelevantReports = account.getRelevantReports(note)
|
||||
|
||||
onReady(newIsAcceptable, newCanPreview, newRelevantReports.toImmutableSet())
|
||||
onReady(
|
||||
NoteComposeReportState(
|
||||
newIsAcceptable,
|
||||
newCanPreview,
|
||||
false,
|
||||
newRelevantReports.toImmutableSet()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -529,6 +493,16 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun retrieveRelayDocument(
|
||||
dirtyUrl: String,
|
||||
onInfo: (RelayInformation) -> Unit,
|
||||
onError: (String, Nip11Retriever.ErrorCode, String?) -> Unit
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
Nip11CachedRetriever.loadRelayInfo(dirtyUrl, onInfo, onError)
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
||||
return AccountViewModel(account) as AccountViewModel
|
||||
|
||||
@@ -23,7 +23,7 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, nav: (Stri
|
||||
|
||||
val feedViewModel: NostrThreadFeedViewModel = viewModel(
|
||||
key = noteId + "NostrThreadFeedViewModel",
|
||||
factory = NostrThreadFeedViewModel.Factory(noteId)
|
||||
factory = NostrThreadFeedViewModel.Factory(accountViewModel.account, noteId)
|
||||
)
|
||||
|
||||
NostrThreadDataSource.loadThread(noteId)
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -52,7 +53,6 @@ import com.vitorpamplona.amethyst.ui.note.FileStorageHeaderDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.HiddenNote
|
||||
import com.vitorpamplona.amethyst.ui.note.LikeReaction
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteComposeReportState
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteDropDownMenu
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderRelay
|
||||
@@ -76,7 +76,6 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
||||
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -128,7 +127,6 @@ fun WatchAccountForVideoScreen(videoFeedView: NostrVideoFeedViewModel, accountVi
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun SaveableFeedState(
|
||||
videoFeedView: NostrVideoFeedViewModel,
|
||||
@@ -250,19 +248,14 @@ fun LoadedVideoCompose(
|
||||
) {
|
||||
var state by remember {
|
||||
mutableStateOf(
|
||||
NoteComposeReportState(
|
||||
isAcceptable = true,
|
||||
canPreview = true,
|
||||
relevantReports = persistentSetOf()
|
||||
)
|
||||
AccountViewModel.NoteComposeReportState()
|
||||
)
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
|
||||
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
|
||||
val newState = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
|
||||
WatchForReports(note, accountViewModel) { newState ->
|
||||
if (state != newState) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
state = newState
|
||||
}
|
||||
@@ -281,7 +274,7 @@ fun LoadedVideoCompose(
|
||||
|
||||
@Composable
|
||||
fun RenderReportState(
|
||||
state: NoteComposeReportState,
|
||||
state: AccountViewModel.NoteComposeReportState,
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
@@ -290,11 +283,12 @@ fun RenderReportState(
|
||||
|
||||
Crossfade(targetState = !state.isAcceptable && !showReportedNote) { showHiddenNote ->
|
||||
if (showHiddenNote) {
|
||||
Column(remember { Modifier.fillMaxSize(1f) }, verticalArrangement = Arrangement.Center) {
|
||||
Column(remember { Modifier.fillMaxSize() }, verticalArrangement = Arrangement.Center) {
|
||||
HiddenNote(
|
||||
state.relevantReports,
|
||||
state.isHiddenAuthor,
|
||||
accountViewModel,
|
||||
Modifier,
|
||||
Modifier.fillMaxWidth(),
|
||||
false,
|
||||
nav,
|
||||
onClick = { showReportedNote = true }
|
||||
@@ -421,14 +415,11 @@ private fun VideoUserOptionAction(
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val noteRelaysState by baseNote.live().relays.observeAsState()
|
||||
val noteRelays = remember(noteRelaysState) {
|
||||
noteRelaysState?.note?.relays ?: emptySet()
|
||||
}
|
||||
val noteRelays by baseNote.live().relayInfo.observeAsState()
|
||||
|
||||
FlowRow() {
|
||||
noteRelays.forEach { dirtyUrl ->
|
||||
RenderRelay(dirtyUrl, accountViewModel, nav)
|
||||
noteRelays?.forEach { relayInfo ->
|
||||
RenderRelay(relayInfo, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,3 +24,9 @@ val DarkerGreen = Color.Green.copy(alpha = 0.32f)
|
||||
val WarningColor = Color(0xFFC62828)
|
||||
|
||||
val RelayIconFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0.5f) })
|
||||
|
||||
val LightWarningColor = Color(0xFFffcc00)
|
||||
val DarkWarningColor = Color(0xFFF8DE22)
|
||||
|
||||
val LightAllGoodColor = Color(0xFF339900)
|
||||
val DarkAllGoodColor = Color(0xFF99cc33)
|
||||
|
||||
@@ -286,6 +286,12 @@ val Colors.overPictureBackground: Color
|
||||
val Colors.bitcoinColor: Color
|
||||
get() = if (isLight) BitcoinLight else BitcoinDark
|
||||
|
||||
val Colors.warningColor: Color
|
||||
get() = if (isLight) LightWarningColor else DarkWarningColor
|
||||
|
||||
val Colors.allGoodColor: Color
|
||||
get() = if (isLight) LightAllGoodColor else DarkAllGoodColor
|
||||
|
||||
val Colors.markdownStyle: RichTextStyle
|
||||
get() = if (isLight) MarkDownStyleOnLight else MarkDownStyleOnDark
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="point_to_the_qr_code">أشر إلى رمز QR</string>
|
||||
<string name="point_to_the_qr_code">أشر إلى رمز (QR Code)</string>
|
||||
<string name="show_qr">أظهر QR</string>
|
||||
<string name="profile_image">صورة الملف الشخصي</string>
|
||||
<string name="scan_qr">مسح QR</string>
|
||||
<string name="scan_qr">مسح رمز ال QR</string>
|
||||
<string name="show_anyway">عرض على أي حال</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">تم وضع علامة على المنشور على أنه غير لائق بواسطة</string>
|
||||
<string name="post_not_found">المنشور غير موجود</string>
|
||||
@@ -12,15 +12,18 @@
|
||||
<string name="could_not_decrypt_the_message">لا يمكن فك تشفير الرسالة</string>
|
||||
<string name="group_picture">صورة المجموعة</string>
|
||||
<string name="explicit_content">محتوى فاضح</string>
|
||||
<string name="spam">رسائل عشوائية</string>
|
||||
<string name="spam">محتوى عشوائي</string>
|
||||
<string name="impersonation">التمثيل</string>
|
||||
<string name="illegal_behavior">تصرف غير قانوني</string>
|
||||
<string name="unknown">غير معروف</string>
|
||||
<string name="relay_icon">أيقونة الخادم</string>
|
||||
<string name="unknown_author">كاتب غير معروف</string>
|
||||
<string name="copy_text">نسخ النص</string>
|
||||
<string name="copy_user_pubkey">نسخ المفتاح العام</string>
|
||||
<string name="copy_note_id">نسخ معرف </string>
|
||||
<string name="broadcast">بث</string>
|
||||
<string name="request_deletion">حذف الطلب</string>
|
||||
<string name="block_report">حظر / تبليغ</string>
|
||||
<string name="block_hide_user"><![CDATA[حظر و إخفاء المستخدم]]></string>
|
||||
<string name="report_spam_scam">الابلاغ عن البريد العشوائي / الاحتيال</string>
|
||||
<string name="report_impersonation">الإبلاغ عن التمثيل</string>
|
||||
@@ -31,6 +34,8 @@
|
||||
<string name="login_with_a_private_key_to_like_posts">تسجيل الدخول باستخدام مفتاح خاص لإبداء الإعجاب بالمنشورات</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">لا يوجد إعداد لمبلغ Zap. اضغط لفترة طويلة للتغيير</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">قم بتسجيل الدخول باستخدام مفتاح خاص لتتمكن من إرسال Zaps</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">قم بتسجيل الدخول باستخدام مفتاح خاص لتتمكن من متابعة المستخدمين</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">قم بتسجيل الدخول بمفتاحك الخاص للتتمكن من إلغاء متابعة المستخدمين </string>
|
||||
<string name="view_count">مشاهدة العد</string>
|
||||
<string name="boost">تعزيز</string>
|
||||
<string name="boosted">معزز</string>
|
||||
@@ -41,6 +46,8 @@
|
||||
<string name="and">" و "</string>
|
||||
<string name="in_channel">"في القناة "</string>
|
||||
<string name="profile_banner">لافتة الملف الشخصي</string>
|
||||
<string name="payment_successful">تم الدفع بنجاح</string>
|
||||
<string name="error_parsing_error_message">خطأ أثناء تحليل رسالة الخطأ</string>
|
||||
<string name="following">"المتابعون"</string>
|
||||
<string name="followers">"متابِعاً"</string>
|
||||
<string name="profile">الملف الشخصي</string>
|
||||
@@ -52,7 +59,7 @@
|
||||
<string name="lightning_tips">Lightning تبرع</string>
|
||||
<string name="note_to_receiver">ملاحظة للمستقبل</string>
|
||||
<string name="thank_you_so_much">شكرا جزيلا!</string>
|
||||
<string name="amount_in_sats">المبلغ في Sats</string>
|
||||
<string name="amount_in_sats">المبلغ ب الSats</string>
|
||||
<string name="send_sats">إرسال Sats</string>
|
||||
<string name="error_parsing_preview_for">"خطأ في تحليل المعاينة لـ %1$s : %2$s"</string>
|
||||
<string name="preview_card_image_for">"معاينة صورة البطاقة لـ %1$s"</string>
|
||||
@@ -68,8 +75,9 @@
|
||||
<string name="create">إنشاء</string>
|
||||
<string name="cancel">إلغاء</string>
|
||||
<string name="failed_to_upload_the_image">فشل تحميل الصورة</string>
|
||||
<string name="relay_address">Relay عنوان</string>
|
||||
<string name="relay_address"> عنوان الخادم (Relay Adress)</string>
|
||||
<string name="posts">المنشورات</string>
|
||||
<string name="bytes">Bytes</string>
|
||||
<string name="errors">الأخطاء</string>
|
||||
<string name="home_feed">المنشورات الرئيسية</string>
|
||||
<string name="private_message_feed"> الرسائل الخاصة</string>
|
||||
@@ -90,7 +98,7 @@
|
||||
<string name="failed_to_save_the_image">فشل حفظ الصورة</string>
|
||||
<string name="upload_image">تحميل الصورة</string>
|
||||
<string name="uploading">جاري التحميل…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">لا يمتلك المستخدم إعداد عنوان lighting لاستقبال sats</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">لا يمتلك المستخدم عنوان (Lightning Address) لاستقبال sats</string>
|
||||
<string name="reply_here">"الرد هنا.. "</string>
|
||||
<string name="copies_the_note_id_to_the_clipboard_for_sharing">نسخ معرف الملاحظة إلى الحافظة للمشاركة</string>
|
||||
<string name="copy_channel_id_note_to_the_clipboard">انسخ معرف القناة (ملاحظة) إلى الحافظة</string>
|
||||
@@ -166,4 +174,190 @@
|
||||
<string name="report_hateful_speech">أبلغ عن كلام يحض على الكراهية</string>
|
||||
<string name="report_nudity_porn">الإبلاغ عن عُري / إباحي</string>
|
||||
<string name="others">آخرون</string>
|
||||
<string name="mark_all_known_as_read">ضع علامة على جميع الرسائل/الاشعارات المعروفة كمقروء</string>
|
||||
<string name="mark_all_new_as_read">ضع علامة على جميع الرسائل/الاشعارات الجديدة كمقروء</string>
|
||||
<string name="mark_all_as_read">ضع علامة على جميع الرسائل/الاشعارات كمقروء</string>
|
||||
<string name="backup_keys">قم بعمل نسخة احتياطية للمفاتيح</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## النصائح الرئيسية للنسخ الاحتياطي والسلامة
|
||||
\n\n- يتم تأمين حسابك بمفتاح سري. المفتاح السري هو سلسلة عشوائية طويلة تبدأ بـ **nsec1**. أي شخص لديه حق الوصول إلى مفتاحك السري يمكنه نشر المحتوى باستخدام هويتك.
|
||||
\n\n- لا تقم بوضع مفتاحك السري في أي موقع أو برنامج لا تثق به.
|
||||
\n- مطوري Amethyst لن يطلبوا منك **أبدا** المفتاح السري الخاص بك.
|
||||
\n- حافظ على نسخة احتياطية آمنة لمفتاحك السري لاسترداد الحساب. نوصي باستخدام برنامج لادارة كلمات المرور.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">مفتاحك السري (nsec) قد تم نسخه في الحافظة</string>
|
||||
<string name="copy_my_secret_key">إنسخ مفتاحي السري</string>
|
||||
<string name="biometric_authentication_failed">فشلت المصادقة</string>
|
||||
<string name="biometric_error">خطأ</string>
|
||||
<string name="badge_created_by">"تم إنشاؤه بواسطة %1$s"</string>
|
||||
<string name="badge_award_image_for">"صورة شارة مكافئة ل %1$s"</string>
|
||||
<string name="new_badge_award_notif">لقد حصلت على شارة جائزة جديدة</string>
|
||||
<string name="copied_note_text_to_clipboard">تم نسخ نص الملاحظة/المنشور إلى الحافظة</string>
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">تم نسخ عنوان @npub المؤلف إلى الحافظة</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">تم نسخ رمز الملاحظة/المنشور (note ID) إلى الحافظة</string>
|
||||
<string name="select_text_dialog_top">تحديد نص</string>
|
||||
<string name="private_conversation_notification">"<استحالة فك تشفير الرسالة الخاصة>
|
||||
\n\n أنت الآن في محادثة خاصة/مشفرة بين %1$s و %2$s ."</string>
|
||||
<string name="account_switch_add_account_dialog_title">إضافة حساب جديد</string>
|
||||
<string name="drawer_accounts">الحسابات</string>
|
||||
<string name="account_switch_select_account">اختر حساب</string>
|
||||
<string name="account_switch_add_account_btn">إضافة حساب جديد</string>
|
||||
<string name="account_switch_active_account">تنشيط الحساب</string>
|
||||
<string name="account_switch_has_private_key">يحتوي على مفتاح خاص</string>
|
||||
<string name="account_switch_pubkey_only">قراءة فقط، لا يوجد مفتاح خاص</string>
|
||||
<string name="back">رجوع</string>
|
||||
<string name="quick_action_select">اختر</string>
|
||||
<string name="quick_action_share_browser_link">شارك رابط المتصفح</string>
|
||||
<string name="quick_action_share">مشاركة</string>
|
||||
<string name="quick_action_copy_user_id">معرف المؤلف (ID)</string>
|
||||
<string name="quick_action_copy_note_id">رمز/معرف الملاحظة (Note ID)</string>
|
||||
<string name="quick_action_copy_text">نسخ النص</string>
|
||||
<string name="quick_action_delete">حذف</string>
|
||||
<string name="quick_action_unfollow">الغاء المتابعة</string>
|
||||
<string name="quick_action_follow">متابعة</string>
|
||||
<string name="quick_action_request_deletion_alert_title">طلب الحذف</string>
|
||||
<string name="quick_action_request_deletion_alert_body">سيطلب Amethyst حذف ملاحظتك من الخوادم (relays) الذي أنت متصل به حاليا. لا يوجد ضمان لحذف ملاحظتك بشكل دائم من تلك الخوادم أو من الخوادم الأخرى التي ربما حفظت فيها.</string>
|
||||
<string name="quick_action_block_dialog_btn">حظر </string>
|
||||
<string name="quick_action_delete_dialog_btn">حذف</string>
|
||||
<string name="quick_action_block">حظر</string>
|
||||
<string name="quick_action_report">التبليغ</string>
|
||||
<string name="quick_action_delete_button">حذف</string>
|
||||
<string name="quick_action_dont_show_again_button">لا تظهر مرة أخرى</string>
|
||||
<string name="report_dialog_illegal">تصرف غير قانوني</string>
|
||||
<string name="report_dialog_blocking_a_user">حظر المستخدم سوف يخفي محتواه في التطبيق الخاص بك. ملاحظاتك لا تزال قابلة للعرض علناً، بما في ذلك للأشخاص الذين تقوم بحظرهم. يتم إدراج المستخدمين المحظورين على شاشة مرشحات الأمان (Security Filters screen).</string>
|
||||
<string name="report_dialog_block_hide_user_btn"><![CDATA[حظر و إخفاء المستخدم]]></string>
|
||||
<string name="report_dialog_reminder_public">ستكون جميع التقارير المنشورة مرئية للجمهور.</string>
|
||||
<string name="report_dialog_additional_reason_placeholder">اختياريا تستطيع توفير سياق إضافي حول تقريرك…</string>
|
||||
<string name="report_dialog_additional_reason_label">سياق إضافي</string>
|
||||
<string name="report_dialog_select_reason_label">السبب</string>
|
||||
<string name="report_dialog_select_reason_placeholder">حدد سببا</string>
|
||||
<string name="report_dialog_post_report_btn">تبليغ المنشور (Report)</string>
|
||||
<string name="report_dialog_title">حظر / تبليغ</string>
|
||||
<string name="block_only">حظر </string>
|
||||
<string name="bookmarks">المفضلة</string>
|
||||
<string name="private_bookmarks">المفضلة الخاصة</string>
|
||||
<string name="public_bookmarks">المفضلة العامة</string>
|
||||
<string name="add_to_private_bookmarks">إضافة إلى المفضلات الخاصة</string>
|
||||
<string name="add_to_public_bookmarks">إضافة إلى المفضلات العامة</string>
|
||||
<string name="remove_from_private_bookmarks">إزالة من المفضلات الخاصة</string>
|
||||
<string name="remove_from_public_bookmarks">إزالة من المفضلات العامة</string>
|
||||
<string name="wallet_connect_service">خدمة Wallet Connect </string>
|
||||
<string name="wallet_connect_service_relay">خادم Wallet Connect </string>
|
||||
<string name="wallet_connect_service_secret">كلمة سر خدمة Wallet Connect</string>
|
||||
<string name="wallet_connect_service_show_secret">إظهار كلمة السر</string>
|
||||
<string name="wallet_connect_service_secret_placeholder">المفتاح السري بصيغة nsec أو hex</string>
|
||||
<string name="post_poll">نشر الاستطلاع</string>
|
||||
<string name="poll_heading_required">الحقول المطلوبة:</string>
|
||||
<string name="poll_primary_description">وصف الاستطلاع الأساسي…</string>
|
||||
<string name="poll_option_index">الخيار %s</string>
|
||||
<string name="poll_option_description">وصف خيار الاستطلاع</string>
|
||||
<string name="poll_heading_optional">حقل اختياري</string>
|
||||
<string name="poll_zap_value_min">الحد الأدنى</string>
|
||||
<string name="poll_zap_value_max">الحد الاقصى</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="poll_closing_time">إغلاق بعد</string>
|
||||
<string name="poll_closing_time_days">أيام</string>
|
||||
<string name="poll_is_closed">تم إغلاق الاستطلاع لأي تصويت جديد</string>
|
||||
<string name="one_vote_per_user_on_atomic_votes">يسمح بصوت واحد فقط لكل مستخدم في هذا النوع من الاستطلاع</string>
|
||||
<string name="looking_for_event">"البحث عن الحدث %1$s"</string>
|
||||
<string name="custom_zaps_add_a_message">أضف رسالة عامة</string>
|
||||
<string name="custom_zaps_add_a_message_private">أضف رسالة خاصة</string>
|
||||
<string name="custom_zaps_add_a_message_nonzap">أضف رسالة مع الفاتورة</string>
|
||||
<string name="custom_zaps_add_a_message_example">شكرا لك على كل مساهمتك!</string>
|
||||
<string name="lightning_create_and_add_invoice">إنشاء وإضافة</string>
|
||||
<string name="poll_author_no_vote">لا يستطيع مؤلف الاستطلاع التصويت في استطلاعه.</string>
|
||||
<string name="hash_verification_passed">هذا المحتوى هو نفسه منذ النشر</string>
|
||||
<string name="hash_verification_failed">لقد تغير هذا المحتوى. ربما لم يشاهد مؤلفه التغيير أو يوافق عليه بعد.</string>
|
||||
<string name="content_description_add_image">أضف صورة</string>
|
||||
<string name="content_description_add_video">اضف فيديو</string>
|
||||
<string name="content_description_add_document">أضف مستند</string>
|
||||
<string name="add_content">أضف إلى الرسالة</string>
|
||||
<string name="content_description">وصف المحتوى/المحتويات</string>
|
||||
<string name="content_description_example">A blue boat in a white sandy beach at sunset
|
||||
(قارب أزرق في شاطئ رملي أبيض عند غروب الشمس)</string>
|
||||
<string name="zap_type_public">عام</string>
|
||||
<string name="zap_type_public_explainer">يستطيع الجميع رؤية المعاملة والرسالة</string>
|
||||
<string name="zap_type_private">خاص</string>
|
||||
<string name="zap_type_private_explainer">يمكن للمرسل والمستقبل رؤية بعضهما البعض وقراءة الرسالة</string>
|
||||
<string name="zap_type_anonymous">مجهول الهوية</string>
|
||||
<string name="zap_type_anonymous_explainer">المستقبل والجمهور لا يعرفان من الذي قام بإرسال الدفع</string>
|
||||
<string name="file_server">خادم الملفات</string>
|
||||
<string name="upload_server_imgur">imgur.com - موثوق به</string>
|
||||
<string name="upload_server_imgur_explainer">يمكن لـ Imgur تعديل الملف</string>
|
||||
<string name="upload_server_nostrimg">nostrimg.com - (موثوق به)</string>
|
||||
<string name="upload_server_nostrimg_explainer">يمكن لـ NostrImg تعديل الملف</string>
|
||||
<string name="upload_server_nostrbuild">nostr.build- (موثوق به)</string>
|
||||
<string name="upload_server_nostrbuild_explainer">يمكن لـ Nostr.build تعديل الملف</string>
|
||||
<string name="upload_server_nostrfilesdev">nostrfiles.dev - (موثوق به)</string>
|
||||
<string name="upload_server_nostrfilesdev_explainer">يمكن لـ Nostrfiles.dev تعديل الملف</string>
|
||||
<string name="upload_server_nostrcheckme">nostrcheck.me - (موثوق به)</string>
|
||||
<string name="upload_server_nostrcheckme_explainer">يمكن لـ nostrcheck.me تعديل الملف</string>
|
||||
<string name="upload_server_imgur_nip94_explainer">تحقق مما إذا قام Imgur بتعديل الملف.
|
||||
NIP جديد: قد لا يراه العملاء الآخرون</string>
|
||||
<string name="app_notification_zaps_channel_message_from">من طرف %1$s</string>
|
||||
<string name="app_notification_zaps_channel_message_for">ل %1$s</string>
|
||||
<string name="reply_notify">تنبيه: </string>
|
||||
<string name="channel_list_join_conversation">الانضمام إلى المحادثة</string>
|
||||
<string name="channel_list_user_or_group_id">معرف المستخدم أو المجموعة</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub, nevent أو hex</string>
|
||||
<string name="channel_list_create_channel">إنشاء</string>
|
||||
<string name="channel_list_join_channel">إنضمام</string>
|
||||
<string name="today">اليوم</string>
|
||||
<string name="content_warning">تحذير من المحتوى</string>
|
||||
<string name="content_warning_hide_all_sensitive_content">إخفاء المحتوى الحساس دائماً</string>
|
||||
<string name="content_warning_show_all_sensitive_content">إظهار المحتوى الحساس دائماً</string>
|
||||
<string name="content_warning_see_warnings">إظهار تحذيرات المحتوى دائمًا</string>
|
||||
<string name="recommended_apps">التطبيقات الموصى بها : </string>
|
||||
<string name="filter_spam_from_strangers">تصفية الرسائل المزعجة من الغرباء</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">تنبيه عندما تكون منشورتك قد تم التبليغ عنها </string>
|
||||
<string name="new_reaction_symbol">رمز رد فعل جديد</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">لم يتم تحديد أي نوع من ردود الفعل. اضغط مطولاً للتغيير</string>
|
||||
<string name="read_from_relay">القراءة من الخادم (Relay)</string>
|
||||
<string name="write_to_relay">الكتابة للخادم</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">حدث خطأ أثناء محاولة الحصول على معلومات الخادم من %1$s</string>
|
||||
<string name="owner">المالك</string>
|
||||
<string name="version">الاصدار</string>
|
||||
<string name="software">البرنامج</string>
|
||||
<string name="contact">جهة الاتصال</string>
|
||||
<string name="supports">الNIPs المدعومة</string>
|
||||
<string name="admission_fees">رسوم القبول</string>
|
||||
<string name="payments_url">رابط الدفع</string>
|
||||
<string name="limitations">الحدود</string>
|
||||
<string name="countries">الدول</string>
|
||||
<string name="languages">اللغات</string>
|
||||
<string name="tags">الوسوم</string>
|
||||
<string name="posting_policy">سياسات النشر</string>
|
||||
<string name="message_length">طول الرسالة</string>
|
||||
<string name="subscriptions">الإشتراكات</string>
|
||||
<string name="filters">الفلاتر</string>
|
||||
<string name="subscription_id_length">طول معرّف الاشتراك</string>
|
||||
<string name="minimum_prefix">الحد الأدنى لل prefix </string>
|
||||
<string name="maximum_event_tags">الحد الأقصى لوسوم الحدث</string>
|
||||
<string name="content_length">طول المحتوى</string>
|
||||
<string name="minimum_pow">الحد الأدنى لل PoW</string>
|
||||
<string name="auth">المصادقة</string>
|
||||
<string name="payment">الدفع</string>
|
||||
<string name="cashu">Cashu Token</string>
|
||||
<string name="geohash_title">عرض الموقع كـ </string>
|
||||
<string name="new_feature_nip24_might_not_be_available_title">ميزة جديدة</string>
|
||||
<string name="new_feature_nip24_activate">تفعيل</string>
|
||||
<string name="messages_create_public_chat">عام</string>
|
||||
<string name="messages_new_message">خاص</string>
|
||||
<string name="messages_new_message_to">إلى</string>
|
||||
<string name="messages_new_message_subject">الموضوع</string>
|
||||
<string name="messages_new_message_subject_caption">موضوع المحادثة</string>
|
||||
<string name="messages_new_message_to_caption">"\@User1, @User2, @User3"</string>
|
||||
<string name="messages_group_descriptor">أعضاء هذه المجموعة</string>
|
||||
<string name="messages_new_subject_message">التوضيح للأعضاء</string>
|
||||
<string name="messages_new_subject_message_placeholder">تغيير اسم الأهداف الجديدة.</string>
|
||||
<string name="language_description">من أجل واجهة التطبيق</string>
|
||||
<string name="theme_description">مظهر غامق أو فاتح أو اوتوماتيكي</string>
|
||||
<string name="automatically_load_images_gifs_description">تحميل الصور و الصور المتحركة تلقائياً</string>
|
||||
<string name="automatically_play_videos_description">تشغيل الفيديوهات و الصور المتحركة تلقائياً</string>
|
||||
<string name="automatically_show_url_preview_description">إظهار معاينات الروابط</string>
|
||||
<string name="load_image_description">متى يتم تحميل الصور</string>
|
||||
<string name="copy_url_to_clipboard">نسخ عنوان الرابط إلى الحافظة</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">انسخ معرف النص (ID) إلى الحافظة</string>
|
||||
<string name="created_at">أنشئ في</string>
|
||||
<string name="rules">القواعد</string>
|
||||
<string name="status_update">تحديث الحالة</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,499 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="point_to_the_qr_code">QR কোডের দিকে নির্দেশ করুন</string>
|
||||
<string name="show_qr">QR দেখান</string>
|
||||
<string name="profile_image">প্রোফাইল ছবি</string>
|
||||
<string name="scan_qr">QR স্ক্যান করুন</string>
|
||||
<string name="show_anyway">যাহোক, দেখান</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">পোস্টটি কারো দ্বারা অভিযুক্ত হয়েছে</string>
|
||||
<string name="post_not_found">ইভেন্টটি লোড হচ্ছে অথবা আপনার রিলে তালিকায় এটি পাওয়া যাচ্ছে না</string>
|
||||
<string name="channel_image">চ্যানেল ইমেজ</string>
|
||||
<string name="referenced_event_not_found">উল্লেখিত ইভেন্টটি পাওয়া যায় নি</string>
|
||||
<string name="could_not_decrypt_the_message">মেসেজটি ডিক্রিপ্ট করা যায় নি</string>
|
||||
<string name="group_picture">দলগত ছবি</string>
|
||||
<string name="explicit_content">খোলামেলা আধেয়</string>
|
||||
<string name="spam">স্প্যাম</string>
|
||||
<string name="impersonation">ছদ্মবেশ</string>
|
||||
<string name="illegal_behavior">বেআইনী আচরণ</string>
|
||||
<string name="unknown">অচেনা</string>
|
||||
<string name="relay_icon">রিলে আইকন</string>
|
||||
<string name="unknown_author">অজ্ঞাত-পরিচয় লেখক</string>
|
||||
<string name="copy_text">টেক্সটটি কপি করুন</string>
|
||||
<string name="copy_user_pubkey">লেখক আইডি কপি করুন</string>
|
||||
<string name="copy_note_id">নোটের আইডি কপি করুন</string>
|
||||
<string name="broadcast">সম্প্রচার</string>
|
||||
<string name="request_deletion">নোটটি মুছে ফেলার অনুরোধ করুন</string>
|
||||
<string name="block_report">ব্লক/অভিযুক্ত করুন</string>
|
||||
<string name="block_hide_user"><![CDATA[ব্লক করুন এবং ব্যবহারকারীকে আড়ালে রাখুন]]></string>
|
||||
<string name="report_spam_scam">স্প্যাম/প্রতারণার অভিযোগ করুন</string>
|
||||
<string name="report_impersonation">ছদ্মবেশ ধারণের অভিযোগ করুন</string>
|
||||
<string name="report_explicit_content">খোলামেলা আধেয় হিসেবে অভিযোগ করুন</string>
|
||||
<string name="report_illegal_behaviour">বেআইনী আচরণের অভিযোগ করুন</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_reply">প্রতিউত্তর দিতে ব্যক্তিগত চাবি দিয়ে লগ ইন করুন</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">পোস্টগুলি বুস্ট করতে ব্যক্তিগত চাবি দিয়ে লগ ইন করুন</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">পোস্টগুলি লাইক করতে ব্যক্তিগত চাবি দিয়ে লগ ইন করুন</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">জ্যাপের কোনো পরিমাণ নির্দিষ্ট করা নেই। এটি পরিবর্তন করতে চাইলে দীর্ঘক্ষণ চাপুন</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">কাউকে জ্যাপ পাঠাতে ব্যক্তিগত চাবি দিয়ে লগ ইন করুন</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">কাউকে অনুসরণ করতে ব্যক্তিগত চাবি দিয়ে লগইন করুন</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">কাউকে আনফলো করতে ব্যক্তিগত চাবি দিয়ে লগ ইন করুন</string>
|
||||
<string name="zaps">জ্যাপস</string>
|
||||
<string name="view_count">পরিমাণ দেখুন</string>
|
||||
<string name="boost">বুস্ট</string>
|
||||
<string name="boosted">বুস্ট করা হয়েছে</string>
|
||||
<string name="quote">উদ্ধৃতি দিন</string>
|
||||
<string name="new_amount_in_sats">স্যাটে নতুন পরিমাণ যুক্ত হয়েছে</string>
|
||||
<string name="add">যুক্ত করুন</string>
|
||||
<string name="replying_to">"এই ব্যক্তির প্রতিউত্তর দিচ্ছেন "</string>
|
||||
<string name="and">" এবং "</string>
|
||||
<string name="in_channel">"চ্যানেলের ভেতর "</string>
|
||||
<string name="profile_banner">প্রোফাইলের ব্যানার</string>
|
||||
<string name="payment_successful">সফলভাবে মূল্য পরিশোধিত হয়েছে</string>
|
||||
<string name="error_parsing_error_message">এরর পার্সিং এরর মেসেজ</string>
|
||||
<string name="following">" অনুসরণ"</string>
|
||||
<string name="followers">" অনুসারী"</string>
|
||||
<string name="profile">প্রোফাইল</string>
|
||||
<string name="security_filters">নিরাপত্তা-ফিল্টার</string>
|
||||
<string name="log_out">লগ আউট</string>
|
||||
<string name="show_more">আরও দেখান</string>
|
||||
<string name="lightning_invoice">লাইটনিং ইনভয়েস</string>
|
||||
<string name="pay">পরিশোধ করুন</string>
|
||||
<string name="lightning_tips">লাইটনিং টিপস</string>
|
||||
<string name="note_to_receiver">প্রাপকের উদ্দেশে চিরকুট</string>
|
||||
<string name="thank_you_so_much">আপনাকে অনেক ধন্যবাদ!</string>
|
||||
<string name="amount_in_sats">স্যাটে মোট পরিমাণ</string>
|
||||
<string name="send_sats">স্যাট পাঠান</string>
|
||||
<string name="error_parsing_preview_for">"এই দুইয়ের পূর্বরূপ দেখানো যাচ্ছে না %1$s : %2$s"</string>
|
||||
<string name="preview_card_image_for">"%1$s এর জন্য কার্ড ইমেজের পূর্বরূপ দেখুন"</string>
|
||||
<string name="new_channel">নতুন চ্যানেল</string>
|
||||
<string name="channel_name">চ্যানেলের নাম</string>
|
||||
<string name="my_awesome_group">আমার চমৎকার গ্রুপ</string>
|
||||
<string name="picture_url">ছবির Url</string>
|
||||
<string name="description">বিবরণ</string>
|
||||
<string name="about_us">"আমাদের সম্পর্কে.. "</string>
|
||||
<string name="what_s_on_your_mind">নিজের কোনো ভাবনা প্রকাশ করতে চান?</string>
|
||||
<string name="post">পেশ করুন</string>
|
||||
<string name="save">সংরক্ষণ করুন</string>
|
||||
<string name="create">তৈরি করুন</string>
|
||||
<string name="cancel">বাতিল করুন</string>
|
||||
<string name="failed_to_upload_the_image">ছবিটি সফলভাবে আপলোড করা যায় নি</string>
|
||||
<string name="relay_address">রিলে ঠিকানা</string>
|
||||
<string name="posts">পোস্টগুলি</string>
|
||||
<string name="bytes">বাইটস</string>
|
||||
<string name="errors">ত্রুটিসমূহ</string>
|
||||
<string name="home_feed">নীড়পাতা</string>
|
||||
<string name="private_message_feed">ব্যক্তিগত বার্তাসমূহ</string>
|
||||
<string name="public_chat_feed">সার্বজনীন কথোপকথন</string>
|
||||
<string name="global_feed">বৈশ্বিক পাতা</string>
|
||||
<string name="search_feed">অনুসন্ধান পাতা</string>
|
||||
<string name="add_a_relay">একটি রিলে যুক্ত করুন</string>
|
||||
<string name="display_name">প্রদর্শিত নাম</string>
|
||||
<string name="my_display_name">আমার প্রদর্শিত নাম</string>
|
||||
<string name="username">ইউজারনেম</string>
|
||||
<string name="my_username">আমার ইউজারনেম</string>
|
||||
<string name="about_me">আমার সম্পর্কে</string>
|
||||
<string name="avatar_url">অবতারের URL</string>
|
||||
<string name="banner_url">ব্যানারের URL</string>
|
||||
<string name="website_url">ওয়েবসাইটের URL</string>
|
||||
<string name="ln_address">LN ঠিকানা</string>
|
||||
<string name="ln_url_outdated">LN URL (অপ্রচলিত)</string>
|
||||
<string name="image_saved_to_the_gallery">ছবিটি গ্যালারিতে সংরক্ষণ করা হয়েছে</string>
|
||||
<string name="failed_to_save_the_image">ছবিটি সফলভাবে সংরক্ষণ করা যায় নি</string>
|
||||
<string name="upload_image">ছবিটি আপলোড করুন</string>
|
||||
<string name="uploading">আপলোড করা হচ্ছে…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">স্যাট গ্রহণের জন্য ব্যবহারকারীর কোনো লাইটনিং ঠিকানা দেওয়া নেই</string>
|
||||
<string name="reply_here">"এখানে প্রতিউত্তর দিন.. "</string>
|
||||
<string name="copies_the_note_id_to_the_clipboard_for_sharing">নস্টারে শেয়ারের জন্য নোট আইডিটি ক্লিপবোর্ডে কপি করা হয়</string>
|
||||
<string name="copy_channel_id_note_to_the_clipboard">চ্যানেল আইডি (নোট) ক্লিপবোর্ডে কপি করুন</string>
|
||||
<string name="edits_the_channel_metadata">চ্যানেলের মেটাডেটা সম্পাদনা করে</string>
|
||||
<string name="join">যুক্ত হোন</string>
|
||||
<string name="known">জ্ঞাত</string>
|
||||
<string name="new_requests">নয়া অনুরোধসমূহ</string>
|
||||
<string name="blocked_users">ব্লক করে রাখা ব্যবহারকারীরা</string>
|
||||
<string name="new_threads">নতুন থ্রেড</string>
|
||||
<string name="conversations">কথোপকথন</string>
|
||||
<string name="notes">নোটস</string>
|
||||
<string name="replies">প্রতিউত্তরসমূহ</string>
|
||||
<string name="follows">"অনুসারী"</string>
|
||||
<string name="reports">"অভিযোগসমূহ"</string>
|
||||
<string name="more_options">আরও বিকল্প</string>
|
||||
<string name="relays">" রিলেগুলি"</string>
|
||||
<string name="website">ওয়েবসাইট</string>
|
||||
<string name="lightning_address">লাইটনিং ঠিকানা</string>
|
||||
<string name="copies_the_nsec_id_your_password_to_the_clipboard_for_backup">ব্যাকআপের জন্য আপনার Nsec আইডি (তথা পাসওয়ার্ড) ক্লিপবোর্ডে কপি করে</string>
|
||||
<string name="copy_private_key_to_the_clipboard">আপনার ব্যক্তিগত চাবিটি ক্লিপবোর্ডে কপি করুন</string>
|
||||
<string name="copies_the_public_key_to_the_clipboard_for_sharing">অন্যদের সাথে শেয়ারের জন্য আপনার গণ চাবি ক্লিপবোর্ডে কপি করে</string>
|
||||
<string name="copy_public_key_npub_to_the_clipboard">গণ চাবি (Npub) ক্লিপবোর্ডে কপি করুন</string>
|
||||
<string name="send_a_direct_message">একটি সরাসরি-বার্তা পাঠান</string>
|
||||
<string name="edits_the_user_s_metadata">ব্যবহারকারীর মেটাডেটা সম্পাদনা করে</string>
|
||||
<string name="follow">অনুসরণ করুন</string>
|
||||
<string name="follow_back">প্রতি-অনুসরণ করুন</string>
|
||||
<string name="unblock">আনব্লক করুন</string>
|
||||
<string name="copy_user_id">ব্যবহারকারীর আইডি কপি করুন</string>
|
||||
<string name="unblock_user">ব্যবহারকারীকে আনব্লক করুন</string>
|
||||
<string name="npub_hex_username">"Npub, ইউজারনেম, টেক্সট"</string>
|
||||
<string name="clear">মুছে ফেলুন</string>
|
||||
<string name="app_logo">অ্যাপের লোগো</string>
|
||||
<string name="nsec_npub_hex_private_key">Nsec.. অথবা Npub..</string>
|
||||
<string name="show_password">পাসওয়ার্ড দেখান</string>
|
||||
<string name="hide_password">পাসওয়ার্ড লুকান</string>
|
||||
<string name="invalid_key">অকেজো কি</string>
|
||||
<string name="i_accept_the">"আমি এটা মেনে নিচ্ছি "</string>
|
||||
<string name="terms_of_use">ব্যবহারের শর্তাবলী</string>
|
||||
<string name="acceptance_of_terms_is_required">প্রদত্ত শর্তাবলী কবুল করা আবশ্যক</string>
|
||||
<string name="key_is_required">কি প্রয়োজন</string>
|
||||
<string name="login">লগ ইন</string>
|
||||
<string name="generate_a_new_key">একটি নতুন কি তৈরি করুন</string>
|
||||
<string name="loading_feed">ফিডটি লোড করা হচ্ছে</string>
|
||||
<string name="error_loading_replies">"প্রতিউত্তরগুলি লোড করা যাচ্ছে না: "</string>
|
||||
<string name="try_again">আবার চেষ্টা করুন</string>
|
||||
<string name="feed_is_empty">ফিডটি খালি.</string>
|
||||
<string name="refresh">রিফ্রেশ করুন</string>
|
||||
<string name="created">তৈরি করা হয়েছে</string>
|
||||
<string name="with_description_of">এর বিবরণ সহ</string>
|
||||
<string name="and_picture">এবং ছবি</string>
|
||||
<string name="changed_chat_name_to">চ্যাটের নাম পরিবর্তন করুন</string>
|
||||
<string name="description_to">বিবরণ</string>
|
||||
<string name="and_picture_to">এবং ছবি</string>
|
||||
<string name="leave">ছেড়ে দিন</string>
|
||||
<string name="unfollow">আনফলো করুন</string>
|
||||
<string name="channel_created">চ্যানেলটি তৈরি করা হয়েছে</string>
|
||||
<string name="channel_information_changed_to">"চ্যানেলের তথ্যের পরিবর্তিত চেহারা"</string>
|
||||
<string name="public_chat">সার্বজনীন আলাপ</string>
|
||||
<string name="posts_received">পেশকৃত আধেয় গৃহীত হয়েছে</string>
|
||||
<string name="remove">সরিয়ে ফেলুন</string>
|
||||
<string name="translations_auto">স্বয়ংক্রিয়</string>
|
||||
<string name="translations_translated_from">এই মূলপাঠ থেকে অনূদিত হয়েছে</string>
|
||||
<string name="translations_to">থেকে</string>
|
||||
<string name="translations_show_in_lang_first">প্রথমে %1$s ভাষায় দেখান</string>
|
||||
<string name="translations_always_translate_to_lang">সর্বদা %1$s ভাষায় অনুবাদ করুন</string>
|
||||
<string name="translations_never_translate_from_lang">কখনোই %1$s ভাষা থেকে অনুবাদ করবেন না</string>
|
||||
<string name="nip_05">নস্টার ঠিকানা</string>
|
||||
<string name="never">কখনো না</string>
|
||||
<string name="now">এখন</string>
|
||||
<string name="h">h</string>
|
||||
<string name="m">m</string>
|
||||
<string name="d">d</string>
|
||||
<string name="nudity">নগ্নতা</string>
|
||||
<string name="profanity_hateful_speech">অশ্লীলতা / বিদ্বেষপূর্ণ বক্তব্য</string>
|
||||
<string name="report_hateful_speech">বিদ্বেষপূর্ণ বক্তব্য হিসেবে অভিযোগ জানান</string>
|
||||
<string name="report_nudity_porn">নগ্নতা / পর্ন হিসেবে অভিযোগ জানান</string>
|
||||
<string name="others">অন্যান্য</string>
|
||||
<string name="mark_all_known_as_read">পরিচিত সবগুলোকে পঠিত হিসেবে চিহ্নিত করুন</string>
|
||||
<string name="mark_all_new_as_read">নতুন সবগুলোকে পঠিত হিসেবে চিহ্নিত করুন</string>
|
||||
<string name="mark_all_as_read">সবগুলোকে পঠিত হিসেবে চিহ্নিত করুন</string>
|
||||
<string name="backup_keys">ব্যাকআপ চাবিগুলি</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## চাবির ব্যাকআপ এবং নিরাপত্তা বিষয়ক টিপস
|
||||
\n\nআপনার অ্যাকাউন্ট একটি ব্যক্তিগত চাবি দ্বারা সুরক্ষিত। চাবি হলো এলোমেলো অক্ষর ও সংখ্যার একটি দীর্ঘ তন্তু যা **nsec** দ্বারা শুরু হয়। আপনার ব্যক্তিগত চাবিটি পেলে যে কেউ আপনার পরিচয় ব্যবহার করে যেকোনো আধেয় প্রকাশ করতে পারবে।
|
||||
\n\n- আপনি বিশ্বাস করেন না এমন কোনো ওয়েবসাইট কিংবা সফটওয়্যারে আপনার ব্যক্তিগত চাবিটি রাখবেন **না**।
|
||||
\n- অ্যামেথিস্টের ডেভেলপাররা **কখনোই** আপনার কাছে আপনার ব্যক্তিগত চাবিটি জানতে চাইবে না।
|
||||
\n- আপনার ব্যক্তিগত চাবির একটি নিরাপদ ব্যাকআপ **অবশ্যই** সংরক্ষণ করবেন কারণ এটি আপনার অ্যাকাউন্ট পুনরুদ্ধারে কাজে আসবে। আমরা একটি পাসওয়ার্ড ম্যানেজার ব্যবহারের পরামর্শ দিই।
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">ব্যক্তিগত চাবি (nsec) ক্লিপবোর্ডে কপি করা হয়েছে</string>
|
||||
<string name="copy_my_secret_key">আমার ব্যক্তিগত চাবিটি কপি করুন</string>
|
||||
<string name="biometric_authentication_failed">প্রমাণীকরণ ব্যর্থ হয়েছে।</string>
|
||||
<string name="biometric_error">ত্রুটি</string>
|
||||
<string name="badge_created_by">"%1$s দ্বারা নির্মিত"</string>
|
||||
<string name="badge_award_image_for">"%1$s -এর জন্য ব্যাজ পুরষ্কারের ছবি"</string>
|
||||
<string name="new_badge_award_notif">আপনি একটি নতুন ব্যাজ পুরষ্কার পেয়েছেন</string>
|
||||
<string name="award_granted_to">ব্যাজ পুরস্কারটি পাচ্ছে</string>
|
||||
<string name="copied_note_text_to_clipboard">নোটের রচনাটি ক্লিপবোর্ডে কপি করা হয়েছে</string>
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">রচয়িতার @npub ক্লিপবোর্ডে কপি করুন</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">নোট আইডিটি (@note1) ক্লিপবোর্ডে কপি করা হয়েছে</string>
|
||||
<string name="select_text_dialog_top">রচনাটি নির্বাচন করুন</string>
|
||||
<string name="private_conversation_notification">"<Unable to decrypt private message>\n\nআপনাকে %1$s এবং %2$s এর মধ্যে একটি ব্যক্তিগত/এনক্রিপ্টেড কথোপকথনে উদ্ধৃত করা হয়েছে৷"</string>
|
||||
<string name="account_switch_add_account_dialog_title">নতুন অ্যাকাউন্ট যুক্ত করুন</string>
|
||||
<string name="drawer_accounts">অ্যাকাউন্টগুলি</string>
|
||||
<string name="account_switch_select_account">অ্যাকাউন্ট নির্বাচন করুন</string>
|
||||
<string name="account_switch_add_account_btn">নতুন অ্যাকাউন্ট যুক্ত করুন</string>
|
||||
<string name="account_switch_active_account">অ্যাকাউন্ট সক্রিয় করুন</string>
|
||||
<string name="account_switch_has_private_key">ব্যক্তিগত চাবি আছে</string>
|
||||
<string name="account_switch_pubkey_only">শুধুমাত্র পাঠযোগ্য, ব্যক্তিগত চাবি নেই</string>
|
||||
<string name="back">পিছে যান</string>
|
||||
<string name="quick_action_select">নির্বাচন করুন</string>
|
||||
<string name="quick_action_share_browser_link">ব্রাউজারের লিংক শেয়ার করুন</string>
|
||||
<string name="quick_action_share">শেয়ার</string>
|
||||
<string name="quick_action_copy_user_id">লেখকের আইডি</string>
|
||||
<string name="quick_action_copy_note_id">নোটের আইডি</string>
|
||||
<string name="quick_action_copy_text">টেক্সটটি কপি করুন</string>
|
||||
<string name="quick_action_delete">মুছে ফেলুন</string>
|
||||
<string name="quick_action_unfollow">আনফলো করুন</string>
|
||||
<string name="quick_action_follow">অনুসরণ করুন</string>
|
||||
<string name="quick_action_request_deletion_alert_title">নোটটি মুছে ফেলার অনুরোধ করুন</string>
|
||||
<string name="quick_action_request_deletion_alert_body">অ্যামেথিস্ট অনুরোধ করবে যেন আপনার নোটটি আপনার বর্তমানে সংযুক্ত রিলেগুলো থেকে মুছে ফেলা হয়। তবে কোনো নিশ্চয়তা নেই যে নোটটি স্থায়ীভাবে সেই রিলেগুলি থেকে বা অন্যান্য রিলে থেকে মুছে ফেলা হবে, যেখানে এটি সংরক্ষণ করা হতে পারে৷</string>
|
||||
<string name="quick_action_block_dialog_btn">ব্লক করুন</string>
|
||||
<string name="quick_action_delete_dialog_btn">মুছে ফেলুন</string>
|
||||
<string name="quick_action_block">ব্লক করুন</string>
|
||||
<string name="quick_action_report">অভিযোগ করুন</string>
|
||||
<string name="quick_action_delete_button">মুছে ফেলুন</string>
|
||||
<string name="quick_action_dont_show_again_button">আর দেখাবেন না</string>
|
||||
<string name="report_dialog_spam">স্প্যাম অথবা প্রতারণা</string>
|
||||
<string name="report_dialog_profanity">অশ্লীলতা বা বিদ্বেষপূর্ণ আচরণ</string>
|
||||
<string name="report_dialog_impersonation">ক্রূর উদ্দেশ্য সাধনে ছদ্মবেশ</string>
|
||||
<string name="report_dialog_nudity">নগ্নতা বা গ্রাফিক আধেয়</string>
|
||||
<string name="report_dialog_illegal">বেআইনী আচরণ</string>
|
||||
<string name="report_dialog_blocking_a_user">কোনো ব্যবহারকারীকে ব্লক করে রাখলে আপনার অ্যাপে তাদের কোনো আধেয় দেখা যাবে না। তবে আপনার নোটগুলি সর্বজনীনভাবে উন্মুক্ত, আপনার দ্বারা ব্লক করা ব্যক্তিরাও তা দেখতে পাবে। ব্লক করে রাখা ব্যবহারকারীদেরকে নিরাপত্তা-ফিল্টার স্ক্রিনে তালিকাভুক্ত করা হয়েছে।</string>
|
||||
<string name="report_dialog_block_hide_user_btn"><![CDATA[ব্যবহারকারীকে ব্লক করুন এবং আড়ালে রাখুন]]></string>
|
||||
<string name="report_dialog_report_btn">অবমাননার অভিযোগ করুন</string>
|
||||
<string name="report_dialog_reminder_public">পেশকৃত সমস্ত অভিযোগ সর্বজনীনভাবে দৃশ্যমান হবে৷</string>
|
||||
<string name="report_dialog_additional_reason_placeholder">আপনি চাইলে আপনার অভিযোগের সাথে অতিরিক্ত অনুসঙ্গ যোগ করতে পারেন…</string>
|
||||
<string name="report_dialog_additional_reason_label">সংযোজিত অনুসঙ্গ</string>
|
||||
<string name="report_dialog_select_reason_label">কারণ</string>
|
||||
<string name="report_dialog_select_reason_placeholder">একটি কারণ নির্বাচন করুন…</string>
|
||||
<string name="report_dialog_post_report_btn">অভিযোগ পেশ করুন</string>
|
||||
<string name="report_dialog_title">ব্লক করুন এবং অভিযোগ পেশ করুন</string>
|
||||
<string name="block_only">ব্লক করুন</string>
|
||||
<string name="bookmarks">বুকমার্ক করুন</string>
|
||||
<string name="private_bookmarks">ব্যক্তিগত বুকমার্ক</string>
|
||||
<string name="public_bookmarks">সর্বজনীন বুকমার্ক</string>
|
||||
<string name="add_to_private_bookmarks">ব্যক্তিগত বুকমার্কে যুক্ত করুন</string>
|
||||
<string name="add_to_public_bookmarks">সর্বজনীন বুকমার্কে যুক্ত করুন</string>
|
||||
<string name="remove_from_private_bookmarks">ব্যক্তিগত বুকমার্ক থেকে সরিয়ে দিন</string>
|
||||
<string name="remove_from_public_bookmarks">সর্বজনীন বুকমার্ক থেকে সরিয়ে দিন</string>
|
||||
<string name="wallet_connect_service">ওয়ালেট সংযোগের পরিষেবা</string>
|
||||
<string name="wallet_connect_service_explainer">অ্যাপ থেকে বের না হয়েই জ্যাপ প্রদান করতে একটি নস্টার-সিক্রেট অনুমোদন করা হয়ে থাকে। এই নস্টার-সিক্রেটটি নিরাপদে রাখুন এবং সম্ভব হলে একটি প্রাইভেট রিলে ব্যবহার করুন</string>
|
||||
<string name="wallet_connect_service_pubkey">ওয়ালেট সংযোগের গণ চাবি</string>
|
||||
<string name="wallet_connect_service_relay">ওয়ালেট সংযোগের রিলে</string>
|
||||
<string name="wallet_connect_service_secret">ওয়ালেট সংযোগের সিক্রেট</string>
|
||||
<string name="wallet_connect_service_show_secret">গোপন চাবি দেখান</string>
|
||||
<string name="wallet_connect_service_secret_placeholder">nsec / hex ব্যক্তিগত চাবি</string>
|
||||
<string name="pledge_amount_in_sats">প্রতিশ্রুত পরিমাণ স্যাট দ্বারা জানান</string>
|
||||
<string name="post_poll">একটি পোল পেশ করুন</string>
|
||||
<string name="poll_heading_required">প্রয়োজনীয় ক্ষেত্রসমূহ:</string>
|
||||
<string name="poll_zap_recipients">জ্যাপ প্রাপকেরা</string>
|
||||
<string name="poll_primary_description">পোলের প্রাথমিক বিবরণ…</string>
|
||||
<string name="poll_option_index">বিকল্প %s</string>
|
||||
<string name="poll_option_description">পোলের বিবিধ বিকল্পের বিবরণ</string>
|
||||
<string name="poll_heading_optional">ঐচ্ছিক ক্ষেত্র:</string>
|
||||
<string name="poll_zap_value_min">ন্যূনতম জ্যাপ</string>
|
||||
<string name="poll_zap_value_max">সর্বোচ্চ জ্যাপ</string>
|
||||
<string name="poll_consensus_threshold">ঐক্যমত</string>
|
||||
<string name="poll_consensus_threshold_percent">(০–১০০)%</string>
|
||||
<string name="poll_closing_time">এই সময় পর বন্ধ করুন</string>
|
||||
<string name="poll_closing_time_days">দিনগুলি</string>
|
||||
<string name="poll_is_closed">পোলটি আর নতুন ভোট গ্রহণ করবে না</string>
|
||||
<string name="poll_zap_amount">পরিমাণটি জ্যাপ করুন</string>
|
||||
<string name="one_vote_per_user_on_atomic_votes">এই জাতীয় পোলে একজন ব্যবহারকারী শুধুমাত্র একটি করেই ভোট দিতে পারবেন</string>
|
||||
<string name="looking_for_event">"ইভেন্ট খোঁজা হচ্ছে %1$s"</string>
|
||||
<string name="custom_zaps_add_a_message">একটি সর্বজনীন বার্তা পাঠান</string>
|
||||
<string name="custom_zaps_add_a_message_private">একটি ব্যক্তিগত বার্তা পাঠান</string>
|
||||
<string name="custom_zaps_add_a_message_nonzap">একটি ইনভয়েস বার্তা পাঠান</string>
|
||||
<string name="custom_zaps_add_a_message_example">আপনার সকল কাজের জন্য আপনাকে অসংখ্য ধন্যবাদ!</string>
|
||||
<string name="lightning_create_and_add_invoice">তৈরি করুন এবং যোগ করুন</string>
|
||||
<string name="poll_author_no_vote">পোল-রচয়িতারা নিজেদের পোলে ভোট দিতে পারবেন না।</string>
|
||||
<string name="hash_verification_passed">ঐ পোস্ট এবং এই আধেয়র বিষয়বস্তু অভিন্ন</string>
|
||||
<string name="hash_verification_failed">এই আধেয়র বিষয়বস্তু পরিবর্তিত হয়েছে। লেখক হয়তো পরিবর্তনটি দেখেন নি বা অনুমোদন করেন নি</string>
|
||||
<string name="content_description_add_image">ছবি যোগ করুন</string>
|
||||
<string name="content_description_add_video">ভিডিও যোগ করুন</string>
|
||||
<string name="content_description_add_document">নথি যোগ করুন</string>
|
||||
<string name="add_content">বার্তায় যোগ করুন</string>
|
||||
<string name="content_description">বিষয়বস্তুর বিবরণ</string>
|
||||
<string name="content_description_example">সূর্যাস্তের সময় সাদা বালুকাময় সমুদ্র সৈকতে একটি নীল নৌকা</string>
|
||||
<string name="zap_type">জ্যাপের ধরন</string>
|
||||
<string name="zap_type_explainer">সকল বিকল্পগুলিতে জ্যাপের ধরন</string>
|
||||
<string name="zap_type_public">সর্বজনীন</string>
|
||||
<string name="zap_type_public_explainer">লেনদেন এবং বার্তা সকলে দেখতে পারেন</string>
|
||||
<string name="zap_type_private">ব্যক্তিগত</string>
|
||||
<string name="zap_type_private_explainer">প্রেরক এবং প্রাপক একে অপরকে দেখতে এবং বার্তা পড়তে পারেন</string>
|
||||
<string name="zap_type_anonymous">অজ্ঞাত-পরিচয়</string>
|
||||
<string name="zap_type_anonymous_explainer">প্রাপক এবং জনসাধারণ জানেন না কে প্রদত্ত অর্থ পাঠিয়েছেন</string>
|
||||
<string name="zap_type_nonzap">নন-জ্যাপ</string>
|
||||
<string name="zap_type_nonzap_explainer">নস্টারে কোনো সন্ধান নেই, কেবল লাইটনিংয়ে আছে</string>
|
||||
<string name="file_server">ফাইল সার্ভার</string>
|
||||
<string name="zap_forward_lnAddress">LN ঠিকানা বা @ইউজার</string>
|
||||
<string name="upload_server_imgur">imgur.com - বিশ্বস্ত</string>
|
||||
<string name="upload_server_imgur_explainer">Imgur ফাইলটি পরিবর্তন করতে পারে</string>
|
||||
<string name="upload_server_nostrimg">nostrimg.com - বিশ্বস্ত</string>
|
||||
<string name="upload_server_nostrimg_explainer">NostrImg ফাইলটি পরিবর্তন করতে পারে</string>
|
||||
<string name="upload_server_nostrbuild">nostr.build - বিশ্বস্ত</string>
|
||||
<string name="upload_server_nostrbuild_explainer">Nostr.build ফাইলটি পরিবর্তন করতে পারে</string>
|
||||
<string name="upload_server_nostrfilesdev">nostrfiles.dev - বিশ্বস্ত</string>
|
||||
<string name="upload_server_nostrfilesdev_explainer">Nostrfiles.dev ফাইলটি পরিবর্তন করতে পারে</string>
|
||||
<string name="upload_server_nostrcheckme">nostrcheck.me - বিশ্বস্ত</string>
|
||||
<string name="upload_server_nostrcheckme_explainer">nostrcheck.me ফাইলটি পরিবর্তন করতে পারে</string>
|
||||
<string name="upload_server_imgur_nip94">যাচাইযোগ্য Imgur (NIP-94)</string>
|
||||
<string name="upload_server_imgur_nip94_explainer">Imgur ফাইলটি পরিবর্তন করেছে কিনা তা পরীক্ষা করে। নতুন NIP: অন্য ক্লায়েন্টরা এটি দেখতে নাও পেতে পারে</string>
|
||||
<string name="upload_server_nostrimg_nip94">যাচাইযোগ্য NostrImg (NIP-94)</string>
|
||||
<string name="upload_server_nostrimg_nip94_explainer">NostrImg ফাইলটি পরিবর্তন করেছে কিনা তা পরীক্ষা করে। নতুন NIP: অন্য ক্লায়েন্টরা এটি দেখতে নাও পেতে পারে</string>
|
||||
<string name="upload_server_nostrbuild_nip94">যাচাইযোগ্য Nostr.build (NIP-94)</string>
|
||||
<string name="upload_server_nostrbuild_nip94_explainer">Nostr.build ফাইলটি পরিবর্তন করেছে কিনা তা পরীক্ষা করে। নতুন NIP: অন্য ক্লায়েন্টরা এটি দেখতে নাও পেতে পারে</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94">যাচাইযোগ্য Nostrfiles.dev (NIP-94)</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94_explainer">Nostrfiles.dev ফাইলটি পরিবর্তন করেছে কিনা তা পরীক্ষা করে। নতুন NIP: অন্য ক্লায়েন্টরা এটি দেখতে নাও পেতে পারে</string>
|
||||
<string name="upload_server_nostrcheckme_nip94">যাচাইযোগ্য Nostrcheck.me (NIP-94)</string>
|
||||
<string name="upload_server_nostrcheckme_nip94_explainer">Nostrcheck.me ফাইলটি পরিবর্তন করেছে কিনা তা পরীক্ষা করে। নতুন NIP: অন্য ক্লায়েন্টরা এটি দেখতে নাও পেতে পারে</string>
|
||||
<string name="upload_server_relays_nip95">আপনার রিলেগুলি (NIP-95)</string>
|
||||
<string name="upload_server_relays_nip95_explainer">ফাইলগুলো আপনার রিলে দ্বারা গৃহীত হয়। নতুন NIP: তারা এটি সমর্থন করে কিনা দেখে নিন</string>
|
||||
<string name="connect_via_tor_short">Tor/Orbot সেটআপ করুন</string>
|
||||
<string name="connect_via_tor">আপনার Orbot সেটআপের মাধ্যমে সংযুক্ত হোন</string>
|
||||
<string name="do_you_really_want_to_disable_tor_title">Orbot/Tor থেকে সংযোগ বিচ্ছিন্ন করতে চান?</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">আপনার ডেটা অবিলম্বে নিয়মিত নেটওয়ার্কে স্থানান্তর করা হবে</string>
|
||||
<string name="yes">হ্যাঁ</string>
|
||||
<string name="no">না</string>
|
||||
<string name="follow_list_selection">অনুসরণ তালিকা</string>
|
||||
<string name="follow_list_kind3follows">অনুসৃত সকলেরা</string>
|
||||
<string name="follow_list_global">বৈশ্বিক</string>
|
||||
<string name="connect_through_your_orbot_setup_markdown"> ## Tor এর মাধ্যমে Orbot এর সাথে সংযুক্ত হোন
|
||||
\n\n১. [Orbot] নামান (https://play.google.com/store/apps/details?id=org.torproject.android)
|
||||
\n২. Orbot চালু করুন
|
||||
\n৩. Orbot এ ঢুকে Socks পোর্টটি খেয়াল করুন। এটি ৯০৫০ তে নির্দিষ্ট করা থাকে
|
||||
\n৪. প্রয়োজনে Orbot এ পোর্টটি বদলে দিন
|
||||
\n৫. স্ক্রিনের Socks পোর্টটি প্রস্তুত করুন
|
||||
\n৬. Orbot কে প্রক্সি হিসেবে ব্যবহার করতে Activate বোতামটি চাপুন
|
||||
</string>
|
||||
<string name="orbot_socks_port">Orbot এর Socks পোর্ট</string>
|
||||
<string name="invalid_port_number">পোর্ট নম্বরটি অকার্যকর</string>
|
||||
<string name="use_orbot">Orbot ব্যবহার করুন</string>
|
||||
<string name="disconnect_from_your_orbot_setup">Tor/Orbot থেকে সংযোগ বিচ্ছিন্ন করুন</string>
|
||||
<string name="app_notification_dms_channel_name">ব্যক্তিগত বার্তাসমূহ</string>
|
||||
<string name="app_notification_dms_channel_description">যখন একটি ব্যক্তিগত বার্তা আসে তখন আপনাকে অবহিত করে</string>
|
||||
<string name="app_notification_zaps_channel_name">জ্যাপ গৃহীত হয়েছে</string>
|
||||
<string name="app_notification_zaps_channel_description">কেউ জ্যাপ পাঠালে আপনাকে অবহিত করে</string>
|
||||
<string name="app_notification_zaps_channel_message">%1$s স্যাট</string>
|
||||
<string name="app_notification_zaps_channel_message_from">%1$s থেকে</string>
|
||||
<string name="app_notification_zaps_channel_message_for">%1$s এর জন্য</string>
|
||||
<string name="reply_notify">অবহিত করুন: </string>
|
||||
<string name="channel_list_join_conversation">আলোচনায় যুক্ত হোন</string>
|
||||
<string name="channel_list_user_or_group_id">ব্যবহারকারী কিংবা গ্রুপের আইডি</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub, nevent অথবা hex</string>
|
||||
<string name="channel_list_create_channel">তৈরি করুন</string>
|
||||
<string name="channel_list_join_channel">যুক্ত হোন</string>
|
||||
<string name="today">আজ</string>
|
||||
<string name="content_warning">আধেয় বিষয়ক সতর্কতা</string>
|
||||
<string name="content_warning_explanation">এই পোস্টে সংবেদনশীল উপাদান রয়েছে যা কারো জন্য আপত্তিকর কিংবা সমস্যাজনক হতে পারে</string>
|
||||
<string name="content_warning_hide_all_sensitive_content">সংবেদনশীল আধেয় সবসময় আড়ালে রাখুন</string>
|
||||
<string name="content_warning_show_all_sensitive_content">সংবেদনশীল বিষয়বস্তু সবসময় খোলামেলা দেখান</string>
|
||||
<string name="content_warning_see_warnings">সবসময় আধেয় বিষয়ক সতর্কতা দেখান</string>
|
||||
<string name="recommended_apps">প্রস্তাবিত: </string>
|
||||
<string name="filter_spam_from_strangers">অপরিচিতদের কাছ থেকে স্প্যাম ফিল্টার করুন</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">কোনো পোস্টে আপনার অনুসরীদের থেকে অভিযোগ থাকলে অবহিত হোন</string>
|
||||
<string name="new_reaction_symbol">প্রতিক্রিয়ার নতুন প্রতীক</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">প্রতিক্রিয়ার কোনো ধরন বেছে নেওয়া হয়নি। পরিবর্তন করতে দীর্ঘক্ষণ চাপুন</string>
|
||||
<string name="zapraiser">জ্যাপরেইজার</string>
|
||||
<string name="zapraiser_explainer">এই পোস্টের জন্য স্যাটের একটি লক্ষ্যমাত্রা যোগ করে। সহায়তাকারী ক্লায়েন্টরা অনুদানকে উৎসাহিত করার জন্য একে একটি অগ্রগতির মানদণ্ড হিসাবে দেখাতে পারে</string>
|
||||
<string name="zapraiser_target_amount_in_sats">পরিমাণের লক্ষ্যমাত্রা স্যাট দ্বারা জানান</string>
|
||||
<string name="sats_to_complete">জ্যাপরাইজার %1$s তে আছে। %2$s স্যাট লক্ষ্যমাত্রা</string>
|
||||
<string name="read_from_relay">রিলে থেকে পড়ুন</string>
|
||||
<string name="write_to_relay">রিলেতে লিখুন</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">%1$s থেকে রিলের তথ্যের জন্য চেষ্টা করার সময় একটি ত্রুটি ঘটেছে৷</string>
|
||||
<string name="owner">মালিক</string>
|
||||
<string name="version">সংস্করণ</string>
|
||||
<string name="software">সফটওয়্যার</string>
|
||||
<string name="contact">যোগাযোগ</string>
|
||||
<string name="supports">সমর্থিত NIP গুলো</string>
|
||||
<string name="admission_fees">প্রবেশমূল্য</string>
|
||||
<string name="payments_url">মূল্যপরিশোধের URL</string>
|
||||
<string name="limitations">সীমাবদ্ধতা</string>
|
||||
<string name="countries">দেশসমূহ</string>
|
||||
<string name="languages">ভাষাসমূহ</string>
|
||||
<string name="tags">ট্যাগগুলি</string>
|
||||
<string name="posting_policy">আধেয় পেশ করার নীতি</string>
|
||||
<string name="message_length">বার্তার ব্যাপ্তি</string>
|
||||
<string name="subscriptions">সকল সদস্যতা</string>
|
||||
<string name="filters">ফিল্টারগুলি</string>
|
||||
<string name="subscription_id_length">সদস্যতা আইডির ব্যাপ্তি</string>
|
||||
<string name="minimum_prefix">ন্যূনতম উপসর্গ</string>
|
||||
<string name="maximum_event_tags">সর্বাধিক ইভেন্ট ট্যাগ</string>
|
||||
<string name="content_length">আধেয়র ব্যাপ্তি</string>
|
||||
<string name="minimum_pow">ন্যূনতম PoW</string>
|
||||
<string name="auth">অনুমোদন</string>
|
||||
<string name="payment">মূল্যপরিশোধ</string>
|
||||
<string name="cashu">ক্যাশু টোকেন</string>
|
||||
<string name="cashu_redeem">খালাস করুন</string>
|
||||
<string name="no_lightning_address_set">কোনো লাইটনিং ঠিকানা সেট করা নেই</string>
|
||||
<string name="copied_token_to_clipboard">টোকেনটি ক্লিপবোর্ডে কপি করা হয়েছে</string>
|
||||
<string name="live_stream_live_tag">লাইভ</string>
|
||||
<string name="live_stream_offline_tag">অফলাইন</string>
|
||||
<string name="live_stream_ended_tag">সমাপ্ত</string>
|
||||
<string name="live_stream_planned_tag">সময়সূচি নির্ধারিত</string>
|
||||
<string name="live_stream_is_offline">লাইভস্ট্রিমটি অফলাইন আছে</string>
|
||||
<string name="live_stream_has_ended">লাইভস্ট্রিম সমাপ্ত</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">লগ আউট করলে আপনার সমস্ত স্থানীয় তথ্য মুছে যাবে। অ্যাকাউন্ট সুরক্ষিত রাখতে আপনার ব্যক্তিগত চাবিটি নিরাপদে সংরক্ষিত আছে কিনা নিশ্চিত হোন। সামনে আগাতে চান?</string>
|
||||
<string name="followed_tags">অনুসৃত ট্যাগগুলি</string>
|
||||
<string name="relay_setup">রিলেগুলি</string>
|
||||
<string name="discover_live">লাইভ</string>
|
||||
<string name="discover_community">কম্যুনিটি</string>
|
||||
<string name="discover_chat">বার্তালাপগুলি</string>
|
||||
<string name="community_approved_posts">অনুমোদিত পোস্টগুলি</string>
|
||||
<string name="groups_no_descriptor">এই দলটির কোনো বিবরণ কিংবা নীতিমালা নেই। এগুলো যুক্ত করতে দলনেতার সাথে কথা বলুন</string>
|
||||
<string name="community_no_descriptor">এই কম্যুনিটির কোনো বিবরণ কিংবা নীতিমালা নেই। এগুলো যুক্ত করতে এর মালিকের সাথে কথা বলুন</string>
|
||||
<string name="add_sensitive_content_label">সংবেদনশীল আধেয়</string>
|
||||
<string name="add_sensitive_content_description">সংবেদনশীল আধেয় দেখানোর আগে এ সম্পর্কিত সতর্কতা প্রদর্শন করে</string>
|
||||
<string name="settings">সেটিংস</string>
|
||||
<string name="connectivity_type_always">সবসময়</string>
|
||||
<string name="connectivity_type_wifi_only">শুধুমাত্র ওয়াইফাইতে</string>
|
||||
<string name="connectivity_type_never">কখনো না</string>
|
||||
<string name="system">সিস্টেম</string>
|
||||
<string name="light">উজ্জ্বল</string>
|
||||
<string name="dark">আঁধারি</string>
|
||||
<string name="application_preferences">অ্যাপ্লিকেশনের পছন্দসমূহ</string>
|
||||
<string name="language">ভাষা</string>
|
||||
<string name="theme">থিম</string>
|
||||
<string name="automatically_load_images_gifs">ছবির পূর্বরূপ</string>
|
||||
<string name="automatically_play_videos">ভিডিও প্লেব্যাক</string>
|
||||
<string name="automatically_show_url_preview">URL-এর পূর্বরূপ</string>
|
||||
<string name="load_image">ছবিটি লোড করুন</string>
|
||||
<string name="spamming_users">স্প্যামাররা</string>
|
||||
<string name="muted_button">মৌন করে রাখা। এটি তুলে নিতে ক্লিক করুন</string>
|
||||
<string name="mute_button">শব্দ চালু আছে। মৌন করতে ক্লিক করুন</string>
|
||||
<string name="search_button">স্থানীয় এবং দূরবর্তী রেকর্ড অনুসন্ধান করুন</string>
|
||||
<string name="nip05_verified">নস্টার ঠিকানাটি যাচাই করা হয়েছে</string>
|
||||
<string name="nip05_failed">নস্টার ঠিকানাটি যাচাই করা যায় নি</string>
|
||||
<string name="nip05_checking">নস্টার ঠিকানাটি পরীক্ষা করে দেখা হচ্ছে</string>
|
||||
<string name="select_deselect_all">সবগুলিকে নির্বাচন/অনির্বাচন করুন</string>
|
||||
<string name="default_relays">পূর্ব-নির্ধারিত</string>
|
||||
<string name="select_a_relay_to_continue">চালিয়ে যেতে একটি রিলে নির্বাচন করুন</string>
|
||||
<string name="zap_forward_title">জ্যাপটি পাঠান:</string>
|
||||
<string name="zap_forward_explainer">সহায়তাকারী ক্লায়েন্টরা আপনার পরিবর্তে নীচের LN ঠিকানা বা ব্যবহারকারীর প্রোফাইলে জ্যাপ পাঠাবে</string>
|
||||
<string name="geohash_title">আপনার অবস্থান এভাবে প্রকাশ করুন </string>
|
||||
<string name="geohash_explainer">পোস্টে আপনার অবস্থানের একটি জিওহ্যাশ যোগ করে। জনসাধারণ জানবে যে আপনি বর্তমান অবস্থান থেকে ৫ কিলোমিটার (৩ মাইল) এর মধ্যে আছেন</string>
|
||||
<string name="add_sensitive_content_explainer">আপনার পেশকৃত আধেয় দেখানোর আগে সংবেদনশীল আধেয়-সতর্কতা জারি করে। এটি সেসকল আধেয়র জন্য আদর্শ যেখানে NSFW উপাদান কিংবা এমনকিছু থাকতে পারে যা কারো কারো জন্য আপত্তিকর কিংবা সমস্যাজনক মনে হতে পারে।</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_title">নতুন ফিচার</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_description">এই মোড সক্রিয় করার জন্য অ্যামেথিস্টকে একটি NIP-24 বার্তা পাঠাতে হবে। (GiftWrapped, সিল করা সরাসরি এবং দলগত বার্তা)। NIP-24 নতুন এবং বেশিরভাগ ক্লায়েন্ট এখনও এটি বাস্তবায়ন করেনি। নিশ্চিত করুন যে প্রাপক একটি সামঞ্জস্যপূর্ণ ক্লায়েন্ট ব্যবহার করছে।</string>
|
||||
<string name="new_feature_nip24_activate">সক্রিয় করুন</string>
|
||||
<string name="messages_create_public_chat">সর্বজনীন</string>
|
||||
<string name="messages_new_message">ব্যক্তিগত</string>
|
||||
<string name="messages_new_message_to">প্রতি</string>
|
||||
<string name="messages_new_message_subject">বিষয়</string>
|
||||
<string name="messages_new_message_subject_caption">আলোচনার বিষয়</string>
|
||||
<string name="messages_new_message_to_caption">"\@User1, @User2, @User3"</string>
|
||||
<string name="messages_group_descriptor">এই দলের সদস্যরা</string>
|
||||
<string name="messages_new_subject_message">সদস্যদের নিকট ব্যাখ্যা</string>
|
||||
<string name="messages_new_subject_message_placeholder">নতুন লক্ষ্যের জন্য নাম পরিবর্তন করা হচ্ছে।</string>
|
||||
<string name="language_description">অ্যাপের ইন্টারফেসের জন্য</string>
|
||||
<string name="theme_description">আঁধারি, উজ্জ্বল কিংবা সিস্টেমের থিম</string>
|
||||
<string name="automatically_load_images_gifs_description">স্বয়ংক্রিয়ভাবে ছবি এবং GIF লোড করুন</string>
|
||||
<string name="automatically_play_videos_description">স্বয়ংক্রিয়ভাবে ভিডিও এবং GIF চালু করুন</string>
|
||||
<string name="automatically_show_url_preview_description">URL এর পূর্বরূপ দেখান</string>
|
||||
<string name="load_image_description">কখন ছবি লোড করতে হবে</string>
|
||||
<string name="copy_url_to_clipboard">URL ক্লিপবোর্ডে কপি করুন</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">নোট আইডি ক্লিপবোর্ডে কপি করুন</string>
|
||||
<string name="created_at">যেখানে নির্মিত</string>
|
||||
<string name="rules">নিয়ম</string>
|
||||
<string name="status_update">আপনার স্ট্যাটাস আপডেট করুন</string>
|
||||
<string name="lightning_wallets_not_found">Error parsing error message</string>
|
||||
<string name="poll_zap_value_min_max_explainer">জ্যাপের পরিমাণ দ্বারা ভোটসংখ্যা গোণা হয়। আপনি স্প্যামার এড়াতে একটি সর্বনিম্ন পরিমাণ এবং একটি বড়ো জ্যাপার দ্বারা পোল দখল এড়াতে সর্বোচ্চ পরিমাণ স্যাট নির্ধারণ করতে পারেন। প্রতিটি ভোটের মূল্য একই পরিমাণে রয়েছে তা নিশ্চিত করতে উভয় ক্ষেত্রেই একই পরিমাণ ব্যবহার করুন। যেকোনো পরিমাণ গ্রহণ করতে এটি খালি ছেড়ে দিন।</string>
|
||||
<string name="error_dialog_zap_error">জ্যাপ পাঠানো যায় নি</string>
|
||||
<string name="error_dialog_talk_to_user">ব্যবহারকারীকে বার্তা দিন</string>
|
||||
<string name="error_dialog_button_ok">ঠিক আছে</string>
|
||||
<string name="relay_information_document_error_assemble_url">%1$s: %2$s পৌঁছাতে ব্যর্থ</string>
|
||||
<string name="relay_information_document_error_reach_server">%1$s: %2$s পৌঁছাতে ব্যর্থ</string>
|
||||
<string name="relay_information_document_error_parse_result">%1$s: %2$s থেকে ফলাফল পার্স করা যায় নি</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s কোড দিয়ে ব্যর্থ হয়েছে %2$s</string>
|
||||
<string name="active_for">এর জন্য সক্রিয় করুন: </string>
|
||||
<string name="active_for_home">নীড়</string>
|
||||
<string name="active_for_msg">বার্তাসমূহ</string>
|
||||
<string name="active_for_chats">আলাপচারিতা</string>
|
||||
<string name="active_for_global">বৈশ্বিক</string>
|
||||
<string name="active_for_search">অনুসন্ধান করুন</string>
|
||||
<string name="zap_split_title">জ্যাপগুলি ভাগ করুন এবং পাঠান</string>
|
||||
<string name="zap_split_explainer">সমর্থিত ক্লায়েন্টরা জ্যাপ ভাগ করবে এবং আপনার পরিবর্তে এখানে সংযুক্ত ব্যবহারকারীদেরকে পাঠাবে</string>
|
||||
<string name="zap_split_serarch_and_add_user">ব্যবহারকারীদের অনুসন্ধান করুন এবং যুক্ত করুন</string>
|
||||
<string name="zap_split_serarch_and_add_user_placeholder">ইউজারনেম কিংবা প্রদর্শিত নাম দিন</string>
|
||||
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">%1$s ব্যবহারকারীর স্যাট গ্রহণের উপযোগী কোনো লাইটনিং ঠিকানা সেট করা নেই</string>
|
||||
<string name="zap_split_weight">শতাংশ</string>
|
||||
<string name="zap_split_weight_placeholder">২৫</string>
|
||||
<string name="splitting_zaps_with">এর সাথে জ্যাপ ভাগ করা হচ্ছে</string>
|
||||
<string name="forwarding_zaps_to">একে জ্যাপ পাঠানো হচ্ছে</string>
|
||||
<string name="lightning_wallets_not_found2">লাইটনিং ওয়ালেট পাওয়া যায় নি</string>
|
||||
<string name="paid">পরিশোধিত</string>
|
||||
<string name="wallet_number">ওয়ালেট %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Přihlaste se pomocí soukromého klíče, abyste mohli zrušit sledování</string>
|
||||
<string name="zaps">Zapy</string>
|
||||
<string name="view_count">Počet zobrazení</string>
|
||||
<string name="boosted">boostováno</string>
|
||||
<string name="boost">Zvýšení</string>
|
||||
<string name="boosted">zvýšeno</string>
|
||||
<string name="quote">Citovat</string>
|
||||
<string name="new_amount_in_sats">Nová částka v sats</string>
|
||||
<string name="add">Přidat</string>
|
||||
@@ -290,6 +291,7 @@
|
||||
<string name="zap_type_private_explainer">Odesílatel a příjemce si mohou navzájem vidět a číst zprávu</string>
|
||||
<string name="zap_type_anonymous">Anonymní</string>
|
||||
<string name="zap_type_anonymous_explainer">Příjemce a veřejnost neví, kdo platbu poslal</string>
|
||||
<string name="zap_type_nonzap">Né Zap</string>
|
||||
<string name="zap_type_nonzap_explainer">Žádná stopa v Nostr, pouze v Lightning</string>
|
||||
<string name="file_server">Souborový server</string>
|
||||
<string name="zap_forward_lnAddress">LnAddress nebo @Uživatel</string>
|
||||
@@ -358,6 +360,7 @@
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">Varovat, když příspěvky obsahují hlášení od vašich sledovaných osob</string>
|
||||
<string name="new_reaction_symbol">Nový symbol reakce</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">Nebyly vybrány žádné typy reakcí. Dlouhým stiskem změňte</string>
|
||||
<string name="zapraiser">Zap-sběr</string>
|
||||
<string name="zapraiser_explainer">Přidá cílovou částku sats pro tento příspěvek. Podporované klienty ji mohou zobrazovat jako ukazatel postupu k podněcování darování</string>
|
||||
<string name="zapraiser_target_amount_in_sats">Cílová částka v sats</string>
|
||||
<string name="sats_to_complete">Zapraiser na %1$s. Do cíle zbývá %2$s sats</string>
|
||||
@@ -451,4 +454,23 @@
|
||||
<string name="automatically_play_videos_description">Automaticky přehrávat videa a GIFy</string>
|
||||
<string name="automatically_show_url_preview_description">Zobrazit náhledy URL</string>
|
||||
<string name="load_image_description">Kdy načíst obrázek</string>
|
||||
<string name="copy_url_to_clipboard">Kopírovat URL do schránky</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Kopírovat ID poznámky do schránky</string>
|
||||
<string name="created_at">Vytvořeno</string>
|
||||
<string name="rules">Pravidla</string>
|
||||
<string name="status_update">Aktualizovat svůj stav</string>
|
||||
<string name="lightning_wallets_not_found">Chyba při zpracování chybové zprávy</string>
|
||||
<string name="poll_zap_value_min_max_explainer">Hlasy jsou váženy podle hodnoty zapu. Můžete nastavit minimální částku, abyste se vyhnuli spammerům, a maximální částku, abyste zabránili velkým zapperům, kteří by mohli ovládnout hlasování. Použijte stejnou částku v obou polích, aby byla hodnota každého hlasu stejná. Nechte prázdné pro přijetí libovolné částky.</string>
|
||||
<string name="error_dialog_zap_error">Nelze odeslat zap</string>
|
||||
<string name="error_dialog_talk_to_user">Poslat zprávu uživateli</string>
|
||||
<string name="relay_information_document_error_assemble_url">Nepodařilo se dosáhnout %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">Nepodařilo se dosáhnout %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">Nepodařilo se zpracovat výsledek z %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s selhalo s kódem %2$s</string>
|
||||
<string name="active_for">Aktivní pro: </string>
|
||||
<string name="active_for_home">Domů</string>
|
||||
<string name="active_for_msg">Zprávy</string>
|
||||
<string name="active_for_chats">Chaty</string>
|
||||
<string name="active_for_global">Celosvětové</string>
|
||||
<string name="active_for_search">Hledání</string>
|
||||
</resources>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Melden Sie sich mit einem privaten Schlüssel an, um zu folgen</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Melden Sie sich mit einem privaten Schlüssel an, um nicht mehr zu folgen</string>
|
||||
<string name="view_count">Aufrufe</string>
|
||||
<string name="boost">Verstärken</string>
|
||||
<string name="boosted">Verstärkt</string>
|
||||
<string name="quote">Zitat</string>
|
||||
<string name="new_amount_in_sats">Neuer Betrag in Sats</string>
|
||||
@@ -169,6 +170,8 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="nip_05">Nostr-Adresse</string>
|
||||
<string name="never">nie</string>
|
||||
<string name="now">jetzt</string>
|
||||
<string name="h">s</string>
|
||||
<string name="d">t</string>
|
||||
<string name="nudity">Nacktheit</string>
|
||||
<string name="profanity_hateful_speech">Beleidigungen / Hassrede</string>
|
||||
<string name="report_hateful_speech">Hassrede melden</string>
|
||||
@@ -246,8 +249,11 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="add_to_public_bookmarks">Zu den öffentlichen Lesezeichen hinzufügen</string>
|
||||
<string name="remove_from_private_bookmarks">Aus den privaten Lesezeichen entfernen</string>
|
||||
<string name="remove_from_public_bookmarks">Aus den öffentlichen Lesezeichen entfernen</string>
|
||||
<string name="wallet_connect_service">Wallet-Verbindungsdienst</string>
|
||||
<string name="wallet_connect_service_explainer">Autorisiert einen Nostr Secret, Zaps ohne Verlassen der App zu bezahlen. Bewahren Sie den geheimen Schlüssel sicher auf und verwenden Sie nach Möglichkeit ein privates Relay</string>
|
||||
<string name="wallet_connect_service_pubkey">Wallet Connect Public Key</string>
|
||||
<string name="wallet_connect_service_relay">Wallet-Verbindungs-Relay</string>
|
||||
<string name="wallet_connect_service_secret">Wallet-Verbindungs-Geheimnis</string>
|
||||
<string name="wallet_connect_service_show_secret">Geheimen Schlüssel anzeigen</string>
|
||||
<string name="wallet_connect_service_secret_placeholder">nsec / hexadezimaler privater Schlüssel</string>
|
||||
<string name="pledge_amount_in_sats">Spendenbetrag in Sats</string>
|
||||
@@ -288,6 +294,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="zap_type_private_explainer">Absender und Empfänger können einander sehen und die Nachricht lesen</string>
|
||||
<string name="zap_type_anonymous">Anonym</string>
|
||||
<string name="zap_type_anonymous_explainer">Empfänger und die Öffentlichkeit wissen nicht, wer die Zahlung gesendet hat</string>
|
||||
<string name="zap_type_nonzap">Keine Zap</string>
|
||||
<string name="zap_type_nonzap_explainer">Keine Spur in Nostr, nur in Lightning</string>
|
||||
<string name="file_server">Dateiserver</string>
|
||||
<string name="zap_forward_lnAddress">LnAddress oder @Benutzer</string>
|
||||
@@ -321,6 +328,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="no">Nein</string>
|
||||
<string name="follow_list_selection">Folgen-Liste</string>
|
||||
<string name="follow_list_kind3follows">Alle Folgen</string>
|
||||
<string name="follow_list_global">Weltweit</string>
|
||||
<string name="connect_through_your_orbot_setup_markdown"> ## Über Tor mit Orbot verbinden
|
||||
\n\n1. Installiere [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)
|
||||
\n2. Starte Orbot
|
||||
@@ -356,6 +364,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">Warnung bei Meldungen von deinen Abonnements</string>
|
||||
<string name="new_reaction_symbol">Neues Reaktionssymbol</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">Keine Reaktionstypen ausgewählt. Lange drücken, um zu ändern</string>
|
||||
<string name="zapraiser">Zap-Sammlung</string>
|
||||
<string name="zapraiser_explainer">Fügt einen Zielbetrag in Sats für diesen Beitrag hinzu. Unterstützende Clients können dies als Fortschrittsbalken anzeigen, um Spenden zu fördern</string>
|
||||
<string name="zapraiser_target_amount_in_sats">Zielbetrag in Sats</string>
|
||||
<string name="sats_to_complete">Zapraiser bei %1$s. %2$s Sats bis zum Ziel</string>
|
||||
@@ -443,4 +452,21 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="automatically_play_videos_description">Videos und GIFs automatisch abspielen</string>
|
||||
<string name="automatically_show_url_preview_description">URL-Vorschauen anzeigen</string>
|
||||
<string name="load_image_description">Wann Bilder geladen werden sollen</string>
|
||||
<string name="copy_url_to_clipboard">URL in die Zwischenablage kopieren</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Notiz-ID in die Zwischenablage kopieren</string>
|
||||
<string name="created_at">Erstellt am</string>
|
||||
<string name="rules">Regeln</string>
|
||||
<string name="status_update">Status aktualisieren</string>
|
||||
<string name="lightning_wallets_not_found">Fehler beim Verarbeiten der Fehlermeldung</string>
|
||||
<string name="poll_zap_value_min_max_explainer">Die Abstimmungen werden nach der Höhe des Zaps gewichtet. Sie können einen Mindestbetrag festlegen, um Spam zu verhindern, und einen Höchstbetrag, um zu verhindern, dass große Zapper die Abstimmung dominieren. Verwenden Sie denselben Betrag in beiden Feldern, um sicherzustellen, dass jeder Stimme der gleiche Wert zukommt. Lassen Sie es leer, um jeden Betrag zu akzeptieren.</string>
|
||||
<string name="error_dialog_zap_error">Zap konnte nicht gesendet werden</string>
|
||||
<string name="error_dialog_talk_to_user">Mit dem Benutzer kommunizieren</string>
|
||||
<string name="relay_information_document_error_assemble_url">Konnte %1$s nicht erreichen: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">Konnte %1$s nicht erreichen: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">Konnte das Ergebnis von %1$s nicht verarbeiten: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s fehlgeschlagen mit Code %2$s</string>
|
||||
<string name="active_for">Aktiv für: </string>
|
||||
<string name="active_for_home">Startseite</string>
|
||||
<string name="active_for_msg">Nachrichten</string>
|
||||
<string name="active_for_search">Suche</string>
|
||||
</resources>
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
<string name="conversations">Konversacioj</string>
|
||||
<string name="notes">Notoj</string>
|
||||
<string name="replies">Respondoj</string>
|
||||
<string name="follows">"Sekvoj"</string>
|
||||
<string name="follows">"Sekvitaj"</string>
|
||||
<string name="reports">"Raportoj"</string>
|
||||
<string name="more_options">Pli da Opcioj</string>
|
||||
<string name="relays">" Plusendiloj"</string>
|
||||
|
||||
@@ -1,2 +1,487 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="point_to_the_qr_code">Osoita QR-koodiin</string>
|
||||
<string name="show_qr">Näytä QR</string>
|
||||
<string name="profile_image">Profiilikuva</string>
|
||||
<string name="scan_qr">Skannaa QR</string>
|
||||
<string name="show_anyway">Näytä silti</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Viesti raportoitiin käyttäjän toimesta</string>
|
||||
<string name="post_not_found">Tapahtumaa ladataan tai sitä ei löydy relelistalta</string>
|
||||
<string name="channel_image">Kanavan kuva</string>
|
||||
<string name="referenced_event_not_found">Viitattua tapahtumaa ei löytynyt</string>
|
||||
<string name="could_not_decrypt_the_message">Viestin purku epäonnistui</string>
|
||||
<string name="group_picture">Ryhmäkuva</string>
|
||||
<string name="explicit_content">Julkeaa sisältöä</string>
|
||||
<string name="spam">Roskaposti</string>
|
||||
<string name="impersonation">Väärennös</string>
|
||||
<string name="illegal_behavior">Laiton toiminta</string>
|
||||
<string name="unknown">Tuntematon</string>
|
||||
<string name="relay_icon">Relekuvake</string>
|
||||
<string name="unknown_author">Tuntematon tekijä</string>
|
||||
<string name="copy_text">Kopioi teksti</string>
|
||||
<string name="copy_user_pubkey">Kopioi tekijän tunniste</string>
|
||||
<string name="copy_note_id">Kopioi viestin tunniste</string>
|
||||
<string name="broadcast">Lähetä</string>
|
||||
<string name="request_deletion">Pyydä poistoa</string>
|
||||
<string name="block_report">Estä / Ilmoita</string>
|
||||
<string name="block_hide_user"><![CDATA[Estä & Piilota käyttäjä]]></string>
|
||||
<string name="report_spam_scam">Ilmoita roskapostista / huijauksesta</string>
|
||||
<string name="report_impersonation">Ilmoita väärennöksestä</string>
|
||||
<string name="report_explicit_content">Ilmoita julkeasta sisällöstä</string>
|
||||
<string name="report_illegal_behaviour">Ilmoita laittomasta toiminnasta</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_reply">Kirjaudu sisään yksityisellä avaimella viestehin vastaamiseksi</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Kirjaudu sisään yksityisellä avaimella tehostaaksesi viestejä</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">Kirjaudu sisään yksityisellä avaimella tykätäksesi viesteistä</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">Zap-määrää ei ole asetettu. Pidä painiketta pohjassa vaihtaaksesi sitä</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Kirjaudu sisään yksityisellä avaimella Zapien lähettämiseksi</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Kirjaudu sisään yksityisellä avaimella voidaksesi seurata</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Kirjaudu sisään yksityisellä avaimella seuraamisen lopettamiseksi</string>
|
||||
<string name="zaps">Zapit</string>
|
||||
<string name="view_count">Katselukerrat</string>
|
||||
<string name="boost">Tehosta</string>
|
||||
<string name="boosted">tehostettu</string>
|
||||
<string name="quote">Lainaa</string>
|
||||
<string name="new_amount_in_sats">Uusi määrä satseissa</string>
|
||||
<string name="add">Lisää</string>
|
||||
<string name="replying_to">"vastaus kohteeseen "</string>
|
||||
<string name="and">" ja "</string>
|
||||
<string name="in_channel">"kanavassa "</string>
|
||||
<string name="profile_banner">Profiilin banneri</string>
|
||||
<string name="payment_successful">Maksu onnistui</string>
|
||||
<string name="error_parsing_error_message">Virhe virhesanomaa tulkittaessa</string>
|
||||
<string name="following">" Seuratut"</string>
|
||||
<string name="followers">" Seuraajat"</string>
|
||||
<string name="profile">Profiili</string>
|
||||
<string name="security_filters">Turvallisuussuodattimet</string>
|
||||
<string name="log_out">Kirjaudu ulos</string>
|
||||
<string name="show_more">Näytä lisää</string>
|
||||
<string name="lightning_invoice">Lightning lasku</string>
|
||||
<string name="pay">Maksa</string>
|
||||
<string name="lightning_tips">Lightning tipit</string>
|
||||
<string name="note_to_receiver">Viesti vastaanottajalle</string>
|
||||
<string name="thank_you_so_much">Kiitos paljon!</string>
|
||||
<string name="amount_in_sats">Määrä satseissa</string>
|
||||
<string name="send_sats">Lähetä satseja</string>
|
||||
<string name="error_parsing_preview_for">"Virhe tulkittaessa esikatselua kohteelle %1$s : %2$s"</string>
|
||||
<string name="preview_card_image_for">"Esikatsele kortin kuvaa kohteelle %1$s"</string>
|
||||
<string name="new_channel">Uusi kanava</string>
|
||||
<string name="channel_name">Kanavan nimi</string>
|
||||
<string name="my_awesome_group">Oma upea ryhmäni</string>
|
||||
<string name="picture_url">Kuvan URL</string>
|
||||
<string name="description">Kuvaus</string>
|
||||
<string name="about_us">"Tietoa meistä.. "</string>
|
||||
<string name="what_s_on_your_mind">Mitä mielessä?</string>
|
||||
<string name="post">Lähetä</string>
|
||||
<string name="save">Tallenna</string>
|
||||
<string name="create">Luo</string>
|
||||
<string name="cancel">Peruuta</string>
|
||||
<string name="failed_to_upload_the_image">Kuvan lataus epäonnistui</string>
|
||||
<string name="relay_address">Releosoite</string>
|
||||
<string name="posts">Viestit</string>
|
||||
<string name="bytes">Tavua</string>
|
||||
<string name="errors">Virheet</string>
|
||||
<string name="home_feed">Kotisyöte</string>
|
||||
<string name="private_message_feed">Yksityinen viestisyöte</string>
|
||||
<string name="public_chat_feed">Julkinen chat-syöte</string>
|
||||
<string name="global_feed">Maailmanlaajuinen syöte</string>
|
||||
<string name="search_feed">Hae syötettä</string>
|
||||
<string name="add_a_relay">Lisää rele</string>
|
||||
<string name="display_name">Näyttönimi</string>
|
||||
<string name="my_display_name">Oma näyttönimeni</string>
|
||||
<string name="username">Käyttäjänimi</string>
|
||||
<string name="my_username">Oma käyttäjänimeni</string>
|
||||
<string name="about_me">Tietoja minusta</string>
|
||||
<string name="avatar_url">Avatarin URL</string>
|
||||
<string name="banner_url">Bannerin URL</string>
|
||||
<string name="website_url">Verkkosivuston URL</string>
|
||||
<string name="ln_address">LN-osoite</string>
|
||||
<string name="ln_url_outdated">LN-URL (vanhentunut)</string>
|
||||
<string name="image_saved_to_the_gallery">Kuva tallennettu galleriaan</string>
|
||||
<string name="failed_to_save_the_image">Kuvan tallennus epäonnistui</string>
|
||||
<string name="upload_image">Lataa kuva</string>
|
||||
<string name="uploading">Ladataan…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">Käyttäjällä ei ole asetettuna Lightning-osoitetta satsien vastaanottamiseksi</string>
|
||||
<string name="reply_here">"vastaa tähän.. "</string>
|
||||
<string name="copies_the_note_id_to_the_clipboard_for_sharing">Kopioi viestin tunniste leikepöydälle Nostressa jakamista varten</string>
|
||||
<string name="copy_channel_id_note_to_the_clipboard">Kopioi kanavan tunniste (viesti) leikepöydälle</string>
|
||||
<string name="edits_the_channel_metadata">Muokkaa kanavan metatietoja</string>
|
||||
<string name="join">Liity</string>
|
||||
<string name="known">Tunnetut</string>
|
||||
<string name="new_requests">Uudet pyynnöt</string>
|
||||
<string name="blocked_users">Estetyt käyttäjät</string>
|
||||
<string name="new_threads">Uudet keskustelut</string>
|
||||
<string name="conversations">Keskustelut</string>
|
||||
<string name="notes">Viestit</string>
|
||||
<string name="replies">Vastaukset</string>
|
||||
<string name="follows">"Seuratut"</string>
|
||||
<string name="reports">"Ilmiannetut"</string>
|
||||
<string name="more_options">Lisää vaihtoehtoja</string>
|
||||
<string name="relays">" Releet"</string>
|
||||
<string name="website">Verkkosivusto</string>
|
||||
<string name="lightning_address">Lightning-osoite</string>
|
||||
<string name="copies_the_nsec_id_your_password_to_the_clipboard_for_backup">Kopioi Nsec-tunniste (salasanasi) leikepöydälle varmuuskopiointia varten</string>
|
||||
<string name="copy_private_key_to_the_clipboard">Kopioi yksityinen avain leikepöydälle</string>
|
||||
<string name="copies_the_public_key_to_the_clipboard_for_sharing">Kopioi julkinen avain leikepöydälle jakamista varten</string>
|
||||
<string name="copy_public_key_npub_to_the_clipboard">Kopioi julkinen avain (NPub) leikepöydälle</string>
|
||||
<string name="send_a_direct_message">Lähetä suora viesti</string>
|
||||
<string name="edits_the_user_s_metadata">Muokkaa käyttäjän metatietoja</string>
|
||||
<string name="follow">Seuraa</string>
|
||||
<string name="follow_back">Seuraa takaisin</string>
|
||||
<string name="unblock">Poista esto</string>
|
||||
<string name="copy_user_id">Kopioi käyttäjän tunniste</string>
|
||||
<string name="unblock_user">Poista käyttäjän esto</string>
|
||||
<string name="npub_hex_username">"npub, käyttäjänimi, teksti"</string>
|
||||
<string name="clear">Tyhjennä</string>
|
||||
<string name="app_logo">Sovelluksen logo</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec.. tai npub..</string>
|
||||
<string name="show_password">Näytä salasana</string>
|
||||
<string name="hide_password">Piilota salasana</string>
|
||||
<string name="invalid_key">Virheellinen avain</string>
|
||||
<string name="i_accept_the">"Hyväksyn "</string>
|
||||
<string name="terms_of_use">käyttöehdot</string>
|
||||
<string name="acceptance_of_terms_is_required">Käyttöehtojen hyväksyminen on pakollista</string>
|
||||
<string name="key_is_required">Avain on pakollinen</string>
|
||||
<string name="login">Kirjaudu sisään</string>
|
||||
<string name="generate_a_new_key">Luo uusi avain</string>
|
||||
<string name="loading_feed">Ladataan syöte</string>
|
||||
<string name="error_loading_replies">"Virhe ladattaessa vastauksia: "</string>
|
||||
<string name="try_again">Yritä uudelleen</string>
|
||||
<string name="feed_is_empty">Syöte on tyhjä.</string>
|
||||
<string name="refresh">Päivitä</string>
|
||||
<string name="created">luotu</string>
|
||||
<string name="with_description_of">kuvaus</string>
|
||||
<string name="and_picture">ja kuva</string>
|
||||
<string name="changed_chat_name_to">muutti keskustelun nimen kohteeksi</string>
|
||||
<string name="description_to">kuvaus kohteeksi</string>
|
||||
<string name="and_picture_to">ja kuva kohteeksi</string>
|
||||
<string name="leave">Poistu</string>
|
||||
<string name="unfollow">Lopeta seuraaminen</string>
|
||||
<string name="channel_created">Kanava luotu</string>
|
||||
<string name="channel_information_changed_to">"Kanavan tiedot muutettu kohteeksi"</string>
|
||||
<string name="public_chat">Julkinen keskustelu</string>
|
||||
<string name="posts_received">vastaanotetut viestit</string>
|
||||
<string name="remove">Poista</string>
|
||||
<string name="translations_auto">Automaattinen</string>
|
||||
<string name="translations_translated_from">käännös kohteesta</string>
|
||||
<string name="translations_to">kohteeseen</string>
|
||||
<string name="translations_show_in_lang_first">Näytä ensin kielellä %1$s</string>
|
||||
<string name="translations_always_translate_to_lang">Aina käännä kielelle %1$s</string>
|
||||
<string name="translations_never_translate_from_lang">Älä koskaan käännä kielestä %1$s</string>
|
||||
<string name="nip_05">Nostr-osoite</string>
|
||||
<string name="never">ei koskaan</string>
|
||||
<string name="now">nyt</string>
|
||||
<string name="h">t</string>
|
||||
<string name="m">m</string>
|
||||
<string name="d">pv</string>
|
||||
<string name="nudity">Alastomuus</string>
|
||||
<string name="profanity_hateful_speech">Loukkaava kielenkäyttö / vihapuhe</string>
|
||||
<string name="report_hateful_speech">Ilmoita vihapuheesta</string>
|
||||
<string name="report_nudity_porn">Ilmoita alastomuudesta / pornosta</string>
|
||||
<string name="others">muut</string>
|
||||
<string name="mark_all_known_as_read">Merkitse kaikki Tunnetut luetuiksi</string>
|
||||
<string name="mark_all_new_as_read">Merkitse kaikki Uudet luetuiksi</string>
|
||||
<string name="mark_all_as_read">Merkitse kaikki luetuiksi</string>
|
||||
<string name="backup_keys">Varmuuskopioi Avaimet</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## 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ä.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">Salainen avain (nsec) kopioitu leikepöydälle</string>
|
||||
<string name="copy_my_secret_key">Kopioi salainen avain</string>
|
||||
<string name="biometric_authentication_failed">Tunnistautuminen epäonnistui</string>
|
||||
<string name="biometric_error">Virhe</string>
|
||||
<string name="badge_created_by">"Luonut %1$s"</string>
|
||||
<string name="badge_award_image_for">"Merkki kohteelle %1$s"</string>
|
||||
<string name="new_badge_award_notif">Olet saanut uuden merkin</string>
|
||||
<string name="award_granted_to">Merkki myönnetty kohteelle</string>
|
||||
<string name="copied_note_text_to_clipboard">Viestin teksti kopioitu leikepöydälle</string>
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">Käyttäjän @npub kopioitu leikepöydälle</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">Viestin tunniste (@note1) kopioitu leikepöydälle</string>
|
||||
<string name="select_text_dialog_top">Valitse teksti</string>
|
||||
<string name="private_conversation_notification">"<Ei voitu purkaa yksityisviestiä>\n\nSinut mainittiin yksityisessä/salatussa keskustelussa kohteiden %1$s ja %2$s välillä."</string>
|
||||
<string name="account_switch_add_account_dialog_title">Lisää uusi tili</string>
|
||||
<string name="drawer_accounts">Tilit</string>
|
||||
<string name="account_switch_select_account">Valitse tili</string>
|
||||
<string name="account_switch_add_account_btn">Lisää uusi tili</string>
|
||||
<string name="account_switch_active_account">Aktiivinen tili</string>
|
||||
<string name="account_switch_has_private_key">Sisältää salaisen avaimen</string>
|
||||
<string name="account_switch_pubkey_only">Vain julkisavain, ei salaista avainta</string>
|
||||
<string name="back">Takaisin</string>
|
||||
<string name="quick_action_select">Valitse</string>
|
||||
<string name="quick_action_share_browser_link">Jaa selaimen linkki</string>
|
||||
<string name="quick_action_share">Jaa</string>
|
||||
<string name="quick_action_copy_user_id">Kirjoittajan tunniste</string>
|
||||
<string name="quick_action_copy_note_id">Viestin tunniste</string>
|
||||
<string name="quick_action_copy_text">Kopioi teksti</string>
|
||||
<string name="quick_action_delete">Poista</string>
|
||||
<string name="quick_action_unfollow">Lopeta seuraaminen</string>
|
||||
<string name="quick_action_follow">Seuraa</string>
|
||||
<string name="quick_action_request_deletion_alert_title">Pyydä poistoa</string>
|
||||
<string name="quick_action_request_deletion_alert_body">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.</string>
|
||||
<string name="quick_action_block_dialog_btn">Estä</string>
|
||||
<string name="quick_action_delete_dialog_btn">Poista</string>
|
||||
<string name="quick_action_block">Estä</string>
|
||||
<string name="quick_action_report">Ilmoita</string>
|
||||
<string name="quick_action_delete_button">Poista</string>
|
||||
<string name="quick_action_dont_show_again_button">Älä näytä uudelleen</string>
|
||||
<string name="report_dialog_spam">Roskaposti tai huijaus</string>
|
||||
<string name="report_dialog_profanity">Loukkaava kielenkäyttö tai vihamielinen käytös</string>
|
||||
<string name="report_dialog_impersonation">Haitallinen henkilöllisyyden omiminen</string>
|
||||
<string name="report_dialog_nudity">Alastomuus tai graafinen sisältö</string>
|
||||
<string name="report_dialog_illegal">Laiton käytös</string>
|
||||
<string name="report_dialog_blocking_a_user">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ä.</string>
|
||||
<string name="report_dialog_block_hide_user_btn"><![CDATA[Estä ja piilota käyttäjä]]></string>
|
||||
<string name="report_dialog_report_btn">Ilmoita väärinkäytöstä</string>
|
||||
<string name="report_dialog_reminder_public">Kaikki ilmoitukset ovat julkisesti nähtävillä.</string>
|
||||
<string name="report_dialog_additional_reason_placeholder">Voit antaa lisäkontekstia ilmoituksellesi valinnaisesti...</string>
|
||||
<string name="report_dialog_additional_reason_label">Lisäkonteksti</string>
|
||||
<string name="report_dialog_select_reason_label">Syy</string>
|
||||
<string name="report_dialog_select_reason_placeholder">Valitse syy...</string>
|
||||
<string name="report_dialog_post_report_btn">Lähetä ilmoitus</string>
|
||||
<string name="report_dialog_title">Estä ja ilmoita</string>
|
||||
<string name="block_only">Estä</string>
|
||||
<string name="bookmarks">Kirjanmerkit</string>
|
||||
<string name="private_bookmarks">Yksityiset kirjanmerkit</string>
|
||||
<string name="public_bookmarks">Julkiset kirjanmerkit</string>
|
||||
<string name="add_to_private_bookmarks">Lisää yksityisiin kirjanmerkkeihin</string>
|
||||
<string name="add_to_public_bookmarks">Lisää julkisiin kirjanmerkkeihin</string>
|
||||
<string name="remove_from_private_bookmarks">Poista yksityisistä kirjanmerkeistä</string>
|
||||
<string name="remove_from_public_bookmarks">Poista julkisista kirjanmerkeistä</string>
|
||||
<string name="wallet_connect_service">Lompakkoyhteyspalvelu</string>
|
||||
<string name="wallet_connect_service_explainer">Valtuuttaa Nostr-salaisuuden maksamaan zapeja ilman sovelluksesta poistumista. Pidä salaisuutesi turvassa ja käytä yksityistä relettä, jos mahdollista.</string>
|
||||
<string name="wallet_connect_service_pubkey">Lompakkoyhteyden julkinen avain</string>
|
||||
<string name="wallet_connect_service_relay">Lompakkoyhteyden rele</string>
|
||||
<string name="wallet_connect_service_secret">Lompakkoyhteyden salainen avain</string>
|
||||
<string name="wallet_connect_service_show_secret">Näytä salainen avain</string>
|
||||
<string name="wallet_connect_service_secret_placeholder">nsec / heksadesimaalinen salainen avain</string>
|
||||
<string name="pledge_amount_in_sats">Luvattu määrä satseissa</string>
|
||||
<string name="post_poll">Luo äänestys</string>
|
||||
<string name="poll_heading_required">Vaaditut kohdat:</string>
|
||||
<string name="poll_zap_recipients">Zap-vastaanottajat</string>
|
||||
<string name="poll_primary_description">Pääasiallinen äänestyksen kuvaus...</string>
|
||||
<string name="poll_option_index">Vaihtoehto %s</string>
|
||||
<string name="poll_option_description">Vaihtoehdon kuvaus</string>
|
||||
<string name="poll_heading_optional">Valinnaiset kohdat:</string>
|
||||
<string name="poll_zap_value_min">Vähimmäiszap</string>
|
||||
<string name="poll_zap_value_max">Enimmäiszap</string>
|
||||
<string name="poll_consensus_threshold">Konsensus</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="poll_closing_time">Sulje äänestys</string>
|
||||
<string name="poll_closing_time_days">päivän kuluttua</string>
|
||||
<string name="poll_is_closed">Äänestys on suljettu uusilta ääniltä</string>
|
||||
<string name="poll_zap_amount">Zap-määrä</string>
|
||||
<string name="one_vote_per_user_on_atomic_votes">Vain yksi ääni käyttäjää kohti tällä äänestystyypillä</string>
|
||||
<string name="looking_for_event">"Etsitään tapahtumaa %1$s"</string>
|
||||
<string name="custom_zaps_add_a_message">Lisää julkinen viesti</string>
|
||||
<string name="custom_zaps_add_a_message_private">Lisää yksityinen viesti</string>
|
||||
<string name="custom_zaps_add_a_message_nonzap">Lisää laskuviesti</string>
|
||||
<string name="custom_zaps_add_a_message_example">Kiitos tehdystä työstä!</string>
|
||||
<string name="lightning_create_and_add_invoice">Luo ja lisää</string>
|
||||
<string name="poll_author_no_vote">Äänestyksen luojat eivät voi äänestää omissa äänestyksissään.</string>
|
||||
<string name="hash_verification_passed">Tämä sisältö on sama kuin viestin julkaisuhetkellä</string>
|
||||
<string name="hash_verification_failed">Tämä sisältö on muuttunut. Kirjoittaja ei ehkä ole nähnyt tai hyväksynyt muutosta</string>
|
||||
<string name="content_description_add_image">Lisää kuva</string>
|
||||
<string name="content_description_add_video">Lisää video</string>
|
||||
<string name="content_description_add_document">Lisää dokumentti</string>
|
||||
<string name="add_content">Lisää viestiin</string>
|
||||
<string name="content_description">Sisällön kuvaus</string>
|
||||
<string name="content_description_example">Sininen vene valkoisella hiekkarannalla auringonlaskussa</string>
|
||||
<string name="zap_type">Zap-tyyppi</string>
|
||||
<string name="zap_type_explainer">Zap-tyyppi kaikille vaihtoehdoille</string>
|
||||
<string name="zap_type_public">Julkinen</string>
|
||||
<string name="zap_type_public_explainer">Kaikki voivat nähdä tapahtuman ja viestin</string>
|
||||
<string name="zap_type_private">Yksityinen</string>
|
||||
<string name="zap_type_private_explainer">Lähettäjä ja vastaanottaja voivat nähdä toisensa ja lukea viestin</string>
|
||||
<string name="zap_type_anonymous">Anonyymi</string>
|
||||
<string name="zap_type_anonymous_explainer">Vastaanottaja ja yleisö eivät tiedä, kuka lähetti maksun</string>
|
||||
<string name="zap_type_nonzap">Ei-Zap</string>
|
||||
<string name="zap_type_nonzap_explainer">Ei jälkiä Nostrissa, vain Lightningissa</string>
|
||||
<string name="file_server">Tiedostopalvelin</string>
|
||||
<string name="zap_forward_lnAddress">LnAddress tai @Käyttäjä</string>
|
||||
<string name="upload_server_imgur">imgur.com - luotettava</string>
|
||||
<string name="upload_server_imgur_explainer">Imgur voi muokata tiedostoa</string>
|
||||
<string name="upload_server_nostrimg">nostrimg.com - luotettava</string>
|
||||
<string name="upload_server_nostrimg_explainer">NostrImg voi muokata tiedostoa</string>
|
||||
<string name="upload_server_nostrbuild">nostr.build - luotettava</string>
|
||||
<string name="upload_server_nostrbuild_explainer">Nostr.build voi muokata tiedostoa</string>
|
||||
<string name="upload_server_nostrfilesdev">nostrfiles.dev - luotettava</string>
|
||||
<string name="upload_server_nostrfilesdev_explainer">Nostrfiles.dev voi muokata tiedostoa</string>
|
||||
<string name="upload_server_nostrcheckme">nostrcheck.me - luotettava</string>
|
||||
<string name="upload_server_nostrcheckme_explainer">Nostrcheck.me voi muokata tiedostoa</string>
|
||||
<string name="upload_server_imgur_nip94">Todennettava Imgur (NIP-94)</string>
|
||||
<string name="upload_server_imgur_nip94_explainer">Tarkistaa, onko Imgur muokannut tiedostoa. Uusi NIP: muut asiakkaat eivät ehkä näe sitä</string>
|
||||
<string name="upload_server_nostrimg_nip94">Todennettava NostrImg (NIP-94)</string>
|
||||
<string name="upload_server_nostrimg_nip94_explainer">Tarkistaa, onko NostrImg muokannut tiedostoa. Uusi NIP: muut asiakkaat eivät ehkä näe sitä</string>
|
||||
<string name="upload_server_nostrbuild_nip94">Todennettava Nostr.build (NIP-94)</string>
|
||||
<string name="upload_server_nostrbuild_nip94_explainer">Tarkistaa, onko Nostr.build muokannut tiedostoa. Uusi NIP: muut asiakkaat eivät ehkä näe sitä</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94">Todennettava Nostrfiles.dev (NIP-94)</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94_explainer">Tarkistaa, onko Nostrfiles.dev muokannut tiedostoa. Uusi NIP: muut asiakkaat eivät ehkä näe sitä</string>
|
||||
<string name="upload_server_nostrcheckme_nip94">Todennettava Nostrcheck.me (NIP-94)</string>
|
||||
<string name="upload_server_nostrcheckme_nip94_explainer">Tarkistaa, onko Nostrcheck.me muokannut tiedostoa. Uusi NIP: muut asiakkaat eivät ehkä näe sitä</string>
|
||||
<string name="upload_server_relays_nip95">Omat releet (NIP-95)</string>
|
||||
<string name="upload_server_relays_nip95_explainer">Tiedostot ovat isännöityinä omilla releilläsi. Uusi NIP: tarkista, tukevatko ne sitä</string>
|
||||
<string name="connect_via_tor_short">Tor/Orbot-asetus</string>
|
||||
<string name="connect_via_tor">Yhdistä Orbot-asetuksesi kautta</string>
|
||||
<string name="do_you_really_want_to_disable_tor_title">Haluatko todella katkaista Orbot/Tor-yhteytesi?</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">Tietosi siirtyvät välittömästi tavalliseen verkkoon</string>
|
||||
<string name="yes">Kyllä</string>
|
||||
<string name="no">Ei</string>
|
||||
<string name="follow_list_selection">Seuraa-lista</string>
|
||||
<string name="follow_list_kind3follows">Kaikki seuraajat</string>
|
||||
<string name="follow_list_global">Maailmanlaajuinen</string>
|
||||
<string name="connect_through_your_orbot_setup_markdown"> ## 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
|
||||
</string>
|
||||
<string name="orbot_socks_port">Orbot Socks-portti</string>
|
||||
<string name="invalid_port_number">Virheellinen porttinumero</string>
|
||||
<string name="use_orbot">Käytä Orbotia</string>
|
||||
<string name="disconnect_from_your_orbot_setup">Katkaise Tor/Orbot-yhteys</string>
|
||||
<string name="app_notification_dms_channel_name">Yksityisviestit</string>
|
||||
<string name="app_notification_dms_channel_description">Ilmoittaa sinulle, kun yksityinen viesti saapuu</string>
|
||||
<string name="app_notification_zaps_channel_name">Saapuneet Zapit</string>
|
||||
<string name="app_notification_zaps_channel_description">Ilmoittaa sinulle, kun joku zappaa sinua</string>
|
||||
<string name="app_notification_zaps_channel_message">%1$s sats</string>
|
||||
<string name="app_notification_zaps_channel_message_from">Lähettäjä: %1$s</string>
|
||||
<string name="app_notification_zaps_channel_message_for">saaja: %1$s</string>
|
||||
<string name="reply_notify">Ilmoitus: </string>
|
||||
<string name="channel_list_join_conversation">Liity keskusteluun</string>
|
||||
<string name="channel_list_user_or_group_id">Käyttäjän tai ryhmän tunniste</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub, nevent tai heksadesimaali</string>
|
||||
<string name="channel_list_create_channel">Luo</string>
|
||||
<string name="channel_list_join_channel">Liity</string>
|
||||
<string name="today">Tänään</string>
|
||||
<string name="content_warning">Sisältövaroitus</string>
|
||||
<string name="content_warning_explanation">Tämä viesti sisältää herkkää sisältöä, joka voi olla loukkaavaa tai häiritsevää joillekin ihmisille</string>
|
||||
<string name="content_warning_hide_all_sensitive_content">Piilota aina herkkä sisältö</string>
|
||||
<string name="content_warning_show_all_sensitive_content">Näytä aina herkkä sisältö</string>
|
||||
<string name="content_warning_see_warnings">Näytä aina sisältövaroitukset</string>
|
||||
<string name="recommended_apps">Suosittelee: </string>
|
||||
<string name="filter_spam_from_strangers">Suodata roskapostia tuntemattomilta</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">Varoita, kun seurattavasi ovat raportoineet viestejä</string>
|
||||
<string name="new_reaction_symbol">Uusi reaktiosymboli</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">Ei valittu reaktiotyyppejä. Pidä pitkään painettuna vaihtaaksesi.</string>
|
||||
<string name="zapraiser">Zapkerääjä</string>
|
||||
<string name="zapraiser_explainer">Lisää tavoitesumma satseina tämän viestin tukemiseksi. Tuettavat asiakkaat voivat näyttää tämän edistymispalkkina tuen kannustamiseksi</string>
|
||||
<string name="zapraiser_target_amount_in_sats">Tavoitesumma satseina</string>
|
||||
<string name="sats_to_complete">Zapraiser %1$s. %2$s satsiin tavoitteesta</string>
|
||||
<string name="read_from_relay">Lue releestä</string>
|
||||
<string name="write_to_relay">Kirjoita releeseen</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Virhe yritettäessä hakea rele-tietoja %1$s:stä</string>
|
||||
<string name="owner">Omistaja</string>
|
||||
<string name="version">Versio</string>
|
||||
<string name="software">Ohjelmisto</string>
|
||||
<string name="contact">Yhteystiedot</string>
|
||||
<string name="supports">Tuetut NIPit</string>
|
||||
<string name="admission_fees">Pääsymaksut</string>
|
||||
<string name="payments_url">Maksut URL</string>
|
||||
<string name="limitations">Rajoitukset</string>
|
||||
<string name="countries">Maat</string>
|
||||
<string name="languages">Kielet</string>
|
||||
<string name="tags">Tagit</string>
|
||||
<string name="posting_policy">Julkaisupolitiikka</string>
|
||||
<string name="message_length">Viestin pituus</string>
|
||||
<string name="subscriptions">Tilaukset</string>
|
||||
<string name="filters">Suodattimet</string>
|
||||
<string name="subscription_id_length">Tilaustunnuksen pituus</string>
|
||||
<string name="minimum_prefix">Vähimmäisliite</string>
|
||||
<string name="maximum_event_tags">Maksimi tapahtumien tagit</string>
|
||||
<string name="content_length">Sisällön pituus</string>
|
||||
<string name="minimum_pow">Vähimmäis PoW</string>
|
||||
<string name="auth">Autentikointi</string>
|
||||
<string name="payment">Maksu</string>
|
||||
<string name="cashu">Cashu-tunniste</string>
|
||||
<string name="cashu_redeem">Lunasta</string>
|
||||
<string name="no_lightning_address_set">Ei Lightning-osoitetta asetettuna</string>
|
||||
<string name="copied_token_to_clipboard">Kopioitu tunniste leikepöydälle</string>
|
||||
<string name="live_stream_live_tag">LIVE</string>
|
||||
<string name="live_stream_offline_tag">POISSA</string>
|
||||
<string name="live_stream_ended_tag">PÄÄTTYNYT</string>
|
||||
<string name="live_stream_planned_tag">AJOITETTU</string>
|
||||
<string name="live_stream_is_offline">Livestream on offline-tilassa</string>
|
||||
<string name="live_stream_has_ended">Livestream on päättynyt</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">Uloskirjautuminen poistaa kaikki paikalliset tiedot. Varmista, että sinulla on varmuuskopio yksityisavaimistasi välttääksesi tilisi menetyksen. Haluatko jatkaa?</string>
|
||||
<string name="followed_tags">Seuratut tagit</string>
|
||||
<string name="relay_setup">Releet</string>
|
||||
<string name="discover_live">Live</string>
|
||||
<string name="discover_community">Yhteisö</string>
|
||||
<string name="discover_chat">Keskustelut</string>
|
||||
<string name="community_approved_posts">Hyväksytyt viestit</string>
|
||||
<string name="groups_no_descriptor">Tällä ryhmällä ei ole kuvausta tai sääntöjä. Keskustele omistajan kanssa sen lisäämiseksi</string>
|
||||
<string name="community_no_descriptor">Tällä yhteisöllä ei ole kuvausta. Keskustele omistajan kanssa sen lisäämiseksi</string>
|
||||
<string name="add_sensitive_content_label">Herkkä sisältö</string>
|
||||
<string name="add_sensitive_content_description">Lisää herkän sisällön varoitus ennen tämän sisällön näyttämistä</string>
|
||||
<string name="settings">Asetukset</string>
|
||||
<string name="connectivity_type_always">Aina</string>
|
||||
<string name="connectivity_type_wifi_only">Vain WiFi</string>
|
||||
<string name="connectivity_type_never">Ei koskaan</string>
|
||||
<string name="system">Järjestelmä</string>
|
||||
<string name="light">Vaalea</string>
|
||||
<string name="dark">Tumma</string>
|
||||
<string name="application_preferences">Sovelluksen asetukset</string>
|
||||
<string name="language">Kieli</string>
|
||||
<string name="theme">Teema</string>
|
||||
<string name="automatically_load_images_gifs">Kuvan esikatselu</string>
|
||||
<string name="automatically_play_videos">Videon toisto</string>
|
||||
<string name="automatically_show_url_preview">URL-esikatselu</string>
|
||||
<string name="load_image">Lataa kuva</string>
|
||||
<string name="spamming_users">Roskapostittajat</string>
|
||||
<string name="muted_button">Ääni pois päältä. Napsauta palauttaaksesi äänet</string>
|
||||
<string name="mute_button">Ääni päällä. Napsauta vaimentaaksesi</string>
|
||||
<string name="search_button">Hae paikallisia ja etämerkintöjä</string>
|
||||
<string name="nip05_verified">Nostr-osoite varmennettiin</string>
|
||||
<string name="nip05_failed">Nostr-osoite ei läpäissyt varmennusta</string>
|
||||
<string name="nip05_checking">Tarkistetaan Nostr-osoite</string>
|
||||
<string name="select_deselect_all">Valitse/Poista valinta kaikista</string>
|
||||
<string name="default_relays">Oletus</string>
|
||||
<string name="select_a_relay_to_continue">Valitse rele jatkaaksesi</string>
|
||||
<string name="zap_forward_title">Ohjaa zappit kohteeseen:</string>
|
||||
<string name="zap_forward_explainer">Sovelluksien tukeminen ohjaa zappit alla olevaan LN-osoitteeseen tai käyttäjäprofiiliin sen sijaan, että ne menisivät sinulle</string>
|
||||
<string name="geohash_title">Näytä sijaintina </string>
|
||||
<string name="geohash_explainer">Lisää sijaintisi Geohashinä viestiin. On julkisesti nähtävänä, että olet 5 km (3 mailin) säteellä nykyisestä sijainnista</string>
|
||||
<string name="add_sensitive_content_explainer">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ä</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_title">Uusi ominaisuus</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_description">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.</string>
|
||||
<string name="new_feature_nip24_activate">Aktivoi</string>
|
||||
<string name="messages_create_public_chat">Julkinen</string>
|
||||
<string name="messages_new_message">Yksityinen</string>
|
||||
<string name="messages_new_message_to">Vastaanottaja</string>
|
||||
<string name="messages_new_message_subject">Aihe</string>
|
||||
<string name="messages_new_message_subject_caption">Keskustelun aihe</string>
|
||||
<string name="messages_new_message_to_caption">"\@Käyttäjä1, @Käyttäjä2, @Käyttäjä3"</string>
|
||||
<string name="messages_group_descriptor">Tämän ryhmän jäsenet</string>
|
||||
<string name="messages_new_subject_message">Selitys jäsenille</string>
|
||||
<string name="messages_new_subject_message_placeholder">Muuta nimi uusille tavoitteille.</string>
|
||||
<string name="language_description">Sovelluksen käyttöliittymälle</string>
|
||||
<string name="theme_description">Tumma, vaalea tai järjestelmäteema</string>
|
||||
<string name="automatically_load_images_gifs_description">Lataa kuvat ja GIF-animaatiot automaattisesti</string>
|
||||
<string name="automatically_play_videos_description">Toista videot ja GIF-animaatiot automaattisesti</string>
|
||||
<string name="automatically_show_url_preview_description">Näytä URL-esikatselut</string>
|
||||
<string name="load_image_description">Koska kuvat ladataan</string>
|
||||
<string name="copy_url_to_clipboard">Kopioi URL leikepöydälle</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Kopioi viestin tunniste leikepöydälle</string>
|
||||
<string name="created_at">Luotu</string>
|
||||
<string name="rules">Säännöt</string>
|
||||
<string name="status_update">Päivitä tilaasi</string>
|
||||
<string name="lightning_wallets_not_found">Virhe tulkittaessa virhesanomaa</string>
|
||||
<string name="poll_zap_value_min_max_explainer">Ää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.</string>
|
||||
<string name="error_dialog_zap_error">Zapin lähettäminen ei onnistunut</string>
|
||||
<string name="error_dialog_talk_to_user">Viesti käyttäjälle</string>
|
||||
<string name="error_dialog_button_ok">Ok</string>
|
||||
<string name="relay_information_document_error_assemble_url">Kohdetta %1$s ei tavoitettu: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">Kohdetta %1$s ei tavoitettu: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">Tuloksen jäsentäminen epäonnistui %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s epäonnistui koodilla %2$s</string>
|
||||
<string name="active_for">Aktiivinen kohteelle: </string>
|
||||
<string name="active_for_home">Koti</string>
|
||||
<string name="active_for_msg">YVt</string>
|
||||
<string name="active_for_chats">Keskustelut</string>
|
||||
<string name="active_for_global">Yleinen</string>
|
||||
<string name="active_for_search">Haku</string>
|
||||
</resources>
|
||||
|
||||
@@ -474,4 +474,26 @@
|
||||
<string name="error_dialog_zap_error">Impossible d\'envoyer un zap</string>
|
||||
<string name="error_dialog_talk_to_user">Contacter l\'Utilisateur</string>
|
||||
<string name="error_dialog_button_ok">Ok</string>
|
||||
<string name="relay_information_document_error_assemble_url">Impossible d\'atteindre %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">Impossible d\'atteindre %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">Impossible d\'analyser le résultat de %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s a échoué avec le code %2$s</string>
|
||||
<string name="active_for">Actif pour : </string>
|
||||
<string name="active_for_home">Accueil</string>
|
||||
<string name="active_for_msg">MPs</string>
|
||||
<string name="active_for_chats">Salons</string>
|
||||
<string name="active_for_global">Général</string>
|
||||
<string name="active_for_search">Rechercher</string>
|
||||
<string name="zap_split_title">Diviser et Transférer des Zaps</string>
|
||||
<string name="zap_split_explainer">Les clients supportés vont diviser et transférer les zaps aux utilisateurs ajoutés ici au lieu du vôtre</string>
|
||||
<string name="zap_split_serarch_and_add_user">Rechercher et Ajouter un Utilisateur</string>
|
||||
<string name="zap_split_serarch_and_add_user_placeholder">Nom d\'utilisateur ou nom affiché</string>
|
||||
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">L\'utilisateur %1$s n\'a pas configuré d\'adresse Lightning pour recevoir des sats</string>
|
||||
<string name="zap_split_weight">Pourcentage</string>
|
||||
<string name="zap_split_weight_placeholder">25</string>
|
||||
<string name="splitting_zaps_with">Diviser les zaps avec</string>
|
||||
<string name="forwarding_zaps_to">Transférer les zaps à</string>
|
||||
<string name="lightning_wallets_not_found2">Portefeuilles Lightning introuvables</string>
|
||||
<string name="paid">Payé</string>
|
||||
<string name="wallet_number">Portefeuille %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<string name="could_not_decrypt_the_message">Az üzenet dekódolása sikertelen</string>
|
||||
<string name="group_picture">Csoport Kép</string>
|
||||
<string name="explicit_content">Szókimondó tartalom</string>
|
||||
<string name="spam">Spam</string>
|
||||
<string name="impersonation">Megszemélyesítés</string>
|
||||
<string name="illegal_behavior">Illegális viselkedés</string>
|
||||
<string name="unknown">Ismeretlen</string>
|
||||
@@ -22,6 +23,7 @@
|
||||
<string name="copy_note_id">A bejegyzés azonosítójának másolása</string>
|
||||
<string name="broadcast">Közvetít</string>
|
||||
<string name="request_deletion">Törlés Kérése</string>
|
||||
<string name="block_report">Tiltás és Jelentés</string>
|
||||
<string name="block_hide_user"><![CDATA[Tiltás és Felhasználó elrejtése]]></string>
|
||||
<string name="report_spam_scam">Spam / Csalás bejelentés</string>
|
||||
<string name="report_impersonation">Megszemélyesítés bejelentés</string>
|
||||
@@ -32,6 +34,8 @@
|
||||
<string name="login_with_a_private_key_to_like_posts">Jelentkezzen be privát kulccsal a bejegyzések kedveléséhez</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">Nincs Zap összeg beállítva. Nyomja meg hosszan a változtatáshoz</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Jelentkezzen be privát kulccsal, hogy Zaps-t küldhessen</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Jelentkezz be a privát kulccsal, hogy követni tudd</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Jelentkezz be a privát kulccsal, hogy őt kikövesd</string>
|
||||
<string name="zaps">Zap-ek</string>
|
||||
<string name="view_count">Megtekintések száma</string>
|
||||
<string name="boost">Megosztás</string>
|
||||
@@ -43,6 +47,8 @@
|
||||
<string name="and">" és "</string>
|
||||
<string name="in_channel">"csatornában "</string>
|
||||
<string name="profile_banner">Profil Banner</string>
|
||||
<string name="payment_successful">Sikeres fizetés</string>
|
||||
<string name="error_parsing_error_message">Hiba a hibaüzenet elemzésekor</string>
|
||||
<string name="following">" Követek"</string>
|
||||
<string name="followers">" Követők"</string>
|
||||
<string name="profile">Profil</string>
|
||||
@@ -161,6 +167,7 @@
|
||||
<string name="translations_show_in_lang_first">Először %1$s nyelven</string>
|
||||
<string name="translations_always_translate_to_lang">Mindig fordítsa le %1$s-ra</string>
|
||||
<string name="translations_never_translate_from_lang">Soha ne fordíts %1$s-ról</string>
|
||||
<string name="nip_05">Nostr Cím</string>
|
||||
<string name="never">soha</string>
|
||||
<string name="now">most</string>
|
||||
<string name="h">ó</string>
|
||||
@@ -257,7 +264,10 @@
|
||||
<string name="poll_option_index">Szavazás %s megoszlása</string>
|
||||
<string name="poll_option_description">Szavazás opcióinak leírása</string>
|
||||
<string name="poll_heading_optional">Kiegészítő mezők:</string>
|
||||
<string name="poll_zap_value_min">Minimum Zap</string>
|
||||
<string name="poll_zap_value_max">Maximum Zap</string>
|
||||
<string name="poll_consensus_threshold">Konszenzus</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="poll_closing_time">Szavazás lezárása</string>
|
||||
<string name="poll_closing_time_days">napok</string>
|
||||
<string name="poll_is_closed">A szavazásra nem lehet már új szavazatot leadni</string>
|
||||
@@ -294,10 +304,22 @@
|
||||
<string name="upload_server_imgur_explainer">Feltöltés az Imgur-ba. Az Imgur a képet/videót bármikor megváltoztathatja</string>
|
||||
<string name="upload_server_nostrimg">nostrimg.com - kevésbé biztonságos</string>
|
||||
<string name="upload_server_nostrimg_explainer">Feltöltés a NostrImg-be. A NostrImg a képet bármikor megváltoztathatja</string>
|
||||
<string name="upload_server_nostrbuild">nostr.build - megbízható</string>
|
||||
<string name="upload_server_nostrbuild_explainer">A Nostr.build a fájlt módosíthatja</string>
|
||||
<string name="upload_server_nostrfilesdev">nostrfiles.dev - megbízható</string>
|
||||
<string name="upload_server_nostrfilesdev_explainer">A Nostrfiles.dev a fájlt módosíthatja</string>
|
||||
<string name="upload_server_nostrcheckme">nostrcheck.me - megbízható</string>
|
||||
<string name="upload_server_nostrcheckme_explainer">a Nostrcheck.me a fájlt módosíthatja</string>
|
||||
<string name="upload_server_imgur_nip94">Ellenőrizhető Imgur (NIP-94)</string>
|
||||
<string name="upload_server_imgur_nip94_explainer">Megvéd attól, hogy az Imgur a képet/videót a közzététel után megváltoztassa</string>
|
||||
<string name="upload_server_nostrimg_nip94">Ellenőrizhető NostrImg (NIP-94)</string>
|
||||
<string name="upload_server_nostrimg_nip94_explainer">Megvéd attól, hogy az NostrImg a képet a közzététel után megváltoztassa</string>
|
||||
<string name="upload_server_nostrbuild_nip94">Ellenőrizhető Nostr.build (NIP-94)</string>
|
||||
<string name="upload_server_nostrbuild_nip94_explainer">Ellenőrzi, hogy a Nostr.build a fájlt módosította-e. Új NIP: előfordulhat, hogy más kliensek nem jelenítik meg</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94">Ellenőrizhető Nostrfiles.dev (NIP-94)</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94_explainer">Ellenőrzi, hogy a Nostrfiles.dev a fájlt módosította-e. Új NIP: előfordulhat, hogy más kliensek nem jelenítik meg</string>
|
||||
<string name="upload_server_nostrcheckme_nip94">Ellenőrizhető Nostrcheck.me (NIP-94)</string>
|
||||
<string name="upload_server_nostrcheckme_nip94_explainer">Ellenőrzi, hogy a Nostrcheck.me a fájlt módosította-e. Új NIP: előfordulhat, hogy más kliensek nem jelenítik meg</string>
|
||||
<string name="upload_server_relays_nip95">Saját csomópontjaid (NIP-95)</string>
|
||||
<string name="upload_server_relays_nip95_explainer">A fájlokat a csomópontokra töltik fel és ott tárolják. Rögzített URL-től mentesek (harmadik féltől való függőség). Győződj meg róla, hogy legalább egy NIP-95 csomópont a csomópontlistában szerepel</string>
|
||||
<string name="connect_via_tor_short">Tor/Orbot beállítása</string>
|
||||
@@ -325,6 +347,7 @@
|
||||
<string name="app_notification_dms_channel_description">Értesítést küld, amikor privát üzeneted érkezik</string>
|
||||
<string name="app_notification_zaps_channel_name">Zap-et kaptál</string>
|
||||
<string name="app_notification_zaps_channel_description">Értesítést küld, amikor valaki neked Zap-et küldött</string>
|
||||
<string name="app_notification_zaps_channel_message">%1$s sats</string>
|
||||
<string name="app_notification_zaps_channel_message_from">%1$s -tól</string>
|
||||
<string name="app_notification_zaps_channel_message_for">%1$s -ért</string>
|
||||
<string name="reply_notify">Értesítés: </string>
|
||||
@@ -350,6 +373,7 @@
|
||||
<string name="sats_to_complete">A ZapGyűjtés %1$s-nál. %2$s sats kell a célig</string>
|
||||
<string name="read_from_relay">Olvassás a csomópontból</string>
|
||||
<string name="write_to_relay">Írás a csomópontra</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Hiba történt a közvetítő információinak lekérése közben a %1$s -ról</string>
|
||||
<string name="owner">Tulajdonos</string>
|
||||
<string name="version">Verzió</string>
|
||||
<string name="software">Szoftver</string>
|
||||
@@ -372,8 +396,12 @@
|
||||
<string name="minimum_pow">Minimális PoW</string>
|
||||
<string name="auth">Azonosítás</string>
|
||||
<string name="payment">Fizetés</string>
|
||||
<string name="cashu">Cashu Token</string>
|
||||
<string name="cashu_redeem">Beváltás</string>
|
||||
<string name="no_lightning_address_set">Nincs Lightning Cím beállítva</string>
|
||||
<string name="copied_token_to_clipboard">Token a vágólapra másolva</string>
|
||||
<string name="live_stream_live_tag">ÉLŐ</string>
|
||||
<string name="live_stream_offline_tag">NINCS ADÁS</string>
|
||||
<string name="live_stream_ended_tag">VÉGE</string>
|
||||
<string name="live_stream_planned_tag">ÜTEMEZETT</string>
|
||||
<string name="live_stream_is_offline">Az élő közvetítés offline</string>
|
||||
@@ -392,6 +420,7 @@
|
||||
<string name="settings">Beállítások</string>
|
||||
<string name="connectivity_type_always">Mindig</string>
|
||||
<string name="connectivity_type_wifi_only">Csak WIFI</string>
|
||||
<string name="connectivity_type_never">Soha</string>
|
||||
<string name="system">Rendszer</string>
|
||||
<string name="light">Világos</string>
|
||||
<string name="dark">Sötét</string>
|
||||
@@ -439,7 +468,20 @@
|
||||
<string name="copy_the_note_id_to_the_clipboard">A bejegyzésazonosító vágólapra másolása</string>
|
||||
<string name="created_at">Létrehozva</string>
|
||||
<string name="rules">Szabályok</string>
|
||||
<string name="status_update">Állapotod változtatása</string>
|
||||
<string name="status_update">Állapotod megjelenítése</string>
|
||||
<string name="lightning_wallets_not_found">Hiba a hibaüzenet elemzésekor</string>
|
||||
<string name="poll_zap_value_min_max_explainer">A szavazatokat a Zap-ek összegével súlyozzuk. Beállíthatsz egy minimális összeget, hogy a kéretlen leveleket elkerüld, és egy maximális összeget annak elkerülésére, hogy a szavazás feletti irányítást a nagy Zapperek vegyék át. Mindkét mezőben ugyanazt az összeget használd, hogy minden szavazat azonos értéket kapjon. Bármilyen összeg elfogadásához, hagyjd üresen.</string>
|
||||
<string name="error_dialog_zap_error">Zap küldése nem sikerült</string>
|
||||
<string name="error_dialog_talk_to_user">Üzenet a Felhasználónak</string>
|
||||
<string name="error_dialog_button_ok">Ok</string>
|
||||
<string name="relay_information_document_error_assemble_url">Nem sikerült elérni %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">Nem sikerült elérni %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">Nem sikerült az eredmény elemzése a %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s hiba a következő kóddal %2$s</string>
|
||||
<string name="active_for">Aktív: </string>
|
||||
<string name="active_for_home">Főoldal</string>
|
||||
<string name="active_for_msg">PÜk</string>
|
||||
<string name="active_for_chats">Chat</string>
|
||||
<string name="active_for_global">Globális</string>
|
||||
<string name="active_for_search">Keresés</string>
|
||||
</resources>
|
||||
|
||||
530
app/src/main/res/values-in/strings.xml
Normal file
530
app/src/main/res/values-in/strings.xml
Normal file
@@ -0,0 +1,530 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="point_to_the_qr_code">Arahkan ke Kode QR</string>
|
||||
<string name="show_qr">Tampilkan QR</string>
|
||||
<string name="profile_image">Foto Profil</string>
|
||||
<string name="scan_qr">Pindai QR</string>
|
||||
<string name="show_anyway">Tampilkan Saja</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Postingan dilaporkan oleh</string>
|
||||
<string name="post_not_found">Data sedang dimuat atau tidak dapat ditemukan di daftar relai Anda</string>
|
||||
<string name="channel_image">Gambar Kanal</string>
|
||||
<string name="referenced_event_not_found">Data yang direferensikan tidak ditemukan</string>
|
||||
<string name="could_not_decrypt_the_message">Tidak dapat mendeskripsikan pesan</string>
|
||||
<string name="group_picture">Gambar Grup</string>
|
||||
<string name="explicit_content">Konten Eksplisit</string>
|
||||
<string name="spam">Spam</string>
|
||||
<string name="impersonation">Peniruan/penipuan</string>
|
||||
<string name="illegal_behavior">Perilaku Ilegal</string>
|
||||
<string name="unknown">Tidak dikenal</string>
|
||||
<string name="relay_icon">Ikon Relai</string>
|
||||
<string name="unknown_author">Penulis Tidak Dikenal</string>
|
||||
<string name="copy_text">Salin Teks</string>
|
||||
<string name="copy_user_pubkey">Salin ID Penulis (Author ID)</string>
|
||||
<string name="copy_note_id">Salin ID Tulisan (Note ID)</string>
|
||||
<string name="broadcast">Siarkan</string>
|
||||
<string name="request_deletion">Minta Penghapusan</string>
|
||||
<string name="block_report">Blokir / Laporkan</string>
|
||||
<string name="block_hide_user"><![CDATA[Blokir & Sembunyikan Pengguna]]></string>
|
||||
<string name="report_spam_scam">Laporkan Spam / Penipuan</string>
|
||||
<string name="report_impersonation">Laporkan Peniruan</string>
|
||||
<string name="report_explicit_content">Laporkan Konten Eksplisit</string>
|
||||
<string name="report_illegal_behaviour">Laporkan Perilaku Ilegal</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_reply">Masuk dengan Kunci pribadi (nsec) untuk dapat membalas tulisan</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Masuk dengan Kunci pribadi (nsec) untuk dapat mempromosikan tulisan</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">Masuk dengan Kunci pribadi (nsec) untuk dapat menyukai tulisan</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">Tidak Ada Pengaturan Jumlah Zap. Tekan dan tahan tombol untuk mengubah</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Masuk dengan Kunci pribadi (nsec) untuk dapat mengirim Zaps</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Masuk dengan Kunci pribadi (nsec) untuk dapat mengikuti</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Masuk dengan Kunci pribadi (nsec) untuk berhenti mengikuti</string>
|
||||
<string name="zaps">Zaps</string>
|
||||
<string name="view_count">Jumlah pengamat</string>
|
||||
<string name="boost">Promosi</string>
|
||||
<string name="boosted">dipromosikan</string>
|
||||
<string name="quote">Kutipan</string>
|
||||
<string name="new_amount_in_sats">Jumlah Baru dalam Sats</string>
|
||||
<string name="add">Tambah</string>
|
||||
<string name="replying_to">"membalas kepada "</string>
|
||||
<string name="and">" dan "</string>
|
||||
<string name="in_channel">dalam kanal</string>
|
||||
<string name="profile_banner">Spanduk Profil</string>
|
||||
<string name="payment_successful">Pembayaran Berhasil</string>
|
||||
<string name="error_parsing_error_message">Gagal menguraikan pesan kesalahan</string>
|
||||
<string name="following">" Mengikuti"</string>
|
||||
<string name="followers">" Pengikut"</string>
|
||||
<string name="profile">Profil</string>
|
||||
<string name="security_filters">Filter Keamanan</string>
|
||||
<string name="log_out">Keluar</string>
|
||||
<string name="show_more">Tampilkan lebih banyak</string>
|
||||
<string name="lightning_invoice">Faktur Tagihan Lightning</string>
|
||||
<string name="pay">Bayar</string>
|
||||
<string name="lightning_tips">Lightning Tip</string>
|
||||
<string name="note_to_receiver">Catatan ke Penerima</string>
|
||||
<string name="thank_you_so_much">Terima kasih banyak!</string>
|
||||
<string name="amount_in_sats">Jumlah dalam Sats</string>
|
||||
<string name="send_sats">Kirim Sats</string>
|
||||
<string name="error_parsing_preview_for">Gagal menampilkan pratinjau untuk %1$s : %2$s</string>
|
||||
<string name="preview_card_image_for">Pratinjau Gambar Kartu untuk %1$s</string>
|
||||
<string name="new_channel">Kanal Baru</string>
|
||||
<string name="channel_name">Nama Kanal</string>
|
||||
<string name="my_awesome_group">Grupku yang Hebat</string>
|
||||
<string name="picture_url">Alamat Url Gambar</string>
|
||||
<string name="description">Deskripsi</string>
|
||||
<string name="about_us">"Tentang kami.. "</string>
|
||||
<string name="what_s_on_your_mind">Apa yang anda pikirkan?</string>
|
||||
<string name="post">Kirim</string>
|
||||
<string name="save">Simpan</string>
|
||||
<string name="create">Buat</string>
|
||||
<string name="cancel">Batal</string>
|
||||
<string name="failed_to_upload_the_image">Gagal mengunggah gambar</string>
|
||||
<string name="relay_address">Alamat Relai</string>
|
||||
<string name="posts">Kiriman</string>
|
||||
<string name="bytes">Bytes</string>
|
||||
<string name="errors">Errors</string>
|
||||
<string name="home_feed">Beranda</string>
|
||||
<string name="private_message_feed">Area Pesan Pribadi</string>
|
||||
<string name="public_chat_feed">Area Percakapan Publik</string>
|
||||
<string name="global_feed">Area Global</string>
|
||||
<string name="search_feed">Area Pencarian</string>
|
||||
<string name="add_a_relay">Tambah Relai</string>
|
||||
<string name="display_name">Nama ditampilkan</string>
|
||||
<string name="my_display_name">Namaku yg ditampilkan</string>
|
||||
<string name="username">Nama pengguna</string>
|
||||
<string name="my_username">Nama penggunaku</string>
|
||||
<string name="about_me">Tentang saya</string>
|
||||
<string name="avatar_url">URL Avatar</string>
|
||||
<string name="banner_url">"URL Spanduk/Banner "</string>
|
||||
<string name="website_url">URL Situs Web</string>
|
||||
<string name="ln_address">Alamat LN</string>
|
||||
<string name="ln_url_outdated">LN URL (versi lama)</string>
|
||||
<string name="image_saved_to_the_gallery">Gambar disimpan ke galeri</string>
|
||||
<string name="failed_to_save_the_image">Gagal menyimpan gambar</string>
|
||||
<string name="upload_image">Unggah Gambar</string>
|
||||
<string name="uploading">Mengunggah…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">Pengguna tidak memiliki alamat lightning (LN) yang diatur untuk menerima sats</string>
|
||||
<string name="reply_here">"balas di sini.. "</string>
|
||||
<string name="copies_the_note_id_to_the_clipboard_for_sharing">Menyalin ID Catatan ke papan klip untuk dibagikan di Nostr</string>
|
||||
<string name="copy_channel_id_note_to_the_clipboard">Salin ID Kanal (Catatan) ke Papan Klip</string>
|
||||
<string name="edits_the_channel_metadata">Ubah Metadata Kanal</string>
|
||||
<string name="join">Gabung</string>
|
||||
<string name="known">Dikenali</string>
|
||||
<string name="new_requests">Permintaan Baru</string>
|
||||
<string name="blocked_users">Pengguna diblokir</string>
|
||||
<string name="new_threads">Utas Baru</string>
|
||||
<string name="conversations">Percakapan</string>
|
||||
<string name="notes">Catatan</string>
|
||||
<string name="replies">Balasan</string>
|
||||
<string name="follows">Mengikuti</string>
|
||||
<string name="reports">Laporan</string>
|
||||
<string name="more_options">Lebih banyak pilihan</string>
|
||||
<string name="relays">" Relai"</string>
|
||||
<string name="website">Situs Web</string>
|
||||
<string name="lightning_address">Alamat Lightning</string>
|
||||
<string name="copies_the_nsec_id_your_password_to_the_clipboard_for_backup">Salin Nsec ID (kata sandi anda) ke papan klip sebagai cadangan</string>
|
||||
<string name="copy_private_key_to_the_clipboard">Salin Kunci Privat ke papan klip</string>
|
||||
<string name="copies_the_public_key_to_the_clipboard_for_sharing">Salin Kunci Publik ke papan klip untuk dibagikan</string>
|
||||
<string name="copy_public_key_npub_to_the_clipboard">Salin Kunci Publik (NPub) ke papan klip</string>
|
||||
<string name="send_a_direct_message">Kirim Pesan Langsung</string>
|
||||
<string name="edits_the_user_s_metadata">Ubah Metadata Pengguna</string>
|
||||
<string name="follow">Ikuti</string>
|
||||
<string name="follow_back">Ikuti balik</string>
|
||||
<string name="unblock">Batalkan blokir</string>
|
||||
<string name="copy_user_id">Salin ID Pengguna</string>
|
||||
<string name="unblock_user">Batalkan blokir pengguna</string>
|
||||
<string name="npub_hex_username">npub, nama pengguna, teks</string>
|
||||
<string name="clear">Bersihkan</string>
|
||||
<string name="app_logo">Logo Aplikasi</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec.. or npub..</string>
|
||||
<string name="show_password">Tampilkan Kata Sandi</string>
|
||||
<string name="hide_password">Sembunyikan Kata Sandi</string>
|
||||
<string name="invalid_key">Kunci tidak sah</string>
|
||||
<string name="i_accept_the">Saya menerima</string>
|
||||
<string name="terms_of_use">syarat penggunaan</string>
|
||||
<string name="acceptance_of_terms_is_required">Penerimaan persyaratan diperlukan</string>
|
||||
<string name="key_is_required">Kunci diperlukan</string>
|
||||
<string name="login">Masuk</string>
|
||||
<string name="generate_a_new_key">Buat kunci baru</string>
|
||||
<string name="loading_feed">Memuat data umpan</string>
|
||||
<string name="error_loading_replies">"Gagal memuat balasan: "</string>
|
||||
<string name="try_again">Coba lagi</string>
|
||||
<string name="feed_is_empty">Data umpan kosong.</string>
|
||||
<string name="refresh">Perbarui</string>
|
||||
<string name="created">dibuat</string>
|
||||
<string name="with_description_of">"dengan deskripsi "</string>
|
||||
<string name="and_picture">dan gambar</string>
|
||||
<string name="changed_chat_name_to">mengubah nama percakapan menjadi</string>
|
||||
<string name="description_to">deskripsi untuk</string>
|
||||
<string name="and_picture_to">dan gambar untuk</string>
|
||||
<string name="leave">Tinggalkan</string>
|
||||
<string name="unfollow">Berhenti mengikuti</string>
|
||||
<string name="channel_created">Kanal dibuat</string>
|
||||
<string name="channel_information_changed_to">Informasi kanal diubah menjadi</string>
|
||||
<string name="public_chat">Percakapan Publik</string>
|
||||
<string name="posts_received">kiriman diterima</string>
|
||||
<string name="remove">Hapus</string>
|
||||
<string name="translations_auto">Otomatis</string>
|
||||
<string name="translations_translated_from">diterjemahkan dari</string>
|
||||
<string name="translations_to">ke</string>
|
||||
<string name="translations_show_in_lang_first">Tampilkan dalam %1$s (prioritas)</string>
|
||||
<string name="translations_always_translate_to_lang">Selalu terjemahkan ke %1$s</string>
|
||||
<string name="translations_never_translate_from_lang">Jangan terjemahkan dari %1$s</string>
|
||||
<string name="nip_05">Alamat Nostr</string>
|
||||
<string name="never">tidak pernah</string>
|
||||
<string name="now">sekarang</string>
|
||||
<string name="h">jam</string>
|
||||
<string name="m">menit</string>
|
||||
<string name="d">hari</string>
|
||||
<string name="nudity">Ketelanjangan</string>
|
||||
<string name="profanity_hateful_speech">Ujaran kotor/kebencian</string>
|
||||
<string name="report_hateful_speech">Laporkan ujaran kebencian</string>
|
||||
<string name="report_nudity_porn">Laporkan Ketelanjangan / Pornografi</string>
|
||||
<string name="others">lainnya</string>
|
||||
<string name="mark_all_known_as_read">Tandai semua yg dikenal telah dibaca</string>
|
||||
<string name="mark_all_new_as_read">Tandai pesan baru telah dibaca</string>
|
||||
<string name="mark_all_as_read">Tandai semua telah dibaca</string>
|
||||
<string name="backup_keys">Cadangkan Kunci</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos">"\n ## Tip Pencadangan dan Keamanan Utama\n \n\nAkun Anda diamankan dengan kunci rahasia. Kuncinya adalah string acak panjang yang dimulai dengan **nsec1**.. Siapa pun yang memiliki akses ke kunci rahasia Anda dapat mempublikasikan konten menggunakan identitas Anda.\n \n\n- **Jangan** letakkan kunci rahasia Anda di situs web atau perangkat lunak apa pun yang tidak Anda percayai.\n \n- Pengembang Aplikasi Amethyst **tidak akan pernah** meminta kunci rahasia Anda.\n \n- **Pastikan** simpan cadangan aman kunci rahasia Anda untuk pemulihan akun. Kami merekomendasikan menggunakan aplikasi khusus pengelola kata sandi.\n "</string>
|
||||
<string name="secret_key_copied_to_clipboard">Kunci rahasia (nsec) disalin ke clipboard</string>
|
||||
<string name="copy_my_secret_key">Salin kunci rahasia saya</string>
|
||||
<string name="biometric_authentication_failed">Autentikasi gagal</string>
|
||||
<string name="biometric_error">Kesalahan</string>
|
||||
<string name="badge_created_by">Dibuat oleh %1$s</string>
|
||||
<string name="badge_award_image_for">Gambar penghargaan lencana untuk %1$s</string>
|
||||
<string name="new_badge_award_notif">Anda Menerima Penghargaan Lencana baru</string>
|
||||
<string name="award_granted_to">Penghargaan lencana diberikan kepada</string>
|
||||
<string name="copied_note_text_to_clipboard">Teks catatan disalin ke papan klip</string>
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">\@npub Penulis disalin ke papan klip</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">Note ID (@note1) disalin ke papan klip</string>
|
||||
<string name="select_text_dialog_top">Pilih Teks</string>
|
||||
<string name="private_conversation_notification"><Gagal mendeskripsikan pesan pribadi>\n\nAnda dikutip dalam percakapan pribadi/terenkripsi antara %1$s dan %2$s.</string>
|
||||
<string name="account_switch_add_account_dialog_title">Tambah Akun Baru</string>
|
||||
<string name="drawer_accounts">Akun</string>
|
||||
<string name="account_switch_select_account">Pilih Akun</string>
|
||||
<string name="account_switch_add_account_btn">Tambah Akun Baru</string>
|
||||
<string name="account_switch_active_account">Akun Aktif</string>
|
||||
<string name="account_switch_has_private_key">Memiliki kunci pribadi</string>
|
||||
<string name="account_switch_pubkey_only">Baca saja, tidak ada kunci pribadi</string>
|
||||
<string name="back">Kembali</string>
|
||||
<string name="quick_action_select">Pilih</string>
|
||||
<string name="quick_action_share_browser_link">Bagikan Tauatan ke Peramban</string>
|
||||
<string name="quick_action_share">Bagikan</string>
|
||||
<string name="quick_action_copy_user_id">ID Penulis</string>
|
||||
<string name="quick_action_copy_note_id">ID Catatan</string>
|
||||
<string name="quick_action_copy_text">Salin Teks</string>
|
||||
<string name="quick_action_delete">Hapus</string>
|
||||
<string name="quick_action_unfollow">Berhenti mengikuti</string>
|
||||
<string name="quick_action_follow">Ikuti</string>
|
||||
<string name="quick_action_request_deletion_alert_title">Meminta Penghapusan</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst akan meminta agar catatan Anda dihapus dari relai yang saat ini Anda sambungkan. Tidak ada jaminan bahwa catatan Anda akan dihapus secara permanen dari relai tersebut, atau dari relai lain tempat catatan itu disimpan.</string>
|
||||
<string name="quick_action_block_dialog_btn">Blokir</string>
|
||||
<string name="quick_action_delete_dialog_btn">Hapus</string>
|
||||
<string name="quick_action_block">Blokir</string>
|
||||
<string name="quick_action_report">Laporkan</string>
|
||||
<string name="quick_action_delete_button">Hapus</string>
|
||||
<string name="quick_action_dont_show_again_button">Jangan tampilkan lagi</string>
|
||||
<string name="report_dialog_spam">Spam atau penipuan</string>
|
||||
<string name="report_dialog_profanity">Perilaku tidak senonoh atau penuh kebencian</string>
|
||||
<string name="report_dialog_impersonation">Peniruan identitas yang berbahaya</string>
|
||||
<string name="report_dialog_nudity">Konten ketelanjangan atau grafis</string>
|
||||
<string name="report_dialog_illegal">Perilaku Ilegal</string>
|
||||
<string name="report_dialog_blocking_a_user">Memblokir pengguna akan menyembunyikan konten mereka di aplikasi Anda. Catatan Anda masih dapat dilihat oleh publik, termasuk oleh orang yang Anda blokir. Pengguna yang diblokir terdaftar di layar Filter Keamanan.</string>
|
||||
<string name="report_dialog_block_hide_user_btn"><![CDATA[Blokir & Sembuniykan Pengguna]]></string>
|
||||
<string name="report_dialog_report_btn">Laporkan Penyalahgunaan</string>
|
||||
<string name="report_dialog_reminder_public">Semua laporan yang diposting akan terlihat oleh publik.</string>
|
||||
<string name="report_dialog_additional_reason_placeholder">Secara opsional, berikan konteks tambahan tentang laporan Anda…</string>
|
||||
<string name="report_dialog_additional_reason_label">Konteks Tambahan</string>
|
||||
<string name="report_dialog_select_reason_label">Alasan</string>
|
||||
<string name="report_dialog_select_reason_placeholder">Pilih alasan…</string>
|
||||
<string name="report_dialog_post_report_btn">Kirim Laporan</string>
|
||||
<string name="report_dialog_title">Blokir dan Laporkan</string>
|
||||
<string name="block_only">Blokir</string>
|
||||
|
||||
<string name="bookmarks">Penanda</string>
|
||||
<string name="private_bookmarks">Penanda Pribadi</string>
|
||||
<string name="public_bookmarks">Penanda Publik</string>
|
||||
<string name="add_to_private_bookmarks">Tambahkan Penanda Pribadi</string>
|
||||
<string name="add_to_public_bookmarks">Tambahkan Penanda Publik</string>
|
||||
<string name="remove_from_private_bookmarks">Hapus dari Penanda Pribadi</string>
|
||||
<string name="remove_from_public_bookmarks">Hapus dari Penanda Publik</string>
|
||||
|
||||
<string name="wallet_connect_service">Layanan Wallet Connect</string>
|
||||
<string name="wallet_connect_service_explainer">Mengijinkan Kunci Rahasia Nostr untuk membayar zaps tanpa meninggalkan aplikasi. Jaga kunci tetap aman dan gunakan relai pribadi jika memungkinkan</string>
|
||||
<string name="wallet_connect_service_pubkey">Kunci Publik Wallet Connect</string>
|
||||
<string name="wallet_connect_service_relay">Relai Wallet Connect</string>
|
||||
<string name="wallet_connect_service_secret">Kunci Rahasia Wallet Connect</string>
|
||||
<string name="wallet_connect_service_show_secret">Tampilkan kunci rahasia</string>
|
||||
<string name="wallet_connect_service_secret_placeholder">nsec / hex (kunci pribadi)</string>
|
||||
|
||||
<string name="pledge_amount_in_sats">Jumlah Dijanjikan dalam Sats</string>
|
||||
<string name="post_poll">Kirim Pemungutan Suara</string>
|
||||
<string name="poll_heading_required">Bidang yang wajib diisi:</string>
|
||||
<string name="poll_zap_recipients">Penerima Zap</string>
|
||||
<string name="poll_primary_description">Deskripsi Utama Pemungutan Suara…</string>
|
||||
<string name="poll_option_index">Pilihan %s</string>
|
||||
<string name="poll_option_description">Deskripsi pilihan pemungutan suara</string>
|
||||
<string name="poll_heading_optional">Bidang pilihan (tidak wajib):</string>
|
||||
<string name="poll_zap_value_min">Zap terendah</string>
|
||||
<string name="poll_zap_value_max">Zap tertinggi</string>
|
||||
<string name="poll_consensus_threshold">Konsensus</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="poll_closing_time">Berakhir pada</string>
|
||||
<string name="poll_closing_time_days">hari</string>
|
||||
<string name="poll_is_closed">Pemungutan suara ditutup untuk voting baru</string>
|
||||
<string name="poll_zap_amount">Jumlah Zap</string>
|
||||
<string name="one_vote_per_user_on_atomic_votes">Hanya satu suara per pengguna yang diperbolehkan pada jenis pemungutan suara ini</string>
|
||||
|
||||
<string name="looking_for_event">Mencari data event %1$s</string>
|
||||
|
||||
<string name="custom_zaps_add_a_message">Tambah pesan publik</string>
|
||||
<string name="custom_zaps_add_a_message_private">Tambah pesan pribadi</string>
|
||||
<string name="custom_zaps_add_a_message_nonzap">Tambah pesan faktur tagihan</string>
|
||||
|
||||
<string name="custom_zaps_add_a_message_example">Terima kasih atas semua pekerjaan Anda!</string>
|
||||
|
||||
<string name="lightning_create_and_add_invoice">Buat dan Tambah</string>
|
||||
<string name="poll_author_no_vote">Pembuat Pemungutan Suara tidak dapat memberikan suara dalam pemungutan suara sendiri.</string>
|
||||
|
||||
<string name="hash_verification_passed">Konten ini sama sejak kiriman tersebut dibuat</string>
|
||||
<string name="hash_verification_failed">Konten ini telah berubah. Penulis mungkin tidak melihat atau menyetujui perubahan tersebut</string>
|
||||
|
||||
<string name="content_description_add_image">Tambah Gambar</string>
|
||||
<string name="content_description_add_video">Tambah Video</string>
|
||||
<string name="content_description_add_document">Tambah Dokumen</string>
|
||||
|
||||
<string name="add_content">Tambahkan ke Pesan</string>
|
||||
<string name="content_description">Deskripsi Konten</string>
|
||||
<string name="content_description_example">Perahu berwarna biru di pantai berpasir putih saat matahari terbenam</string>
|
||||
|
||||
<string name="zap_type">Tipe Zap</string>
|
||||
<string name="zap_type_explainer">Tipe Zap untuk semua pilihan</string>
|
||||
|
||||
<string name="zap_type_public">Publik</string>
|
||||
<string name="zap_type_public_explainer">Semua orang dapat melihat transaksi dan pesannya</string>
|
||||
|
||||
<string name="zap_type_private">Pribadi</string>
|
||||
<string name="zap_type_private_explainer">Pengirim dan Penerima dapat saling melihat dan membaca pesan</string>
|
||||
|
||||
<string name="zap_type_anonymous">Anonim</string>
|
||||
<string name="zap_type_anonymous_explainer">Penerima dan semua orang tidak dapat mengetahui siapa pengirim pembayaran</string>
|
||||
|
||||
<string name="zap_type_nonzap">Bukan Zap</string>
|
||||
<string name="zap_type_nonzap_explainer">Tidak ada jejak di Nostr, hanya di Lightning Network</string>
|
||||
|
||||
|
||||
<string name="file_server">Server Berkas</string>
|
||||
<string name="zap_forward_lnAddress">Alamat LN atau @Pengguna</string>
|
||||
|
||||
<string name="upload_server_imgur">imgur.com - terpercaya</string>
|
||||
<string name="upload_server_imgur_explainer">Imgur dapat memodifikasi berkas</string>
|
||||
|
||||
<string name="upload_server_nostrimg">nostrimg.com - terpercaya</string>
|
||||
<string name="upload_server_nostrimg_explainer">NostrImg dapat memodifikasi berkas</string>
|
||||
|
||||
<string name="upload_server_nostrbuild">nostr.build - terpercaya</string>
|
||||
<string name="upload_server_nostrbuild_explainer">Nostr.build dapat memodifikasi berkas</string>
|
||||
|
||||
<string name="upload_server_nostrfilesdev">nostrfiles.dev - terpercaya</string>
|
||||
<string name="upload_server_nostrfilesdev_explainer">Nostrfiles.dev dapat memodifikasi berkas</string>
|
||||
|
||||
<string name="upload_server_nostrcheckme">nostrcheck.me - terpercaya</string>
|
||||
<string name="upload_server_nostrcheckme_explainer">nostrcheck.me dapat memodifikasi berkas</string>
|
||||
|
||||
|
||||
<string name="upload_server_imgur_nip94">Imgur yg dapat diverifikasi (NIP-94)</string>
|
||||
<string name="upload_server_imgur_nip94_explainer">Periksa jika Imgur mengubah berkas. NIP baru: aplikasi Nostr lain mungkin belum mendukung</string>
|
||||
|
||||
<string name="upload_server_nostrimg_nip94">NostrImg yg dapat diverifikasi (NIP-94)</string>
|
||||
<string name="upload_server_nostrimg_nip94_explainer">Periksa jika NostrImg mengubah berkas. NIP baru: aplikasi Nostr lain mungkin belum mendukung</string>
|
||||
|
||||
<string name="upload_server_nostrbuild_nip94">Nostr.build yg dapat diverifikasi (NIP-94)</string>
|
||||
<string name="upload_server_nostrbuild_nip94_explainer">Periksa jika Nostr.build mengubah berkas. NIP baru: aplikasi Nostr lain mungkin belum mendukung</string>
|
||||
|
||||
<string name="upload_server_nostrfilesdev_nip94">Nostrfiles.dev yg dapat diverifikasi (NIP-94)</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94_explainer">Periksa jika Nostrfiles.dev mengubah berkas. NIP baru: aplikasi Nostr lain mungkin belum mendukung</string>
|
||||
|
||||
<string name="upload_server_nostrcheckme_nip94">Nostrcheck.me yg dapat diverifikasi (NIP-94)</string>
|
||||
<string name="upload_server_nostrcheckme_nip94_explainer">Periksa jika Nostrcheck.me mengubah berkas. NIP baru: aplikasi Nostr lain mungkin belum mendukung</string>
|
||||
|
||||
<string name="upload_server_relays_nip95">Relai anda (NIP-95)</string>
|
||||
<string name="upload_server_relays_nip95_explainer">Berkas disimpan di relai anda. NIP baru: periksa apakah relai mendukung fitur</string>
|
||||
|
||||
<string name="connect_via_tor_short">Pengaturan Tor/Orbot</string>
|
||||
<string name="connect_via_tor">Hubungkan melalui pengaturan Orbot Anda</string>
|
||||
|
||||
<string name="do_you_really_want_to_disable_tor_title">Putuskan sambungan dari Orbot/Tor Anda?</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">Data Anda akan segera ditransfer di jaringan reguler (bukan TOR)</string>
|
||||
<string name="yes">Ya</string>
|
||||
<string name="no">Tidak</string>
|
||||
|
||||
|
||||
<string name="follow_list_selection">Daftar yang Diikuti</string>
|
||||
<string name="follow_list_kind3follows">Semua yang diikuti</string>
|
||||
<string name="follow_list_global">Global</string>
|
||||
<string name="connect_through_your_orbot_setup_markdown">"\n ## Terhubung melalui Tor menggunakan Orbot\n \n\n1. Pasang [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)\n \n2. Jalankan Orbot\n \n3. Di dalam Orbot, periksa nomor port Socks. Secara bawaan menggunakan nomor 9050\n \n4. Jika diperlukan, ubah nomor port Orbot\n \n5. Atur nomor port Socks pada layar ini\n \n6. Tekan tombol Aktifkan untuk menggunakan Orbot sebagai proxy\n "</string>
|
||||
<string name="orbot_socks_port">Port Orbot Socks</string>
|
||||
<string name="invalid_port_number">Nomor port tidak sah</string>
|
||||
<string name="use_orbot">Gunakan Orbot</string>
|
||||
<string name="disconnect_from_your_orbot_setup">Putuskan Tor/Orbot</string>
|
||||
|
||||
<string name="app_notification_dms_channel_name">Pesan Pribadi</string>
|
||||
<string name="app_notification_dms_channel_description">Memberi tahu Anda ketika pesan pribadi tiba</string>
|
||||
|
||||
<string name="app_notification_zaps_channel_name">Zaps diterima</string>
|
||||
<string name="app_notification_zaps_channel_description">Memberi tahu Anda ketika seseorang mengirimkan anda zap</string>
|
||||
<string name="app_notification_zaps_channel_message">%1$s sats</string>
|
||||
<string name="app_notification_zaps_channel_message_from">Dari %1$s</string>
|
||||
<string name="app_notification_zaps_channel_message_for">untuk %1$s</string>
|
||||
|
||||
<string name="reply_notify">"Beritahu: "</string>
|
||||
|
||||
<string name="channel_list_join_conversation">Gabung Percakapan</string>
|
||||
<string name="channel_list_user_or_group_id">ID Pengguna atau Grup</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub, nevent atau kode heksa</string>
|
||||
<string name="channel_list_create_channel">Buat</string>
|
||||
<string name="channel_list_join_channel">Gabung</string>
|
||||
|
||||
<string name="today">Hari ini</string>
|
||||
|
||||
<string name="content_warning">Peringatan konten</string>
|
||||
<string name="content_warning_explanation">Postingan ini berisi konten sensitif yang mungkin dianggap menyinggung atau mengganggu oleh sebagian orang</string>
|
||||
<string name="content_warning_hide_all_sensitive_content">Selalu sembunyikan konten sensitif</string>
|
||||
<string name="content_warning_show_all_sensitive_content">Selalu tampilkan konten sensitif</string>
|
||||
<string name="content_warning_see_warnings">Selalu tampilkan peringatan konten</string>
|
||||
|
||||
<string name="recommended_apps">"Rekomendasi: "</string>
|
||||
<string name="filter_spam_from_strangers">Saring spam dari orang asing (tak diikuti)</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">Peringatkan ketika kiriman memiliki laporan dari pengikut Anda</string>
|
||||
|
||||
<string name="new_reaction_symbol">Simbol Reaksi Baru</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">Tidak ada jenis reaksi yang dipilih. Tekan dan tahan untuk mengubah</string>
|
||||
|
||||
<string name="zapraiser">Galang Dana Zap</string>
|
||||
<string name="zapraiser_explainer">Menambahkan jumlah target sats yang ingin dikumpulkan untuk kiriman ini. Klien yang mendukung mungkin menunjukkan hal ini sebagai indikator kemajuan dalam memberi insentif pada donasi</string>
|
||||
<string name="zapraiser_target_amount_in_sats">Jumlah Target dalam Sats</string>
|
||||
|
||||
<string name="sats_to_complete">Galang Dana Zap mencapai %1$s. %2$s sats untuk memenuhi target</string>
|
||||
<string name="read_from_relay">Baca dari Relai</string>
|
||||
<string name="write_to_relay">Tulis ke Relai</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Terjadi kesalahan saat mencoba mendapatkan informasi relai %1$s</string>
|
||||
<string name="owner">Pemilik</string>
|
||||
<string name="version">Versi</string>
|
||||
<string name="software">Perangkat Lunak</string>
|
||||
<string name="contact">Kontak</string>
|
||||
<string name="supports">NIP yg didukung</string>
|
||||
<string name="admission_fees">Biaya Pendaftaran</string>
|
||||
<string name="payments_url">Alamat Pembayaran</string>
|
||||
<string name="limitations">Batasan</string>
|
||||
<string name="countries">Negara</string>
|
||||
<string name="languages">Bahasa</string>
|
||||
<string name="tags">Tags</string>
|
||||
<string name="posting_policy">Kebijakan kiriman</string>
|
||||
<string name="message_length">Panjang pesan</string>
|
||||
<string name="subscriptions">Langganan</string>
|
||||
<string name="filters">Penyaringan</string>
|
||||
<string name="subscription_id_length">Panjang ID Subscription</string>
|
||||
<string name="minimum_prefix">Awalan minimal</string>
|
||||
<string name="maximum_event_tags">Jumlah Tags maksimum</string>
|
||||
<string name="content_length">Panjang konten</string>
|
||||
<string name="minimum_pow">PoW terendah</string>
|
||||
<string name="auth">Autentikasi</string>
|
||||
<string name="payment">Pembayaran</string>
|
||||
<string name="cashu">Token Cashu</string>
|
||||
<string name="cashu_redeem">Tukarkan</string>
|
||||
<string name="no_lightning_address_set">Tidak ada alamat Lightning yang diatur</string>
|
||||
<string name="copied_token_to_clipboard">Token disalin ke papan klip</string>
|
||||
|
||||
<string name="live_stream_live_tag">DARING (ON)</string>
|
||||
<string name="live_stream_offline_tag">LURING (OFF)</string>
|
||||
<string name="live_stream_ended_tag">BERAKHIR</string>
|
||||
<string name="live_stream_planned_tag">DIJADWALKAN</string>
|
||||
|
||||
<string name="live_stream_is_offline">Siaran langsung sedang luring</string>
|
||||
<string name="live_stream_has_ended">Siaran langsung berakhir</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">Keluar akan menghapus semua informasi lokal Anda. Pastikan kunci pribadi Anda dicadangkan untuk menghindari kehilangan akun Anda. Apakah Anda ingin melanjutkan?</string>
|
||||
<string name="followed_tags">Tags yg diikuti</string>
|
||||
|
||||
<string name="relay_setup">Relai</string>
|
||||
|
||||
<string name="discover_live">Siaran Langsung</string>
|
||||
<string name="discover_community">Komunitas</string>
|
||||
<string name="discover_chat">Percakapan</string>
|
||||
<string name="community_approved_posts">Kiriman yg disetujui</string>
|
||||
|
||||
<string name="groups_no_descriptor">Grup ini tidak memiliki deskripsi atau aturan. Bicaralah dengan pemiliknya untuk menambahkannya</string>
|
||||
<string name="community_no_descriptor">Komunitas ini tidak memiliki deskripsi. Bicaralah dengan pemiliknya untuk menambahkannya</string>
|
||||
|
||||
<string name="add_sensitive_content_label">Konten Sensitif</string>
|
||||
<string name="add_sensitive_content_description">Menambahkan peringatan konten sensitif sebelum menampilkan konten ini</string>
|
||||
<string name="settings">Pengaturan</string>
|
||||
<string name="connectivity_type_always">Selalu</string>
|
||||
<string name="connectivity_type_wifi_only">Hanya Wifi</string>
|
||||
<string name="connectivity_type_never">Tidak Pernah</string>
|
||||
|
||||
<string name="system">Sistem</string>
|
||||
<string name="light">Terang</string>
|
||||
<string name="dark">Gelap</string>
|
||||
<string name="application_preferences">Pengaturan Aplikasi</string>
|
||||
<string name="language">Bahasa</string>
|
||||
<string name="theme">Tema</string>
|
||||
<string name="automatically_load_images_gifs">Pratinjau Gambar</string>
|
||||
<string name="automatically_play_videos">Pemutaran Video</string>
|
||||
<string name="automatically_show_url_preview">Pratinjau URL</string>
|
||||
<string name="load_image">Muat Gambar</string>
|
||||
|
||||
<string name="spamming_users">Pengirim Spam</string>
|
||||
|
||||
<string name="muted_button">Diredam. Klik untuk bersuara</string>
|
||||
<string name="mute_button">Bersuara. Kilik untuk meredam</string>
|
||||
<string name="search_button">Cari catatan lokal dan jarak jauh</string>
|
||||
|
||||
<string name="nip05_verified">Alamat Nostr diverifikasi</string>
|
||||
<string name="nip05_failed">Alamat Nostr gagal diverifikasi</string>
|
||||
<string name="nip05_checking">Memeriksa Alamat Nostr</string>
|
||||
<string name="select_deselect_all">Pilih / Hapus Semua</string>
|
||||
<string name="default_relays">Bawaan</string>
|
||||
<string name="select_a_relay_to_continue">Pilih Relai untuk melanjutkan</string>
|
||||
|
||||
<string name="zap_forward_title">Teruskan Zaps ke:</string>
|
||||
<string name="zap_forward_explainer">Klien yg mendukung akan meneruskan zaps ke Alamat LN atau Profil Pengguna di bawah ini, bukan ke alamat LN Anda</string>
|
||||
|
||||
<string name="geohash_title">Paparkan Lokasi sebagai</string>
|
||||
<string name="geohash_explainer">Menambahkan Geohash lokasi Anda ke postingan. Publik akan mengetahui bahwa Anda berada dalam jarak 5 km (3 mil) dari lokasi saat ini</string>
|
||||
|
||||
<string name="add_sensitive_content_explainer">Menambahkan peringatan konten sensitif sebelum menampilkan konten Anda. Ini ideal untuk konten NSFW apa pun atau konten yang mungkin dianggap menyinggung atau mengganggu oleh sebagian orang</string>
|
||||
|
||||
<string name="new_feature_nip24_might_not_be_available_title">Fitur Baru</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_description">Mengaktifkan mode ini mengharuskan Amethyst mengirim pesan NIP-24 (GiftWrapped, Sealed Direct, dan Group Messages). NIP-24 masih baru dan sebagian besar klien belum menerapkannya. Pastikan penerima menggunakan klien yang kompatibel.</string>
|
||||
<string name="new_feature_nip24_activate">Aktifkan</string>
|
||||
|
||||
<string name="messages_create_public_chat">Publik</string>
|
||||
<string name="messages_new_message">Pribadi</string>
|
||||
<string name="messages_new_message_to">Ke</string>
|
||||
<string name="messages_new_message_subject">Subjek</string>
|
||||
<string name="messages_new_message_subject_caption">Topik percakapan</string>
|
||||
<string name="messages_new_message_to_caption">\@Pengguna1, @Pengguna2, @Pengguna3</string>
|
||||
|
||||
<string name="messages_group_descriptor">Anggota grup ini</string>
|
||||
<string name="messages_new_subject_message">Penjelasan kepada anggota</string>
|
||||
<string name="messages_new_subject_message_placeholder">Mengubah nama untuk tujuan baru.</string>
|
||||
|
||||
<string name="language_description">Untuk Antarmuka Aplikasi</string>
|
||||
<string name="theme_description">Tema Gelap, Terang, atau Sistem</string>
|
||||
<string name="automatically_load_images_gifs_description">Memuat gambar dan GIF secara otomatis</string>
|
||||
<string name="automatically_play_videos_description">Memutar video dan GIF secara otomatis</string>
|
||||
<string name="automatically_show_url_preview_description">Tampilkan pratinjau URL</string>
|
||||
<string name="load_image_description">Kapan memuat gambar</string>
|
||||
|
||||
<string name="copy_url_to_clipboard">Salin URL ke papan klip</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Salin ID Catatan ke papan klip</string>
|
||||
|
||||
<string name="created_at">Dibuat pada</string>
|
||||
<string name="rules">Aturan</string>
|
||||
|
||||
<string name="status_update">Perbaharui statusmu</string>
|
||||
|
||||
<string name="lightning_wallets_not_found">Kesalahan menguraikan pesan kesalahan</string>
|
||||
<string name="poll_zap_value_min_max_explainer">Suara ditimbang berdasarkan jumlah zap. Anda dapat menetapkan jumlah minimum untuk menghindari pembuat spam dan jumlah maksimum untuk menghindari pengirim zap besar mengambil alih pemungutan suara. Gunakan jumlah yang sama di kedua kolom untuk memastikan setiap suara bernilai sama. Biarkan kosong untuk menerima jumlah berapa pun.</string>
|
||||
|
||||
<string name="error_dialog_zap_error">Tidak dapat mengirim zap</string>
|
||||
<string name="error_dialog_talk_to_user">Kirim Pesan ke Pengguna</string>
|
||||
<string name="error_dialog_button_ok">Ok</string>
|
||||
</resources>
|
||||
@@ -1,42 +1,487 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="point_to_the_qr_code">Inquadra il QR Code</string>
|
||||
<string name="point_to_the_qr_code">Inquadra il codice QR</string>
|
||||
<string name="show_qr">Mostra QR</string>
|
||||
<string name="profile_image">Foto profilo</string>
|
||||
<string name="scan_qr">Scansiona QR</string>
|
||||
<string name="show_anyway">Mostra comunque</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Post segnalato da</string>
|
||||
<string name="post_not_found">L\'evento sta caricando o non può essere trovato nei tuoi relay</string>
|
||||
<string name="post_not_found">L\'evento sta caricando o non può essere trovato nei tuoi relè</string>
|
||||
<string name="channel_image">Foto del canale</string>
|
||||
<string name="referenced_event_not_found">L\'evento referenziato non è stato trovato</string>
|
||||
<string name="could_not_decrypt_the_message">Impossibile decrittare il messaggio</string>
|
||||
<string name="referenced_event_not_found">L\'evento di riferimento non è stato trovato</string>
|
||||
<string name="could_not_decrypt_the_message">Impossibile decriptare il messaggio</string>
|
||||
<string name="group_picture">Foto del gruppo</string>
|
||||
<string name="explicit_content">Contenuto esplicito</string>
|
||||
<string name="spam">Spam</string>
|
||||
<string name="impersonation">Impersonazione</string>
|
||||
<string name="impersonation">Impersonificazione</string>
|
||||
<string name="illegal_behavior">Comportamento scorretto</string>
|
||||
<string name="unknown">Sconosciuto</string>
|
||||
<string name="relay_icon">Icona del relay</string>
|
||||
<string name="relay_icon">Icona del relè</string>
|
||||
<string name="unknown_author">Autore sconosciuto</string>
|
||||
<string name="copy_text">Copia testo</string>
|
||||
<string name="copy_user_pubkey">Copia ID autore</string>
|
||||
<string name="copy_note_id">Copia ID nota</string>
|
||||
<string name="copy_user_pubkey">Copia ID dell\'autore</string>
|
||||
<string name="copy_note_id">Copia ID della nota</string>
|
||||
<string name="broadcast">Trasmissione</string>
|
||||
<string name="request_deletion">Richiedi cancellazione</string>
|
||||
<string name="block_report">Blocca / Segnala</string>
|
||||
<string name="block_hide_user"><![CDATA[Blocca e Nascondi Utente]]></string>
|
||||
<string name="report_spam_scam">Segnala Spam / Truffa</string>
|
||||
<string name="report_impersonation">Segnala impersonatore</string>
|
||||
<string name="report_impersonation">Segnala impersonificatore</string>
|
||||
<string name="report_explicit_content">Segnala contenuto esplicito</string>
|
||||
<string name="report_illegal_behaviour">Segnala comportamento inapproriato</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_reply">Accedi con una Chiave Privata per rispondere</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Accedi con una Chiave Privata per ricondividere i post</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">Accedi con una Chiave Privata per poter mettere mi piace ai post</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">Nessun importo per gli Zap impostato. Tieni premuto per aggiungerne</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Accedi con una Chiave Privata per poter inviare Zaps</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Accedi con una Chiave Privata per seguire</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Accedi con una Chiave Privata per smettere di seguire</string>
|
||||
<string name="zaps">Zaps</string>
|
||||
<string name="view_count">Visualizzazioni</string>
|
||||
<string name="boost">Boost</string>
|
||||
<string name="boosted">ricondiviso</string>
|
||||
<string name="report_illegal_behaviour">Segnala comportamento scorretto</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_reply">Accedi con una chiave privata per poter rispondere</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Accedi con una chiave privata per poter condividere i post</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">Accedi con una chiave privata per mettere mi piace ai post</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">Nessun ammontare per gli Zap impostato. Tieni premuto per cambiare</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Accedi con una chiave privata per poter inviare gli Zap</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Accedi con una chiave privata per poter seguire</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Accedi con una chiave privata per poter smettere di seguire</string>
|
||||
<string name="zaps">Zap</string>
|
||||
<string name="view_count">Mostra conteggio</string>
|
||||
<string name="boost">Condividi</string>
|
||||
<string name="boosted">condiviso</string>
|
||||
<string name="quote">Cita</string>
|
||||
<string name="new_amount_in_sats">Nuovo importo in Sats</string>
|
||||
<string name="add">Aggiungi</string>
|
||||
<string name="replying_to">"rispondendo a"</string>
|
||||
<string name="and">" e "</string>
|
||||
<string name="in_channel">"nel canale "</string>
|
||||
<string name="profile_banner">Immagine di copertina</string>
|
||||
<string name="payment_successful">Pagamento effettuato</string>
|
||||
<string name="error_parsing_error_message">Errore analisi messaggio d\'errore</string>
|
||||
<string name="following">" Seguiti"</string>
|
||||
<string name="followers">" Seguaci"</string>
|
||||
<string name="profile">Profilo</string>
|
||||
<string name="security_filters">Filtri di sicurezza</string>
|
||||
<string name="log_out">Esci</string>
|
||||
<string name="show_more">Mostra altro</string>
|
||||
<string name="lightning_invoice">Fattura Lightning</string>
|
||||
<string name="pay">Paga</string>
|
||||
<string name="lightning_tips">Mance Lightning</string>
|
||||
<string name="note_to_receiver">Nota per il Ricevente</string>
|
||||
<string name="thank_you_so_much">Grazie mille!</string>
|
||||
<string name="amount_in_sats">Importo in Sats</string>
|
||||
<string name="send_sats">Invia Sats</string>
|
||||
<string name="error_parsing_preview_for">"Errore nella creazione dell'anteprima di %1$s : %2$s"</string>
|
||||
<string name="preview_card_image_for">"Anteprima immagine per %1$s"</string>
|
||||
<string name="new_channel">Nuovo canale</string>
|
||||
<string name="channel_name">Nome canale</string>
|
||||
<string name="my_awesome_group">Il mio fantastico gruppo</string>
|
||||
<string name="picture_url">Url foto</string>
|
||||
<string name="description">Descrizione</string>
|
||||
<string name="about_us">"Su di noi... "</string>
|
||||
<string name="what_s_on_your_mind">A cosa stai pensando?</string>
|
||||
<string name="post">Post</string>
|
||||
<string name="save">Salva</string>
|
||||
<string name="create">Crea</string>
|
||||
<string name="cancel">Cancella</string>
|
||||
<string name="failed_to_upload_the_image">Impossibile caricare l\'immagine</string>
|
||||
<string name="relay_address">Indirizzo Relay</string>
|
||||
<string name="posts">Posts</string>
|
||||
<string name="bytes">Bytes</string>
|
||||
<string name="errors">Errori</string>
|
||||
<string name="home_feed">Home Feed</string>
|
||||
<string name="private_message_feed">Lista Messaggi Privati</string>
|
||||
<string name="public_chat_feed">Lista Chat Pubbliche</string>
|
||||
<string name="global_feed">Feed Globale</string>
|
||||
<string name="search_feed">Risultati Ricerca</string>
|
||||
<string name="add_a_relay">Aggiungi un Relay</string>
|
||||
<string name="display_name">Nome Visualizzato</string>
|
||||
<string name="my_display_name">Il mio nome visualizzato</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="my_username">Il mio nome utente</string>
|
||||
<string name="about_me">Su di me</string>
|
||||
<string name="avatar_url">Immagine del profilo URL</string>
|
||||
<string name="banner_url">Copertina URL</string>
|
||||
<string name="website_url">Sito web URL</string>
|
||||
<string name="ln_address">Indirizzo LN</string>
|
||||
<string name="ln_url_outdated">LN URL (obsoleto)</string>
|
||||
<string name="image_saved_to_the_gallery">Immagine salvata nella galleria</string>
|
||||
<string name="failed_to_save_the_image">Salvataggio dell\'immagine non riuscito</string>
|
||||
<string name="upload_image">Carica immagine</string>
|
||||
<string name="uploading">Caricamento…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">L\'utente non dispone di un indirizzo Lightning per ricevere sats</string>
|
||||
<string name="reply_here">"rispondi qui.. "</string>
|
||||
<string name="copies_the_note_id_to_the_clipboard_for_sharing">Copia l\'ID della nota negli appunti per la condivisione in Nostr</string>
|
||||
<string name="copy_channel_id_note_to_the_clipboard">Copia l\'ID del canale (Nota) negli appunti</string>
|
||||
<string name="edits_the_channel_metadata">Modifica i metadati del canale</string>
|
||||
<string name="join">Unisciti</string>
|
||||
<string name="known">Conosciuto</string>
|
||||
<string name="new_requests">Nuove richieste</string>
|
||||
<string name="blocked_users">Utenti bloccati</string>
|
||||
<string name="new_threads">Nuove Discussioni</string>
|
||||
<string name="conversations">Conversazioni</string>
|
||||
<string name="notes">Note</string>
|
||||
<string name="replies">Risposte</string>
|
||||
<string name="follows">"Seguiti"</string>
|
||||
<string name="reports">"Segnalazioni"</string>
|
||||
<string name="more_options">Più Opzioni</string>
|
||||
<string name="relays">" Relè"</string>
|
||||
<string name="website">Sito web</string>
|
||||
<string name="lightning_address">Indirizzo Lightning</string>
|
||||
<string name="copies_the_nsec_id_your_password_to_the_clipboard_for_backup">Copia l\'ID Nsec (la password) negli appunti per il backup</string>
|
||||
<string name="copy_private_key_to_the_clipboard">Copia la chiave segreta negli appunti</string>
|
||||
<string name="copies_the_public_key_to_the_clipboard_for_sharing">Copia la chiave pubblica negli appunti per la condivisione</string>
|
||||
<string name="copy_public_key_npub_to_the_clipboard">Copia la chiave pubblica (Npub) negli appunti</string>
|
||||
<string name="send_a_direct_message">Invia un messaggio diretto</string>
|
||||
<string name="edits_the_user_s_metadata">Modifica i metadati dell\'utente</string>
|
||||
<string name="follow">Segui</string>
|
||||
<string name="follow_back">Segui anche tu</string>
|
||||
<string name="unblock">Sblocca</string>
|
||||
<string name="copy_user_id">Copia ID Utente</string>
|
||||
<string name="unblock_user">Sblocca utente</string>
|
||||
<string name="npub_hex_username">"npub, nome utente, testo"</string>
|
||||
<string name="clear">Pulisci</string>
|
||||
<string name="app_logo">Logo dell\'app</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec.. o npub..</string>
|
||||
<string name="show_password">Mostra password</string>
|
||||
<string name="hide_password">Nascondi password</string>
|
||||
<string name="invalid_key">Chiave non valida</string>
|
||||
<string name="i_accept_the">"Accetto il/lo/la/i/gli/le "</string>
|
||||
<string name="terms_of_use">Condizioni di utilizzo</string>
|
||||
<string name="acceptance_of_terms_is_required">L\'accettazione dei termini è obbligatoria</string>
|
||||
<string name="key_is_required">La chiave è obbligatoria</string>
|
||||
<string name="login">Accesso</string>
|
||||
<string name="generate_a_new_key">Genera una nuova chiave</string>
|
||||
<string name="loading_feed">Caricamento del feed</string>
|
||||
<string name="error_loading_replies">"Errore durante il caricamento delle risposte: "</string>
|
||||
<string name="try_again">Prova di nuovo</string>
|
||||
<string name="feed_is_empty">Il feed è vuoto.</string>
|
||||
<string name="refresh">Aggiorna</string>
|
||||
<string name="created">creato</string>
|
||||
<string name="with_description_of">con la descrizione di</string>
|
||||
<string name="and_picture">e l\'immagine</string>
|
||||
<string name="changed_chat_name_to">modificato il nome della chat in</string>
|
||||
<string name="description_to">descrizione per</string>
|
||||
<string name="and_picture_to">e immagine per</string>
|
||||
<string name="leave">Esci</string>
|
||||
<string name="unfollow">Non seguire più</string>
|
||||
<string name="channel_created">Canale creato</string>
|
||||
<string name="channel_information_changed_to">"Informazioni del canale modificate in"</string>
|
||||
<string name="public_chat">Chat Pubblica</string>
|
||||
<string name="posts_received">post ricevuti</string>
|
||||
<string name="remove">Cancella</string>
|
||||
<string name="translations_auto">Auto</string>
|
||||
<string name="translations_translated_from">tradotto da</string>
|
||||
<string name="translations_to">a</string>
|
||||
<string name="translations_show_in_lang_first">Mostra prima in %1$s</string>
|
||||
<string name="translations_always_translate_to_lang">Traduci sempre in %1$s</string>
|
||||
<string name="translations_never_translate_from_lang">Non tradurre mai da %1$s</string>
|
||||
<string name="nip_05">Indirizzo Nostr</string>
|
||||
<string name="never">mai</string>
|
||||
<string name="now">adesso</string>
|
||||
<string name="h">h</string>
|
||||
<string name="m">m</string>
|
||||
<string name="d">d</string>
|
||||
<string name="nudity">Nuditá</string>
|
||||
<string name="profanity_hateful_speech">Volgaritá / Linguaggio forte</string>
|
||||
<string name="report_hateful_speech">Segnala linguaggio forte</string>
|
||||
<string name="report_nudity_porn">Segnala Nuditá / Pornografia</string>
|
||||
<string name="others">altri</string>
|
||||
<string name="mark_all_known_as_read">Segna tutti i conosciuti come letti</string>
|
||||
<string name="mark_all_new_as_read">Segna tutti i nuovi come letti</string>
|
||||
<string name="mark_all_as_read">Segna tutti come letti</string>
|
||||
<string name="backup_keys">Esegui backup delle chiavi</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Backup delle Chiavi e Consigli di Sicurezza
|
||||
\n\nIl tuo account è reso sicuro da una chiave privata. La chiave è una lunga stringa di caratteri casuali che inizia con **nsec1**. Chiunque abbia accesso alla tua chiave privata puó pubblicare contenuti con la tua identitá.
|
||||
\n\n- Non devi **mai** inserire la tua chiave privata in un sito o software di cui non ti fidi.
|
||||
\n- Gli sviluppatori di Amethyst non chiederanno **mai** la tua chiave privata.
|
||||
\n- Mantieni **sempre** al sicuro un backup della tua chiave privata per recuperare l\'account. Ti suggeriamo di usare un password manager.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">Chiave segreta (nsec) copiata negli appunti</string>
|
||||
<string name="copy_my_secret_key">Copia la mia chiave segreta</string>
|
||||
<string name="biometric_authentication_failed">Autenticazione fallita</string>
|
||||
<string name="biometric_error">Errore</string>
|
||||
<string name="badge_created_by">"Creato da %1$s"</string>
|
||||
<string name="badge_award_image_for">"Immagine premio badge per %1$s"</string>
|
||||
<string name="new_badge_award_notif">Hai ricevuto un nuovo premio Badge</string>
|
||||
<string name="award_granted_to">Premio badge assegnato a</string>
|
||||
<string name="copied_note_text_to_clipboard">Testo della nota copiato negli appunti</string>
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">\@npub dell\'autore copiata negli appunti</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">ID della nota copiato (@note1) negli appunti</string>
|
||||
<string name="select_text_dialog_top">Seleziona testo</string>
|
||||
<string name="private_conversation_notification">"<Incapace di decriptare il messaggio privato>\n\nSei stato citato in una conversazione privata/criptata tra %1$s e %2$s."</string>
|
||||
<string name="account_switch_add_account_dialog_title">Aggiungi un nuovo profilo</string>
|
||||
<string name="drawer_accounts">Profili</string>
|
||||
<string name="account_switch_select_account">Seleziona profilo</string>
|
||||
<string name="account_switch_add_account_btn">Aggiungi un nuovo profilo</string>
|
||||
<string name="account_switch_active_account">Profilo attivo</string>
|
||||
<string name="account_switch_has_private_key">Ha la chiave privata</string>
|
||||
<string name="account_switch_pubkey_only">Solo lettura, nessuna chiave privata</string>
|
||||
<string name="back">Indietro</string>
|
||||
<string name="quick_action_select">Seleziona</string>
|
||||
<string name="quick_action_share_browser_link">Condividi Collegamento Browser</string>
|
||||
<string name="quick_action_share">Condividi</string>
|
||||
<string name="quick_action_copy_user_id">ID dell\'autore</string>
|
||||
<string name="quick_action_copy_note_id">ID della nota</string>
|
||||
<string name="quick_action_copy_text">Copia testo</string>
|
||||
<string name="quick_action_delete">Elimina</string>
|
||||
<string name="quick_action_unfollow">Smetti di seguire</string>
|
||||
<string name="quick_action_follow">Segui</string>
|
||||
<string name="quick_action_request_deletion_alert_title">Richiedi eliminazione</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst richiederà che la tua nota sia cancellata dai relè a cui sei attualmente connesso. Non vi è alcuna garanzia che la tua nota venga cancellata definitivamente da quei relè o da altri relè dove può essere memorizzata.</string>
|
||||
<string name="quick_action_block_dialog_btn">Blocca</string>
|
||||
<string name="quick_action_delete_dialog_btn">Elimina</string>
|
||||
<string name="quick_action_block">Blocca</string>
|
||||
<string name="quick_action_report">Segnala</string>
|
||||
<string name="quick_action_delete_button">Elimina</string>
|
||||
<string name="quick_action_dont_show_again_button">Non mostrare più</string>
|
||||
<string name="report_dialog_spam">Spam o truffe</string>
|
||||
<string name="report_dialog_profanity">Blasfemia o comportamento odioso</string>
|
||||
<string name="report_dialog_impersonation">Impersonificazione malevola</string>
|
||||
<string name="report_dialog_nudity">Nudità o contenuto grafico</string>
|
||||
<string name="report_dialog_illegal">Comportamento scorretto</string>
|
||||
<string name="report_dialog_blocking_a_user">Il blocco di un utente nasconderà il suo contenuto nella tua app. Le tue note sono ancora visibili pubblicamente, anche per le persone che blocchi. Gli utenti bloccati sono elencati nella schermata Filtri di sicurezza.</string>
|
||||
<string name="report_dialog_block_hide_user_btn"><![CDATA[Blocca e Nascondi Utente]]></string>
|
||||
<string name="report_dialog_report_btn">Segnala un abuso</string>
|
||||
<string name="report_dialog_reminder_public">Tutte le segnalazioni pubblicate saranno visibili pubblicamente.</string>
|
||||
<string name="report_dialog_additional_reason_placeholder">Facoltativamente fornisci un contesto aggiuntivo sulla tua segnalazione…</string>
|
||||
<string name="report_dialog_additional_reason_label">Contesto Aggiuntivo</string>
|
||||
<string name="report_dialog_select_reason_label">Motivo</string>
|
||||
<string name="report_dialog_select_reason_placeholder">Seleziona un motivo…</string>
|
||||
<string name="report_dialog_post_report_btn">Pubblica una segnalazione</string>
|
||||
<string name="report_dialog_title">Blocca e Segnala</string>
|
||||
<string name="block_only">Blocca</string>
|
||||
<string name="bookmarks">Segnalibri</string>
|
||||
<string name="private_bookmarks">Segnalibri privati</string>
|
||||
<string name="public_bookmarks">Segnalibri pubblici</string>
|
||||
<string name="add_to_private_bookmarks">Aggiungi ai segnalibri privati</string>
|
||||
<string name="add_to_public_bookmarks">Aggiungi ai segnalibri pubblici</string>
|
||||
<string name="remove_from_private_bookmarks">Rimuovi dai segnalibri privati</string>
|
||||
<string name="remove_from_public_bookmarks">Rimuovi dai segnalibri pubblici</string>
|
||||
<string name="wallet_connect_service">Servizio di Connessione Portafoglio</string>
|
||||
<string name="wallet_connect_service_explainer">Autorizza un Nostr Secret a pagare gli zaps senza lasciare l\'app. Mantieni il segreto al sicuro e usa un relè privato se possibile</string>
|
||||
<string name="wallet_connect_service_pubkey">Wallet Connect Pubkey</string>
|
||||
<string name="wallet_connect_service_relay">Wallet Connect Relè</string>
|
||||
<string name="wallet_connect_service_secret">Wallet Connect Secret</string>
|
||||
<string name="wallet_connect_service_show_secret">Mostra chiave segreta</string>
|
||||
<string name="wallet_connect_service_secret_placeholder">nsec / chiave privata esadecimale</string>
|
||||
<string name="pledge_amount_in_sats">Importo in pegno in sats</string>
|
||||
<string name="post_poll">Pubblica sondaggio</string>
|
||||
<string name="poll_heading_required">Campi obbligatori:</string>
|
||||
<string name="poll_zap_recipients">Destinatari zap</string>
|
||||
<string name="poll_primary_description">Descrizione principale del sondaggio…</string>
|
||||
<string name="poll_option_index">Opzione %s</string>
|
||||
<string name="poll_option_description">Descrizione opzione sondaggio</string>
|
||||
<string name="poll_heading_optional">Campi facoltativi:</string>
|
||||
<string name="poll_zap_value_min">Zap minimo</string>
|
||||
<string name="poll_zap_value_max">Zap massimo</string>
|
||||
<string name="poll_consensus_threshold">Consenso</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="poll_closing_time">Chiudi dopo</string>
|
||||
<string name="poll_closing_time_days">giorni</string>
|
||||
<string name="poll_is_closed">Il sondaggio è chiuso a nuovi voti</string>
|
||||
<string name="poll_zap_amount">Quantità in zap</string>
|
||||
<string name="one_vote_per_user_on_atomic_votes">Per questo tipo di sondaggio è consentito solo un voto per utente</string>
|
||||
<string name="looking_for_event">"Cercando l'evento %1$s"</string>
|
||||
<string name="custom_zaps_add_a_message">Aggiungi un messaggio pubblico</string>
|
||||
<string name="custom_zaps_add_a_message_private">Aggiungi un messaggio privato</string>
|
||||
<string name="custom_zaps_add_a_message_nonzap">Aggiungi un messaggio di fattura</string>
|
||||
<string name="custom_zaps_add_a_message_example">Grazie per tutto il tuo lavoro!</string>
|
||||
<string name="lightning_create_and_add_invoice">Crea e Aggiungi</string>
|
||||
<string name="poll_author_no_vote">Gli autori del sondaggio non possono votare nei propri sondaggi.</string>
|
||||
<string name="hash_verification_passed">Questo contenuto è lo stesso da quando è stato pubblicato</string>
|
||||
<string name="hash_verification_failed">Questo contenuto è cambiato. L\'autore potrebbe non aver visto o approvato la modifica</string>
|
||||
<string name="content_description_add_image">Aggiungi Immagine</string>
|
||||
<string name="content_description_add_video">Aggiungi Video</string>
|
||||
<string name="content_description_add_document">Aggiungi Documento</string>
|
||||
<string name="add_content">Aggiungi al Messaggio</string>
|
||||
<string name="content_description">Descrizione dei contenuti</string>
|
||||
<string name="content_description_example">Una barca blu in una spiaggia di sabbia bianca al tramonto</string>
|
||||
<string name="zap_type">Tipo di Zap</string>
|
||||
<string name="zap_type_explainer">Tipo di Zap per tutte le opzioni</string>
|
||||
<string name="zap_type_public">Pubblico</string>
|
||||
<string name="zap_type_public_explainer">Tutti possono vedere la transazione e il messaggio</string>
|
||||
<string name="zap_type_private">Privato</string>
|
||||
<string name="zap_type_private_explainer">Mittente e Destinatario possono vedersi e leggere il messaggio</string>
|
||||
<string name="zap_type_anonymous">Anonimo</string>
|
||||
<string name="zap_type_anonymous_explainer">Il destinatario e il pubblico non sanno chi ha inviato il pagamento</string>
|
||||
<string name="zap_type_nonzap">Non-Zap</string>
|
||||
<string name="zap_type_nonzap_explainer">Nessuna traccia in Nostr, solo in Lightning</string>
|
||||
<string name="file_server">File Server</string>
|
||||
<string name="zap_forward_lnAddress">Indirizzo LN o @User</string>
|
||||
<string name="upload_server_imgur">imgur.com - fidato</string>
|
||||
<string name="upload_server_imgur_explainer">Imgur può modificare il file</string>
|
||||
<string name="upload_server_nostrimg">nostrimg.com - fidato</string>
|
||||
<string name="upload_server_nostrimg_explainer">NostrImg può modificare il file</string>
|
||||
<string name="upload_server_nostrbuild">nostr.build - fidato</string>
|
||||
<string name="upload_server_nostrbuild_explainer">Nostr.build può modificare il file</string>
|
||||
<string name="upload_server_nostrfilesdev">nostrfiles.dev - fidato</string>
|
||||
<string name="upload_server_nostrfilesdev_explainer">Nostrfiles.dev può modificare il file</string>
|
||||
<string name="upload_server_nostrcheckme">nostrcheck.me - fidato</string>
|
||||
<string name="upload_server_nostrcheckme_explainer">nostrcheck.me può modificare il file</string>
|
||||
<string name="upload_server_imgur_nip94">Imgur verificabile (NIP-94)</string>
|
||||
<string name="upload_server_imgur_nip94_explainer">Controlla se Imgur ha modificato il file. Nuovo NIP: altri client potrebbero non vederlo</string>
|
||||
<string name="upload_server_nostrimg_nip94">NostrImg verificabile (NIP-94)</string>
|
||||
<string name="upload_server_nostrimg_nip94_explainer">Controlla se NostrImg ha modificato il file. Nuovo NIP: altri client potrebbero non vederlo</string>
|
||||
<string name="upload_server_nostrbuild_nip94">Nostr.build verificabile (NIP-94)</string>
|
||||
<string name="upload_server_nostrbuild_nip94_explainer">Controlla se Nostr.build ha modificato il file. Nuovo NIP: altri client potrebbero non vederlo</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94">Nostrfiles.dev verificabile (NIP-94)</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94_explainer">Controlla se Nostrfiles.dev ha modificato il file. Nuovo NIP: altri client potrebbero non vederlo</string>
|
||||
<string name="upload_server_nostrcheckme_nip94">Nostrcheck.me verificabile (NIP-94)</string>
|
||||
<string name="upload_server_nostrcheckme_nip94_explainer">Controlla se Nostrcheck.me ha modificato il file. Nuovo NIP: altri client potrebbero non vederlo</string>
|
||||
<string name="upload_server_relays_nip95">I tuoi relè (NIP-95)</string>
|
||||
<string name="upload_server_relays_nip95_explainer">I file sono ospitati dai tuoi relè. Nuovo NIP: controlla se lo supportano</string>
|
||||
<string name="connect_via_tor_short">Configurazione Tor/Orbot</string>
|
||||
<string name="connect_via_tor">Connettiti tramite la tua configurazione di Orbot</string>
|
||||
<string name="do_you_really_want_to_disable_tor_title">Disconnettere dal tuo Orbot/Tor?</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">I tuoi dati verranno immediatamente trasferiti nella rete regolare</string>
|
||||
<string name="yes">Sì</string>
|
||||
<string name="no">No</string>
|
||||
<string name="follow_list_selection">Segui Lista</string>
|
||||
<string name="follow_list_kind3follows">Tutti i seguiti</string>
|
||||
<string name="follow_list_global">Globale</string>
|
||||
<string name="connect_through_your_orbot_setup_markdown"> ## Connettiti tramite Tor con Orbot
|
||||
\n\n1. Installa [Orbot](https://play.google. om/store/apps/details?id=org.torproject.android)
|
||||
\n2. Avvia Orbot
|
||||
\n3. In Orbot, controlla la porta Socks. Il valore predefinito utilizza 9050
|
||||
\n4. Se necessario, cambiare la porta in Orbot
|
||||
\n5. Configura la porta Socks in questa schermata
|
||||
\n6. Premi il pulsante Attiva per usare Orbot come proxy
|
||||
</string>
|
||||
<string name="orbot_socks_port">Porta Socks di Orbot</string>
|
||||
<string name="invalid_port_number">Numero della porta non valido</string>
|
||||
<string name="use_orbot">Usa Orbot</string>
|
||||
<string name="disconnect_from_your_orbot_setup">Disconnetti Tor/Orbot</string>
|
||||
<string name="app_notification_dms_channel_name">Messaggi privati</string>
|
||||
<string name="app_notification_dms_channel_description">Notifica quando arriva un messaggio privato</string>
|
||||
<string name="app_notification_zaps_channel_name">Zaps Ricevuti</string>
|
||||
<string name="app_notification_zaps_channel_description">Notifica quando qualcuno ti invia uno zap</string>
|
||||
<string name="app_notification_zaps_channel_message">%1$s sats</string>
|
||||
<string name="app_notification_zaps_channel_message_from">Da %1$s</string>
|
||||
<string name="app_notification_zaps_channel_message_for">per %1$s</string>
|
||||
<string name="reply_notify">Notificare: </string>
|
||||
<string name="channel_list_join_conversation">Unisciti Alla Conversazione</string>
|
||||
<string name="channel_list_user_or_group_id">ID utente o di gruppo</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub, nevent o esadecimale</string>
|
||||
<string name="channel_list_create_channel">Crea</string>
|
||||
<string name="channel_list_join_channel">Unisciti</string>
|
||||
<string name="today">Oggi</string>
|
||||
<string name="content_warning">Avviso sui contenuti</string>
|
||||
<string name="content_warning_explanation">Questo post contiene contenuti sensibili che alcune persone possono trovare offensivi o disturbanti</string>
|
||||
<string name="content_warning_hide_all_sensitive_content">Nascondi sempre i contenuti sensibili</string>
|
||||
<string name="content_warning_show_all_sensitive_content">Mostra sempre i contenuti sensibili</string>
|
||||
<string name="content_warning_see_warnings">Mostra sempre gli avvisi sui contenuti</string>
|
||||
<string name="recommended_apps">Consigliati: </string>
|
||||
<string name="filter_spam_from_strangers">Filtra spam dagli sconosciuti</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">Avvisa quando i post hanno segnalazioni da parte di chi segui</string>
|
||||
<string name="new_reaction_symbol">Nuovo Simbolo Di Reazione</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">Nessun tipo di reazione selezionato. Premi a lungo per cambiare</string>
|
||||
<string name="zapraiser">Zapraiser</string>
|
||||
<string name="zapraiser_explainer">Aggiunge una quantità di sats da raccogliere per questo post. I client che supportano questa funzione la mostreranno con una barra di avanzamento per incentivare le donazioni</string>
|
||||
<string name="zapraiser_target_amount_in_sats">Ammontare dell\'obiettivo in sats</string>
|
||||
<string name="sats_to_complete">Zapraiser a %1$s. %2$s sats all\'obiettivo</string>
|
||||
<string name="read_from_relay">Leggi dal Relè</string>
|
||||
<string name="write_to_relay">Scrivi su Relè</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Si è verificato un errore nel tentativo di ottenere le informazioni del relè da %1$s</string>
|
||||
<string name="owner">Proprietario</string>
|
||||
<string name="version">Versione</string>
|
||||
<string name="software">Software</string>
|
||||
<string name="contact">Contatto</string>
|
||||
<string name="supports">NIPs Supportati</string>
|
||||
<string name="admission_fees">Commissioni di Ammissione</string>
|
||||
<string name="payments_url">Url pagamenti</string>
|
||||
<string name="limitations">Limitazioni</string>
|
||||
<string name="countries">Paesi</string>
|
||||
<string name="languages">Lingue</string>
|
||||
<string name="tags">Tags</string>
|
||||
<string name="posting_policy">Politica di pubblicazione</string>
|
||||
<string name="message_length">Lunghezza del messaggio</string>
|
||||
<string name="subscriptions">Iscrizioni</string>
|
||||
<string name="filters">Filtri</string>
|
||||
<string name="subscription_id_length">Lunghezza ID Iscrizione</string>
|
||||
<string name="minimum_prefix">Prefisso minimo</string>
|
||||
<string name="maximum_event_tags">Tags massimi dell\'evento</string>
|
||||
<string name="content_length">Lunghezza del contenuto</string>
|
||||
<string name="minimum_pow">PoW Minima</string>
|
||||
<string name="auth">Auth</string>
|
||||
<string name="payment">Pagamento</string>
|
||||
<string name="cashu">Token di Cashu</string>
|
||||
<string name="cashu_redeem">Riscatta</string>
|
||||
<string name="no_lightning_address_set">Nessun indirizzo Lightning impostato</string>
|
||||
<string name="copied_token_to_clipboard">Token copiato negli appunti</string>
|
||||
<string name="live_stream_live_tag">IN DIRETTA</string>
|
||||
<string name="live_stream_offline_tag">NON IN LINEA</string>
|
||||
<string name="live_stream_ended_tag">CONCLUSO</string>
|
||||
<string name="live_stream_planned_tag">PROGRAMMATO</string>
|
||||
<string name="live_stream_is_offline">Livestream non è in linea</string>
|
||||
<string name="live_stream_has_ended">Livestream concluso</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">La disconnessione elimina tutte le tue informazioni locali. Assicurati di avere le tue chiavi private salvate per evitare di perdere il tuo account. Vuoi continuare?</string>
|
||||
<string name="followed_tags">Tag Seguiti</string>
|
||||
<string name="relay_setup">Relè</string>
|
||||
<string name="discover_live">In diretta</string>
|
||||
<string name="discover_community">Comunità</string>
|
||||
<string name="discover_chat">Chat</string>
|
||||
<string name="community_approved_posts">Post approvati</string>
|
||||
<string name="groups_no_descriptor">Questo gruppo non ha una descrizione o regole. Parla con il proprietario per aggiungerne una</string>
|
||||
<string name="community_no_descriptor">Questa comunità non ha una descrizione. Parla con il proprietario per aggiungerne una</string>
|
||||
<string name="add_sensitive_content_label">Contenuto Sensibile</string>
|
||||
<string name="add_sensitive_content_description">Aggiunge un avviso di contenuto sensibile prima di mostrare questo contenuto</string>
|
||||
<string name="settings">Impostazioni</string>
|
||||
<string name="connectivity_type_always">Sempre</string>
|
||||
<string name="connectivity_type_wifi_only">Solo Wi-Fi</string>
|
||||
<string name="connectivity_type_never">Mai</string>
|
||||
<string name="system">Sistema</string>
|
||||
<string name="light">Chiaro</string>
|
||||
<string name="dark">Scuro</string>
|
||||
<string name="application_preferences">Preferenze dell\'Applicazione</string>
|
||||
<string name="language">Lingua</string>
|
||||
<string name="theme">Tema</string>
|
||||
<string name="automatically_load_images_gifs">Anteprima dell\'immagine</string>
|
||||
<string name="automatically_play_videos">Riproduzione video</string>
|
||||
<string name="automatically_show_url_preview">Anteprima URL</string>
|
||||
<string name="load_image">Carica immagine</string>
|
||||
<string name="spamming_users">Spammers</string>
|
||||
<string name="muted_button">Mutato. Clicca per riattivare l\'audio</string>
|
||||
<string name="mute_button">Audio acceso. Clicca per disattivarlo</string>
|
||||
<string name="search_button">Cerca registrazioni locali e remote</string>
|
||||
<string name="nip05_verified">L\'indirizzo Nostr è stato verificato</string>
|
||||
<string name="nip05_failed">Verifica dell\'indirizzo Nostr non riuscita</string>
|
||||
<string name="nip05_checking">Controllo dell\'indirizzo Nostr</string>
|
||||
<string name="select_deselect_all">Seleziona/Deseleziona tutto</string>
|
||||
<string name="default_relays">Predefinito</string>
|
||||
<string name="select_a_relay_to_continue">Seleziona un relè per continuare</string>
|
||||
<string name="zap_forward_title">Inoltra gli Zap a:</string>
|
||||
<string name="zap_forward_explainer">I client che lo supportano inoltreranno gli Zap all\'indirizzo LN o al profilo dell\'utente di seguito anzichè al tuo</string>
|
||||
<string name="geohash_title">Esporre la posizione come </string>
|
||||
<string name="geohash_explainer">Aggiunge un Geohash della tua posizione al post. Il pubblico saprà che ti trovi a 5 km dalla posizione attuale</string>
|
||||
<string name="add_sensitive_content_explainer">Aggiunge un avviso di contenuto sensibile prima di mostrare il tuo contenuto. Questo è ideale per qualsiasi contenuto NSFW o che alcune persone potrebbero trovare offensivo o disturbante</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_title">Nuova Funzionalità</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_description">Attivare questa modalità richiede ad Amethyst di inviare un messaggio NIP-24 (GiftWrapped, Sealed Direct e messaggi di gruppo). NIP-24 è nuovo e la maggior parte dei client non l\'hanno ancora implementato. Assicurarsi che il ricevitore stia utilizzando un client compatibile.</string>
|
||||
<string name="new_feature_nip24_activate">Attivare</string>
|
||||
<string name="messages_create_public_chat">Pubblico/a</string>
|
||||
<string name="messages_new_message">Privato</string>
|
||||
<string name="messages_new_message_to">A</string>
|
||||
<string name="messages_new_message_subject">Soggetto</string>
|
||||
<string name="messages_new_message_subject_caption">Argomento della conversazione</string>
|
||||
<string name="messages_new_message_to_caption">"\@User1, @User2, @User3"</string>
|
||||
<string name="messages_group_descriptor">Membri di questo gruppo</string>
|
||||
<string name="messages_new_subject_message">Spiegazione ai membri</string>
|
||||
<string name="messages_new_subject_message_placeholder">Cambiare il nome per i nuovi obiettivi.</string>
|
||||
<string name="language_description">Per l\'interfaccia dell\'app</string>
|
||||
<string name="theme_description">Tema scuro, chiaro o di sistema</string>
|
||||
<string name="automatically_load_images_gifs_description">Carica automaticamente immagini e GIF</string>
|
||||
<string name="automatically_play_videos_description">Riproduce automaticamente video e GIF</string>
|
||||
<string name="automatically_show_url_preview_description">Mostra anteprime URL</string>
|
||||
<string name="load_image_description">Quando caricare le immagini</string>
|
||||
<string name="copy_url_to_clipboard">Copia URL negli appunti</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Copia ID della Nota negli appunti</string>
|
||||
<string name="created_at">Creato il</string>
|
||||
<string name="rules">Regole</string>
|
||||
<string name="status_update">Aggiorna il tuo stato</string>
|
||||
<string name="lightning_wallets_not_found">Errore analisi messaggio d\'errore</string>
|
||||
<string name="poll_zap_value_min_max_explainer">I voti sono ponderati con l\'importo dello zap. È possibile impostare una quantità minima per evitare gli spammer e una quantità massima per evitare che un grande zapper predomini il sondaggio. Usa lo stesso importo in entrambi i campi per assicurarti che ogni voto valga lo stesso importo. Lascialo vuoto per accettare qualsiasi importo.</string>
|
||||
<string name="error_dialog_zap_error">Impossibile inviare lo zap</string>
|
||||
<string name="error_dialog_talk_to_user">Invia un messaggio all\'utente</string>
|
||||
<string name="error_dialog_button_ok">OK</string>
|
||||
<string name="relay_information_document_error_assemble_url">Impossibile raggiungere %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">Impossibile raggiungere %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">Impossibile analizzare il risultato da %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s fallito con il codice %2$s</string>
|
||||
<string name="active_for">Attivo per: </string>
|
||||
<string name="active_for_home">Pagina iniziale</string>
|
||||
<string name="active_for_msg">Messaggi diretti</string>
|
||||
<string name="active_for_chats">Chat</string>
|
||||
<string name="active_for_global">Globale</string>
|
||||
<string name="active_for_search">Cerca</string>
|
||||
</resources>
|
||||
|
||||
@@ -474,4 +474,9 @@
|
||||
<string name="error_dialog_zap_error">Kan zap niet verzenden</string>
|
||||
<string name="error_dialog_talk_to_user">Bericht de gebruiker</string>
|
||||
<string name="error_dialog_button_ok">Ok</string>
|
||||
<string name="relay_information_document_error_assemble_url">Mislukt om %1$s te bereiken: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">Mislukt om %1$s te bereiken: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">Mislukt om het resultaat van %1$s te parsen: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s mislukt met code %2$s</string>
|
||||
<string name="active_for">Actief voor: </string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="point_to_the_qr_code">Peka på QR-koden</string>
|
||||
<string name="point_to_the_qr_code">Peka mot QR-koden</string>
|
||||
<string name="show_qr">Visa QR</string>
|
||||
<string name="profile_image">Profilbild</string>
|
||||
<string name="scan_qr">Scanna QR</string>
|
||||
@@ -472,4 +472,14 @@
|
||||
<string name="error_dialog_zap_error">Kunde inte skicka zap</string>
|
||||
<string name="error_dialog_talk_to_user">Meddela användaren</string>
|
||||
<string name="error_dialog_button_ok">Ok</string>
|
||||
<string name="relay_information_document_error_assemble_url">Misslyckades att nå %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">Misslyckades att nå %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">Misslyckades att parsa resultatet från %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s misslyckades med kod %2$s</string>
|
||||
<string name="active_for">Aktiv för: </string>
|
||||
<string name="active_for_home">Hem</string>
|
||||
<string name="active_for_msg">Meddelanden</string>
|
||||
<string name="active_for_chats">Chattar</string>
|
||||
<string name="active_for_global">Globalt</string>
|
||||
<string name="active_for_search">Sök</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,486 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="point_to_the_qr_code">Elekeza kwa Msimbo wa QR</string>
|
||||
<string name="show_qr">Onyesha QR</string>
|
||||
<string name="profile_image">Picha ya Wasifu</string>
|
||||
<string name="scan_qr">Changanua QR</string>
|
||||
<string name="show_anyway">Onyesha Hata hivyo</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Chapisho liliripotiwa na</string>
|
||||
<string name="post_not_found">Tukio linapakia au halipatikani kwenye orodha yako ya upeanaji</string>
|
||||
<string name="channel_image">Picha ya Kituo</string>
|
||||
<string name="referenced_event_not_found">Tukio linalorejelewa halijapatikana</string>
|
||||
<string name="could_not_decrypt_the_message">Haikuweza kusimbua ujumbe</string>
|
||||
<string name="group_picture">Picha ya Kikundi</string>
|
||||
<string name="explicit_content">Maudhui Dhahiri</string>
|
||||
<string name="spam">Barua taka</string>
|
||||
<string name="impersonation">Uigaji</string>
|
||||
<string name="illegal_behavior">Tabia Haramu</string>
|
||||
<string name="unknown">Haijulikani</string>
|
||||
<string name="relay_icon">Aikoni ya Relay</string>
|
||||
<string name="unknown_author">Mwandishi asiyejulikana</string>
|
||||
<string name="copy_text">Nakili Maandishi</string>
|
||||
<string name="copy_user_pubkey">Nakili Kitambulisho cha Mwandishi</string>
|
||||
<string name="copy_note_id">Nakili Kitambulisho cha Dokezo</string>
|
||||
<string name="broadcast">Tangaza</string>
|
||||
<string name="request_deletion">Omba Kufutwa</string>
|
||||
<string name="block_report">Zuia / Ripoti</string>
|
||||
<string name="block_hide_user"><![CDATA[Zuia na Ufiche Mtumiaji]]></string>
|
||||
<string name="report_spam_scam">Ripoti Barua Taka / Ulaghai</string>
|
||||
<string name="report_impersonation">Ripoti Uigaji</string>
|
||||
<string name="report_explicit_content">Ripoti Maudhui Dhahiri</string>
|
||||
<string name="report_illegal_behaviour">Ripoti Tabia Haramu</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_reply">Ingia kwa kutumia Ufunguo wa Faragha ili uweze kujibu</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Ingia kwa kutumia ufunguo wa Faragha ili uweze kuboresha machapisho</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">Ingia kwa kutumia ufunguo wa Faragha ili kupenda Machapisho</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">Hakuna Usanidi wa Kiasi cha Zap. Bonyeza kwa Muda mrefu ili kubadilisha</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Ingia kwa kutumia ufunguo wa Faragha ili uweze kutuma Zaps</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Ingia kwa kutumia Ufunguo wa Faragha ili uweze Kufuata</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Ingia kwa kutumia Ufunguo wa Faragha ili uweze Kuacha Kufuata</string>
|
||||
<string name="zaps">Zaps</string>
|
||||
<string name="view_count">Idadi ya maoni</string>
|
||||
<string name="boost">Kuongeza</string>
|
||||
<string name="boosted">imeimarishwa</string>
|
||||
<string name="quote">Nukuu</string>
|
||||
<string name="new_amount_in_sats">Kiasi Kipya kwa Sats</string>
|
||||
<string name="add">Ongeza</string>
|
||||
<string name="replying_to">"kujibu "</string>
|
||||
<string name="and">" na "</string>
|
||||
<string name="in_channel">"katika chaneli "</string>
|
||||
<string name="profile_banner">Bango la Wasifu</string>
|
||||
<string name="payment_successful">Malipo Yamefaulu</string>
|
||||
<string name="error_parsing_error_message">Hitilafu katika kuchanganua ujumbe wa hitilafu</string>
|
||||
<string name="following">" Kufuatia"</string>
|
||||
<string name="followers">" Wafuasi"</string>
|
||||
<string name="profile">Wasifu</string>
|
||||
<string name="security_filters">Vichujio vya Usalama</string>
|
||||
<string name="log_out">Ondoka</string>
|
||||
<string name="show_more">Onyesha Zaidi</string>
|
||||
<string name="lightning_invoice">Ankara ya umeme</string>
|
||||
<string name="pay">Lipa</string>
|
||||
<string name="lightning_tips">Vidokezo vya Umeme</string>
|
||||
<string name="note_to_receiver">Kumbuka kwa Mpokeaji</string>
|
||||
<string name="thank_you_so_much">Asante sana!</string>
|
||||
<string name="amount_in_sats">Kiasi katika Sats</string>
|
||||
<string name="send_sats">Tuma Sats</string>
|
||||
<string name="error_parsing_preview_for">"Hitilafu katika kuchanganua onyesho la kukagua %1$s : %2$s"</string>
|
||||
<string name="preview_card_image_for">"Hakiki Picha ya Kadi ya %1$s"</string>
|
||||
<string name="new_channel">Kituo Kipya</string>
|
||||
<string name="channel_name">Kituo Kipya</string>
|
||||
<string name="my_awesome_group">Kundi Langu la Ajabu</string>
|
||||
<string name="picture_url">Url ya picha</string>
|
||||
<string name="description">Maelezo</string>
|
||||
<string name="about_us">"Kuhusu sisi.. "</string>
|
||||
<string name="what_s_on_your_mind">Unafikiria nini?</string>
|
||||
<string name="post">Tuma</string>
|
||||
<string name="save">Hifadhi</string>
|
||||
<string name="create">Unda</string>
|
||||
<string name="cancel">Ghairi</string>
|
||||
<string name="failed_to_upload_the_image">Kushindwa kupakia picha</string>
|
||||
<string name="relay_address">Anwani ya Relay</string>
|
||||
<string name="posts">Machapisho</string>
|
||||
<string name="bytes">Bayti</string>
|
||||
<string name="errors">Makosa</string>
|
||||
<string name="home_feed">Malisho ya Nyumbani</string>
|
||||
<string name="private_message_feed">Malisho ya Ujumbe Binafsi</string>
|
||||
<string name="public_chat_feed">Malisho ya Mazungumzo ya Umma</string>
|
||||
<string name="global_feed">Malisho ya Kimataifa</string>
|
||||
<string name="search_feed">Malisho ya Utafutaji</string>
|
||||
<string name="add_a_relay">Ongeza Relay</string>
|
||||
<string name="display_name">Jina la Kuonyesha</string>
|
||||
<string name="my_display_name">Jina langu la kuonyesha</string>
|
||||
<string name="username">Jina la mtumiaji</string>
|
||||
<string name="my_username">Jina langu la mtumiaji</string>
|
||||
<string name="about_me">Kuhusu mimi</string>
|
||||
<string name="avatar_url">URL ya Picha ya Profaili</string>
|
||||
<string name="banner_url">URL ya Bango</string>
|
||||
<string name="website_url">URL ya Tovuti</string>
|
||||
<string name="ln_address">Anwani ya LN</string>
|
||||
<string name="ln_url_outdated">URL ya LN (imepitwa na wakati)</string>
|
||||
<string name="image_saved_to_the_gallery">Picha imesave kwenye galeria</string>
|
||||
<string name="failed_to_save_the_image">Kushindwa kuhifadhi picha</string>
|
||||
<string name="upload_image">Pakia Picha</string>
|
||||
<string name="uploading">Inapakia…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">Mtumiaji hana anwani ya umeme iliyoandaliwa kupokea sati</string>
|
||||
<string name="reply_here">"jibu hapa.. "</string>
|
||||
<string name="copies_the_note_id_to_the_clipboard_for_sharing">Hunakili Kitambulisho cha Dokezo kwenye ubao wa kunakili ili kushiriki katika Nostr</string>
|
||||
<string name="copy_channel_id_note_to_the_clipboard">Nakili Kitambulisho cha Kituo (Kumbuka) kwenye Ubao wa kunakili</string>
|
||||
<string name="edits_the_channel_metadata">Huhariri Metadata ya Kituo</string>
|
||||
<string name="join">Jiunge</string>
|
||||
<string name="known">Inajulikana</string>
|
||||
<string name="new_requests">Maombi Mapya</string>
|
||||
<string name="blocked_users">Watumiaji Waliozuiwa</string>
|
||||
<string name="new_threads">Nyuzi Mpya</string>
|
||||
<string name="conversations">Mazungumzo</string>
|
||||
<string name="notes">Vidokezo</string>
|
||||
<string name="replies">Majibu</string>
|
||||
<string name="follows">"Anafuata"</string>
|
||||
<string name="reports">"Reports"</string>
|
||||
<string name="more_options">Chaguo Zaidi</string>
|
||||
<string name="relays">" Relays"</string>
|
||||
<string name="website">Tovuti</string>
|
||||
<string name="lightning_address">Anwani ya Umeme</string>
|
||||
<string name="copies_the_nsec_id_your_password_to_the_clipboard_for_backup">Hunakili Kitambulisho cha Nsec (nywila yako) kwenye ubao wa kunakili kwa ajili ya nakala rudufu</string>
|
||||
<string name="copy_private_key_to_the_clipboard">Nakili Ufunguo wa Siri kwenye Ubao wa Kunakili</string>
|
||||
<string name="copies_the_public_key_to_the_clipboard_for_sharing">Hunakili ufunguo wa umma kwenye ubao wa kunakili kwa ajili ya kushiriki</string>
|
||||
<string name="copy_public_key_npub_to_the_clipboard">Nakili Ufunguo wa Umma (NPub) kwenye Ubao wa Kunakili</string>
|
||||
<string name="send_a_direct_message">Tuma Ujumbe wa Moja kwa Moja</string>
|
||||
<string name="edits_the_user_s_metadata">Huhariri Metadata ya Mtumiaji</string>
|
||||
<string name="follow">Fuata</string>
|
||||
<string name="follow_back">Fuata tena</string>
|
||||
<string name="unblock">Fungua</string>
|
||||
<string name="copy_user_id">Nakili Kitambulisho cha Mtumiaji</string>
|
||||
<string name="unblock_user">Fungua Mtumiaji</string>
|
||||
<string name="npub_hex_username">"npub, jina la mtumiaji, maandishi"</string>
|
||||
<string name="clear">Ondoa</string>
|
||||
<string name="app_logo">Nembo ya Programu</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec.. au npub..</string>
|
||||
<string name="show_password">Onyesha Nywila</string>
|
||||
<string name="hide_password">Ficha Nywila</string>
|
||||
<string name="invalid_key">Ufunguo usiofaa</string>
|
||||
<string name="i_accept_the">"Nakubali "</string>
|
||||
<string name="terms_of_use">masharti ya matumizi</string>
|
||||
<string name="acceptance_of_terms_is_required">Kukubaliana na masharti ni lazima</string>
|
||||
<string name="key_is_required">Ufunguo unahitajika</string>
|
||||
<string name="login">Ingia</string>
|
||||
<string name="generate_a_new_key">Tengeneza Ufunguo Mpya</string>
|
||||
<string name="loading_feed">Inapakia malisho</string>
|
||||
<string name="error_loading_replies">"Kosa katika kupakia majibu: "</string>
|
||||
<string name="try_again">Jaribu tena</string>
|
||||
<string name="feed_is_empty">Malisho ni tupu.</string>
|
||||
<string name="refresh">Sasisha</string>
|
||||
<string name="created">imeundwa</string>
|
||||
<string name="with_description_of">na maelezo ya</string>
|
||||
<string name="and_picture">na picha</string>
|
||||
<string name="changed_chat_name_to">ilisitisha jina la mazungumzo hadi</string>
|
||||
<string name="description_to">maelezo ya</string>
|
||||
<string name="and_picture_to">na picha ya</string>
|
||||
<string name="leave">Ondoka</string>
|
||||
<string name="unfollow">Acha kufuata</string>
|
||||
<string name="channel_created">Kituo kimeundwa</string>
|
||||
<string name="channel_information_changed_to">"Maelezo ya Kituo yamebadilika hadi"</string>
|
||||
<string name="public_chat">Mazungumzo ya Umma</string>
|
||||
<string name="posts_received">machapisho yamepokelewa</string>
|
||||
<string name="remove">Ondoa</string>
|
||||
<string name="translations_auto">Auto</string>
|
||||
<string name="translations_translated_from">imetafsiriwa kutoka</string>
|
||||
<string name="translations_to">kwa</string>
|
||||
<string name="translations_show_in_lang_first">Onyesha kwa %1$s kwanza</string>
|
||||
<string name="translations_always_translate_to_lang">Daima itafsiriwe kwa %1$s</string>
|
||||
<string name="translations_never_translate_from_lang">Kamwe usitafsiri kutoka %1$s</string>
|
||||
<string name="nip_05">Anwani ya Nostr</string>
|
||||
<string name="never">kamwe</string>
|
||||
<string name="now">sasa</string>
|
||||
<string name="h">h</string>
|
||||
<string name="m">m</string>
|
||||
<string name="d">d</string>
|
||||
<string name="nudity">Uchi</string>
|
||||
<string name="profanity_hateful_speech">Lugha chafu / Maneno yenye chuki</string>
|
||||
<string name="report_hateful_speech">Ripoti Lugha yenye chuki</string>
|
||||
<string name="report_nudity_porn">Ripoti Uchi / Pornografia</string>
|
||||
<string name="others">wengine</string>
|
||||
<string name="mark_all_known_as_read">Funga kama Zimejulikana</string>
|
||||
<string name="mark_all_new_as_read">Funga kama Mpya</string>
|
||||
<string name="mark_all_as_read">Funga kama Zimejulikana</string>
|
||||
<string name="backup_keys">Nakili Nakala za Ufunguo</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Ufundi wa Nakala ya Ufunguo na Usalama
|
||||
\n\nAkaunti yako inalindwa na ufunguo wa siri. Ufunguo ni mfululizo mrefu wa herufi zisizo na mpangilio, ukitangulia na **nsec1**. Mtu yeyote aliye na ufikiaji wa ufunguo wako wa siri anaweza kuchapisha maudhui kwa kutumia kitambulisho chako.
|
||||
\n\n- **Usiweke** ufunguo wako wa siri kwenye tovuti au programu usio na imani.
|
||||
\n- Watengenezaji wa Amethyst **hawata** kuuliza ufunguo wako wa siri.
|
||||
\n- **Tunashauri** kuwa na nakala rudufu salama ya ufunguo wako wa siri kwa ajili ya kupata akaunti. Tunapendekeza kutumia meneja wa nywila.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">Ufunguo wa siri (nsec) umeinakiliwa kwenye ubao wa kunakili</string>
|
||||
<string name="copy_my_secret_key">Nakili Ufunguo Wangu wa Siri</string>
|
||||
<string name="biometric_authentication_failed">Uthibitishaji umeshindwa</string>
|
||||
<string name="biometric_error">Kosa</string>
|
||||
<string name="badge_created_by">"Imeundwa na %1$s"</string>
|
||||
<string name="badge_award_image_for">"Picha ya tuzo ya alama kwa %1$s"</string>
|
||||
<string name="new_badge_award_notif">Umepokea Tuzo Mpya ya Alama</string>
|
||||
<string name="award_granted_to">Tuzo ya alama imetolewa kwa</string>
|
||||
<string name="copied_note_text_to_clipboard">Maudhui ya dokezo yamenakiliwa kwenye ubao wa kunakili</string>
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">Imesakinishwa @npub ya mwandishi kwa ubao wa kunakili</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">Imesakinishwa ID ya andiko (@note1) kwa ubao wa kunakili</string>
|
||||
<string name="select_text_dialog_top">Chagua Matini</string>
|
||||
<string name="private_conversation_notification">"<Haiwezi kuifanyia utawala ujumbe binafsi uliofichwa>\n\nUlinukuliwa katika mazungumzo ya faragha/yaliyofichwa kati ya %1$s na %2$s."</string>
|
||||
<string name="account_switch_add_account_dialog_title">Ongeza Akaunti Mpya</string>
|
||||
<string name="drawer_accounts">Akaunti</string>
|
||||
<string name="account_switch_select_account">Chagua Akaunti</string>
|
||||
<string name="account_switch_add_account_btn">Ongeza Akaunti Mpya</string>
|
||||
<string name="account_switch_active_account">Akaunti Iliyo Hai</string>
|
||||
<string name="account_switch_has_private_key">Ina ufunguo wa faragha</string>
|
||||
<string name="account_switch_pubkey_only">Soma tu, hakuna ufunguo wa faragha</string>
|
||||
<string name="back">Rudi</string>
|
||||
<string name="quick_action_select">Chagua</string>
|
||||
<string name="quick_action_share_browser_link">Shiriki Kiungo cha Kivinjari</string>
|
||||
<string name="quick_action_share">Shiriki</string>
|
||||
<string name="quick_action_copy_user_id">ID ya Mwandishi</string>
|
||||
<string name="quick_action_copy_note_id">ID ya Andiko</string>
|
||||
<string name="quick_action_copy_text">Nakili Matini</string>
|
||||
<string name="quick_action_delete">Futa</string>
|
||||
<string name="quick_action_unfollow">Acha Kufuata</string>
|
||||
<string name="quick_action_follow">Fuata</string>
|
||||
<string name="quick_action_request_deletion_alert_title">Omba Kufutwa</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst itaomba kwamba andiko lako lifutwe kutoka kwa relays ambazo umekuwa umeunganishwa. Hakuna dhamana kwamba andiko lako litafutwa kabisa kutoka kwa relays hizo, au kutoka kwa relays nyingine ambapo linaweza kuwa limehifadhiwa.</string>
|
||||
<string name="quick_action_block_dialog_btn">Zuia</string>
|
||||
<string name="quick_action_delete_dialog_btn">Futa</string>
|
||||
<string name="quick_action_block">Zuia</string>
|
||||
<string name="quick_action_report">Ripoti</string>
|
||||
<string name="quick_action_delete_button">Futa</string>
|
||||
<string name="quick_action_dont_show_again_button">Usionyeshe Tena</string>
|
||||
<string name="report_dialog_spam">Barua Taka au Udanganyifu</string>
|
||||
<string name="report_dialog_profanity">Lugha chafu au tabia yenye chuki</string>
|
||||
<string name="report_dialog_impersonation">Uigaaji mbaya</string>
|
||||
<string name="report_dialog_nudity">Uchi au yaliyomo ya kushangaza</string>
|
||||
<string name="report_dialog_illegal">Tabia Haramu</string>
|
||||
<string name="report_dialog_blocking_a_user">Kuzuia mtumiaji kutaficha maudhui yao kwenye programu yako. Andiko zako bado zinaweza kuonekana kwa umma, pamoja na watu wanaokuzuia. Watumiaji waliozuiliwa wameorodheshwa kwenye skrini ya Filta za Usalama.</string>
|
||||
<string name="report_dialog_block_hide_user_btn"><![CDATA[Zuia na Ficha Mtumiaji]]></string>
|
||||
<string name="report_dialog_report_btn">Ripoti Matusi</string>
|
||||
<string name="report_dialog_reminder_public">Ripoti zote zilizopostiwa zitaonekana kwa umma.</string>
|
||||
<string name="report_dialog_additional_reason_placeholder">Toa muktadha wa ziada ikiwa unataka…</string>
|
||||
<string name="report_dialog_additional_reason_label">Muktadha wa Ziada</string>
|
||||
<string name="report_dialog_select_reason_label">Sababu</string>
|
||||
<string name="report_dialog_select_reason_placeholder">Chagua sababu…</string>
|
||||
<string name="report_dialog_post_report_btn">Chapisha Ripoti</string>
|
||||
<string name="report_dialog_title">Zuia na Ripoti</string>
|
||||
<string name="block_only">Zuia</string>
|
||||
<string name="bookmarks">Vialamisho</string>
|
||||
<string name="private_bookmarks">Vialamisho Binafsi</string>
|
||||
<string name="public_bookmarks">Vialamisho vya Umma</string>
|
||||
<string name="add_to_private_bookmarks">Ongeza kwenye Vialamisho Binafsi</string>
|
||||
<string name="add_to_public_bookmarks">Ongeza kwenye Vialamisho vya Umma</string>
|
||||
<string name="remove_from_private_bookmarks">Ondoa kutoka kwenye Vialamisho Binafsi</string>
|
||||
<string name="remove_from_public_bookmarks">Ondoa kutoka kwenye Vialamisho vya Umma</string>
|
||||
<string name="wallet_connect_service">Huduma ya Kuchanganya Pochi</string>
|
||||
<string name="wallet_connect_service_explainer">Inaidhinisha Nostr Secret kulipa zaps bila kuacha programu. Weka siri salama na tumia relay ya faragha ikiwezekana</string>
|
||||
<string name="wallet_connect_service_pubkey">Ufunguo wa Umma wa Kuchanganya Pochi</string>
|
||||
<string name="wallet_connect_service_relay">Kuchanganya Relay</string>
|
||||
<string name="wallet_connect_service_secret">Siri ya Kuchanganya Pochi</string>
|
||||
<string name="wallet_connect_service_show_secret">Onyesha ufunguo wa siri</string>
|
||||
<string name="wallet_connect_service_secret_placeholder">nsec / ufunguo wa siri wa hex</string>
|
||||
<string name="pledge_amount_in_sats">Kiasi cha Ahadi katika Sats</string>
|
||||
<string name="post_poll">Chapisha Kura</string>
|
||||
<string name="poll_heading_required">Sehemu zinazohitajika:</string>
|
||||
<string name="poll_zap_recipients">Wapokeaji wa Zaps</string>
|
||||
<string name="poll_primary_description">Maelezo ya msingi ya kura…</string>
|
||||
<string name="poll_option_index">Chaguo %s</string>
|
||||
<string name="poll_option_description">Maelezo ya chaguo cha kura</string>
|
||||
<string name="poll_heading_optional">Sehemu za hiari:</string>
|
||||
<string name="poll_zap_value_min">Kima cha Chini cha Zaps</string>
|
||||
<string name="poll_zap_value_max">Kima cha Juu cha Zaps</string>
|
||||
<string name="poll_consensus_threshold">Uamuzi</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="poll_closing_time">Funga baada ya</string>
|
||||
<string name="poll_closing_time_days">siku</string>
|
||||
<string name="poll_is_closed">Kura imefungwa kwa kura mpya</string>
|
||||
<string name="poll_zap_amount">Kiasi cha Zaps</string>
|
||||
<string name="one_vote_per_user_on_atomic_votes">Kura moja tu kwa mtumiaji inaruhusiwa kwenye aina hii ya kura</string>
|
||||
<string name="looking_for_event">"Inatafuta Tukio %1$s"</string>
|
||||
<string name="custom_zaps_add_a_message">Ongeza ujumbe wa umma</string>
|
||||
<string name="custom_zaps_add_a_message_private">Ongeza ujumbe wa faragha</string>
|
||||
<string name="custom_zaps_add_a_message_nonzap">Ongeza ujumbe wa ankra</string>
|
||||
<string name="custom_zaps_add_a_message_example">Asante kwa kazi yako yote!</string>
|
||||
<string name="lightning_create_and_add_invoice">Tengeneza na Ongeza</string>
|
||||
<string name="poll_author_no_vote">Waandishi wa kura hawawezi kupiga kura kwenye kura zao wenyewe.</string>
|
||||
<string name="hash_verification_passed">Yaliyomo haya ni sawa tangu chapisho</string>
|
||||
<string name="hash_verification_failed">Yaliyomo haya yamebadilika. Mwandishi huenda hakuyapata au kuyaidhinisha mabadiliko</string>
|
||||
<string name="content_description_add_image">Ongeza Picha</string>
|
||||
<string name="content_description_add_video">Ongeza Video</string>
|
||||
<string name="content_description_add_document">Ongeza Hati</string>
|
||||
<string name="add_content">Ongeza kwenye Ujumbe</string>
|
||||
<string name="content_description">Maelezo ya yaliyomo</string>
|
||||
<string name="content_description_example">Boti ya buluu kwenye ufukwe mweupe wa mchanga wakati wa jua</string>
|
||||
<string name="zap_type">Aina ya Zap</string>
|
||||
<string name="zap_type_explainer">Aina ya Zap kwa chaguo zote</string>
|
||||
<string name="zap_type_public">Umma</string>
|
||||
<string name="zap_type_public_explainer">Kila mtu anaweza kuona shughuli na ujumbe</string>
|
||||
<string name="zap_type_private">Faragha</string>
|
||||
<string name="zap_type_private_explainer">Mpokeaji na Mjumbe wanaweza kuona kila mmoja na kusoma ujumbe</string>
|
||||
<string name="zap_type_anonymous">Bila Kutambuliwa</string>
|
||||
<string name="zap_type_anonymous_explainer">Mpokeaji na umma hawajui ni nani aliyetuma malipo</string>
|
||||
<string name="zap_type_nonzap">Bila Zap</string>
|
||||
<string name="zap_type_nonzap_explainer">Hakuna alama katika Nostr, tu katika Lightning</string>
|
||||
<string name="file_server">Seva ya Faili</string>
|
||||
<string name="zap_forward_lnAddress">LnAddress au @Mtumiaji</string>
|
||||
<string name="upload_server_imgur">imgur.com - inaaminika</string>
|
||||
<string name="upload_server_imgur_explainer">Imgur inaweza kubadilisha faili</string>
|
||||
<string name="upload_server_nostrimg">nostrimg.com - inaaminika</string>
|
||||
<string name="upload_server_nostrimg_explainer">NostrImg inaweza kubadilisha faili</string>
|
||||
<string name="upload_server_nostrbuild">nostr.build - inaaminika</string>
|
||||
<string name="upload_server_nostrbuild_explainer">Nostr.build inaweza kubadilisha faili</string>
|
||||
<string name="upload_server_nostrfilesdev">nostrfiles.dev - inaaminika</string>
|
||||
<string name="upload_server_nostrfilesdev_explainer">Nostrfiles.dev inaweza kubadilisha faili</string>
|
||||
<string name="upload_server_nostrcheckme">nostrcheck.me - inaaminika</string>
|
||||
<string name="upload_server_nostrcheckme_explainer">nostrcheck.me inaweza kubadilisha faili</string>
|
||||
<string name="upload_server_imgur_nip94">Imgur inayoweza kuthibitika (NIP-94)</string>
|
||||
<string name="upload_server_imgur_nip94_explainer">Inachunguza ikiwa Imgur imebadilisha faili. NIP mpya: wateja wengine huenda wasiione</string>
|
||||
<string name="upload_server_nostrimg_nip94">NostrImg inayoweza kuthibitika (NIP-94)</string>
|
||||
<string name="upload_server_nostrimg_nip94_explainer">Inachunguza ikiwa NostrImg imebadilisha faili. NIP mpya: wateja wengine huenda wasiione</string>
|
||||
<string name="upload_server_nostrbuild_nip94">Nostr.build inayoweza kuthibitika (NIP-94)</string>
|
||||
<string name="upload_server_nostrbuild_nip94_explainer">Inachunguza ikiwa Nostr.build imebadilisha faili. NIP mpya: wateja wengine huenda wasiione</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94">Nostrfiles.dev inayoweza kuthibitika (NIP-94)</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94_explainer">Inachunguza ikiwa Nostrfiles.dev imebadilisha faili. NIP mpya: wateja wengine huenda wasiione</string>
|
||||
<string name="upload_server_nostrcheckme_nip94">Nostrcheck.me inayoweza kuthibitika (NIP-94)</string>
|
||||
<string name="upload_server_nostrcheckme_nip94_explainer">Inachunguza ikiwa Nostrcheck.me imebadilisha faili. NIP mpya: wateja wengine huenda wasiione</string>
|
||||
<string name="upload_server_relays_nip95">Relays Zako (NIP-95)</string>
|
||||
<string name="upload_server_relays_nip95_explainer">Faili zinahifadhiwa na relays zako. NIP mpya: hakikisha zinazisaidia</string>
|
||||
<string name="connect_via_tor_short">Usanidi wa Tor/Orbot</string>
|
||||
<string name="connect_via_tor">Unaweza kuunganisha kupitia usanidi wako wa Orbot</string>
|
||||
<string name="do_you_really_want_to_disable_tor_title">Kujitenga na Orbot/Tor yako?</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">Data yako itahamishwa mara moja kwenye mtandao wa kawaida</string>
|
||||
<string name="yes">Ndio</string>
|
||||
<string name="no">Hapana</string>
|
||||
<string name="follow_list_selection">Orodha ya Kufuatilia</string>
|
||||
<string name="follow_list_kind3follows">Yafuatayo Yote</string>
|
||||
<string name="follow_list_global">Kote</string>
|
||||
<string name="connect_through_your_orbot_setup_markdown">## Kuungana kupitia Tor na Orbot
|
||||
\n\n1. Sakinisha [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)
|
||||
\n2. Anza Orbot
|
||||
\n3. Katika Orbot, angalia bandari ya Socks. Chaguo-msingi hutumia 9050
|
||||
\n4. Ikiwa ni lazima, badilisha bandari katika Orbot
|
||||
\n5. Sanidi bandari ya Socks kwenye skrini hii
|
||||
\n6. Bonyeza kitufe cha Kuamilisha kutumia Orbot kama mbadala</string>
|
||||
<string name="orbot_socks_port">Bandari ya Socks ya Orbot</string>
|
||||
<string name="invalid_port_number">Nambari ya bandari sio sahihi</string>
|
||||
<string name="use_orbot">Tumia Orbot</string>
|
||||
<string name="disconnect_from_your_orbot_setup">Jitenge na Tor/Orbot yako</string>
|
||||
<string name="app_notification_dms_channel_name">Ujumbe Binafsi</string>
|
||||
<string name="app_notification_dms_channel_description">Inakuarifu wakati ujumbe binafsi unapowasili</string>
|
||||
<string name="app_notification_zaps_channel_name">Zaps Zilizopokelewa</string>
|
||||
<string name="app_notification_zaps_channel_description">Inakuarifu wakati mtu anapokuzap</string>
|
||||
<string name="app_notification_zaps_channel_message">%1$s sats</string>
|
||||
<string name="app_notification_zaps_channel_message_from">Kutoka kwa %1$s</string>
|
||||
<string name="app_notification_zaps_channel_message_for">kwa %1$s</string>
|
||||
<string name="reply_notify">Taarifa: </string>
|
||||
<string name="channel_list_join_conversation">jiunge kwenye mazungumzo</string>
|
||||
<string name="channel_list_user_or_group_id">Kitambulisho cha Mtumiaji au Kikundi</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub, nevent au hex</string>
|
||||
<string name="channel_list_create_channel">Unda</string>
|
||||
<string name="channel_list_join_channel">jiunge</string>
|
||||
<string name="today">Leo</string>
|
||||
<string name="content_warning">Onyo la Yaliyomo</string>
|
||||
<string name="content_warning_explanation">Chapisho hili lina yaliyomo nyeti ambayo baadhi ya watu wanaweza kuona kama ya kuchukiza au kusumbua</string>
|
||||
<string name="content_warning_hide_all_sensitive_content">Daima ficha yaliyomo nyeti</string>
|
||||
<string name="content_warning_show_all_sensitive_content">Daima onyesha yaliyomo nyeti</string>
|
||||
<string name="content_warning_see_warnings">Daima onyesha onyo za yaliyomo</string>
|
||||
<string name="recommended_apps">Inapendekeza: </string>
|
||||
<string name="filter_spam_from_strangers">Chuja barua taka kutoka kwa wageni</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">Onya wakati machapisho yana ripoti kutoka kwa wafuatavyo wako</string>
|
||||
<string name="new_reaction_symbol">Alama Mpya ya Majibu</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">Hakuna aina za majibu zilizochaguliwa. Bonyeza kwa muda mrefu kubadilisha</string>
|
||||
<string name="zapraiser">Mzushi wa Zaps</string>
|
||||
<string name="zapraiser_explainer">Inaongeza kiasi lengwa cha sats kwa chapisho hili. Wateja wanaosaidia wanaweza kuonyesha hii kama bar ya maendeleo ili kuhimiza michango</string>
|
||||
<string name="zapraiser_target_amount_in_sats">Kiasi Lengo katika Sats</string>
|
||||
<string name="sats_to_complete">Mzushi wa Zaps kwa %1$s. Sats %2$s hadi kufikia lengo</string>
|
||||
<string name="read_from_relay">Soma kutoka kwa Relay</string>
|
||||
<string name="write_to_relay">Andika kwa Relay</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Kuna kosa lililotokea jaribu kupata habari za Relay kutoka kwa %1$s</string>
|
||||
<string name="owner">Mmiliki</string>
|
||||
<string name="version">Toleo</string>
|
||||
<string name="software">Programu</string>
|
||||
<string name="contact">Wasiliana</string>
|
||||
<string name="supports">NIPs Zinazoungwa mkono</string>
|
||||
<string name="admission_fees">Ada za Kuingia</string>
|
||||
<string name="payments_url">URL ya Malipo</string>
|
||||
<string name="limitations">Vikwazo</string>
|
||||
<string name="countries">Nchi</string>
|
||||
<string name="languages">Lugha</string>
|
||||
<string name="tags">Alama</string>
|
||||
<string name="posting_policy">Sera ya Kutuma</string>
|
||||
<string name="message_length">Urefu wa Ujumbe</string>
|
||||
<string name="subscriptions">Michango</string>
|
||||
<string name="filters">Vichujio</string>
|
||||
<string name="subscription_id_length">Urefu wa Kitambulisho cha Michango</string>
|
||||
<string name="minimum_prefix">Awamu ya Chini</string>
|
||||
<string name="maximum_event_tags">Vichwa vya Tukio vya Juu</string>
|
||||
<string name="content_length">Urefu wa Yaliyomo</string>
|
||||
<string name="minimum_pow">PoW ya Chini</string>
|
||||
<string name="auth">Uthibitishaji</string>
|
||||
<string name="payment">Malipo</string>
|
||||
<string name="cashu">Cashu Token</string>
|
||||
<string name="cashu_redeem">Kukomboa</string>
|
||||
<string name="no_lightning_address_set">Hakuna Anwani ya Lightning Imewekwa</string>
|
||||
<string name="copied_token_to_clipboard">Kitufe kimekopwa kwenye ubao wa kunakili</string>
|
||||
<string name="live_stream_live_tag">Moja kwa Moja</string>
|
||||
<string name="live_stream_offline_tag">Nje ya Mtandao</string>
|
||||
<string name="live_stream_ended_tag">Imemalizika</string>
|
||||
<string name="live_stream_planned_tag">Imepangwa</string>
|
||||
<string name="live_stream_is_offline">Moja kwa Moja Imekwama</string>
|
||||
<string name="live_stream_has_ended">Moja kwa Moja Imekwisha</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">Kutoka kunaondoa taarifa zako za eneo la kuhifadhia data. Hakikisha una nakala za funguo zako binafsi ili kuepuka kupoteza akaunti yako. Je, unataka kuendelea?</string>
|
||||
<string name="followed_tags">Mada Zinazofuatwa</string>
|
||||
<string name="relay_setup">Usanidi wa Relays</string>
|
||||
<string name="discover_live">Moja kwa Moja</string>
|
||||
<string name="discover_community">Jumuiya</string>
|
||||
<string name="discover_chat">Mazungumzo</string>
|
||||
<string name="community_approved_posts">Machapisho Yaliyoidhinishwa</string>
|
||||
<string name="groups_no_descriptor">Kikundi hiki hakitumii maelezo au kanuni. Ongea na mmiliki ili aongeze.</string>
|
||||
<string name="community_no_descriptor">Jumuiya hii haitumii maelezo. Ongea na mmiliki ili aongeze.</string>
|
||||
<string name="add_sensitive_content_label">Yaliyomo Nyeti</string>
|
||||
<string name="add_sensitive_content_description">Inaongeza onyo la yaliyomo nyeti kabla ya kuonyesha yaliyomo hii</string>
|
||||
<string name="settings">Mipangilio</string>
|
||||
<string name="connectivity_type_always">Daima</string>
|
||||
<string name="connectivity_type_wifi_only">Wi-Fi tu</string>
|
||||
<string name="connectivity_type_never">Kamwe</string>
|
||||
<string name="system">Mfumo</string>
|
||||
<string name="light">Mwanga</string>
|
||||
<string name="dark">Giza</string>
|
||||
<string name="application_preferences">Mapendeleo ya Programu</string>
|
||||
<string name="language">Lugha</string>
|
||||
<string name="theme">Mandhari</string>
|
||||
<string name="automatically_load_images_gifs">Onesha Picha</string>
|
||||
<string name="automatically_play_videos">Cheza Video</string>
|
||||
<string name="automatically_show_url_preview">Onesha Hakikisho za URL</string>
|
||||
<string name="load_image">Soma Picha</string>
|
||||
<string name="spamming_users">Wahasibu</string>
|
||||
<string name="muted_button">Walemavu. Bonyeza kuwasha sauti</string>
|
||||
<string name="mute_button">Sauti imewashwa. Bonyeza kulegeza</string>
|
||||
<string name="search_button">Tafuta rekodi za eneo la kuhifadhia data na rekodi za mbali</string>
|
||||
<string name="nip05_verified">Anwani ya Nostr ilithibitishwa</string>
|
||||
<string name="nip05_failed">Anwani ya Nostr ilishindwa kuthibitishwa</string>
|
||||
<string name="nip05_checking">Kuthibitisha Anwani ya Nostr</string>
|
||||
<string name="select_deselect_all">Chagua/Chagua Vingine Vyote</string>
|
||||
<string name="default_relays">Chaguo-msingi</string>
|
||||
<string name="select_a_relay_to_continue">Chagua kiotomatiki kuendelea</string>
|
||||
<string name="zap_forward_title">Tuma Zaps kwa:</string>
|
||||
<string name="zap_forward_explainer">Wateja wanaosaidia watatuma Zaps kwa LNAddress au Wasifu wa Mtumiaji hapa chini badala ya yako</string>
|
||||
<string name="geohash_title">Tumia Mahali kama </string>
|
||||
<string name="geohash_explainer">Inaongeza Geohash ya eneo lako kwenye chapisho. Umma utajua kuwa uko ndani ya kilometa 5 (maili 3) ya eneo la sasa</string>
|
||||
<string name="add_sensitive_content_explainer">Inaongeza onyo la yaliyomo nyeti kabla ya kuonyesha yaliyomo yako. Hii ni nzuri kwa yaliyomo yoyote ya NSFW au yaliyomo ambayo baadhi ya watu wanaweza kuona kama ya kuchukiza au kusumbua</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_title">Kipengee Kipya</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_description">Kuwezesha hali hii kunahitaji Amethyst kutuma ujumbe wa NIP-24 (Ufungaji wa Zawadi, Ujumbe wa Moja kwa Moja Uliosajiliwa, na Ujumbe wa Kikundi). NIP-24 ni mpya na wateja wengi bado hawajaijumuisha. Hakikisha mpokeaji anatumia mteja unaokubaliana.</string>
|
||||
<string name="new_feature_nip24_activate">Tumia</string>
|
||||
<string name="messages_create_public_chat">Umma</string>
|
||||
<string name="messages_new_message">Binafsi</string>
|
||||
<string name="messages_new_message_to">Kwa</string>
|
||||
<string name="messages_new_message_subject">Somo</string>
|
||||
<string name="messages_new_message_subject_caption">Mada ya mazungumzo</string>
|
||||
<string name="messages_new_message_to_caption">"\@User1, @User2, @User3"</string>
|
||||
<string name="messages_group_descriptor">Wanachama wa kikundi hiki</string>
|
||||
<string name="messages_new_subject_message">Maelezo kwa wanachama</string>
|
||||
<string name="messages_new_subject_message_placeholder">Kubadilisha jina kwa malengo mapya.</string>
|
||||
<string name="language_description">Kwa Interface ya Programu</string>
|
||||
<string name="theme_description">Mada ya Giza, Mada ya Mwanga, au Mada ya Mfumo</string>
|
||||
<string name="automatically_load_images_gifs_description">Lisha picha na GIFs kiotomatiki</string>
|
||||
<string name="automatically_play_videos_description">Cheza video na GIFs kiotomatiki</string>
|
||||
<string name="automatically_show_url_preview_description">Onyesha hakikisho za URL kiotomatiki</string>
|
||||
<string name="load_image_description">Lini kusoma picha</string>
|
||||
<string name="copy_url_to_clipboard">Nakili URL kwa ubao wa kunakili</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Nakili Kitambulisho cha Andiko kwa ubao wa kunakili</string>
|
||||
<string name="created_at">Imetengenezwa saa</string>
|
||||
<string name="rules">Miongozo</string>
|
||||
<string name="status_update">Sasisha hali yako</string>
|
||||
<string name="lightning_wallets_not_found">Kosa katika uchambuzi wa ujumbe wa kosa</string>
|
||||
<string name="poll_zap_value_min_max_explainer">Kura zina uzito kulingana na kiasi cha zap. Unaweza kuweka kiasi cha chini ili kuepuka wahasibu na kiasi cha juu ili kuepuka wahasibu wakubwa kuchukua kura ya maoni. Tumia kiasi sawa katika sehemu zote mbili ili kuhakikisha kila kura inathaminiwa kwa kiasi sawa. Acha tupu ili kukubali kiasi chochote.</string>
|
||||
<string name="error_dialog_zap_error">Haiwezekani kutuma zap</string>
|
||||
<string name="error_dialog_talk_to_user">Tuma ujumbe kwa mtumiaji</string>
|
||||
<string name="error_dialog_button_ok">Sawa</string>
|
||||
<string name="relay_information_document_error_assemble_url">Imeshindwa kufikia %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">Imeshindwa kufikia %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">Imeshindwa kuchanganua matokeo kutoka %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s imeshindwa kwa kutumia msimbo %2$s</string>
|
||||
<string name="active_for">Inatumika kwa: </string>
|
||||
<string name="active_for_home">Nyumbani</string>
|
||||
<string name="active_for_msg">DMs</string>
|
||||
<string name="active_for_chats">Soga</string>
|
||||
<string name="active_for_global">Ulimwenguni</string>
|
||||
<string name="active_for_search">Tafuta</string>
|
||||
</resources>
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
<string name="show_qr">显示二维码</string>
|
||||
<string name="profile_image">头像图片</string>
|
||||
<string name="scan_qr">扫描二维码</string>
|
||||
<string name="show_anyway">一直显示</string>
|
||||
<string name="show_anyway">仍然显示</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">帖文被标记为不当</string>
|
||||
<string name="post_not_found">事件正在加载或无法在你的中继列表中找到</string>
|
||||
<string name="channel_image">频道图片</string>
|
||||
<string name="referenced_event_not_found">未找到相关事件</string>
|
||||
<string name="could_not_decrypt_the_message">无法解密消息</string>
|
||||
<string name="group_picture">群聊图片</string>
|
||||
<string name="explicit_content">明确内容</string>
|
||||
<string name="spam">垃圾邮件</string>
|
||||
@@ -21,6 +23,7 @@
|
||||
<string name="copy_note_id">复制笔记ID</string>
|
||||
<string name="broadcast">广播</string>
|
||||
<string name="request_deletion">请求删除</string>
|
||||
<string name="block_report">屏蔽/举报</string>
|
||||
<string name="block_hide_user"><![CDATA[阻止并隐藏用户]]></string>
|
||||
<string name="report_spam_scam">举报垃圾邮件/诈骗</string>
|
||||
<string name="report_impersonation">举报冒充</string>
|
||||
@@ -30,7 +33,10 @@
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">使用私钥登录以便提升帖子</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">使用私钥登录以便点赞帖子</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">没有设置Zap金额。长按以更改</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">使用私钥登录以便发送Zaps</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">使用私钥登录以便发送打闪</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">使用私钥登录以便能够关注</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">使用私钥登录以便能够取消关注</string>
|
||||
<string name="zaps">打闪</string>
|
||||
<string name="view_count">浏览次数</string>
|
||||
<string name="boost">提升</string>
|
||||
<string name="boosted">已提升</string>
|
||||
@@ -41,6 +47,8 @@
|
||||
<string name="and">" 和 "</string>
|
||||
<string name="in_channel">" 在频道 "</string>
|
||||
<string name="profile_banner">个人档案横幅</string>
|
||||
<string name="payment_successful">付款成功</string>
|
||||
<string name="error_parsing_error_message">错误解析错误消息</string>
|
||||
<string name="following">" 关注"</string>
|
||||
<string name="followers">" 粉丝"</string>
|
||||
<string name="profile">个人档案</string>
|
||||
@@ -86,7 +94,7 @@
|
||||
<string name="avatar_url">头像链接</string>
|
||||
<string name="banner_url">横幅链接</string>
|
||||
<string name="website_url">网站链接</string>
|
||||
<string name="ln_address">LN地址</string>
|
||||
<string name="ln_address">闪电地址</string>
|
||||
<string name="ln_url_outdated">LN链接(过期)</string>
|
||||
<string name="image_saved_to_the_gallery">图片已保存到相册</string>
|
||||
<string name="failed_to_save_the_image">保存图片失败</string>
|
||||
@@ -118,6 +126,7 @@
|
||||
<string name="send_a_direct_message">发送直接消息</string>
|
||||
<string name="edits_the_user_s_metadata">编辑用户元数据</string>
|
||||
<string name="follow">关注</string>
|
||||
<string name="follow_back">回关</string>
|
||||
<string name="unblock">不隐藏</string>
|
||||
<string name="copy_user_id">复制用户ID</string>
|
||||
<string name="unblock_user">隐藏该用户</string>
|
||||
@@ -158,6 +167,7 @@
|
||||
<string name="translations_show_in_lang_first">先显示 %1$s 在前</string>
|
||||
<string name="translations_always_translate_to_lang">总是翻译为 %1$s</string>
|
||||
<string name="translations_never_translate_from_lang">不再翻译 %1$s</string>
|
||||
<string name="nip_05">Nostr 地址</string>
|
||||
<string name="never">从不</string>
|
||||
<string name="now">现在</string>
|
||||
<string name="h">时</string>
|
||||
@@ -183,13 +193,13 @@
|
||||
<string name="biometric_error">错误</string>
|
||||
<string name="badge_created_by">"由 %1$s 创建"</string>
|
||||
<string name="badge_award_image_for">"颁发给 %1$s 的徽章图片"</string>
|
||||
<string name="new_badge_award_notif">您收到了新的徽章奖励</string>
|
||||
<string name="new_badge_award_notif">你收到了新的徽章奖励</string>
|
||||
<string name="award_granted_to">徽章奖励授予</string>
|
||||
<string name="copied_note_text_to_clipboard">文本已复制到剪贴板</string>
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">复制作者的 @npub 到剪贴板</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">复制文章ID (@note1) 到剪贴板</string>
|
||||
<string name="select_text_dialog_top">选择文本</string>
|
||||
<string name="private_conversation_notification">"<无法解密私人消息>\n\n您被%1$s和%2$s之间的私人/加密会话引用。"</string>
|
||||
<string name="private_conversation_notification">"<无法解密私人消息>\n\n你被 %1$s 和 %2$s 之间的私人/加密会话引用。"</string>
|
||||
<string name="account_switch_add_account_dialog_title">添加新账户</string>
|
||||
<string name="drawer_accounts">账户</string>
|
||||
<string name="account_switch_select_account">选择账户</string>
|
||||
@@ -208,7 +218,7 @@
|
||||
<string name="quick_action_unfollow">取关</string>
|
||||
<string name="quick_action_follow">关注</string>
|
||||
<string name="quick_action_request_deletion_alert_title">请求删贴</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst 将请求您的记录从当前连接的中继器中删除。不能保证您发布的笔记将被永久从那些中继器或其他存储笔记的中继器中删除。</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst 将请求你的记录从当前连接的中继器中删除。不能保证你发布的笔记将被永久从那些中继器或其他存储笔记的中继器中删除。</string>
|
||||
<string name="quick_action_block_dialog_btn">阻止</string>
|
||||
<string name="quick_action_delete_dialog_btn">删除</string>
|
||||
<string name="quick_action_block">隐藏</string>
|
||||
@@ -220,12 +230,12 @@
|
||||
<string name="report_dialog_impersonation">恶意冒充</string>
|
||||
<string name="report_dialog_nudity">暴露裸露或恶心内容</string>
|
||||
<string name="report_dialog_illegal">违规行为</string>
|
||||
<string name="report_dialog_blocking_a_user">阻止一位用户将会隐藏他/她的内容在您的应用中。您的记录仍然是公开可见的,包括被您阻止的用户。阻止的用户将会列在安全过滤器屏幕上。</string>
|
||||
<string name="report_dialog_blocking_a_user">屏蔽一位用户将会在你的应用中隐藏他的内容。你的笔记仍然是公开可见的,包括对被你屏蔽的用户。屏蔽的用户将会被列在安全过滤器屏幕上。</string>
|
||||
<string name="report_dialog_block_hide_user_btn"><![CDATA[阻止并隐藏用户]]></string>
|
||||
<string name="report_dialog_report_btn">违规报告</string>
|
||||
<string name="report_dialog_reminder_public">所有举报都将被公开可见</string>
|
||||
<string name="report_dialog_additional_reason_placeholder">可选择提供关于您的举报的额外背景...</string>
|
||||
<string name="report_dialog_additional_reason_label">其他背景</string>
|
||||
<string name="report_dialog_additional_reason_placeholder">可选择提供关于你的举报的附加背景...</string>
|
||||
<string name="report_dialog_additional_reason_label">附加背景</string>
|
||||
<string name="report_dialog_select_reason_label">理由</string>
|
||||
<string name="report_dialog_select_reason_placeholder">选择一个理由...</string>
|
||||
<string name="report_dialog_post_report_btn">发布举报</string>
|
||||
@@ -239,9 +249,238 @@
|
||||
<string name="remove_from_private_bookmarks">从私人书签中移除</string>
|
||||
<string name="remove_from_public_bookmarks">从公开书签中移除</string>
|
||||
<string name="wallet_connect_service">钱包绑定服务</string>
|
||||
<string name="wallet_connect_service_explainer">使用您的私钥在不离开应用程序的情况下支付 zaps。 任何具有访问 Nostr 私钥的人都可以使用您的钱包余额。要保留您的资金避免损失,如果可能,请使用私人中继器。中继器操作员可以看到您的付款元数据。</string>
|
||||
<string name="wallet_connect_service_explainer">允许使用 Nostr 私钥在不离开应用程序的情况下支付打闪。请安全保存私钥并在有可能的情况下使用私人中继器</string>
|
||||
<string name="wallet_connect_service_pubkey">钱包绑定公钥</string>
|
||||
<string name="wallet_connect_service_relay">钱包绑定中继</string>
|
||||
<string name="pledge_amount_in_sats">聪数量</string>
|
||||
<string name="looking_for_event">"“正在查找事件%1$s”"</string>
|
||||
<string name="wallet_connect_service_secret">钱包绑定私钥</string>
|
||||
<string name="wallet_connect_service_show_secret">显示私钥</string>
|
||||
<string name="wallet_connect_service_secret_placeholder">nsec / 十六进制私钥</string>
|
||||
<string name="pledge_amount_in_sats">承诺聪金额</string>
|
||||
<string name="post_poll">发布投票</string>
|
||||
<string name="poll_heading_required">必填字段:</string>
|
||||
<string name="poll_zap_recipients">打闪接收方</string>
|
||||
<string name="poll_primary_description">投票主要说明…</string>
|
||||
<string name="poll_option_index">选项 %s</string>
|
||||
<string name="poll_option_description">投票选项说明</string>
|
||||
<string name="poll_heading_optional">可选字段:</string>
|
||||
<string name="poll_zap_value_min">打闪最低金额</string>
|
||||
<string name="poll_zap_value_max">打闪最高金额</string>
|
||||
<string name="poll_consensus_threshold">共识</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="poll_closing_time">后关闭</string>
|
||||
<string name="poll_closing_time_days">天</string>
|
||||
<string name="poll_is_closed">投票不再接收新投票</string>
|
||||
<string name="poll_zap_amount">打闪金额</string>
|
||||
<string name="one_vote_per_user_on_atomic_votes">这种投票只允许每个用户一票</string>
|
||||
<string name="looking_for_event">"正在查找事件 %1$s"</string>
|
||||
<string name="custom_zaps_add_a_message">添加公开消息</string>
|
||||
<string name="custom_zaps_add_a_message_private">添加私信</string>
|
||||
<string name="custom_zaps_add_a_message_nonzap">添加发票消息</string>
|
||||
<string name="custom_zaps_add_a_message_example">感谢你的所有工作!</string>
|
||||
<string name="lightning_create_and_add_invoice">创建并添加</string>
|
||||
<string name="poll_author_no_vote">投票作者不能在自己的投票中投票。</string>
|
||||
<string name="hash_verification_passed">此内容与发布时相同</string>
|
||||
<string name="hash_verification_failed">此内容已更改。作者可能没有看到或批准更改</string>
|
||||
<string name="content_description_add_image">添加图片</string>
|
||||
<string name="content_description_add_video">添加视频</string>
|
||||
<string name="content_description_add_document">添加文件</string>
|
||||
<string name="add_content">添加到消息</string>
|
||||
<string name="content_description">内容描述</string>
|
||||
<string name="content_description_example">一艘蓝色的船,在白沙滩上的日落点</string>
|
||||
<string name="zap_type">打闪种类</string>
|
||||
<string name="zap_type_explainer">所有选项的打闪类型</string>
|
||||
<string name="zap_type_public">公开</string>
|
||||
<string name="zap_type_public_explainer">所有人都能看到交易和消息</string>
|
||||
<string name="zap_type_private">私人</string>
|
||||
<string name="zap_type_private_explainer">发送方和接收方能互相看到并读取消息</string>
|
||||
<string name="zap_type_anonymous">匿名</string>
|
||||
<string name="zap_type_anonymous_explainer">接收方和公众不知道谁发送了付款</string>
|
||||
<string name="zap_type_nonzap">非打闪</string>
|
||||
<string name="zap_type_nonzap_explainer">Nostr 上没有痕迹,仅在闪电上</string>
|
||||
<string name="file_server">文件服务器</string>
|
||||
<string name="zap_forward_lnAddress">闪电地址或 @User</string>
|
||||
<string name="upload_server_imgur">imgur.com - 受信任的</string>
|
||||
<string name="upload_server_imgur_explainer">Imgur 可以修改文件</string>
|
||||
<string name="upload_server_nostrimg">nostrimg.com - 受信任的</string>
|
||||
<string name="upload_server_nostrimg_explainer">NostrImg 可以修改文件</string>
|
||||
<string name="upload_server_nostrbuild">nostr.build - 受信任的</string>
|
||||
<string name="upload_server_nostrbuild_explainer">NostrImg 可以修改文件</string>
|
||||
<string name="upload_server_nostrfilesdev">nostrfiles.dev - 受信任的</string>
|
||||
<string name="upload_server_nostrfilesdev_explainer">Nostrfiles.dev 可以修改文件</string>
|
||||
<string name="upload_server_nostrcheckme">nostrcheck.me - 受信任的</string>
|
||||
<string name="upload_server_nostrcheckme_explainer">nostrcheck.me 可以修改文件</string>
|
||||
<string name="upload_server_imgur_nip94">可验证的 Imgur (NIP-94)</string>
|
||||
<string name="upload_server_imgur_nip94_explainer">检查 Imgur 是否修改了文件。新的 NIP:其他客户端可能看不到它</string>
|
||||
<string name="upload_server_nostrimg_nip94">可验证的 NostrImg (NIP-94)</string>
|
||||
<string name="upload_server_nostrimg_nip94_explainer">检查 NostrImg 是否修改了文件。新的 NIP:其他客户端不一定能看到</string>
|
||||
<string name="upload_server_nostrbuild_nip94">可验证的 Nostr.build (NIP-94)</string>
|
||||
<string name="upload_server_nostrbuild_nip94_explainer">检查 Nostr.build 是否修改了文件。新的 NIP:其他客户端不一定能看到</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94">可验证的 Nostrfiles.dev (NIP-94)</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94_explainer">检查 Nostrfiles.dev 是否修改了文件。新的 NIP:其他客户端不一定能看到</string>
|
||||
<string name="upload_server_nostrcheckme_nip94">可验证的 Nostrcheck.me (NIP-94)</string>
|
||||
<string name="upload_server_nostrcheckme_nip94_explainer">检查 Nostrcheck.me 是否修改了文件。新的 NIP:其他客户端不一定能看到</string>
|
||||
<string name="upload_server_relays_nip95">你的中继器 (NIP-95)</string>
|
||||
<string name="upload_server_relays_nip95_explainer">文件由你的中继器托管。新的 NIP:检查它们是否支持</string>
|
||||
<string name="connect_via_tor_short">Tor/Orbot 设置</string>
|
||||
<string name="connect_via_tor">通过你的 Orbot 设置连接</string>
|
||||
<string name="do_you_really_want_to_disable_tor_title">断开与你的 Orbot/Tor 连接?</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">你的数据将立即在普通网络上传输</string>
|
||||
<string name="yes">是</string>
|
||||
<string name="no">否</string>
|
||||
<string name="follow_list_selection">关注列表</string>
|
||||
<string name="follow_list_kind3follows">所有关注</string>
|
||||
<string name="follow_list_global">全球</string>
|
||||
<string name="connect_through_your_orbot_setup_markdown"> ## 通过 Orbot 连线 Tor
|
||||
\n\n1. 安装 [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)
|
||||
\n2. 开启 Orbot
|
||||
\n3. 在 Orbot 中检查 Socks 端口。默认使用9050
|
||||
\n4. 如果需要,在 Orbot 中更改端口
|
||||
\n5. 在此屏幕中配置 Socks 端口
|
||||
\n6. 按“启用”按钮以使用 Orbot 作为代理
|
||||
</string>
|
||||
<string name="orbot_socks_port">Orbot Socks 端口</string>
|
||||
<string name="invalid_port_number">无效端口</string>
|
||||
<string name="use_orbot">使用 Orbot</string>
|
||||
<string name="disconnect_from_your_orbot_setup">断开 Tor/Orbot 连接</string>
|
||||
<string name="app_notification_dms_channel_name">私信</string>
|
||||
<string name="app_notification_dms_channel_description">收到私信时通知你</string>
|
||||
<string name="app_notification_zaps_channel_name">收到打闪</string>
|
||||
<string name="app_notification_zaps_channel_description">收到打闪时通知你</string>
|
||||
<string name="app_notification_zaps_channel_message">%1$s聪</string>
|
||||
<string name="app_notification_zaps_channel_message_from">来自 %1$s</string>
|
||||
<string name="app_notification_zaps_channel_message_for">为 %1$s</string>
|
||||
<string name="reply_notify">通知:</string>
|
||||
<string name="channel_list_join_conversation">加入对话</string>
|
||||
<string name="channel_list_user_or_group_id">用户或群组 ID</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub、nevent 或 hex</string>
|
||||
<string name="channel_list_create_channel">创建</string>
|
||||
<string name="channel_list_join_channel">加入</string>
|
||||
<string name="today">今天</string>
|
||||
<string name="content_warning">内容警告</string>
|
||||
<string name="content_warning_explanation">这个帖子包含敏感内容,一些人可能会觉得有冒犯性或令人不安</string>
|
||||
<string name="content_warning_hide_all_sensitive_content">始终隐藏敏感内容</string>
|
||||
<string name="content_warning_show_all_sensitive_content">始终显示敏感内容</string>
|
||||
<string name="content_warning_see_warnings">始终显示内容警告</string>
|
||||
<string name="recommended_apps">推荐:</string>
|
||||
<string name="filter_spam_from_strangers">过滤来自陌生人的垃圾信息</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">当帖子有你的关注的报告时警告</string>
|
||||
<string name="new_reaction_symbol">新回应符号</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">未选择回应类型。长按可更改</string>
|
||||
<string name="zapraiser">Zapraiser</string>
|
||||
<string name="zapraiser_explainer">为这个帖子添加目标聪金额。支持的客户端可以将此显示为奖励捐赠的进度条</string>
|
||||
<string name="zapraiser_target_amount_in_sats">目标聪金额</string>
|
||||
<string name="sats_to_complete">Zapraiser 位于 %1$s。距离目标%2$s聪</string>
|
||||
<string name="read_from_relay">从中继器读取</string>
|
||||
<string name="write_to_relay">写入到继电器</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">尝试从 %1$s 获取中继器信息时出错</string>
|
||||
<string name="owner">机主</string>
|
||||
<string name="version">版本</string>
|
||||
<string name="software">软件</string>
|
||||
<string name="contact">联络</string>
|
||||
<string name="supports">支持的 NIPs</string>
|
||||
<string name="admission_fees">接纳费</string>
|
||||
<string name="payments_url">付款网址</string>
|
||||
<string name="limitations">限制</string>
|
||||
<string name="countries">国家</string>
|
||||
<string name="languages">语言</string>
|
||||
<string name="tags">标签</string>
|
||||
<string name="posting_policy">发布政策</string>
|
||||
<string name="message_length">消息长度</string>
|
||||
<string name="subscriptions">订阅</string>
|
||||
<string name="filters">筛选器</string>
|
||||
<string name="subscription_id_length">订阅 ID 长度</string>
|
||||
<string name="minimum_prefix">最少前缀</string>
|
||||
<string name="maximum_event_tags">最多事件标签</string>
|
||||
<string name="content_length">内容长度</string>
|
||||
<string name="minimum_pow">最低工作量证明</string>
|
||||
<string name="auth">认证</string>
|
||||
<string name="payment">支付</string>
|
||||
<string name="cashu">Cashu 代币</string>
|
||||
<string name="cashu_redeem">兑换</string>
|
||||
<string name="no_lightning_address_set">未设置闪电地址</string>
|
||||
<string name="copied_token_to_clipboard">已将代币复制至剪贴板</string>
|
||||
<string name="live_stream_live_tag">直播</string>
|
||||
<string name="live_stream_offline_tag">离线</string>
|
||||
<string name="live_stream_ended_tag">已结束</string>
|
||||
<string name="live_stream_planned_tag">预定</string>
|
||||
<string name="live_stream_is_offline">直播处于离线状态</string>
|
||||
<string name="live_stream_has_ended">直播已结束</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">登出将删除你的本地信息。 请确保备份你的私钥以避免失去你的帐户。你想要继续吗?</string>
|
||||
<string name="followed_tags">已关注的标签</string>
|
||||
<string name="relay_setup">中继器</string>
|
||||
<string name="discover_live">直播</string>
|
||||
<string name="discover_community">社区</string>
|
||||
<string name="discover_chat">聊天</string>
|
||||
<string name="community_approved_posts">批准的帖子</string>
|
||||
<string name="groups_no_descriptor">此群组没有描述或规则。联系群主来添加</string>
|
||||
<string name="community_no_descriptor">此社区没有描述或规则。联系群主来添加</string>
|
||||
<string name="add_sensitive_content_label">敏感内容</string>
|
||||
<string name="add_sensitive_content_description">在显示此内容之前添加敏感内容警告</string>
|
||||
<string name="settings">设置</string>
|
||||
<string name="connectivity_type_always">始终</string>
|
||||
<string name="connectivity_type_wifi_only">仅限 WiFi</string>
|
||||
<string name="connectivity_type_never">从不</string>
|
||||
<string name="system">系统</string>
|
||||
<string name="light">浅色</string>
|
||||
<string name="dark">深色</string>
|
||||
<string name="application_preferences">应用程序首选项</string>
|
||||
<string name="language">语言</string>
|
||||
<string name="theme">主题</string>
|
||||
<string name="automatically_load_images_gifs">图像预览</string>
|
||||
<string name="automatically_play_videos">视频播放</string>
|
||||
<string name="automatically_show_url_preview">URL 预览</string>
|
||||
<string name="load_image">加载图像</string>
|
||||
<string name="spamming_users">垃圾邮件</string>
|
||||
<string name="muted_button">静音。点击取消静音</string>
|
||||
<string name="mute_button">声音开启。点击静音</string>
|
||||
<string name="search_button">搜索本地和远程记录</string>
|
||||
<string name="nip05_verified">Nostr 地址已验证</string>
|
||||
<string name="nip05_failed">Nostr 地址验证失败</string>
|
||||
<string name="nip05_checking">正在检查 Nostr 地址</string>
|
||||
<string name="select_deselect_all">全部选择/取消选择</string>
|
||||
<string name="default_relays">默认</string>
|
||||
<string name="select_a_relay_to_continue">选择中继器以继续</string>
|
||||
<string name="zap_forward_title">将打闪转发到:</string>
|
||||
<string name="zap_forward_explainer">支持的客户端会将打闪转发到以下的闪电地址或用户个人档案,而不是你的</string>
|
||||
<string name="geohash_title">将位置显示为 </string>
|
||||
<string name="geohash_explainer">将你所在位置的地理位置添加到帖子。公众会知道你在当前位置的5公里之内(3英里)</string>
|
||||
<string name="add_sensitive_content_explainer">在显示你的内容之前添加敏感的内容警告。针对任何 NSFW 内容或一些人可能觉得有冒犯性或令人不安的内容。</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_title">新功能</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_description">启用此模式需要 Amethyst 发送一条 NIP-24 消息(包装的、密封的私信和群聊消息)。因为 NIP-24 是新的,大多数客户端尚未执行。请确保接收方正在使用兼容的客户端。</string>
|
||||
<string name="new_feature_nip24_activate">启用</string>
|
||||
<string name="messages_create_public_chat">公开</string>
|
||||
<string name="messages_new_message">私人</string>
|
||||
<string name="messages_new_message_to">至</string>
|
||||
<string name="messages_new_message_subject">主题</string>
|
||||
<string name="messages_new_message_subject_caption">对话主题</string>
|
||||
<string name="messages_new_message_to_caption">"\@User1、@User2、@User3"</string>
|
||||
<string name="messages_group_descriptor">此群组成员</string>
|
||||
<string name="messages_new_subject_message">对成员的解释</string>
|
||||
<string name="messages_new_subject_message_placeholder">为新目标更改名称。</string>
|
||||
<string name="language_description">用于应用程序界面</string>
|
||||
<string name="theme_description">暗色、亮色或系统主题</string>
|
||||
<string name="automatically_load_images_gifs_description">自动加载图像和 GIF</string>
|
||||
<string name="automatically_play_videos_description">自动播放视频和 GIF</string>
|
||||
<string name="automatically_show_url_preview_description">显示 URL 预览</string>
|
||||
<string name="load_image_description">何时加载图像</string>
|
||||
<string name="copy_url_to_clipboard">复制链接到剪贴板</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">复制笔记 ID 到剪贴板</string>
|
||||
<string name="created_at">创建于</string>
|
||||
<string name="rules">规则</string>
|
||||
<string name="status_update">更新你的状态</string>
|
||||
<string name="lightning_wallets_not_found">错误解析错误消息</string>
|
||||
<string name="poll_zap_value_min_max_explainer">投票按照打闪金额进行加权。 你可以设置最小金额以避免垃圾邮件,也可以设置最大金额以避免大型攻击者攻击民意调查。在两个字段中使用相同的金额以确保每张投票的价值相同。 留空即可接受任何金额。</string>
|
||||
<string name="error_dialog_zap_error">无法发送打闪</string>
|
||||
<string name="error_dialog_talk_to_user">向用户发送消息</string>
|
||||
<string name="error_dialog_button_ok">好</string>
|
||||
<string name="relay_information_document_error_assemble_url">无法连接 %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">无法连接 %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">无法解析来自 %1$s的结果: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s 失败,代码 %2$s</string>
|
||||
<string name="active_for">活跃于: </string>
|
||||
<string name="active_for_home">主页</string>
|
||||
<string name="active_for_msg">私信</string>
|
||||
<string name="active_for_chats">聊天</string>
|
||||
<string name="active_for_global">全球</string>
|
||||
<string name="active_for_search">搜索</string>
|
||||
</resources>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<string name="scan_qr">掃描 QR</string>
|
||||
<string name="show_anyway">一直顯示</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">貼文被標記為不當</string>
|
||||
<string name="post_not_found">未找到貼文</string>
|
||||
<string name="post_not_found">事件正在加載或無法在你的中繼器列表中找到</string>
|
||||
<string name="channel_image">頻道圖片</string>
|
||||
<string name="referenced_event_not_found">未找到相關事件</string>
|
||||
<string name="could_not_decrypt_the_message">無法解密消息</string>
|
||||
@@ -22,6 +22,8 @@
|
||||
<string name="copy_user_pubkey">複製作者@npub</string>
|
||||
<string name="copy_note_id">複製筆記ID</string>
|
||||
<string name="broadcast">廣播</string>
|
||||
<string name="request_deletion">請求刪除</string>
|
||||
<string name="block_report">屏蔽/舉報</string>
|
||||
<string name="block_hide_user"><![CDATA[阻止並隱藏用户]]></string>
|
||||
<string name="report_spam_scam">舉報垃圾郵件/詐騙</string>
|
||||
<string name="report_impersonation">舉報冒充</string>
|
||||
@@ -31,7 +33,10 @@
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">使用私鑰登錄以便提升貼文</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">使用私鑰登錄以便點贊貼文</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">沒有設置Zap金額。長按以更改</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">使用私鑰登錄以便發送Zaps</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">使用私鑰登錄以便發送打閃</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">使用私鑰登錄以便關注</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">使用私鑰登錄以便取關</string>
|
||||
<string name="zaps">打閃</string>
|
||||
<string name="view_count">瀏覽次數</string>
|
||||
<string name="boost">提升</string>
|
||||
<string name="boosted">已提升</string>
|
||||
@@ -42,6 +47,8 @@
|
||||
<string name="and">" 和 "</string>
|
||||
<string name="in_channel">" 在頻道 "</string>
|
||||
<string name="profile_banner">個人檔案橫幅</string>
|
||||
<string name="payment_successful">付款成功</string>
|
||||
<string name="error_parsing_error_message">錯誤解析錯誤消息</string>
|
||||
<string name="following">" 關注"</string>
|
||||
<string name="followers">" 粉絲"</string>
|
||||
<string name="profile">個人檔案</string>
|
||||
@@ -71,6 +78,7 @@
|
||||
<string name="failed_to_upload_the_image">上傳圖片失敗</string>
|
||||
<string name="relay_address">中繼器地址</string>
|
||||
<string name="posts">貼文</string>
|
||||
<string name="bytes">位元組</string>
|
||||
<string name="errors">錯誤</string>
|
||||
<string name="home_feed">主頁</string>
|
||||
<string name="private_message_feed">私信</string>
|
||||
@@ -118,6 +126,7 @@
|
||||
<string name="send_a_direct_message">發送直接消息</string>
|
||||
<string name="edits_the_user_s_metadata">編輯用户元數據</string>
|
||||
<string name="follow">關注</string>
|
||||
<string name="follow_back">回關</string>
|
||||
<string name="unblock">不隱藏</string>
|
||||
<string name="copy_user_id">複製用户ID</string>
|
||||
<string name="unblock_user">隱藏該用户</string>
|
||||
@@ -158,6 +167,7 @@
|
||||
<string name="translations_show_in_lang_first">顯示 %1$s 在前</string>
|
||||
<string name="translations_always_translate_to_lang">總是翻譯為 %1$s</string>
|
||||
<string name="translations_never_translate_from_lang">不再翻譯 %1$s</string>
|
||||
<string name="nip_05">Nostr 地址</string>
|
||||
<string name="never">從不</string>
|
||||
<string name="now">現在</string>
|
||||
<string name="h">小時</string>
|
||||
@@ -171,6 +181,7 @@
|
||||
<string name="mark_all_known_as_read">將所有已知內容標記為已讀</string>
|
||||
<string name="mark_all_new_as_read">將所有新內容標記為已讀</string>
|
||||
<string name="mark_all_as_read">將所有內容標記為已讀</string>
|
||||
<string name="backup_keys">備份密鑰</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## 備份與安全提示
|
||||
\n\n您的帳户由一個私人密鑰保護。 密鑰是以**nsec1**開頭的長隨機字符串。任何擁有您的私人密鑰的人都可以使用您的身份發佈內容。
|
||||
\n\n- **不要**將您的私人密鑰添加到您不信任的任何網站或軟件,亦不要在網上公開。
|
||||
@@ -189,6 +200,15 @@
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">複製作者的 @npub 到剪貼板</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">複製文章ID (@note1) 到剪貼板</string>
|
||||
<string name="select_text_dialog_top">選擇文本</string>
|
||||
<string name="private_conversation_notification">"<無法解密私人消息>\n\n你被 %1$s 和 %2$s 之間的私人/加密對話引用。"</string>
|
||||
<string name="account_switch_add_account_dialog_title">添加新帳戶</string>
|
||||
<string name="drawer_accounts">帳戶</string>
|
||||
<string name="account_switch_select_account">選擇帳戶</string>
|
||||
<string name="account_switch_add_account_btn">添加新帳戶</string>
|
||||
<string name="account_switch_active_account">活躍帳戶</string>
|
||||
<string name="account_switch_has_private_key">有私鑰</string>
|
||||
<string name="account_switch_pubkey_only">只讀,無私鑰</string>
|
||||
<string name="back">返回</string>
|
||||
<string name="quick_action_select">選擇</string>
|
||||
<string name="quick_action_share_browser_link">分享瀏覽網址</string>
|
||||
<string name="quick_action_share">分享</string>
|
||||
@@ -200,4 +220,268 @@
|
||||
<string name="quick_action_follow">關注</string>
|
||||
<string name="quick_action_request_deletion_alert_title">請求刪貼</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst 將請求您的記錄從當前連接的中繼器中刪除。不能保證您發佈的筆記將被永久從那些中繼器或其他存儲筆記的中繼器中刪除。</string>
|
||||
<string name="quick_action_block_dialog_btn">屏蔽</string>
|
||||
<string name="quick_action_delete_dialog_btn">刪除</string>
|
||||
<string name="quick_action_block">屏蔽</string>
|
||||
<string name="quick_action_report">舉報</string>
|
||||
<string name="quick_action_delete_button">刪除</string>
|
||||
<string name="quick_action_dont_show_again_button">不再顯示</string>
|
||||
<string name="report_dialog_spam">垃圾信息或詐騙</string>
|
||||
<string name="report_dialog_profanity">不文明語言或仇恨行為</string>
|
||||
<string name="report_dialog_impersonation">惡意冒充</string>
|
||||
<string name="report_dialog_nudity">暴露裸體或噁心內容</string>
|
||||
<string name="report_dialog_illegal">非法行為</string>
|
||||
<string name="report_dialog_blocking_a_user">屏蔽一位用戶將會在你的應用中隱藏他的內容。你的筆記仍然是公開可見的,包括對被你屏蔽的用戶。屏蔽的用戶將會被列在安全過濾器屏幕上。</string>
|
||||
<string name="report_dialog_block_hide_user_btn"><![CDATA[阻止並隱藏用户]]></string>
|
||||
<string name="report_dialog_report_btn">舉報違規</string>
|
||||
<string name="report_dialog_reminder_public">所有舉報都將被公開可見。</string>
|
||||
<string name="report_dialog_additional_reason_placeholder">可選擇提供關於你的舉報的附加背景…</string>
|
||||
<string name="report_dialog_additional_reason_label">附加背景</string>
|
||||
<string name="report_dialog_select_reason_label">原因</string>
|
||||
<string name="report_dialog_select_reason_placeholder">選擇原因...</string>
|
||||
<string name="report_dialog_post_report_btn">發佈舉報</string>
|
||||
<string name="report_dialog_title">屏蔽並舉報</string>
|
||||
<string name="block_only">屏蔽</string>
|
||||
<string name="bookmarks">書籤</string>
|
||||
<string name="private_bookmarks">私人書籤</string>
|
||||
<string name="public_bookmarks">公開書籤</string>
|
||||
<string name="add_to_private_bookmarks">添加到私人書籤</string>
|
||||
<string name="add_to_public_bookmarks">添加到公開書籤</string>
|
||||
<string name="remove_from_private_bookmarks">從私人書籤移除</string>
|
||||
<string name="remove_from_public_bookmarks">從公開書籤移除</string>
|
||||
<string name="wallet_connect_service">錢包綁定服務</string>
|
||||
<string name="wallet_connect_service_explainer">允許使用 Nostr 私鑰在不離開應用程式的情況下支付打閃。請安全保存私鑰並在有可能的情況下使用私人中繼器</string>
|
||||
<string name="wallet_connect_service_pubkey">錢包綁定公鑰</string>
|
||||
<string name="wallet_connect_service_relay">錢包綁定中繼</string>
|
||||
<string name="wallet_connect_service_secret">錢包綁定私鑰</string>
|
||||
<string name="wallet_connect_service_show_secret">顯示私鑰</string>
|
||||
<string name="wallet_connect_service_secret_placeholder">nsec / 十六進制私鑰</string>
|
||||
<string name="pledge_amount_in_sats">承諾聰金額</string>
|
||||
<string name="post_poll">發佈投票</string>
|
||||
<string name="poll_heading_required">必填欄位:</string>
|
||||
<string name="poll_zap_recipients">打閃接收人</string>
|
||||
<string name="poll_primary_description">投票主要說明…</string>
|
||||
<string name="poll_option_index">選項 %s</string>
|
||||
<string name="poll_option_description">投票選擇說明</string>
|
||||
<string name="poll_heading_optional">可選欄位:</string>
|
||||
<string name="poll_zap_value_min">打閃最低金額</string>
|
||||
<string name="poll_zap_value_max">打閃最高金額</string>
|
||||
<string name="poll_consensus_threshold">共識</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="poll_closing_time">後關閉</string>
|
||||
<string name="poll_closing_time_days">天</string>
|
||||
<string name="poll_is_closed">投票不再接收新投票</string>
|
||||
<string name="poll_zap_amount">打閃金額</string>
|
||||
<string name="one_vote_per_user_on_atomic_votes">這種投票只允許每個用戶一票</string>
|
||||
<string name="looking_for_event">"正在查找事件 %1$s"</string>
|
||||
<string name="custom_zaps_add_a_message">添加公開消息</string>
|
||||
<string name="custom_zaps_add_a_message_private">添加私人消息</string>
|
||||
<string name="custom_zaps_add_a_message_nonzap">添加發票消息</string>
|
||||
<string name="custom_zaps_add_a_message_example">感謝你的所有工作!</string>
|
||||
<string name="lightning_create_and_add_invoice">創建並添加</string>
|
||||
<string name="poll_author_no_vote">投票作者不能在自己的投票中投票。</string>
|
||||
<string name="hash_verification_passed">此內容與發佈時相同</string>
|
||||
<string name="hash_verification_failed">此內容已更改。作者可能沒有看到或批准更改</string>
|
||||
<string name="content_description_add_image">添加圖像</string>
|
||||
<string name="content_description_add_video">添加視頻</string>
|
||||
<string name="content_description_add_document">添加文件</string>
|
||||
<string name="add_content">添加到消息</string>
|
||||
<string name="content_description">內容描述</string>
|
||||
<string name="content_description_example">一艘藍色的船,在白沙灘上的日落點</string>
|
||||
<string name="zap_type">打閃種類</string>
|
||||
<string name="zap_type_explainer">所有選項的打閃類型</string>
|
||||
<string name="zap_type_public">公開</string>
|
||||
<string name="zap_type_public_explainer">所有人都能看到交易和消息</string>
|
||||
<string name="zap_type_private">私人</string>
|
||||
<string name="zap_type_private_explainer">發送方和接收方能互相看到並讀取消息</string>
|
||||
<string name="zap_type_anonymous">匿名</string>
|
||||
<string name="zap_type_anonymous_explainer">接收方和公眾不知道誰發送了付款</string>
|
||||
<string name="zap_type_nonzap">非打閃</string>
|
||||
<string name="zap_type_nonzap_explainer">Nostr 上沒有痕跡,僅在閃電上</string>
|
||||
<string name="file_server">文檔服務器</string>
|
||||
<string name="zap_forward_lnAddress">LnAddress 或 @User</string>
|
||||
<string name="upload_server_imgur">imgur.com - 受信任的</string>
|
||||
<string name="upload_server_imgur_explainer">Imgur 可以修改文件</string>
|
||||
<string name="upload_server_nostrimg">nostrimg.com - 受信任的</string>
|
||||
<string name="upload_server_nostrimg_explainer">NostrImg 可以修改文件</string>
|
||||
<string name="upload_server_nostrbuild">nostr.build - 受信任的</string>
|
||||
<string name="upload_server_nostrbuild_explainer">Nostr.build 可以修改文件</string>
|
||||
<string name="upload_server_nostrfilesdev">nostrfiles.dev - 受信任的</string>
|
||||
<string name="upload_server_nostrfilesdev_explainer">Nostrfiles.dev 可以修改文件</string>
|
||||
<string name="upload_server_nostrcheckme">nostrcheck.me - 受信任的</string>
|
||||
<string name="upload_server_nostrcheckme_explainer">nostrcheck.me 可以修改文件</string>
|
||||
<string name="upload_server_imgur_nip94">可驗證的 Imgur (NIP-94)</string>
|
||||
<string name="upload_server_imgur_nip94_explainer">檢查 Imgur 是否修改了文件。新的 NIP:其他客戶端不一定能看到</string>
|
||||
<string name="upload_server_nostrimg_nip94">可驗證的 NostrImg (NIP-94)</string>
|
||||
<string name="upload_server_nostrimg_nip94_explainer">檢查 NostrImg 是否修改了文件。新的 NIP:其他客戶端不一定能看到</string>
|
||||
<string name="upload_server_nostrbuild_nip94">可驗證的 Nostr.build (NIP-94)</string>
|
||||
<string name="upload_server_nostrbuild_nip94_explainer">檢查 Nostr.build 是否修改了文件。新的 NIP:其他客戶端不一定能看到</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94">可驗證的 Nostrfiles.dev (NIP-94)</string>
|
||||
<string name="upload_server_nostrfilesdev_nip94_explainer">檢查 Nostrfiles.dev 是否修改了文件。新的 NIP:其他客戶端不一定能看到</string>
|
||||
<string name="upload_server_nostrcheckme_nip94">可驗證的 Nostrcheck.me (NIP-94)</string>
|
||||
<string name="upload_server_nostrcheckme_nip94_explainer">檢查 Nostrcheck.me 是否修改了文件。新的 NIP:其他客戶端不一定能看到</string>
|
||||
<string name="upload_server_relays_nip95">你的中繼器 (NIP-95)</string>
|
||||
<string name="upload_server_relays_nip95_explainer">文檔由你的中繼器託管。新的 NIP:檢查它們是否支持</string>
|
||||
<string name="connect_via_tor_short">Tor/Orbot 設置</string>
|
||||
<string name="connect_via_tor">通過你的 Orbot 設置連接</string>
|
||||
<string name="do_you_really_want_to_disable_tor_title">斷開與你的 Orbot/Tor 連接?</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">你的數據將立即在普通網絡上傳輸</string>
|
||||
<string name="yes">是</string>
|
||||
<string name="no">否</string>
|
||||
<string name="follow_list_selection">關注列表</string>
|
||||
<string name="follow_list_kind3follows">所有關注</string>
|
||||
<string name="follow_list_global">全球</string>
|
||||
<string name="connect_through_your_orbot_setup_markdown"> ## 通過 Orbot 連線 Tor
|
||||
\n\n1. 安裝 [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)
|
||||
\n2. 開啟 Orbot
|
||||
\n3. 在 Orbot 中檢查 Socks 端口。默認使用9050
|
||||
\n4. 如果需要,在 Orbot 中更改端口
|
||||
\n5. 在此屏幕中配置 Socks 端口
|
||||
\n6. 按“啟用”按鈕以使用 Orbot 為代理
|
||||
</string>
|
||||
<string name="orbot_socks_port">Orbot Socks 端口</string>
|
||||
<string name="invalid_port_number">無效端口</string>
|
||||
<string name="use_orbot">使用 Orbot</string>
|
||||
<string name="disconnect_from_your_orbot_setup">斷連 Tor/Orbot</string>
|
||||
<string name="app_notification_dms_channel_name">私信</string>
|
||||
<string name="app_notification_dms_channel_description">受到私信時通知你</string>
|
||||
<string name="app_notification_zaps_channel_name">收到打閃</string>
|
||||
<string name="app_notification_zaps_channel_description">收到打閃時通知你</string>
|
||||
<string name="app_notification_zaps_channel_message">%1$s聰</string>
|
||||
<string name="app_notification_zaps_channel_message_from">來自%1$s</string>
|
||||
<string name="app_notification_zaps_channel_message_for">為 %1$s</string>
|
||||
<string name="reply_notify">通知: </string>
|
||||
<string name="channel_list_join_conversation">加入對話</string>
|
||||
<string name="channel_list_user_or_group_id">用戶或群組 ID</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub,、nevent 或 hex</string>
|
||||
<string name="channel_list_create_channel">創建</string>
|
||||
<string name="channel_list_join_channel">加入</string>
|
||||
<string name="today">今天</string>
|
||||
<string name="content_warning">內容警告</string>
|
||||
<string name="content_warning_explanation">這個帖子含有敏感內容,一些人可能會覺得有冒犯性或令人不安</string>
|
||||
<string name="content_warning_hide_all_sensitive_content">始終隱藏敏感內容</string>
|
||||
<string name="content_warning_show_all_sensitive_content">始終顯示敏感內容</string>
|
||||
<string name="content_warning_see_warnings">始終顯示內容警告</string>
|
||||
<string name="recommended_apps">推薦:</string>
|
||||
<string name="filter_spam_from_strangers">過濾來自陌生人的垃圾信息</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">當帖子有你的關注的報告時警告</string>
|
||||
<string name="new_reaction_symbol">新回應符號</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">未選擇回應類型。長按可更改</string>
|
||||
<string name="zapraiser">Zapraiser</string>
|
||||
<string name="zapraiser_explainer">為這個帖子添加目標聰金額。支持的客戶端可以將此顯示為獎勵捐贈的進度條</string>
|
||||
<string name="zapraiser_target_amount_in_sats">目標聰金額</string>
|
||||
<string name="sats_to_complete">Zapraiser 位於 %1$s。距離目標%2$s聰</string>
|
||||
<string name="read_from_relay">從中繼器讀取</string>
|
||||
<string name="write_to_relay">寫入到中繼器</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">嘗試從 %1$s 獲取中繼器信息時出錯</string>
|
||||
<string name="owner">機主</string>
|
||||
<string name="version">版本</string>
|
||||
<string name="software">軟體</string>
|
||||
<string name="contact">聯絡</string>
|
||||
<string name="supports">支持的 NIPs</string>
|
||||
<string name="admission_fees">接納費</string>
|
||||
<string name="payments_url">付款網址</string>
|
||||
<string name="limitations">限制</string>
|
||||
<string name="countries">國家</string>
|
||||
<string name="languages">語言</string>
|
||||
<string name="tags">標籤</string>
|
||||
<string name="posting_policy">發佈政策</string>
|
||||
<string name="message_length">消息長度</string>
|
||||
<string name="subscriptions">訂閱</string>
|
||||
<string name="filters">篩選器</string>
|
||||
<string name="subscription_id_length">訂閱 ID 長度</string>
|
||||
<string name="minimum_prefix">最少前綴</string>
|
||||
<string name="maximum_event_tags">最多事件標籤</string>
|
||||
<string name="content_length">內容長度</string>
|
||||
<string name="minimum_pow">最低工作量證明</string>
|
||||
<string name="auth">認證</string>
|
||||
<string name="payment">付款</string>
|
||||
<string name="cashu">Cashu 代幣</string>
|
||||
<string name="cashu_redeem">兌換</string>
|
||||
<string name="no_lightning_address_set">未設定閃電地址</string>
|
||||
<string name="copied_token_to_clipboard">已將代幣複製到剪貼簿</string>
|
||||
<string name="live_stream_live_tag">直播</string>
|
||||
<string name="live_stream_offline_tag">離線</string>
|
||||
<string name="live_stream_ended_tag">已結束</string>
|
||||
<string name="live_stream_planned_tag">預定</string>
|
||||
<string name="live_stream_is_offline">直播處於離線狀態</string>
|
||||
<string name="live_stream_has_ended">直播已結束</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">登出將刪除你的本地信息。請確保備份你的私鑰以避免失去你的帳戶。你想要繼續嗎?</string>
|
||||
<string name="followed_tags">以關注的標籤</string>
|
||||
<string name="relay_setup">中繼器</string>
|
||||
<string name="discover_live">直播</string>
|
||||
<string name="discover_community">社群</string>
|
||||
<string name="discover_chat">聊天</string>
|
||||
<string name="community_approved_posts">批准的帖子</string>
|
||||
<string name="groups_no_descriptor">此群組沒有描述或規則。聯繫群主來添加</string>
|
||||
<string name="community_no_descriptor">此社區沒有描述或規則。聯繫群主來添加</string>
|
||||
<string name="add_sensitive_content_label">敏感內容</string>
|
||||
<string name="add_sensitive_content_description">在顯示此內容之前添加敏感內容警告</string>
|
||||
<string name="settings">設置</string>
|
||||
<string name="connectivity_type_always">始終</string>
|
||||
<string name="connectivity_type_wifi_only">僅限 WiFi</string>
|
||||
<string name="connectivity_type_never">永不</string>
|
||||
<string name="system">系統</string>
|
||||
<string name="light">淺色</string>
|
||||
<string name="dark">深色</string>
|
||||
<string name="application_preferences">應用程式首選項</string>
|
||||
<string name="language">語言</string>
|
||||
<string name="theme">主題</string>
|
||||
<string name="automatically_load_images_gifs">圖像預覽</string>
|
||||
<string name="automatically_play_videos">視頻播放</string>
|
||||
<string name="automatically_show_url_preview">鏈接預覽</string>
|
||||
<string name="load_image">加載圖像</string>
|
||||
<string name="spamming_users">垃圾郵件</string>
|
||||
<string name="muted_button">靜音。點擊取消靜音</string>
|
||||
<string name="mute_button">聲音開啟。點擊靜音</string>
|
||||
<string name="search_button">搜索本地和遠程記錄</string>
|
||||
<string name="nip05_verified">Nostr 地址已驗證</string>
|
||||
<string name="nip05_failed">Nostr 地址驗證失敗</string>
|
||||
<string name="nip05_checking">正在檢查 Nostr 地址</string>
|
||||
<string name="select_deselect_all">全部選擇/取消選擇</string>
|
||||
<string name="default_relays">默認</string>
|
||||
<string name="select_a_relay_to_continue">選擇中繼器以繼續</string>
|
||||
<string name="zap_forward_title">將打閃轉發到:</string>
|
||||
<string name="zap_forward_explainer">支持的客戶端會將打閃轉發到以下的閃電地址或用戶個人檔案,而不是你的</string>
|
||||
<string name="geohash_title">將位置顯示為 </string>
|
||||
<string name="geohash_explainer">將你所在位置的地理位置添加到帖子。公眾會知道你在當前位置的5公里之內(3英里)</string>
|
||||
<string name="add_sensitive_content_explainer">在顯示你的內容之前添加敏感內容警告。針對任何 NSFW 內容或一些人可能覺得有冒犯性或令人不安的內容。</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_title">新功能</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_description">啟用此模式需要 Amethyst 發送一條 NIP-24 消息(包裝的、密封的私信和群聊消息)。因為 NIP-24 是新的,大多數客戶端尚未執行。請確保接收方正在使用兼容的客戶端。</string>
|
||||
<string name="new_feature_nip24_activate">啟用</string>
|
||||
<string name="messages_create_public_chat">公開</string>
|
||||
<string name="messages_new_message">私人</string>
|
||||
<string name="messages_new_message_to">至</string>
|
||||
<string name="messages_new_message_subject">主題</string>
|
||||
<string name="messages_new_message_subject_caption">對話主題</string>
|
||||
<string name="messages_new_message_to_caption">"\@User1、@User2、@User3"</string>
|
||||
<string name="messages_group_descriptor">此群組成員</string>
|
||||
<string name="messages_new_subject_message">對成員的解釋</string>
|
||||
<string name="messages_new_subject_message_placeholder">為新目標更改名稱。</string>
|
||||
<string name="language_description">用於應用程式界面</string>
|
||||
<string name="theme_description">暗色、亮色或系統主題</string>
|
||||
<string name="automatically_load_images_gifs_description">自動加載圖像和 GIF</string>
|
||||
<string name="automatically_play_videos_description">自動播放視頻和 GIF</string>
|
||||
<string name="automatically_show_url_preview_description">顯示鏈接預覽</string>
|
||||
<string name="load_image_description">何時加載圖像</string>
|
||||
<string name="copy_url_to_clipboard">將 URL 複製到剪貼簿</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">將筆記 ID 複製到剪貼簿</string>
|
||||
<string name="created_at">創建於</string>
|
||||
<string name="rules">規則</string>
|
||||
<string name="status_update">更新你的狀態</string>
|
||||
<string name="lightning_wallets_not_found">錯誤解析錯誤消息</string>
|
||||
<string name="poll_zap_value_min_max_explainer">投票按照打閃金額進行加權。你可以設置最小金額以避免垃圾郵件,也可以設置最大金額以避免大型攻擊者攻擊民意調查。在兩個字段中使用相同的金額則確保每張投票的價值相同。留空即可接受任何金額。</string>
|
||||
<string name="error_dialog_zap_error">無法發送打閃</string>
|
||||
<string name="error_dialog_talk_to_user">向用戶發送消息</string>
|
||||
<string name="error_dialog_button_ok">確定</string>
|
||||
<string name="relay_information_document_error_assemble_url">無法連接 %1$s:%2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">無法連接 %1$s:%2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">無法解析來自 %1$s 的結果:%2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s 失敗了,代碼 %2$s</string>
|
||||
<string name="active_for">活躍於: </string>
|
||||
<string name="active_for_home">首頁</string>
|
||||
<string name="active_for_msg">私信</string>
|
||||
<string name="active_for_chats">聊天</string>
|
||||
<string name="active_for_global">全球</string>
|
||||
<string name="active_for_search">搜索</string>
|
||||
</resources>
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
<string name="failed_to_save_the_image">Failed to save the image</string>
|
||||
<string name="upload_image">Upload Image</string>
|
||||
<string name="uploading">Uploading…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">User does not have a lightning address setup to receive sats</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">User does not have a lightning address set up to receive sats</string>
|
||||
<string name="reply_here">"reply here.. "</string>
|
||||
<string name="copies_the_note_id_to_the_clipboard_for_sharing">Copies the Note ID to the clipboard for sharing in Nostr</string>
|
||||
<string name="copy_channel_id_note_to_the_clipboard">Copy Channel ID (Note) to the Clipboard</string>
|
||||
@@ -558,4 +558,30 @@
|
||||
<string name="error_dialog_zap_error">Unable to send zap</string>
|
||||
<string name="error_dialog_talk_to_user">Message the User</string>
|
||||
<string name="error_dialog_button_ok">Ok</string>
|
||||
|
||||
<string name="relay_information_document_error_assemble_url">Failed to reach %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">Failed to reach %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">Failed to parse result from %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">%1$s failed with code %2$s</string>
|
||||
<string name="active_for">Active for: </string>
|
||||
|
||||
<string name="active_for_home">Home</string>
|
||||
<string name="active_for_msg">DMs</string>
|
||||
<string name="active_for_chats">Chats</string>
|
||||
<string name="active_for_global">Global</string>
|
||||
<string name="active_for_search">Search</string>
|
||||
|
||||
<string name="zap_split_title">Split and Forward Zaps</string>
|
||||
<string name="zap_split_explainer">Supporting clients will split and forward zaps to the users added here instead of yours</string>
|
||||
<string name="zap_split_serarch_and_add_user">Search and Add User</string>
|
||||
<string name="zap_split_serarch_and_add_user_placeholder">Username or display name</string>
|
||||
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">User %1$s does not have a lightning address set up to receive sats</string>
|
||||
<string name="zap_split_weight">Percentage</string>
|
||||
<string name="zap_split_weight_placeholder">25</string>
|
||||
<string name="splitting_zaps_with">Splitting zaps with</string>
|
||||
<string name="forwarding_zaps_to">Forwarding zaps to</string>
|
||||
|
||||
<string name="lightning_wallets_not_found2">Lightning wallets not found</string>
|
||||
<string name="paid">Paid</string>
|
||||
<string name="wallet_number">Wallet %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<locale android:name="fa"/>
|
||||
<locale android:name="fr"/>
|
||||
<locale android:name="hu"/>
|
||||
<locale android:name="in"/>
|
||||
<locale android:name="ja"/>
|
||||
<locale android:name="nl"/>
|
||||
<locale android:name="pt-BR"/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.util.LruCache
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
@@ -8,6 +9,7 @@ import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateDMChannel
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -15,19 +17,34 @@ import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PushNotificationReceiverService : FirebaseMessagingService() {
|
||||
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val eventCache = LruCache<String, String>(100)
|
||||
|
||||
// this is called when a message is received
|
||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
remoteMessage.data.let {
|
||||
val eventStr = remoteMessage.data["event"] ?: return@let
|
||||
val event = Event.fromJson(eventStr)
|
||||
EventNotificationConsumer(applicationContext).consume(event)
|
||||
parseMessage(remoteMessage.data)?.let {
|
||||
receiveIfNew(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseMessage(params: Map<String, String>): GiftWrapEvent? {
|
||||
params["encryptedEvent"]?.let { eventStr ->
|
||||
(Event.fromJson(eventStr) as? GiftWrapEvent)?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun receiveIfNew(event: GiftWrapEvent) {
|
||||
if (eventCache.get(event.id) == null) {
|
||||
eventCache.put(event.id, event.id)
|
||||
EventNotificationConsumer(applicationContext).consume(event)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
scope.cancel()
|
||||
super.onDestroy()
|
||||
|
||||
89
app/src/test/java/com/vitorpamplona/amethyst/SplitterTest.kt
Normal file
89
app/src/test/java/com/vitorpamplona/amethyst/SplitterTest.kt
Normal file
@@ -0,0 +1,89 @@
|
||||
package com.vitorpamplona.amethyst
|
||||
|
||||
import com.vitorpamplona.amethyst.ui.components.Split
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class SplitterTest {
|
||||
@Test
|
||||
fun testSplit() {
|
||||
val mySplit = Split<String>()
|
||||
|
||||
val vitor = mySplit.addItem("Vitor")
|
||||
|
||||
assertEquals(1f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertTrue(mySplit.isEqualSplit())
|
||||
|
||||
val pablo = mySplit.addItem("Pablo")
|
||||
|
||||
assertEquals(0.5f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(0.5f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertTrue(mySplit.isEqualSplit())
|
||||
|
||||
val gigi = mySplit.addItem("Gigi")
|
||||
|
||||
assertEquals(0.33f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertEquals(0.33f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(0.33f, mySplit.items[gigi].percentage, 0.01f)
|
||||
assertTrue(mySplit.isEqualSplit())
|
||||
|
||||
mySplit.updatePercentage(vitor, 0.5f)
|
||||
|
||||
assertEquals(0.5f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertEquals(0.33f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(0.16f, mySplit.items[gigi].percentage, 0.01f)
|
||||
assertFalse(mySplit.isEqualSplit())
|
||||
|
||||
mySplit.updatePercentage(vitor, 0.95f)
|
||||
|
||||
assertEquals(0.95f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertEquals(0.05f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(0.0f, mySplit.items[gigi].percentage, 0.01f)
|
||||
assertFalse(mySplit.isEqualSplit())
|
||||
|
||||
mySplit.updatePercentage(vitor, 0.15f)
|
||||
|
||||
assertEquals(0.15f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertEquals(0.05f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(0.80f, mySplit.items[gigi].percentage, 0.01f)
|
||||
assertFalse(mySplit.isEqualSplit())
|
||||
|
||||
mySplit.updatePercentage(pablo, 0.95f)
|
||||
|
||||
assertEquals(0.05f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertEquals(0.95f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(0.00f, mySplit.items[gigi].percentage, 0.01f)
|
||||
|
||||
mySplit.updatePercentage(gigi, 1f)
|
||||
|
||||
assertEquals(0.00f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertEquals(0.00f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(1.00f, mySplit.items[gigi].percentage, 0.01f)
|
||||
|
||||
mySplit.updatePercentage(vitor, 0.5f)
|
||||
|
||||
assertEquals(0.50f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertEquals(0.00f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(0.50f, mySplit.items[gigi].percentage, 0.01f)
|
||||
|
||||
mySplit.updatePercentage(pablo, 0.3f)
|
||||
|
||||
assertEquals(0.50f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertEquals(0.30f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(0.20f, mySplit.items[gigi].percentage, 0.01f)
|
||||
|
||||
mySplit.updatePercentage(gigi, 1f)
|
||||
|
||||
assertEquals(0.00f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertEquals(0.00f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(1.00f, mySplit.items[gigi].percentage, 0.01f)
|
||||
|
||||
mySplit.updatePercentage(gigi, 0.5f)
|
||||
|
||||
assertEquals(0.00f, mySplit.items[vitor].percentage, 0.01f)
|
||||
assertEquals(0.50f, mySplit.items[pablo].percentage, 0.01f)
|
||||
assertEquals(0.50f, mySplit.items[gigi].percentage, 0.01f)
|
||||
}
|
||||
}
|
||||
10
build.gradle
10
build.gradle
@@ -3,16 +3,16 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
buildscript {
|
||||
ext {
|
||||
fragment_version = "1.6.1"
|
||||
lifecycle_version = '2.6.1'
|
||||
compose_ui_version = '1.5.0'
|
||||
nav_version = "2.7.1"
|
||||
lifecycle_version = '2.6.2'
|
||||
compose_ui_version = '1.5.1'
|
||||
nav_version = '2.7.2'
|
||||
room_version = "2.4.3"
|
||||
accompanist_version = '0.30.1'
|
||||
coil_version = '2.4.0'
|
||||
vico_version = '1.11.0'
|
||||
vico_version = '1.11.3'
|
||||
exoplayer_version = '1.1.1'
|
||||
media3_version = '1.1.1'
|
||||
core_ktx_version = '1.10.1'
|
||||
core_ktx_version = '1.12.0'
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.google.gms:google-services:4.3.15'
|
||||
|
||||
@@ -19,6 +19,7 @@ files:
|
||||
fa: fa
|
||||
fr: fr
|
||||
hu: hu
|
||||
in: in
|
||||
ja: ja
|
||||
nl: nl
|
||||
ru: ru
|
||||
|
||||
@@ -5,6 +5,8 @@ import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.decodePublicKey
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@@ -5,7 +5,9 @@ import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.decodePublicKey
|
||||
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||
@@ -557,4 +559,28 @@ class GiftWrapEventTest {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decryptMsgFromNostrTools() {
|
||||
val receiversPrivateKey = Hex.decode("df51ec558372612918a83446d279d683039bece79b7a721274b1d3cb612dc6af")
|
||||
val msg = """
|
||||
{
|
||||
"tags": [],
|
||||
"content": "AUC1i3lHsEOYQZaqav8jAw/Dv25r6BpUX4r7ARaj/7JEqvtHkbtaWXEx3LvMlDJstNX1C90RIelgYTzxb4Xnql7zFmXtxGGd/gXOZzW/OCNWECTrhFTruZUcsyn2ssJMgEMBZKY3PgbAKykHlGCuWR3KI9bo+IA5sTqHlrwDGAysxBypRuAxTdtEApw1LSu2A+1UQsdHK/4HcW/fQLPguWGyPv09dftJIJkFWM8VYBQT7b5FeAEMhjlUM+lEmLMnx6qb07Ji/YMESkhzFlgGjHNVl1Q/BT4i6X+Skogl6Si3lWQzlS9oebUim1BQW+RO0IOyQLalZwjzGP+eE7Ry62ukQg7cPiqk62p7NNula17SF2Q8aVFLxr8WjbLXoWhZOWY25uFbTl7OPGGQb5TewRsjHoFeU4h05Ien3Ymf1VPqJVJCMIxU+yFZ1IMZh/vQW4BSx8VotRdNA05fz03ST88GzGxUvqEm4VW/Yp5q4UUkCDQTKmUImaSFmTser39WmvS5+dHY6ne4RwnrZR0ZYrG1bthRHycnPmaJiYsHn9Ox37EzgLR07pmNxr2+86NR3S3TLAVfTDN3XaXRee/7UfW/MXULVyuyweksIHOYBvANC0PxmGSs4UiFoCbwNi45DT2y0SwP6CxzDuM=",
|
||||
"kind": 1059,
|
||||
"created_at": 1694192155914,
|
||||
"pubkey": "8253eb518413b57f0df329d3d4287bdef4031fd71c32ad1952d854e703dae6a7",
|
||||
"id": "ae625fd43612127d63bfd1967ba32ae915100842a205fc2c3b3fc02ab3827f08",
|
||||
"sig": "2807a7ab5728984144676fd34686267cbe6fe38bc2f65a3640ba9243c13e8a1ae5a9a051e8852aa0c997a3623d7fa066cf2073a233c6d7db46fb1a0d4c01e5a3"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val wrap = Event.fromJson(msg) as GiftWrapEvent
|
||||
wrap.checkSignature()
|
||||
|
||||
val event = wrap.unwrap(receiversPrivateKey)
|
||||
assertNotNull(event)
|
||||
|
||||
println(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,14 +69,14 @@ object Bech32 {
|
||||
values.forEach { v ->
|
||||
val b = chk shr 25
|
||||
chk = ((chk and 0x1ffffff) shl 5) xor v.toInt()
|
||||
for (i in 0..5) {
|
||||
for (i in 0..4) {
|
||||
if (((b shr i) and 1) != 0) chk = chk xor GEN[i]
|
||||
}
|
||||
}
|
||||
values1.forEach { v ->
|
||||
val b = chk shr 25
|
||||
chk = ((chk and 0x1ffffff) shl 5) xor v.toInt()
|
||||
for (i in 0..5) {
|
||||
for (i in 0..4) {
|
||||
if (((b shr i) and 1) != 0) chk = chk xor GEN[i]
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ object Bech32 {
|
||||
}
|
||||
}
|
||||
|
||||
val hrp = bech32.take(pos)
|
||||
val hrp = bech32.take(pos).lowercase() // strings must be lower case
|
||||
require(hrp.length in 1..83) { "hrp must contain 1 to 83 characters" }
|
||||
|
||||
val data = Array(bech32.length - pos - 1) {
|
||||
|
||||
@@ -12,18 +12,19 @@ fun HexKey.hexToByteArray(): ByteArray {
|
||||
}
|
||||
|
||||
object HexValidator {
|
||||
private fun isHex2(c: Char): Boolean {
|
||||
private fun isHexChar(c: Char): Boolean {
|
||||
return when (c) {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F', ' ' -> true
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F' -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun isHex(hex: String?): Boolean {
|
||||
if (hex == null) return false
|
||||
if (hex.length % 2 != 0) return false // must be even
|
||||
var isHex = true
|
||||
for (c in hex.toCharArray()) {
|
||||
if (!isHex2(c)) {
|
||||
if (!isHexChar(c)) {
|
||||
isHex = false
|
||||
break
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.util.regex.Pattern
|
||||
|
||||
object LnWithdrawalUtil {
|
||||
private val withdrawalPattern = Pattern.compile(
|
||||
"lnurl.+",
|
||||
"lnurl1[02-9ac-hj-np-z]+",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.vitorpamplona.quartz.encoders
|
||||
|
||||
import android.util.Log
|
||||
import java.util.regex.Pattern
|
||||
|
||||
val lnurlpPattern = Pattern.compile("(?i:http|https):\\/\\/((.+)\\/)*\\.well-known\\/lnurlp\\/(.*)")
|
||||
|
||||
class Lud06 {
|
||||
fun toLud16(str: String): String? {
|
||||
return try {
|
||||
val url = toLnUrlp(str)
|
||||
|
||||
val matcher = lnurlpPattern.matcher(url)
|
||||
matcher.find()
|
||||
val domain = matcher.group(2)
|
||||
val username = matcher.group(3)
|
||||
|
||||
"$username@$domain"
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
Log.w("Lud06ToLud16","Fail to convert LUD06 to LUD16",t)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun toLnUrlp(str: String): String? {
|
||||
return try {
|
||||
String(Bech32.decodeBytes(str, false).second)
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
Log.w("Lud06ToLud16","Fail to convert LUD06 to LUD16",t)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class ChannelMessageEvent(
|
||||
channel: String,
|
||||
replyTos: List<String>? = null,
|
||||
mentions: List<String>? = null,
|
||||
zapReceiver: String?,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
privateKey: ByteArray,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
markAsSensitive: Boolean,
|
||||
@@ -50,8 +50,8 @@ class ChannelMessageEvent(
|
||||
mentions?.forEach {
|
||||
tags.add(listOf("p", it))
|
||||
}
|
||||
zapReceiver?.let {
|
||||
tags.add(listOf("zap", it))
|
||||
zapReceiver?.forEach {
|
||||
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||
}
|
||||
if (markAsSensitive) {
|
||||
tags.add(listOf("content-warning", ""))
|
||||
|
||||
@@ -57,7 +57,7 @@ class ChatMessageEvent(
|
||||
subject: String? = null,
|
||||
replyTos: List<String>? = null,
|
||||
mentions: List<String>? = null,
|
||||
zapReceiver: String? = null,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
markAsSensitive: Boolean = false,
|
||||
zapRaiserAmount: Long? = null,
|
||||
geohash: String? = null,
|
||||
@@ -75,8 +75,8 @@ class ChatMessageEvent(
|
||||
mentions?.forEach {
|
||||
tags.add(listOf("p", it, "", "mention"))
|
||||
}
|
||||
zapReceiver?.let {
|
||||
tags.add(listOf("zap", it))
|
||||
zapReceiver?.forEach {
|
||||
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||
}
|
||||
if (markAsSensitive) {
|
||||
tags.add(listOf("content-warning", ""))
|
||||
|
||||
@@ -62,7 +62,7 @@ open class Event(
|
||||
|
||||
override fun toJson(): String = mapper.writeValueAsString(toJsonObject())
|
||||
|
||||
fun hasAnyTaggedUser() = tags.any { it.size > 1 && it[0] == "p" }
|
||||
override fun hasAnyTaggedUser() = tags.any { it.size > 1 && it[0] == "p" }
|
||||
|
||||
override fun taggedUsers() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||
override fun taggedEvents() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
||||
@@ -93,7 +93,25 @@ open class Event(
|
||||
(it.size > 1 && it[0] == "zapraiser")
|
||||
}?.get(1)?.toLongOrNull()
|
||||
|
||||
override fun zapAddress() = tags.firstOrNull { it.size > 1 && it[0] == "zap" }?.get(1)
|
||||
override fun hasZapSplitSetup() = tags.any { it.size > 1 && it[0] == "zap" }
|
||||
|
||||
override fun zapSplitSetup(): List<ZapSplitSetup> {
|
||||
return tags.filter { it.size > 1 && it[0] == "zap" }.mapNotNull {
|
||||
val isLnAddress = it[0].contains("@") || it[0].startsWith("LNURL", true)
|
||||
val weight = if (isLnAddress) 1.0 else (it.getOrNull(3)?.toDoubleOrNull() ?: 0.0)
|
||||
|
||||
if (weight > 0) {
|
||||
ZapSplitSetup(
|
||||
it[1],
|
||||
it.getOrNull(2),
|
||||
weight,
|
||||
isLnAddress
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun taggedAddresses() = tags.filter { it.size > 1 && it[0] == "a" }.mapNotNull {
|
||||
val aTagValue = it[1]
|
||||
@@ -421,3 +439,9 @@ fun String.bytesUsedInMemory(): Int {
|
||||
return (8 * ((((this.length) * 2) + 45) / 8))
|
||||
}
|
||||
|
||||
data class ZapSplitSetup(
|
||||
val lnAddressOrPubKeyHex: String,
|
||||
val relay: String?,
|
||||
val weight: Double,
|
||||
val isLnAddress: Boolean,
|
||||
)
|
||||
@@ -58,11 +58,13 @@ interface EventInterface {
|
||||
fun getPoWRank(): Int
|
||||
fun getGeoHash(): String?
|
||||
|
||||
fun zapAddress(): String?
|
||||
fun zapSplitSetup(): List<ZapSplitSetup>
|
||||
fun isSensitive(): Boolean
|
||||
fun subject(): String?
|
||||
fun zapraiserAmount(): Long?
|
||||
|
||||
fun hasAnyTaggedUser(): Boolean
|
||||
|
||||
fun taggedAddresses(): List<ATag>
|
||||
fun taggedUsers(): List<HexKey>
|
||||
fun taggedEvents(): List<HexKey>
|
||||
@@ -76,4 +78,5 @@ interface EventInterface {
|
||||
fun taggedEmojis(): List<EmojiUrl>
|
||||
fun matchTag1With(text: String): Boolean
|
||||
fun isExpired(): Boolean
|
||||
fun hasZapSplitSetup(): Boolean
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class LiveActivitiesChatMessageEvent(
|
||||
activity: ATag,
|
||||
replyTos: List<String>? = null,
|
||||
mentions: List<String>? = null,
|
||||
zapReceiver: String?,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
privateKey: ByteArray,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
markAsSensitive: Boolean,
|
||||
@@ -66,8 +66,8 @@ class LiveActivitiesChatMessageEvent(
|
||||
mentions?.forEach {
|
||||
tags.add(listOf("p", it))
|
||||
}
|
||||
zapReceiver?.let {
|
||||
tags.add(listOf("zap", it))
|
||||
zapReceiver?.forEach {
|
||||
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||
}
|
||||
if (markAsSensitive) {
|
||||
tags.add(listOf("content-warning", ""))
|
||||
|
||||
@@ -21,7 +21,7 @@ class LnZapEvent(
|
||||
fromJson(it)
|
||||
} as? LnZapRequestEvent
|
||||
} catch (e: Exception) {
|
||||
Log.e("LnZapEvent", "Failed to Parse Contained Post ${description()}", e)
|
||||
Log.w("LnZapEvent", "Failed to Parse Contained Post ${description()} in event ${id}", e)
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ class LnZapRequestEvent(
|
||||
pollOption: Int?,
|
||||
message: String,
|
||||
zapType: LnZapEvent.ZapType,
|
||||
toUserPubHex: String?, // Overrides in case of Zap Splits
|
||||
createdAt: Long = TimeUtils.now()
|
||||
): LnZapRequestEvent {
|
||||
var content = message
|
||||
@@ -72,7 +73,7 @@ class LnZapRequestEvent(
|
||||
var pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
||||
var tags = listOf(
|
||||
listOf("e", originalNote.id()),
|
||||
listOf("p", originalNote.pubKey()),
|
||||
listOf("p", toUserPubHex ?: originalNote.pubKey()),
|
||||
listOf("relays") + relays
|
||||
)
|
||||
if (originalNote is AddressableEvent) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.vitorpamplona.quartz.events
|
||||
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
|
||||
class NIP24Factory {
|
||||
@@ -12,7 +13,7 @@ class NIP24Factory {
|
||||
subject: String? = null,
|
||||
replyTos: List<String>? = null,
|
||||
mentions: List<String>? = null,
|
||||
zapReceiver: String? = null,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
markAsSensitive: Boolean = false,
|
||||
zapRaiserAmount: Long? = null,
|
||||
geohash: String? = null
|
||||
@@ -85,4 +86,50 @@ class NIP24Factory {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createTextNoteNIP24(
|
||||
msg: String,
|
||||
to: List<HexKey>,
|
||||
from: ByteArray,
|
||||
replyTos: List<String>? = null,
|
||||
mentions: List<String>? = null,
|
||||
addresses: List<ATag>?,
|
||||
extraTags: List<String>?,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
markAsSensitive: Boolean = false,
|
||||
replyingTo: String?,
|
||||
root: String?,
|
||||
directMentions: Set<HexKey>,
|
||||
zapRaiserAmount: Long? = null,
|
||||
geohash: String? = null
|
||||
): List<GiftWrapEvent> {
|
||||
val senderPublicKey = CryptoUtils.pubkeyCreate(from).toHexKey()
|
||||
|
||||
val senderMessage = TextNoteEvent.create(
|
||||
msg = msg,
|
||||
privateKey = from,
|
||||
replyTos = replyTos,
|
||||
mentions = mentions,
|
||||
zapReceiver = zapReceiver,
|
||||
root = root,
|
||||
extraTags = extraTags,
|
||||
addresses = addresses,
|
||||
directMentions = directMentions,
|
||||
replyingTo = replyingTo,
|
||||
markAsSensitive = markAsSensitive,
|
||||
zapRaiserAmount = zapRaiserAmount,
|
||||
geohash = geohash
|
||||
)
|
||||
|
||||
return to.plus(senderPublicKey).map {
|
||||
GiftWrapEvent.create(
|
||||
event = SealedGossipEvent.create(
|
||||
event = senderMessage,
|
||||
encryptTo = it,
|
||||
privateKey = from
|
||||
),
|
||||
recipientPubKey = it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class PollNoteEvent(
|
||||
valueMinimum: Int?,
|
||||
consensusThreshold: Int?,
|
||||
closedAt: Int?,
|
||||
zapReceiver: String?,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
markAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long?,
|
||||
geohash: String? = null
|
||||
@@ -76,8 +76,8 @@ class PollNoteEvent(
|
||||
tags.add(listOf(CONSENSUS_THRESHOLD, consensusThreshold.toString()))
|
||||
tags.add(listOf(CLOSED_AT, closedAt.toString()))
|
||||
|
||||
if (zapReceiver != null) {
|
||||
tags.add(listOf("zap", zapReceiver))
|
||||
zapReceiver?.forEach {
|
||||
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||
}
|
||||
if (markAsSensitive) {
|
||||
tags.add(listOf("content-warning", ""))
|
||||
|
||||
@@ -85,7 +85,7 @@ class PrivateDmEvent(
|
||||
msg: String,
|
||||
replyTos: List<String>? = null,
|
||||
mentions: List<String>? = null,
|
||||
zapReceiver: String?,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
privateKey: ByteArray,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
publishedRecipientPubKey: ByteArray? = null,
|
||||
@@ -110,8 +110,8 @@ class PrivateDmEvent(
|
||||
mentions?.forEach {
|
||||
tags.add(listOf("p", it))
|
||||
}
|
||||
zapReceiver?.let {
|
||||
tags.add(listOf("zap", it))
|
||||
zapReceiver?.forEach {
|
||||
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||
}
|
||||
if (markAsSensitive) {
|
||||
tags.add(listOf("content-warning", ""))
|
||||
|
||||
@@ -30,7 +30,7 @@ class TextNoteEvent(
|
||||
mentions: List<String>?,
|
||||
addresses: List<ATag>?,
|
||||
extraTags: List<String>?,
|
||||
zapReceiver: String?,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
markAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long?,
|
||||
replyingTo: String?,
|
||||
@@ -80,8 +80,8 @@ class TextNoteEvent(
|
||||
extraTags?.forEach {
|
||||
tags.add(listOf("t", it))
|
||||
}
|
||||
zapReceiver?.let {
|
||||
tags.add(listOf("zap", it))
|
||||
zapReceiver?.forEach {
|
||||
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||
}
|
||||
findURLs(msg).forEach {
|
||||
tags.add(listOf("r", it))
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.vitorpamplona.quartz.encoders
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class Lud06Test {
|
||||
val lnTips = "LNURL1DP68GURN8GHJ7MRW9E6XJURN9UH8WETVDSKKKMN0WAHZ7MRWW4EXCUP0XPURXEFEX9SKGCT9V5ER2V33X4NRGP2NE42"
|
||||
@Test()
|
||||
fun parseLnUrlp() {
|
||||
assertEquals("https://ln.tips/.well-known/lnurlp/0x3e91adaee25215f4", Lud06().toLnUrlp(lnTips))
|
||||
assertEquals("0x3e91adaee25215f4@ln.tips", Lud06().toLud16(lnTips))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user