mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-06 14:54:36 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
133d7d5ce0 | ||
|
|
215989108b | ||
|
|
ed7dcf94f8 | ||
|
|
b2c7f150ec | ||
|
|
c85d74a688 | ||
|
|
cbf23fedf1 | ||
|
|
5ed64bd5b5 | ||
|
|
b79f9c8395 | ||
|
|
02da606461 | ||
|
|
7074446cae | ||
|
|
4746b5b18a | ||
|
|
103a61f35c | ||
|
|
d9c48d4039 | ||
|
|
9479e86b66 | ||
|
|
bb7ec1ee4b | ||
|
|
7a213545a2 | ||
|
|
48f27f656d | ||
|
|
47176421e0 | ||
|
|
9c84a538d6 | ||
|
|
8bc1c2ec68 | ||
|
|
b94c1e2792 | ||
|
|
74f0393706 | ||
|
|
0ec4b9b59f | ||
|
|
1592d55db5 | ||
|
|
629c8b8167 | ||
|
|
9d8f70e04b | ||
|
|
f75104e616 | ||
|
|
c9d59fd5ac | ||
|
|
d41eb4ce31 | ||
|
|
60e87d943a | ||
|
|
1deed59d5b | ||
|
|
bbc4ec2625 | ||
|
|
5109b65e37 | ||
|
|
c0a9ce8e21 | ||
|
|
bdff8c6c0d | ||
|
|
a3ac8a445d | ||
|
|
84d4d68116 | ||
|
|
27248bff17 | ||
|
|
dc9a9f288b | ||
|
|
88662a4e55 | ||
|
|
9d269f7cba | ||
|
|
04fe439e0b | ||
|
|
72503b9061 | ||
|
|
f7ab963db9 | ||
|
|
f97128e11c | ||
|
|
e2d4107297 | ||
|
|
18709a1b22 | ||
|
|
ddfe2abb42 | ||
|
|
0cf82f7edd | ||
|
|
d1c40efd90 | ||
|
|
68e90ce3ed | ||
|
|
95e19cf681 | ||
|
|
3298fd4493 | ||
|
|
248bdffd50 | ||
|
|
cce7a20918 | ||
|
|
845c99b798 | ||
|
|
b13b832696 | ||
|
|
eb6e235f1b | ||
|
|
36c49b2171 | ||
|
|
5cd3096221 | ||
|
|
5bdc59d0e3 | ||
|
|
be43f2025f | ||
|
|
0bb39f91b9 | ||
|
|
63a7b44264 | ||
|
|
b5341f67e0 | ||
|
|
3a2045eccc | ||
|
|
c78239a6b7 | ||
|
|
4be4306540 | ||
|
|
48887f23c1 | ||
|
|
103809d35b | ||
|
|
9d92976eb9 | ||
|
|
d3d19b497b | ||
|
|
316c32f076 | ||
|
|
6f27201244 | ||
|
|
e56377f8c3 | ||
|
|
44e04864ac | ||
|
|
eea727570c | ||
|
|
54155a3c30 | ||
|
|
34d373c293 | ||
|
|
d7563032c4 | ||
|
|
ed75489975 |
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.10" />
|
||||
<option name="version" value="1.9.22" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -27,7 +27,7 @@ height="80">](https://github.com/vitorpamplona/amethyst/releases)
|
||||
- [ ] WebBrowser Signer (NIP-07, Not applicable)
|
||||
- [x] Old-style mentions (NIP-08)
|
||||
- [x] Event Deletion (NIP-09)
|
||||
- [x] Replies, mentions, Threads and Notifications (NIP-10)
|
||||
- [x] Replies, mentions, Threads, and Notifications (NIP-10)
|
||||
- [x] Relay Information Document (NIP-11)
|
||||
- [x] Generic Tag Queries (NIP-12)
|
||||
- [x] Proof of Work Display (NIP-13)
|
||||
@@ -59,7 +59,7 @@ height="80">](https://github.com/vitorpamplona/amethyst/releases)
|
||||
- [ ] Nostr Connect (NIP-46)
|
||||
- [x] Wallet Connect API (NIP-47)
|
||||
- [ ] Proxy Tags (NIP-48, Not applicable)
|
||||
- [ ] Private key encryption for import/export (NIP-49)
|
||||
- [x] Private key encryption for import/export (NIP-49)
|
||||
- [x] Online Relay Search (NIP-50)
|
||||
- [x] Lists (NIP-51)
|
||||
- [ ] Calendar Events (NIP-52)
|
||||
@@ -81,6 +81,7 @@ height="80">](https://github.com/vitorpamplona/amethyst/releases)
|
||||
- [x] Highlights (NIP-84)
|
||||
- [x] Recommended Application Handlers (NIP-89)
|
||||
- [ ] Data Vending Machine (NIP-90)
|
||||
- [x] Inline Metadata (NIP-92)
|
||||
- [x] Verifiable file URLs (NIP-94)
|
||||
- [x] Binary Blobs (NIP-95)
|
||||
- [x] HTTP File Storage Integration (NIP-96 Draft)
|
||||
@@ -112,13 +113,13 @@ The overall architecture consists of the UI, which uses the usual State/ViewMode
|
||||
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
|
||||
allow the UI and other parts of the app to subscribe to each individual Note/User and receive updates when they happen.
|
||||
allow the UI and other parts of the app to subscribe to each Note/User and receive updates when they happen.
|
||||
They are also responsible for updating viewModels when needed. Filters react to changes in the screen. As the user
|
||||
sees different Events, the Datasource classes are used to receive more information about those particular Events.
|
||||
|
||||
Most of the UI is reactive to changes in the repository classes. The service layer assembles Nostr filters for each need of the app,
|
||||
receives the data from the Relay, and sends it to the repository. Connection with relays is never closed during the use of the app.
|
||||
The UI receives a notification that objects were updated. Instances of User and Notes are mutable directly.
|
||||
The UI receives a notification that objects have been updated. Instances of User and Notes are mutable directly.
|
||||
There will never be two Notes with the same ID or two User instances with the same pubkey.
|
||||
|
||||
Lastly, the user's account information (priv key/pub key) is stored in the Android KeyStore for security.
|
||||
@@ -130,7 +131,7 @@ Make sure to have the following pre-requisites installed:
|
||||
2. Android Studio
|
||||
3. Android 8.0+ Phone or Emulation setup
|
||||
|
||||
Fork and clone this repository and import into Android Studio
|
||||
Fork and clone this repository and import it into Android Studio
|
||||
```bash
|
||||
git clone https://github.com/vitorpamplona/amethyst.git
|
||||
```
|
||||
@@ -171,7 +172,13 @@ For the Play build:
|
||||
|
||||
[Issues](https://github.com/vitorpamplona/amethyst/issues) and [pull requests](https://github.com/vitorpamplona/amethyst/pulls) here are also very welcome.
|
||||
|
||||
You can also send patches in Nostr using [GitStr](https://github.com/fiatjaf/gitstr) to `naddr1qqyxzmt9w358jum5qyg8v6t5daezumn0wd68yvfwvdhk6qg7waehxw309ahx7um5wgkhqatz9emk2mrvdaexgetj9ehx2ap0qy2hwumn8ghj7un9d3shjtnwdaehgu3wvfnj7q3qgcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqxpqqqpmej720gac`
|
||||
Translations can be provided via [Crowdin](https://crowdin.com/project/amethyst-social)
|
||||
|
||||
You can also send patches through Nostr using [GitStr](https://github.com/fiatjaf/gitstr) to [this nostr address](https://patch34.pages.dev/naddr1qqyxzmt9w358jum5qyg8v6t5daezumn0wd68yvfwvdhk6qg7waehxw309ahx7um5wgkhqatz9emk2mrvdaexgetj9ehx2ap0qy2hwumn8ghj7un9d3shjtnwdaehgu3wvfnj7q3qgcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqxpqqqpmej720gac)
|
||||
|
||||
By contributing to this repository, you agree to license your work under the MIT license. Any work
|
||||
contributed where you are not the original author must contain its license header with the original
|
||||
author(s) and source.
|
||||
|
||||
## How to Deploy
|
||||
|
||||
@@ -190,7 +197,7 @@ openssl base64 < <my-release-key.keystore> | tr -d '\n' | tee some_signing_key.j
|
||||
5. Tag the commit with `v{x.x.x}`
|
||||
6. Let the [Create Release GitHub Action](https://github.com/vitorpamplona/amethyst/actions/workflows/create-release.yml) build a new `aab` file.
|
||||
7. Add your CHANGE LOG to the description of the new release
|
||||
8. Download the `aab` file and upload it to the` PlayStore.
|
||||
8. Download the `aab` file and upload it to the PlayStore.
|
||||
|
||||
# Privacy on Relays & nostr
|
||||
Your internet protocol (IP) address is exposed to the relays you connect to. If you want to improve your privacy, consider utilizing a service that masks your IP address (e.g. a VPN) from trackers online.
|
||||
|
||||
+9
-6
@@ -12,8 +12,8 @@ android {
|
||||
applicationId "com.vitorpamplona.amethyst"
|
||||
minSdk 26
|
||||
targetSdk 34
|
||||
versionCode 349
|
||||
versionName "0.83.8"
|
||||
versionCode 357
|
||||
versionName "0.84.2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
@@ -141,13 +141,15 @@ android {
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion "1.5.3"
|
||||
// Should match compose version : https://developer.android.com/jetpack/androidx/releases/compose-kotlin
|
||||
kotlinCompilerExtensionVersion "1.5.8"
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
exclude '**/libscrypt.dylib'
|
||||
}
|
||||
|
||||
lint {
|
||||
@@ -161,6 +163,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':quartz')
|
||||
implementation project(path: ':commons')
|
||||
implementation "androidx.core:core-ktx:$core_ktx_version"
|
||||
implementation 'androidx.activity:activity-compose:1.8.2'
|
||||
implementation "androidx.compose.ui:ui:$compose_ui_version"
|
||||
@@ -191,7 +194,7 @@ dependencies {
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
||||
// Zoomable images
|
||||
implementation 'net.engawapg.lib:zoomable:1.5.3'
|
||||
implementation 'net.engawapg.lib:zoomable:1.6.0'
|
||||
|
||||
// Biometrics
|
||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||
@@ -228,7 +231,7 @@ dependencies {
|
||||
implementation "com.google.accompanist:accompanist-permissions:$accompanist_version"
|
||||
|
||||
// For QR generation
|
||||
implementation 'com.google.zxing:core:3.5.2'
|
||||
implementation 'com.google.zxing:core:3.5.3'
|
||||
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
||||
|
||||
// Markdown
|
||||
@@ -251,7 +254,7 @@ dependencies {
|
||||
playImplementation 'com.google.mlkit:translate:17.0.2'
|
||||
|
||||
// PushNotifications
|
||||
playImplementation platform('com.google.firebase:firebase-bom:32.7.1')
|
||||
playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
|
||||
playImplementation 'com.google.firebase:firebase-messaging-ktx'
|
||||
|
||||
//PushNotifications(FDroid)
|
||||
|
||||
Vendored
+5
@@ -33,6 +33,11 @@
|
||||
# JNA For Libsodium
|
||||
-keep class com.goterl.lazysodium.** { *; }
|
||||
|
||||
# libscrypt
|
||||
-keep class com.lambdaworks.codec.** { *; }
|
||||
-keep class com.lambdaworks.crypto.** { *; }
|
||||
-keep class com.lambdaworks.jni.** { *; }
|
||||
|
||||
# JNA also requires AWT, which Android does not have. So the classes are broken down to filter AWT out
|
||||
-keep class com.sun.jna.ToNativeConverter { *; }
|
||||
-keep class com.sun.jna.NativeMapped { *; }
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.os.StrictMode
|
||||
import android.os.StrictMode.ThreadPolicy
|
||||
import android.os.StrictMode.VmPolicy
|
||||
import android.util.Log
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import coil.ImageLoader
|
||||
import coil.disk.DiskCache
|
||||
import com.vitorpamplona.amethyst.service.playback.VideoCache
|
||||
@@ -87,6 +88,10 @@ class Amethyst : Application() {
|
||||
return ImageLoader.Builder(applicationContext).diskCache { imageCache }
|
||||
}
|
||||
|
||||
fun encryptedStorage(npub: String? = null): EncryptedSharedPreferences {
|
||||
return EncryptedStorage.preferences(instance, npub)
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var instance: Amethyst
|
||||
private set
|
||||
|
||||
@@ -20,32 +20,35 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst
|
||||
|
||||
import android.content.Context
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKey
|
||||
|
||||
object EncryptedStorage {
|
||||
private const val PREFERENCES_NAME = "secret_keeper"
|
||||
class EncryptedStorage {
|
||||
companion object {
|
||||
private const val PREFERENCES_NAME = "secret_keeper"
|
||||
|
||||
// returns the preferences for each account or a global file if null.
|
||||
fun prefsFileName(npub: String? = null): String {
|
||||
return if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub"
|
||||
}
|
||||
// returns the preferences for each account or a global file if null.
|
||||
fun prefsFileName(npub: String? = null): String {
|
||||
return if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub"
|
||||
}
|
||||
|
||||
fun preferences(npub: String? = null): EncryptedSharedPreferences {
|
||||
val context = Amethyst.instance
|
||||
val masterKey: MasterKey =
|
||||
MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.build()
|
||||
fun preferences(
|
||||
applicationContext: Context,
|
||||
npub: String? = null,
|
||||
): EncryptedSharedPreferences {
|
||||
val masterKey: MasterKey =
|
||||
MasterKey.Builder(applicationContext, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.build()
|
||||
|
||||
val preferencesName = prefsFileName(npub)
|
||||
|
||||
return EncryptedSharedPreferences.create(
|
||||
context,
|
||||
preferencesName,
|
||||
masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
||||
) as EncryptedSharedPreferences
|
||||
return EncryptedSharedPreferences.create(
|
||||
applicationContext,
|
||||
prefsFileName(npub),
|
||||
masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
||||
) as EncryptedSharedPreferences
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ object LocalPreferences {
|
||||
if (npub == null) DEBUG_PREFERENCES_NAME else "${DEBUG_PREFERENCES_NAME}_$npub"
|
||||
Amethyst.instance.getSharedPreferences(preferenceFile, Context.MODE_PRIVATE)
|
||||
} else {
|
||||
return EncryptedStorage.preferences(npub)
|
||||
return Amethyst.instance.encryptedStorage(npub)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1325,7 +1325,7 @@ class Account(
|
||||
directMentions: Set<HexKey>,
|
||||
relayList: List<Relay>? = null,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<Event>? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
@@ -1381,7 +1381,7 @@ class Account(
|
||||
zapRaiserAmount: Long? = null,
|
||||
relayList: List<Relay>? = null,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<Event>? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
@@ -1428,7 +1428,7 @@ class Account(
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long? = null,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<Event>? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
@@ -1461,7 +1461,7 @@ class Account(
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long? = null,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<Event>? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
@@ -1495,6 +1495,7 @@ class Account(
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long? = null,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
) {
|
||||
sendPrivateMessage(
|
||||
message,
|
||||
@@ -1505,6 +1506,7 @@ class Account(
|
||||
wantsToMarkAsSensitive,
|
||||
zapRaiserAmount,
|
||||
geohash,
|
||||
nip94attachments,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1517,6 +1519,7 @@ class Account(
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long? = null,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
@@ -1533,6 +1536,7 @@ class Account(
|
||||
markAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = zapRaiserAmount,
|
||||
geohash = geohash,
|
||||
nip94attachments = nip94attachments,
|
||||
signer = signer,
|
||||
advertiseNip18 = false,
|
||||
) {
|
||||
@@ -1551,6 +1555,7 @@ class Account(
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long? = null,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
@@ -1567,6 +1572,7 @@ class Account(
|
||||
markAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = zapRaiserAmount,
|
||||
geohash = geohash,
|
||||
nip94attachments = nip94attachments,
|
||||
signer = signer,
|
||||
) {
|
||||
broadcastPrivately(it)
|
||||
|
||||
@@ -32,6 +32,7 @@ fun checkForHashtagWithIcon(
|
||||
primary: Color,
|
||||
): HashtagIcon? {
|
||||
return when (tag.lowercase()) {
|
||||
"₿itcoin",
|
||||
"bitcoin",
|
||||
"btc",
|
||||
"timechain",
|
||||
|
||||
@@ -50,6 +50,7 @@ import com.vitorpamplona.quartz.events.CalendarRSVPEvent
|
||||
import com.vitorpamplona.quartz.events.CalendarTimeSlotEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelHideMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelListEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelMuteUserEvent
|
||||
@@ -57,6 +58,7 @@ import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.events.CommunityListEvent
|
||||
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||
import com.vitorpamplona.quartz.events.DeletionEvent
|
||||
@@ -463,6 +465,20 @@ object LocalCache {
|
||||
consumeBaseReplaceable(event, relay)
|
||||
}
|
||||
|
||||
fun consume(
|
||||
event: CommunityListEvent,
|
||||
relay: Relay?,
|
||||
) {
|
||||
consumeBaseReplaceable(event, relay)
|
||||
}
|
||||
|
||||
fun consume(
|
||||
event: ChannelListEvent,
|
||||
relay: Relay?,
|
||||
) {
|
||||
consumeBaseReplaceable(event, relay)
|
||||
}
|
||||
|
||||
fun consume(
|
||||
event: FileServersEvent,
|
||||
relay: Relay?,
|
||||
@@ -1772,6 +1788,7 @@ object LocalCache {
|
||||
is CalendarTimeSlotEvent -> consume(event, relay)
|
||||
is CalendarRSVPEvent -> consume(event, relay)
|
||||
is ChannelCreateEvent -> consume(event)
|
||||
is ChannelListEvent -> consume(event, relay)
|
||||
is ChannelHideMessageEvent -> consume(event)
|
||||
is ChannelMessageEvent -> consume(event, relay)
|
||||
is ChannelMetadataEvent -> consume(event)
|
||||
@@ -1779,6 +1796,7 @@ object LocalCache {
|
||||
is ChatMessageEvent -> consume(event, relay)
|
||||
is ClassifiedsEvent -> consume(event, relay)
|
||||
is CommunityDefinitionEvent -> consume(event, relay)
|
||||
is CommunityListEvent -> consume(event, relay)
|
||||
is CommunityPostApprovalEvent -> {
|
||||
event.containedPost()?.let { verifyAndConsume(it, relay) }
|
||||
consume(event)
|
||||
|
||||
@@ -615,7 +615,7 @@ open class Note(val idHex: String) {
|
||||
|
||||
// Regular Zap Receipts
|
||||
zaps.forEach {
|
||||
val noteEvent = it?.value?.event
|
||||
val noteEvent = it.value?.event
|
||||
if (noteEvent is LnZapEvent) {
|
||||
sumOfAmounts += noteEvent.amount ?: BigDecimal.ZERO
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ import com.vitorpamplona.amethyst.ui.components.imageExtensions
|
||||
import com.vitorpamplona.amethyst.ui.components.removeQueryParamsForExtensionComparison
|
||||
import com.vitorpamplona.amethyst.ui.components.tagIndex
|
||||
import com.vitorpamplona.amethyst.ui.components.videoExtensions
|
||||
import com.vitorpamplona.quartz.encoders.Nip54
|
||||
import com.vitorpamplona.quartz.encoders.Nip92
|
||||
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
@@ -42,6 +45,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@@ -94,27 +98,33 @@ val HTTPRegex =
|
||||
.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
class RichTextParser() {
|
||||
fun parseMediaUrl(fullUrl: String): ZoomableUrlContent? {
|
||||
fun parseMediaUrl(
|
||||
fullUrl: String,
|
||||
eventTags: ImmutableListOfLists<String>,
|
||||
): ZoomableUrlContent? {
|
||||
val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl)
|
||||
return if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) {
|
||||
val frags = Nip44UrlParser().parse(fullUrl)
|
||||
val frags = Nip54().parse(fullUrl)
|
||||
val tags = Nip92().parse(fullUrl, eventTags.lists)
|
||||
|
||||
ZoomableUrlImage(
|
||||
url = fullUrl,
|
||||
description = frags["alt"],
|
||||
hash = frags["x"],
|
||||
blurhash = frags["blurhash"],
|
||||
dim = frags["dim"],
|
||||
contentWarning = frags["content-warning"],
|
||||
description = frags[FileHeaderEvent.ALT] ?: tags[FileHeaderEvent.ALT],
|
||||
hash = frags[FileHeaderEvent.HASH] ?: tags[FileHeaderEvent.HASH],
|
||||
blurhash = frags[FileHeaderEvent.BLUR_HASH] ?: tags[FileHeaderEvent.BLUR_HASH],
|
||||
dim = frags[FileHeaderEvent.DIMENSION] ?: tags[FileHeaderEvent.DIMENSION],
|
||||
contentWarning = frags["content-warning"] ?: tags["content-warning"],
|
||||
)
|
||||
} else if (videoExtensions.any { removedParamsFromUrl.endsWith(it) }) {
|
||||
val frags = Nip44UrlParser().parse(fullUrl)
|
||||
val frags = Nip54().parse(fullUrl)
|
||||
val tags = Nip92().parse(fullUrl, eventTags.lists)
|
||||
ZoomableUrlVideo(
|
||||
url = fullUrl,
|
||||
description = frags["alt"],
|
||||
hash = frags["x"],
|
||||
blurhash = frags["blurhash"],
|
||||
dim = frags["dim"],
|
||||
contentWarning = frags["content-warning"],
|
||||
description = frags[FileHeaderEvent.ALT] ?: tags[FileHeaderEvent.ALT],
|
||||
hash = frags[FileHeaderEvent.HASH] ?: tags[FileHeaderEvent.HASH],
|
||||
blurhash = frags[FileHeaderEvent.BLUR_HASH] ?: tags[FileHeaderEvent.BLUR_HASH],
|
||||
dim = frags[FileHeaderEvent.DIMENSION] ?: tags[FileHeaderEvent.DIMENSION],
|
||||
contentWarning = frags["content-warning"] ?: tags["content-warning"],
|
||||
)
|
||||
} else {
|
||||
null
|
||||
@@ -146,7 +156,7 @@ class RichTextParser() {
|
||||
}
|
||||
|
||||
val imagesForPager =
|
||||
urlSet.mapNotNull { fullUrl -> parseMediaUrl(fullUrl) }.associateBy { it.url }
|
||||
urlSet.mapNotNull { fullUrl -> parseMediaUrl(fullUrl, tags) }.associateBy { it.url }
|
||||
val imageList = imagesForPager.values.toList()
|
||||
|
||||
val emojiMap =
|
||||
@@ -170,34 +180,34 @@ class RichTextParser() {
|
||||
emojis: Map<String, String>,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
): ImmutableList<ParagraphState> {
|
||||
var paragraphSegments = persistentListOf<ParagraphState>()
|
||||
val lines = content.split('\n')
|
||||
val paragraphSegments = ArrayList<ParagraphState>(lines.size)
|
||||
|
||||
content.split('\n').forEach { paragraph ->
|
||||
var segments = persistentListOf<Segment>()
|
||||
lines.forEach { paragraph ->
|
||||
var isDirty = false
|
||||
|
||||
val isRTL = isArabic(paragraph)
|
||||
|
||||
val wordList = paragraph.trimEnd().split(' ')
|
||||
val segments = ArrayList<Segment>(wordList.size)
|
||||
wordList.forEach { word ->
|
||||
val wordSegment = wordIdentifier(word, images, urls, emojis, tags)
|
||||
if (wordSegment !is RegularTextSegment) {
|
||||
isDirty = true
|
||||
}
|
||||
segments = segments.add(wordSegment)
|
||||
segments.add(wordSegment)
|
||||
}
|
||||
|
||||
val newSegments =
|
||||
if (isDirty) {
|
||||
ParagraphState(segments, isRTL)
|
||||
ParagraphState(segments.toPersistentList(), isRTL)
|
||||
} else {
|
||||
ParagraphState(persistentListOf<Segment>(RegularTextSegment(paragraph)), isRTL)
|
||||
}
|
||||
|
||||
paragraphSegments = paragraphSegments.add(newSegments)
|
||||
paragraphSegments.add(newSegments)
|
||||
}
|
||||
|
||||
return paragraphSegments
|
||||
return paragraphSegments.toImmutableList()
|
||||
}
|
||||
|
||||
fun isNumber(word: String): Boolean {
|
||||
@@ -247,7 +257,7 @@ class RichTextParser() {
|
||||
parseHash(word, tags)
|
||||
} else if (word.contains(".") && schemelessMatcher.find()) {
|
||||
val url = schemelessMatcher.group(1) // url
|
||||
val additionalChars = schemelessMatcher.group(4) // additional chars
|
||||
val additionalChars = schemelessMatcher.group(4).ifEmpty { null } // additional chars
|
||||
val pattern =
|
||||
"""^([A-Za-z0-9-_]+(\.[A-Za-z0-9-_]+)+)(:[0-9]+)?(/[^?#]*)?(\?[^#]*)?(#.*)?"""
|
||||
.toRegex(RegexOption.IGNORE_CASE)
|
||||
@@ -297,7 +307,7 @@ class RichTextParser() {
|
||||
if (hashtagMatcher.find()) {
|
||||
val hashtag = hashtagMatcher.group(1)
|
||||
if (hashtag != null) {
|
||||
return HashTagSegment(word, hashtag, hashtagMatcher.group(2))
|
||||
return HashTagSegment(word, hashtag, hashtagMatcher.group(2).ifEmpty { null })
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service.previews
|
||||
|
||||
import com.vitorpamplona.amethyst.service.HttpClientManager
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType
|
||||
@@ -91,6 +92,7 @@ suspend fun getDocument(
|
||||
withContext(Dispatchers.IO) {
|
||||
val request: Request = Request.Builder().url(url).get().build()
|
||||
HttpClientManager.getHttpClient().newCall(request).execute().use {
|
||||
checkNotInMainThread()
|
||||
if (it.isSuccessful) {
|
||||
val mimeType =
|
||||
it.headers.get("Content-Type")?.toMediaType()
|
||||
|
||||
@@ -77,7 +77,7 @@ class Relay(
|
||||
var errorCounter = 0
|
||||
var pingInMs: Long? = null
|
||||
|
||||
var closingTimeInSeconds = 0L
|
||||
var lastConnectTentative: Long = 0L
|
||||
|
||||
var afterEOSEPerSubscription = mutableMapOf<String, Boolean>()
|
||||
|
||||
@@ -120,6 +120,8 @@ class Relay(
|
||||
|
||||
if (socket != null) return
|
||||
|
||||
lastConnectTentative = TimeUtils.now()
|
||||
|
||||
try {
|
||||
val request =
|
||||
Request.Builder()
|
||||
@@ -251,7 +253,6 @@ class Relay(
|
||||
this.isReady = false
|
||||
this.usingCompression = false
|
||||
this.resetEOSEStatuses()
|
||||
this.closingTimeInSeconds = TimeUtils.now()
|
||||
}
|
||||
|
||||
fun processNewRelayMessage(newMessage: String) {
|
||||
@@ -331,7 +332,7 @@ class Relay(
|
||||
Log.d("Relay", "Relay.disconnect $url")
|
||||
checkNotInMainThread()
|
||||
|
||||
closingTimeInSeconds = TimeUtils.now()
|
||||
lastConnectTentative = 0L // this is not an error, so prepare to reconnect as soon as requested.
|
||||
socket?.cancel()
|
||||
socket = null
|
||||
isReady = false
|
||||
@@ -373,7 +374,7 @@ class Relay(
|
||||
}
|
||||
} else {
|
||||
// waits 60 seconds to reconnect after disconnected.
|
||||
if (TimeUtils.now() > closingTimeInSeconds + RECONNECTING_IN_SECONDS) {
|
||||
if (TimeUtils.now() > lastConnectTentative + RECONNECTING_IN_SECONDS) {
|
||||
// sends all filters after connection is successful.
|
||||
connect()
|
||||
}
|
||||
@@ -413,7 +414,7 @@ class Relay(
|
||||
|
||||
if (socket == null) {
|
||||
// waits 60 seconds to reconnect after disconnected.
|
||||
if (TimeUtils.now() > closingTimeInSeconds + RECONNECTING_IN_SECONDS) {
|
||||
if (TimeUtils.now() > lastConnectTentative + RECONNECTING_IN_SECONDS) {
|
||||
// println("sendfilter Only if Disconnected ${url} ")
|
||||
connect()
|
||||
}
|
||||
|
||||
@@ -113,7 +113,9 @@ class MainActivity : AppCompatActivity() {
|
||||
val accountStateViewModel: AccountStateViewModel = viewModel()
|
||||
accountStateViewModel.serviceManager = serviceManager
|
||||
|
||||
LaunchedEffect(key1 = Unit) { accountStateViewModel.tryLoginExistingAccountAsync() }
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
accountStateViewModel.tryLoginExistingAccountAsync()
|
||||
}
|
||||
|
||||
AccountScreen(accountStateViewModel, sharedPreferencesViewModel)
|
||||
}
|
||||
@@ -158,7 +160,9 @@ class MainActivity : AppCompatActivity() {
|
||||
override fun onPause() {
|
||||
Log.d("Lifetime Event", "MainActivity.onPause")
|
||||
|
||||
LanguageTranslatorService.clear()
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
LanguageTranslatorService.clear()
|
||||
}
|
||||
serviceManager.cleanObservers()
|
||||
|
||||
// if (BuildConfig.DEBUG) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.widget.Toast
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -49,8 +50,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -98,7 +97,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
@@ -157,9 +155,9 @@ 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.ShowUserSuggestionList
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
|
||||
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
|
||||
@@ -263,7 +261,7 @@ fun NewPostView(
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.relays),
|
||||
contentDescription = null,
|
||||
contentDescription = stringResource(id = R.string.relay_list_selector),
|
||||
modifier = Modifier.height(25.dp),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
@@ -304,25 +302,38 @@ fun NewPostView(
|
||||
) { pad ->
|
||||
Surface(
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
start = Size10dp,
|
||||
top = pad.calculateTopPadding(),
|
||||
end = Size10dp,
|
||||
bottom = pad.calculateBottomPadding(),
|
||||
)
|
||||
Modifier
|
||||
.padding(
|
||||
start = Size10dp,
|
||||
top = pad.calculateTopPadding(),
|
||||
end = Size10dp,
|
||||
bottom = pad.calculateBottomPadding(),
|
||||
)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.imePadding().weight(1f),
|
||||
modifier =
|
||||
Modifier
|
||||
.imePadding()
|
||||
.weight(1f),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().weight(1f),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().verticalScroll(scrollState),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(scrollState),
|
||||
) {
|
||||
postViewModel.originalNote?.let {
|
||||
Row(Modifier.heightIn(max = 200.dp)) {
|
||||
@@ -474,7 +485,8 @@ fun NewPostView(
|
||||
contentDescription = myUrlPreview,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier =
|
||||
Modifier.padding(top = 4.dp)
|
||||
Modifier
|
||||
.padding(top = 4.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
@@ -511,79 +523,13 @@ fun NewPostView(
|
||||
}
|
||||
}
|
||||
|
||||
val userSuggestions = postViewModel.userSuggestions
|
||||
if (userSuggestions.isNotEmpty()) {
|
||||
LazyColumn(
|
||||
contentPadding =
|
||||
PaddingValues(
|
||||
top = 10.dp,
|
||||
),
|
||||
modifier = Modifier.heightIn(0.dp, 300.dp),
|
||||
) {
|
||||
itemsIndexed(
|
||||
userSuggestions,
|
||||
key = { _, item -> item.pubkeyHex },
|
||||
) { _, item ->
|
||||
UserLine(item, accountViewModel) { postViewModel.autocompleteWithUser(item) }
|
||||
}
|
||||
}
|
||||
}
|
||||
ShowUserSuggestionList(
|
||||
postViewModel,
|
||||
accountViewModel,
|
||||
modifier = Modifier.heightIn(0.dp, 300.dp),
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().height(50.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
UploadFromGallery(
|
||||
isUploading = postViewModel.isUploadingImage,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
modifier = Modifier,
|
||||
) {
|
||||
postViewModel.selectImage(it)
|
||||
}
|
||||
|
||||
if (postViewModel.canUsePoll) {
|
||||
// These should be hashtag recommendations the user selects in the future.
|
||||
// val hashtag = stringResource(R.string.poll_hashtag)
|
||||
// postViewModel.includePollHashtagInMessage(postViewModel.wantsPoll, hashtag)
|
||||
AddPollButton(postViewModel.wantsPoll) {
|
||||
postViewModel.wantsPoll = !postViewModel.wantsPoll
|
||||
if (postViewModel.wantsPoll) {
|
||||
postViewModel.wantsProduct = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddClassifiedsButton(postViewModel) {
|
||||
postViewModel.wantsProduct = !postViewModel.wantsProduct
|
||||
if (postViewModel.wantsProduct) {
|
||||
postViewModel.wantsPoll = false
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.canAddInvoice) {
|
||||
AddLnInvoiceButton(postViewModel.wantsInvoice) {
|
||||
postViewModel.wantsInvoice = !postViewModel.wantsInvoice
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.canAddZapRaiser) {
|
||||
AddZapraiserButton(postViewModel.wantsZapraiser) {
|
||||
postViewModel.wantsZapraiser = !postViewModel.wantsZapraiser
|
||||
}
|
||||
}
|
||||
|
||||
MarkAsSensitive(postViewModel) {
|
||||
postViewModel.wantsToMarkAsSensitive = !postViewModel.wantsToMarkAsSensitive
|
||||
}
|
||||
|
||||
AddGeoHash(postViewModel) {
|
||||
postViewModel.wantsToAddGeoHash = !postViewModel.wantsToAddGeoHash
|
||||
}
|
||||
|
||||
ForwardZapTo(postViewModel) {
|
||||
postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo
|
||||
}
|
||||
}
|
||||
BottomRowActions(postViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -591,6 +537,71 @@ fun NewPostView(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomRowActions(postViewModel: NewPostViewModel) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.horizontalScroll(scrollState)
|
||||
.fillMaxWidth()
|
||||
.height(50.dp),
|
||||
verticalAlignment = CenterVertically,
|
||||
) {
|
||||
UploadFromGallery(
|
||||
isUploading = postViewModel.isUploadingImage,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
modifier = Modifier,
|
||||
) {
|
||||
postViewModel.selectImage(it)
|
||||
}
|
||||
|
||||
if (postViewModel.canUsePoll) {
|
||||
// These should be hashtag recommendations the user selects in the future.
|
||||
// val hashtag = stringResource(R.string.poll_hashtag)
|
||||
// postViewModel.includePollHashtagInMessage(postViewModel.wantsPoll, hashtag)
|
||||
AddPollButton(postViewModel.wantsPoll) {
|
||||
postViewModel.wantsPoll = !postViewModel.wantsPoll
|
||||
if (postViewModel.wantsPoll) {
|
||||
postViewModel.wantsProduct = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddClassifiedsButton(postViewModel) {
|
||||
postViewModel.wantsProduct = !postViewModel.wantsProduct
|
||||
if (postViewModel.wantsProduct) {
|
||||
postViewModel.wantsPoll = false
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.canAddInvoice) {
|
||||
AddLnInvoiceButton(postViewModel.wantsInvoice) {
|
||||
postViewModel.wantsInvoice = !postViewModel.wantsInvoice
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.canAddZapRaiser) {
|
||||
AddZapraiserButton(postViewModel.wantsZapraiser) {
|
||||
postViewModel.wantsZapraiser = !postViewModel.wantsZapraiser
|
||||
}
|
||||
}
|
||||
|
||||
MarkAsSensitive(postViewModel) {
|
||||
postViewModel.wantsToMarkAsSensitive = !postViewModel.wantsToMarkAsSensitive
|
||||
}
|
||||
|
||||
AddGeoHash(postViewModel) {
|
||||
postViewModel.wantsToAddGeoHash = !postViewModel.wantsToAddGeoHash
|
||||
}
|
||||
|
||||
ForwardZapTo(postViewModel) {
|
||||
postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PollField(postViewModel: NewPostViewModel) {
|
||||
Column(
|
||||
@@ -623,7 +634,6 @@ private fun PollField(postViewModel: NewPostViewModel) {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun MessageField(postViewModel: NewPostViewModel) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
@@ -644,7 +654,8 @@ private fun MessageField(postViewModel: NewPostViewModel) {
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
@@ -684,21 +695,32 @@ fun ContentSensitivityExplainer(postViewModel: NewPostViewModel) {
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp),
|
||||
) {
|
||||
Box(
|
||||
Modifier.height(20.dp).width(25.dp),
|
||||
Modifier
|
||||
.height(20.dp)
|
||||
.width(25.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.VisibilityOff,
|
||||
contentDescription = stringResource(id = R.string.content_warning),
|
||||
modifier = Modifier.size(18.dp).align(Alignment.BottomStart),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(18.dp)
|
||||
.align(Alignment.BottomStart),
|
||||
tint = Color.Red,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Warning,
|
||||
contentDescription = stringResource(id = R.string.content_warning),
|
||||
modifier = Modifier.size(10.dp).align(Alignment.TopEnd),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(10.dp)
|
||||
.align(Alignment.TopEnd),
|
||||
tint = Color.Yellow,
|
||||
)
|
||||
}
|
||||
@@ -921,7 +943,10 @@ fun SellProduct(postViewModel: NewPostViewModel) {
|
||||
placeholder = conditionTypes.filter { it.first == postViewModel.condition }.first().second,
|
||||
options = conditionOptions,
|
||||
onSelect = { postViewModel.condition = conditionTypes[it].first },
|
||||
modifier = Modifier.weight(1f).padding(end = 5.dp, bottom = 1.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 5.dp, bottom = 1.dp),
|
||||
) { currentOption, modifier ->
|
||||
MyTextField(
|
||||
value = TextFieldValue(currentOption),
|
||||
@@ -982,7 +1007,10 @@ fun SellProduct(postViewModel: NewPostViewModel) {
|
||||
?: "",
|
||||
options = categoryOptions,
|
||||
onSelect = { postViewModel.category = TextFieldValue(categoryTypes[it].second) },
|
||||
modifier = Modifier.weight(1f).padding(end = 5.dp, bottom = 1.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 5.dp, bottom = 1.dp),
|
||||
) { currentOption, modifier ->
|
||||
MyTextField(
|
||||
value = TextFieldValue(currentOption),
|
||||
@@ -1047,21 +1075,32 @@ fun FowardZapTo(
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp),
|
||||
) {
|
||||
Box(
|
||||
Modifier.height(20.dp).width(25.dp),
|
||||
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),
|
||||
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),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(13.dp)
|
||||
.align(Alignment.CenterEnd),
|
||||
tint = BitcoinOrange,
|
||||
)
|
||||
}
|
||||
@@ -1161,10 +1200,15 @@ fun LocationAsHash(postViewModel: NewPostViewModel) {
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp),
|
||||
) {
|
||||
Box(
|
||||
Modifier.height(20.dp).width(20.dp),
|
||||
Modifier
|
||||
.height(20.dp)
|
||||
.width(20.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.LocationOn,
|
||||
@@ -1305,32 +1349,46 @@ private fun AddZapraiserButton(
|
||||
onClick = { onClick() },
|
||||
) {
|
||||
Box(
|
||||
Modifier.height(20.dp).width(25.dp),
|
||||
Modifier
|
||||
.height(20.dp)
|
||||
.width(25.dp),
|
||||
) {
|
||||
if (!isLnInvoiceActive) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ShowChart,
|
||||
null,
|
||||
modifier = Modifier.size(20.dp).align(Alignment.TopStart),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(20.dp)
|
||||
.align(Alignment.TopStart),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Bolt,
|
||||
contentDescription = stringResource(R.string.zaps),
|
||||
modifier = Modifier.size(13.dp).align(Alignment.BottomEnd),
|
||||
contentDescription = stringResource(R.string.add_zapraiser),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(13.dp)
|
||||
.align(Alignment.BottomEnd),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ShowChart,
|
||||
null,
|
||||
modifier = Modifier.size(20.dp).align(Alignment.TopStart),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(20.dp)
|
||||
.align(Alignment.TopStart),
|
||||
tint = BitcoinOrange,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Bolt,
|
||||
contentDescription = stringResource(R.string.zaps),
|
||||
modifier = Modifier.size(13.dp).align(Alignment.BottomEnd),
|
||||
contentDescription = stringResource(R.string.cancel_zapraiser),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(13.dp)
|
||||
.align(Alignment.BottomEnd),
|
||||
tint = BitcoinOrange,
|
||||
)
|
||||
}
|
||||
@@ -1349,14 +1407,14 @@ fun AddGeoHash(
|
||||
if (!postViewModel.wantsToAddGeoHash) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.LocationOff,
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.add_location),
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.LocationOn,
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.remove_location),
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
@@ -1375,14 +1433,14 @@ private fun AddLnInvoiceButton(
|
||||
if (!isLnInvoiceActive) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.CurrencyBitcoin,
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.add_bitcoin_invoice),
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.CurrencyBitcoin,
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.cancel_bitcoin_invoice),
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = BitcoinOrange,
|
||||
)
|
||||
@@ -1399,32 +1457,46 @@ private fun ForwardZapTo(
|
||||
onClick = { onClick() },
|
||||
) {
|
||||
Box(
|
||||
Modifier.height(20.dp).width(25.dp),
|
||||
Modifier
|
||||
.height(20.dp)
|
||||
.width(25.dp),
|
||||
) {
|
||||
if (!postViewModel.wantsForwardZapTo) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Bolt,
|
||||
contentDescription = stringResource(R.string.zaps),
|
||||
modifier = Modifier.size(20.dp).align(Alignment.CenterStart),
|
||||
contentDescription = stringResource(R.string.add_zap_split),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(20.dp)
|
||||
.align(Alignment.CenterStart),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowForwardIos,
|
||||
contentDescription = stringResource(R.string.zaps),
|
||||
modifier = Modifier.size(13.dp).align(Alignment.CenterEnd),
|
||||
contentDescription = null,
|
||||
modifier =
|
||||
Modifier
|
||||
.size(13.dp)
|
||||
.align(Alignment.CenterEnd),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Bolt,
|
||||
contentDescription = stringResource(id = R.string.zaps),
|
||||
modifier = Modifier.size(20.dp).align(Alignment.CenterStart),
|
||||
contentDescription = stringResource(id = R.string.cancel_zap_split),
|
||||
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),
|
||||
contentDescription = null,
|
||||
modifier =
|
||||
Modifier
|
||||
.size(13.dp)
|
||||
.align(Alignment.CenterEnd),
|
||||
tint = BitcoinOrange,
|
||||
)
|
||||
}
|
||||
@@ -1450,7 +1522,7 @@ private fun AddClassifiedsButton(
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Sell,
|
||||
contentDescription = stringResource(id = R.string.classifieds),
|
||||
contentDescription = stringResource(id = R.string.cancel_classifieds),
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = BitcoinOrange,
|
||||
)
|
||||
@@ -1467,32 +1539,46 @@ private fun MarkAsSensitive(
|
||||
onClick = { onClick() },
|
||||
) {
|
||||
Box(
|
||||
Modifier.height(20.dp).width(23.dp),
|
||||
Modifier
|
||||
.height(20.dp)
|
||||
.width(23.dp),
|
||||
) {
|
||||
if (!postViewModel.wantsToMarkAsSensitive) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Visibility,
|
||||
contentDescription = stringResource(R.string.content_warning),
|
||||
modifier = Modifier.size(18.dp).align(Alignment.BottomStart),
|
||||
contentDescription = stringResource(R.string.add_content_warning),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(18.dp)
|
||||
.align(Alignment.BottomStart),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Warning,
|
||||
contentDescription = stringResource(R.string.content_warning),
|
||||
modifier = Modifier.size(10.dp).align(Alignment.TopEnd),
|
||||
contentDescription = null,
|
||||
modifier =
|
||||
Modifier
|
||||
.size(10.dp)
|
||||
.align(Alignment.TopEnd),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.VisibilityOff,
|
||||
contentDescription = stringResource(id = R.string.content_warning),
|
||||
modifier = Modifier.size(18.dp).align(Alignment.BottomStart),
|
||||
contentDescription = stringResource(id = R.string.remove_content_warning),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(18.dp)
|
||||
.align(Alignment.BottomStart),
|
||||
tint = Color.Red,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Warning,
|
||||
contentDescription = stringResource(id = R.string.content_warning),
|
||||
modifier = Modifier.size(10.dp).align(Alignment.TopEnd),
|
||||
contentDescription = null,
|
||||
modifier =
|
||||
Modifier
|
||||
.size(10.dp)
|
||||
.align(Alignment.TopEnd),
|
||||
tint = Color.Yellow,
|
||||
)
|
||||
}
|
||||
@@ -1601,7 +1687,8 @@ fun ImageVideoDescription(
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 30.dp, end = 30.dp)
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
@@ -1611,11 +1698,17 @@ fun ImageVideoDescription(
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(30.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(30.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp),
|
||||
) {
|
||||
Text(
|
||||
text =
|
||||
@@ -1633,13 +1726,17 @@ fun ImageVideoDescription(
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier =
|
||||
Modifier.padding(start = 10.dp)
|
||||
Modifier
|
||||
.padding(start = 10.dp)
|
||||
.weight(1.0f)
|
||||
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)),
|
||||
)
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.size(30.dp).padding(end = 5.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(30.dp)
|
||||
.padding(end = 5.dp),
|
||||
onClick = onCancel,
|
||||
) {
|
||||
CancelIcon()
|
||||
@@ -1651,7 +1748,8 @@ fun ImageVideoDescription(
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp)
|
||||
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)),
|
||||
) {
|
||||
@@ -1661,7 +1759,8 @@ fun ImageVideoDescription(
|
||||
contentDescription = uri.toString(),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier =
|
||||
Modifier.padding(top = 4.dp)
|
||||
Modifier
|
||||
.padding(top = 4.dp)
|
||||
.fillMaxWidth()
|
||||
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)),
|
||||
)
|
||||
@@ -1687,7 +1786,10 @@ fun ImageVideoDescription(
|
||||
bitmap = it.asImageBitmap(),
|
||||
contentDescription = "some useful description",
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.padding(top = 4.dp).fillMaxWidth(),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -1709,7 +1811,10 @@ fun ImageVideoDescription(
|
||||
?: fileServers[0].server.name,
|
||||
options = fileServerOptions,
|
||||
onSelect = { selectedServer = fileServers[it] },
|
||||
modifier = Modifier.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)).weight(1f),
|
||||
modifier =
|
||||
Modifier
|
||||
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
|
||||
.weight(1f),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1718,7 +1823,10 @@ fun ImageVideoDescription(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
SettingSwitchItem(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
checked = sensitiveContent,
|
||||
onCheckedChange = { sensitiveContent = it },
|
||||
title = R.string.add_sensitive_content_label,
|
||||
@@ -1729,12 +1837,16 @@ fun ImageVideoDescription(
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier =
|
||||
Modifier.fillMaxWidth().windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)),
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.content_description)) },
|
||||
modifier =
|
||||
Modifier.fillMaxWidth().windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)),
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)),
|
||||
value = message,
|
||||
onValueChange = { message = it },
|
||||
placeholder = {
|
||||
@@ -1751,7 +1863,10 @@ fun ImageVideoDescription(
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 10.dp),
|
||||
onClick = { onAdd(message, selectedServer, sensitiveContent) },
|
||||
shape = QuoteBorder,
|
||||
colors =
|
||||
@@ -1767,7 +1882,10 @@ fun ImageVideoDescription(
|
||||
|
||||
@Composable
|
||||
fun SettingSwitchItem(
|
||||
modifier: Modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
modifier: Modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
title: Int,
|
||||
|
||||
@@ -71,7 +71,6 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URLEncoder
|
||||
|
||||
enum class UserSuggestionAnchor {
|
||||
MAIN_MESSAGE,
|
||||
@@ -89,6 +88,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||
|
||||
var pTags by mutableStateOf<List<User>?>(null)
|
||||
var eTags by mutableStateOf<List<Note>?>(null)
|
||||
var imetaTags = mutableStateListOf<Array<String>>()
|
||||
|
||||
var nip94attachments by mutableStateOf<List<FileHeaderEvent>>(emptyList())
|
||||
var nip95attachments by
|
||||
@@ -266,44 +266,46 @@ open class NewPostViewModel() : ViewModel() {
|
||||
|
||||
val urls = findURLs(tagger.message)
|
||||
val usedAttachments = nip94attachments.filter { it.urls().intersect(urls.toSet()).isNotEmpty() }
|
||||
usedAttachments.forEach { account?.sendHeader(it, relayList, {}) }
|
||||
// Doesn't send as nip94 yet because we don't know if it makes sense.
|
||||
// usedAttachments.forEach { account?.sendHeader(it, relayList, {}) }
|
||||
|
||||
if (originalNote?.channelHex() != null) {
|
||||
if (originalNote is AddressableEvent && originalNote?.address() != null) {
|
||||
account?.sendLiveMessage(
|
||||
tagger.message,
|
||||
originalNote?.address()!!,
|
||||
tagger.eTags,
|
||||
tagger.pTags,
|
||||
zapReceiver,
|
||||
wantsToMarkAsSensitive,
|
||||
localZapRaiserAmount,
|
||||
geoHash,
|
||||
message = tagger.message,
|
||||
toChannel = originalNote?.address()!!,
|
||||
replyTo = tagger.eTags,
|
||||
mentions = tagger.pTags,
|
||||
zapReceiver = zapReceiver,
|
||||
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = localZapRaiserAmount,
|
||||
geohash = geoHash,
|
||||
nip94attachments = usedAttachments,
|
||||
)
|
||||
} else {
|
||||
account?.sendChannelMessage(
|
||||
tagger.message,
|
||||
tagger.channelHex!!,
|
||||
tagger.eTags,
|
||||
tagger.pTags,
|
||||
zapReceiver,
|
||||
wantsToMarkAsSensitive,
|
||||
localZapRaiserAmount,
|
||||
geoHash,
|
||||
message = tagger.message,
|
||||
toChannel = tagger.channelHex!!,
|
||||
replyTo = tagger.eTags,
|
||||
mentions = tagger.pTags,
|
||||
zapReceiver = zapReceiver,
|
||||
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = localZapRaiserAmount,
|
||||
geohash = geoHash,
|
||||
nip94attachments = usedAttachments,
|
||||
)
|
||||
}
|
||||
} else if (originalNote?.event is PrivateDmEvent) {
|
||||
account?.sendPrivateMessage(
|
||||
tagger.message,
|
||||
originalNote!!.author!!,
|
||||
originalNote!!,
|
||||
tagger.pTags,
|
||||
zapReceiver,
|
||||
wantsToMarkAsSensitive,
|
||||
localZapRaiserAmount,
|
||||
geoHash,
|
||||
message = tagger.message,
|
||||
toUser = originalNote!!.author!!,
|
||||
replyingTo = originalNote!!,
|
||||
mentions = tagger.pTags,
|
||||
zapReceiver = zapReceiver,
|
||||
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = localZapRaiserAmount,
|
||||
geohash = geoHash,
|
||||
nip94attachments = usedAttachments,
|
||||
)
|
||||
} else if (originalNote?.event is ChatMessageEvent) {
|
||||
val receivers =
|
||||
@@ -324,6 +326,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||
zapReceiver = zapReceiver,
|
||||
zapRaiserAmount = localZapRaiserAmount,
|
||||
geohash = geoHash,
|
||||
nip94attachments = usedAttachments,
|
||||
)
|
||||
} else if (!dmUsers.isNullOrEmpty()) {
|
||||
if (nip24 || dmUsers.size > 1) {
|
||||
@@ -337,6 +340,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||
zapReceiver = zapReceiver,
|
||||
zapRaiserAmount = localZapRaiserAmount,
|
||||
geohash = geoHash,
|
||||
nip94attachments = usedAttachments,
|
||||
)
|
||||
} else {
|
||||
account?.sendPrivateMessage(
|
||||
@@ -348,6 +352,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||
zapReceiver = zapReceiver,
|
||||
zapRaiserAmount = localZapRaiserAmount,
|
||||
geohash = geoHash,
|
||||
nip94attachments = usedAttachments,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -461,21 +466,12 @@ open class NewPostViewModel() : ViewModel() {
|
||||
onProgress = {},
|
||||
)
|
||||
|
||||
if (!isPrivate) {
|
||||
createNIP94Record(
|
||||
uploadingResult = result,
|
||||
localContentType = contentType,
|
||||
alt = alt,
|
||||
sensitiveContent = sensitiveContent,
|
||||
)
|
||||
} else {
|
||||
noNIP94(
|
||||
uploadingResult = result,
|
||||
localContentType = contentType,
|
||||
alt = alt,
|
||||
sensitiveContent = sensitiveContent,
|
||||
)
|
||||
}
|
||||
createNIP94Record(
|
||||
uploadingResult = result,
|
||||
localContentType = contentType,
|
||||
alt = alt,
|
||||
sensitiveContent = sensitiveContent,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e(
|
||||
@@ -508,6 +504,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||
urlPreview = null
|
||||
isUploadingImage = false
|
||||
pTags = null
|
||||
imetaTags.clear()
|
||||
|
||||
wantsDirectMessage = false
|
||||
|
||||
@@ -703,21 +700,6 @@ open class NewPostViewModel() : ViewModel() {
|
||||
contentToAddUrl == null
|
||||
}
|
||||
|
||||
fun includePollHashtagInMessage(
|
||||
include: Boolean,
|
||||
hashtag: String,
|
||||
) {
|
||||
if (include) {
|
||||
updateMessage(TextFieldValue(message.text + " $hashtag"))
|
||||
} else {
|
||||
updateMessage(
|
||||
TextFieldValue(
|
||||
message.text.replace(" $hashtag", "").replace(hashtag, ""),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createNIP94Record(
|
||||
uploadingResult: Nip96Uploader.PartialEvent,
|
||||
localContentType: String?,
|
||||
@@ -751,25 +733,13 @@ open class NewPostViewModel() : ViewModel() {
|
||||
mimeType = remoteMimeType ?: localContentType,
|
||||
dimPrecomputed = dim,
|
||||
onReady = { header: FileHeader ->
|
||||
account?.createHeader(imageUrl, magnet, header, alt, sensitiveContent, originalHash) {
|
||||
event,
|
||||
->
|
||||
account?.createHeader(imageUrl, magnet, header, alt, sensitiveContent, originalHash) { event ->
|
||||
isUploadingImage = false
|
||||
nip94attachments = nip94attachments + event
|
||||
val contentWarning = if (sensitiveContent) "" else null
|
||||
|
||||
message =
|
||||
TextFieldValue(
|
||||
message.text +
|
||||
"\n" +
|
||||
addInlineMetadataAsNIP54(
|
||||
imageUrl,
|
||||
header.dim,
|
||||
header.mimeType,
|
||||
alt,
|
||||
header.blurHash,
|
||||
header.hash,
|
||||
contentWarning,
|
||||
),
|
||||
message.text + "\n" + imageUrl,
|
||||
)
|
||||
urlPreview = findUrlInMessage()
|
||||
}
|
||||
@@ -781,84 +751,6 @@ open class NewPostViewModel() : ViewModel() {
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun noNIP94(
|
||||
uploadingResult: Nip96Uploader.PartialEvent,
|
||||
localContentType: String?,
|
||||
alt: String?,
|
||||
sensitiveContent: Boolean,
|
||||
) {
|
||||
// Images don't seem to be ready immediately after upload
|
||||
val imageUrl = uploadingResult.tags?.firstOrNull { it.size > 1 && it[0] == "url" }?.get(1)
|
||||
val remoteMimeType =
|
||||
uploadingResult.tags?.firstOrNull { it.size > 1 && it[0] == "m" }?.get(1)?.ifBlank { null }
|
||||
val dim =
|
||||
uploadingResult.tags?.firstOrNull { it.size > 1 && it[0] == "dim" }?.get(1)?.ifBlank { null }
|
||||
|
||||
if (imageUrl.isNullOrBlank()) {
|
||||
Log.e("ImageDownload", "Couldn't download image from server")
|
||||
cancel()
|
||||
isUploadingImage = false
|
||||
viewModelScope.launch { imageUploadingError.emit("Server failed to return a url") }
|
||||
return
|
||||
}
|
||||
|
||||
FileHeader.prepare(
|
||||
fileUrl = imageUrl,
|
||||
mimeType = remoteMimeType ?: localContentType,
|
||||
dimPrecomputed = dim,
|
||||
onReady = { header: FileHeader ->
|
||||
isUploadingImage = false
|
||||
val contentWarning = if (sensitiveContent) "" else null
|
||||
message =
|
||||
TextFieldValue(
|
||||
message.text +
|
||||
"\n" +
|
||||
addInlineMetadataAsNIP54(
|
||||
imageUrl,
|
||||
header.dim,
|
||||
header.mimeType,
|
||||
alt,
|
||||
header.blurHash,
|
||||
header.hash,
|
||||
contentWarning,
|
||||
),
|
||||
)
|
||||
urlPreview = findUrlInMessage()
|
||||
},
|
||||
onError = {
|
||||
isUploadingImage = false
|
||||
viewModelScope.launch { imageUploadingError.emit("Failed to upload the image / video") }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun addInlineMetadataAsNIP54(
|
||||
imageUrl: String,
|
||||
dim: String?,
|
||||
m: String?,
|
||||
alt: String?,
|
||||
blurHash: String?,
|
||||
x: String?,
|
||||
sensitiveContent: String?,
|
||||
): String {
|
||||
val extension =
|
||||
listOfNotNull(
|
||||
m?.ifBlank { null }?.let { "m=${URLEncoder.encode(it, "utf-8")}" },
|
||||
dim?.ifBlank { null }?.let { "dim=${URLEncoder.encode(it, "utf-8")}" },
|
||||
alt?.ifBlank { null }?.let { "alt=${URLEncoder.encode(it, "utf-8")}" },
|
||||
blurHash?.ifBlank { null }?.let { "blurhash=${URLEncoder.encode(it, "utf-8")}" },
|
||||
x?.ifBlank { null }?.let { "x=${URLEncoder.encode(it, "utf-8")}" },
|
||||
sensitiveContent?.let { "content-warning=${URLEncoder.encode(it, "utf-8")}" },
|
||||
)
|
||||
.joinToString("&")
|
||||
|
||||
return if (imageUrl.contains("#")) {
|
||||
"$imageUrl&$extension"
|
||||
} else {
|
||||
"$imageUrl#$extension"
|
||||
}
|
||||
}
|
||||
|
||||
fun createNIP95Record(
|
||||
bytes: ByteArray,
|
||||
mimeType: String?,
|
||||
|
||||
+5
-13
@@ -56,11 +56,9 @@ fun buildAnnotatedStringWithUrlHighlighting(
|
||||
val builderAfter = StringBuilder() // important to correctly measure Tag start and end
|
||||
append(
|
||||
text
|
||||
.split('\n')
|
||||
.map { paragraph: String ->
|
||||
.split('\n').joinToString("\n") { paragraph: String ->
|
||||
paragraph
|
||||
.split(' ')
|
||||
.map { word: String ->
|
||||
.split(' ').joinToString(" ") { word: String ->
|
||||
try {
|
||||
if (word.startsWith("@npub") && word.length >= 64) {
|
||||
val keyB32 = word.substring(0, 64)
|
||||
@@ -121,9 +119,7 @@ fun buildAnnotatedStringWithUrlHighlighting(
|
||||
word
|
||||
}
|
||||
}
|
||||
.joinToString(" ")
|
||||
}
|
||||
.joinToString("\n"),
|
||||
},
|
||||
)
|
||||
|
||||
substitutions.forEach {
|
||||
@@ -143,9 +139,7 @@ fun buildAnnotatedStringWithUrlHighlighting(
|
||||
object : OffsetMapping {
|
||||
override fun originalToTransformed(offset: Int): Int {
|
||||
val inInsideRange =
|
||||
substitutions
|
||||
.filter { offset > it.original.start && offset < it.original.end }
|
||||
.firstOrNull()
|
||||
substitutions.firstOrNull { offset > it.original.start && offset < it.original.end }
|
||||
|
||||
if (inInsideRange != null) {
|
||||
val percentInRange =
|
||||
@@ -165,9 +159,7 @@ fun buildAnnotatedStringWithUrlHighlighting(
|
||||
|
||||
override fun transformedToOriginal(offset: Int): Int {
|
||||
val inInsideRange =
|
||||
substitutions
|
||||
.filter { offset > it.modified.start && offset < it.modified.end }
|
||||
.firstOrNull()
|
||||
substitutions.firstOrNull { offset > it.modified.start && offset < it.modified.end }
|
||||
|
||||
if (inInsideRange != null) {
|
||||
val percentInRange =
|
||||
|
||||
@@ -123,7 +123,7 @@ fun ChannelFabColumn(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Add,
|
||||
contentDescription = stringResource(R.string.messages_create_public_chat),
|
||||
contentDescription = stringResource(R.string.messages_create_public_private_chat_desription),
|
||||
modifier = Modifier.size(26.dp),
|
||||
tint = Color.White,
|
||||
)
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
@@ -72,7 +73,7 @@ fun NewCommunityNoteButton(
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_compose),
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.new_community_note),
|
||||
modifier = Modifier.size(26.dp),
|
||||
tint = Color.White,
|
||||
)
|
||||
|
||||
@@ -46,6 +46,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -135,7 +136,7 @@ fun NewImageButton(
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_compose),
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.new_short),
|
||||
modifier = Modifier.size(26.dp),
|
||||
tint = Color.White,
|
||||
)
|
||||
@@ -153,7 +154,10 @@ private fun ShowProgress(postViewModel: NewMediaModel) {
|
||||
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
||||
)
|
||||
.value,
|
||||
modifier = Size55Modifier.clip(CircleShape).background(MaterialTheme.colorScheme.background),
|
||||
modifier =
|
||||
Size55Modifier
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
strokeWidth = 5.dp,
|
||||
)
|
||||
postViewModel.uploadingDescription.value?.let {
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
@@ -58,7 +59,7 @@ fun NewNoteButton(
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_compose),
|
||||
null,
|
||||
contentDescription = stringResource(R.string.new_post),
|
||||
modifier = Modifier.size(26.dp),
|
||||
tint = Color.White,
|
||||
)
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
@@ -298,27 +297,24 @@ fun CreateClickableText(
|
||||
suffix: String?,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
overrideColor: Color? = null,
|
||||
fontWeight: FontWeight = FontWeight.Normal,
|
||||
fontWeight: FontWeight? = null,
|
||||
route: String,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
val currentStyle = LocalTextStyle.current
|
||||
val primaryColor = MaterialTheme.colorScheme.primary
|
||||
val onBackgroundColor = MaterialTheme.colorScheme.onBackground
|
||||
|
||||
val clickablePartStyle =
|
||||
remember(primaryColor, overrideColor) {
|
||||
currentStyle
|
||||
.copy(color = overrideColor ?: primaryColor, fontWeight = fontWeight)
|
||||
.toSpanStyle()
|
||||
}
|
||||
SpanStyle(
|
||||
color = overrideColor ?: primaryColor,
|
||||
fontWeight = fontWeight,
|
||||
)
|
||||
|
||||
val nonClickablePartStyle =
|
||||
remember(onBackgroundColor, overrideColor) {
|
||||
currentStyle
|
||||
.copy(color = overrideColor ?: onBackgroundColor, fontWeight = fontWeight)
|
||||
.toSpanStyle()
|
||||
}
|
||||
SpanStyle(
|
||||
color = overrideColor ?: onBackgroundColor,
|
||||
fontWeight = fontWeight,
|
||||
)
|
||||
|
||||
val text =
|
||||
remember(clickablePartStyle, nonClickablePartStyle, clickablePart, suffix) {
|
||||
@@ -333,11 +329,45 @@ fun CreateClickableText(
|
||||
ClickableText(
|
||||
text = text,
|
||||
maxLines = maxLines,
|
||||
style = currentStyle,
|
||||
onClick = { nav(route) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ClickableText(
|
||||
text: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
softWrap: Boolean = true,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
onClick: (Int) -> Unit,
|
||||
) {
|
||||
val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
|
||||
val pressIndicator =
|
||||
Modifier.pointerInput(onClick) {
|
||||
detectTapGestures { pos ->
|
||||
layoutResult.value?.let { layoutResult ->
|
||||
onClick(layoutResult.getOffsetForPosition(pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier.then(pressIndicator),
|
||||
style = style,
|
||||
softWrap = softWrap,
|
||||
overflow = overflow,
|
||||
maxLines = maxLines,
|
||||
onTextLayout = {
|
||||
layoutResult.value = it
|
||||
onTextLayout(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CreateTextWithEmoji(
|
||||
text: String,
|
||||
|
||||
+11
-39
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -44,6 +43,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.ExpandableTextParser
|
||||
import com.vitorpamplona.amethyst.ui.note.getGradient
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
@@ -51,9 +51,6 @@ import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
|
||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||
|
||||
const val SHORT_TEXT_LENGTH = 350
|
||||
const val SHORTEN_AFTER_LINES = 10
|
||||
|
||||
@Composable
|
||||
fun ExpandableRichTextViewer(
|
||||
content: String,
|
||||
@@ -66,30 +63,7 @@ fun ExpandableRichTextViewer(
|
||||
) {
|
||||
var showFullText by remember { mutableStateOf(false) }
|
||||
|
||||
val whereToCut =
|
||||
remember(content) {
|
||||
// Cuts the text in the first space or new line after SHORT_TEXT_LENGTH characters
|
||||
val firstSpaceAfterCut =
|
||||
content.indexOf(' ', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
|
||||
val firstNewLineAfterCut =
|
||||
content.indexOf('\n', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
|
||||
|
||||
// or after SHORTEN_AFTER_LINES lines
|
||||
val numberOfLines = content.count { it == '\n' }
|
||||
|
||||
var charactersInLines = minOf(firstSpaceAfterCut, firstNewLineAfterCut)
|
||||
|
||||
if (numberOfLines > SHORTEN_AFTER_LINES) {
|
||||
val shortContent = content.lines().take(SHORTEN_AFTER_LINES)
|
||||
charactersInLines = 0
|
||||
for (line in shortContent) {
|
||||
// +1 because new line character is omitted from .lines
|
||||
charactersInLines += (line.length + 1)
|
||||
}
|
||||
}
|
||||
|
||||
minOf(firstSpaceAfterCut, firstNewLineAfterCut, charactersInLines)
|
||||
}
|
||||
val whereToCut = remember(content) { ExpandableTextParser.computeWhereToCutIfPostIsTooLong(content) }
|
||||
|
||||
val text by
|
||||
remember(content) {
|
||||
@@ -103,17 +77,15 @@ fun ExpandableRichTextViewer(
|
||||
}
|
||||
|
||||
Box {
|
||||
Crossfade(text, label = "ExpandableRichTextViewer") {
|
||||
RichTextViewer(
|
||||
it,
|
||||
canPreview,
|
||||
modifier.align(Alignment.TopStart),
|
||||
tags,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
RichTextViewer(
|
||||
text,
|
||||
canPreview,
|
||||
modifier.align(Alignment.TopStart),
|
||||
tags,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
|
||||
if (content.length > whereToCut && !showFullText) {
|
||||
Row(
|
||||
|
||||
@@ -28,11 +28,10 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
@@ -49,7 +48,6 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalFontFamilyResolver
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
@@ -62,8 +60,10 @@ import androidx.compose.ui.text.TextMeasurer
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import com.halilibo.richtext.markdown.Markdown
|
||||
import com.halilibo.richtext.markdown.MarkdownParseOptions
|
||||
@@ -100,9 +100,9 @@ import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.MarkdownTextStyle
|
||||
import com.vitorpamplona.amethyst.ui.theme.innerPostModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.markdownStyle
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
import com.vitorpamplona.amethyst.ui.uriToRoute
|
||||
import com.vitorpamplona.quartz.encoders.Nip19
|
||||
import com.vitorpamplona.quartz.events.EmptyTagList
|
||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -174,7 +174,73 @@ fun RichTextViewer(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Preview()
|
||||
@Composable
|
||||
fun RenderRegularPreview() {
|
||||
val backgroundColor = MaterialTheme.colorScheme.background
|
||||
val backgroundColorState =
|
||||
remember {
|
||||
mutableStateOf(backgroundColor)
|
||||
}
|
||||
|
||||
val nav: (String) -> Unit = {}
|
||||
|
||||
Column(modifier = Modifier.padding(10.dp)) {
|
||||
RenderRegular(
|
||||
"nostr:npub1e0z776cpe0gllgktjk54fuzv8pdfxmq6smsmh8xd7t8s7n474n9smk0txy but i'm Monthly funding" +
|
||||
" 7 other humans vitor@vitorpamplona.com at the moment so spread #test a bit thin, but won't always be the case.",
|
||||
EmptyTagList,
|
||||
) { word, state ->
|
||||
when (word) {
|
||||
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
||||
// is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, accountViewModel)
|
||||
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
|
||||
is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
|
||||
is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
|
||||
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
|
||||
is EmailSegment -> ClickableEmail(word.segmentText)
|
||||
is PhoneSegment -> ClickablePhone(word.segmentText)
|
||||
is BechSegment -> {
|
||||
CreateClickableText(
|
||||
word.segmentText.substring(0, 10),
|
||||
"",
|
||||
1,
|
||||
route = "",
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
is HashTagSegment -> HashTag(word, nav)
|
||||
// is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
|
||||
// is HashIndexEventSegment -> TagLink(word, true, backgroundColorState, accountViewModel, nav)
|
||||
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
|
||||
is RegularTextSegment -> Text(word.segmentText)
|
||||
}
|
||||
}
|
||||
|
||||
RenderRegular(
|
||||
"#Amethyst v0.84.1: ncryptsec support (NIP-49)",
|
||||
EmptyTagList,
|
||||
) { word, state ->
|
||||
when (word) {
|
||||
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
||||
// is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, accountViewModel)
|
||||
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
|
||||
is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
|
||||
is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
|
||||
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
|
||||
is EmailSegment -> ClickableEmail(word.segmentText)
|
||||
is PhoneSegment -> ClickablePhone(word.segmentText)
|
||||
// is BechSegment -> BechLink(word.segmentText, true, backgroundColor, accountViewModel, nav)
|
||||
is HashTagSegment -> HashTag(word, nav)
|
||||
// is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
|
||||
// is HashIndexEventSegment -> TagLink(word, true, backgroundColorState, accountViewModel, nav)
|
||||
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
|
||||
is RegularTextSegment -> Text(word.segmentText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderRegular(
|
||||
content: String,
|
||||
@@ -184,75 +250,65 @@ private fun RenderRegular(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
val state by remember(content) { mutableStateOf(CachedRichTextParser.parseText(content, tags)) }
|
||||
RenderRegular(content, tags) { word, state ->
|
||||
if (canPreview) {
|
||||
RenderWordWithPreview(
|
||||
word,
|
||||
state,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
} else {
|
||||
RenderWordWithoutPreview(
|
||||
word,
|
||||
state,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun RenderRegular(
|
||||
content: String,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
wordRenderer: @Composable (Segment, RichTextViewerState) -> Unit,
|
||||
) {
|
||||
val state by remember(content, tags) { mutableStateOf(CachedRichTextParser.parseText(content, tags)) }
|
||||
|
||||
val spaceWidth = measureSpaceWidth(LocalTextStyle.current)
|
||||
|
||||
val currentTextStyle = LocalTextStyle.current
|
||||
val currentTextColor = LocalContentColor.current
|
||||
|
||||
val textStyle =
|
||||
remember(currentTextStyle, currentTextColor) {
|
||||
remember(currentTextStyle) {
|
||||
currentTextStyle.copy(
|
||||
lineHeight = 1.4.em,
|
||||
color = currentTextStyle.color.takeOrElse { currentTextColor },
|
||||
)
|
||||
}
|
||||
|
||||
val spaceWidth = measureSpaceWidth(textStyle)
|
||||
|
||||
Column {
|
||||
if (canPreview) {
|
||||
// FlowRow doesn't work well with paragraphs. So we need to split them
|
||||
state.paragraphs.forEach { paragraph ->
|
||||
val direction =
|
||||
// FlowRow doesn't work well with paragraphs. So we need to split them
|
||||
state.paragraphs.forEach { paragraph ->
|
||||
CompositionLocalProvider(
|
||||
LocalLayoutDirection provides
|
||||
if (paragraph.isRTL) {
|
||||
LayoutDirection.Rtl
|
||||
} else {
|
||||
LayoutDirection.Ltr
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalLayoutDirection provides direction) {
|
||||
FlowRow(
|
||||
modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start),
|
||||
horizontalArrangement = Arrangement.spacedBy(spaceWidth),
|
||||
) {
|
||||
paragraph.words.forEach { word ->
|
||||
RenderWordWithPreview(
|
||||
word,
|
||||
state,
|
||||
backgroundColor,
|
||||
textStyle,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// FlowRow doesn't work well with paragraphs. So we need to split them
|
||||
state.paragraphs.forEach { paragraph ->
|
||||
val direction =
|
||||
if (paragraph.isRTL) {
|
||||
LayoutDirection.Rtl
|
||||
} else {
|
||||
LayoutDirection.Ltr
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalLayoutDirection provides direction) {
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(spaceWidth),
|
||||
modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start),
|
||||
) {
|
||||
paragraph.words.forEach { word ->
|
||||
RenderWordWithoutPreview(
|
||||
word,
|
||||
state,
|
||||
backgroundColor,
|
||||
textStyle,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
},
|
||||
LocalTextStyle provides textStyle,
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start),
|
||||
horizontalArrangement = Arrangement.spacedBy(spaceWidth),
|
||||
) {
|
||||
paragraph.words.forEach { word ->
|
||||
wordRenderer(word, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,7 +337,6 @@ private fun RenderWordWithoutPreview(
|
||||
word: Segment,
|
||||
state: RichTextViewerState,
|
||||
backgroundColor: MutableState<Color>,
|
||||
style: TextStyle,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
@@ -291,10 +346,10 @@ private fun RenderWordWithoutPreview(
|
||||
is LinkSegment -> ClickableUrl(word.segmentText, word.segmentText)
|
||||
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
|
||||
// Don't offer to pay invoices
|
||||
is InvoiceSegment -> NormalWord(word.segmentText, style)
|
||||
is InvoiceSegment -> Text(word.segmentText)
|
||||
// Don't offer to withdraw
|
||||
is WithdrawSegment -> NormalWord(word.segmentText, style)
|
||||
is CashuSegment -> NormalWord(word.segmentText, style)
|
||||
is WithdrawSegment -> Text(word.segmentText)
|
||||
is CashuSegment -> Text(word.segmentText)
|
||||
is EmailSegment -> ClickableEmail(word.segmentText)
|
||||
is PhoneSegment -> ClickablePhone(word.segmentText)
|
||||
is BechSegment -> BechLink(word.segmentText, false, backgroundColor, accountViewModel, nav)
|
||||
@@ -302,7 +357,7 @@ private fun RenderWordWithoutPreview(
|
||||
is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
|
||||
is HashIndexEventSegment -> TagLink(word, false, backgroundColor, accountViewModel, nav)
|
||||
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
|
||||
is RegularTextSegment -> NormalWord(word.segmentText, style)
|
||||
is RegularTextSegment -> Text(word.segmentText)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +366,6 @@ private fun RenderWordWithPreview(
|
||||
word: Segment,
|
||||
state: RichTextViewerState,
|
||||
backgroundColor: MutableState<Color>,
|
||||
style: TextStyle,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
@@ -329,7 +383,7 @@ private fun RenderWordWithPreview(
|
||||
is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
|
||||
is HashIndexEventSegment -> TagLink(word, true, backgroundColor, accountViewModel, nav)
|
||||
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
|
||||
is RegularTextSegment -> NormalWord(word.segmentText, style)
|
||||
is RegularTextSegment -> Text(word.segmentText)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,23 +401,7 @@ private fun ZoomableContentView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NormalWord(
|
||||
word: String,
|
||||
style: TextStyle,
|
||||
) {
|
||||
BasicText(
|
||||
text = word,
|
||||
style = style,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NoProtocolUrlRenderer(word: SchemelessUrlSegment) {
|
||||
RenderUrl(word)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderUrl(segment: SchemelessUrlSegment) {
|
||||
private fun NoProtocolUrlRenderer(segment: SchemelessUrlSegment) {
|
||||
ClickableUrl(segment.url, "https://${segment.url}")
|
||||
segment.extras?.let { it1 -> Text(it1) }
|
||||
}
|
||||
@@ -416,8 +454,8 @@ private fun RenderContentAsMarkdown(
|
||||
onMediaCompose = { title, destination ->
|
||||
ZoomableContentView(
|
||||
content =
|
||||
remember(destination) {
|
||||
RichTextParser().parseMediaUrl(destination) ?: ZoomableUrlImage(url = destination)
|
||||
remember(destination, tags) {
|
||||
RichTextParser().parseMediaUrl(destination, tags ?: EmptyTagList) ?: ZoomableUrlImage(url = destination)
|
||||
},
|
||||
roundedCorner = true,
|
||||
accountViewModel = accountViewModel,
|
||||
@@ -606,7 +644,7 @@ private fun DisplayFullNote(
|
||||
NoteCompose(
|
||||
baseNote = it,
|
||||
accountViewModel = accountViewModel,
|
||||
modifier = MaterialTheme.colorScheme.replyModifier,
|
||||
modifier = MaterialTheme.colorScheme.innerPostModifier,
|
||||
parentBackgroundColor = backgroundColor,
|
||||
isQuotedNote = true,
|
||||
nav = nav,
|
||||
@@ -623,14 +661,6 @@ private fun DisplayFullNote(
|
||||
|
||||
@Composable
|
||||
fun HashTag(
|
||||
word: HashTagSegment,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
RenderHashtag(word, nav)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderHashtag(
|
||||
segment: HashTagSegment,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
@@ -657,7 +687,7 @@ private fun RenderHashtag(
|
||||
}
|
||||
}
|
||||
|
||||
segment.extras?.ifBlank { "" }?.let { withStyle(regularText) { append(it) } }
|
||||
segment.extras?.let { withStyle(regularText) { append(it) } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ fun RobohashFallbackAsyncImage(
|
||||
|
||||
Image(
|
||||
painter = base64Painter,
|
||||
contentDescription = null,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
alignment = alignment,
|
||||
contentScale = contentScale,
|
||||
|
||||
@@ -20,9 +20,11 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.dal
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
class HiddenAccountsFeedFilter(val account: Account) : FeedFilter<User>() {
|
||||
override fun feedKey(): String {
|
||||
@@ -34,7 +36,15 @@ class HiddenAccountsFeedFilter(val account: Account) : FeedFilter<User>() {
|
||||
}
|
||||
|
||||
override fun feed(): List<User> {
|
||||
return account.flowHiddenUsers.value.hiddenUsers.map { LocalCache.getOrCreateUser(it) }
|
||||
return account.flowHiddenUsers.value.hiddenUsers.reversed().mapNotNull {
|
||||
try {
|
||||
LocalCache.getOrCreateUser(it)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("HiddenAccountsFeedFilter", "Failed to parse key $it")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
@@ -180,7 +181,7 @@ private fun NotifiableIcon(
|
||||
Box(route.notifSize) {
|
||||
Icon(
|
||||
painter = painterResource(id = route.icon),
|
||||
contentDescription = null,
|
||||
contentDescription = stringResource(route.contentDescriptor),
|
||||
modifier = route.iconSize,
|
||||
tint = if (selected) MaterialTheme.colorScheme.primary else Color.Unspecified,
|
||||
)
|
||||
@@ -216,7 +217,12 @@ private fun NotificationDotIcon(modifier: Modifier) {
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = Font12SP,
|
||||
modifier = remember { Modifier.wrapContentHeight().align(Alignment.TopEnd) },
|
||||
modifier =
|
||||
remember {
|
||||
Modifier
|
||||
.wrapContentHeight()
|
||||
.align(Alignment.TopEnd)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import androidx.core.util.Consumer
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||
import com.vitorpamplona.amethyst.ui.note.UserReactionsViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
|
||||
@@ -74,6 +75,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.VideoScreen
|
||||
import com.vitorpamplona.amethyst.ui.uriToRoute
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URLDecoder
|
||||
|
||||
@Composable
|
||||
fun AppNavigation(
|
||||
@@ -288,9 +290,13 @@ fun AppNavigation(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
val decodedMessage =
|
||||
it.arguments?.getString("message")?.let {
|
||||
URLDecoder.decode(it, "utf-8")
|
||||
}
|
||||
ChatroomScreen(
|
||||
roomId = it.arguments?.getString("id"),
|
||||
draftMessage = it.arguments?.getString("message"),
|
||||
draftMessage = decodedMessage,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
@@ -354,31 +360,60 @@ fun AppNavigation(
|
||||
}
|
||||
|
||||
val activity = LocalContext.current.getActivity()
|
||||
var actionableNextPage by remember {
|
||||
mutableStateOf(uriToRoute(activity.intent?.data?.toString()?.ifBlank { null }))
|
||||
|
||||
var currentIntentNextPage by remember {
|
||||
mutableStateOf(activity.intent?.data?.toString()?.ifBlank { null })
|
||||
}
|
||||
actionableNextPage?.let {
|
||||
LaunchedEffect(it) {
|
||||
navController.navigate(it) {
|
||||
popUpTo(Route.Home.route)
|
||||
launchSingleTop = true
|
||||
}
|
||||
|
||||
currentIntentNextPage?.let { intentNextPage ->
|
||||
var actionableNextPage by remember {
|
||||
mutableStateOf(uriToRoute(intentNextPage))
|
||||
}
|
||||
|
||||
LaunchedEffect(intentNextPage) {
|
||||
if (actionableNextPage != null) {
|
||||
actionableNextPage?.let {
|
||||
navController.navigate(it) {
|
||||
popUpTo(Route.Home.route)
|
||||
launchSingleTop = true
|
||||
}
|
||||
actionableNextPage = null
|
||||
}
|
||||
} else {
|
||||
accountViewModel.toast(
|
||||
R.string.invalid_nip19_uri,
|
||||
R.string.invalid_nip19_uri_description,
|
||||
intentNextPage,
|
||||
)
|
||||
}
|
||||
|
||||
currentIntentNextPage = null
|
||||
}
|
||||
actionableNextPage = null
|
||||
}
|
||||
|
||||
DisposableEffect(activity) {
|
||||
val consumer =
|
||||
Consumer<Intent> { intent ->
|
||||
val uri = intent?.data?.toString()
|
||||
val newPage = uriToRoute(uri)
|
||||
if (!uri.isNullOrBlank()) {
|
||||
val newPage = uriToRoute(uri)
|
||||
|
||||
newPage?.let { route ->
|
||||
val currentRoute = getRouteWithArguments(navController)
|
||||
if (!isSameRoute(currentRoute, route)) {
|
||||
navController.navigate(route) {
|
||||
popUpTo(Route.Home.route)
|
||||
launchSingleTop = true
|
||||
if (newPage != null) {
|
||||
val currentRoute = getRouteWithArguments(navController)
|
||||
if (!isSameRoute(currentRoute, newPage)) {
|
||||
navController.navigate(newPage) {
|
||||
popUpTo(Route.Home.route)
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scope.launch {
|
||||
delay(1000)
|
||||
accountViewModel.toast(
|
||||
R.string.invalid_nip19_uri,
|
||||
R.string.invalid_nip19_uri_description,
|
||||
uri,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,23 +542,17 @@ private fun LoggedInUserPictureDrawer(
|
||||
) {
|
||||
val profilePicture by
|
||||
accountViewModel.account.userProfile().live().profilePictureChanges.observeAsState()
|
||||
val pubkeyHex = remember { accountViewModel.userProfile().pubkeyHex }
|
||||
|
||||
val automaticallyShowProfilePicture =
|
||||
remember {
|
||||
accountViewModel.settings.showProfilePictures.value
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
) {
|
||||
RobohashFallbackAsyncImage(
|
||||
robot = pubkeyHex,
|
||||
robot = accountViewModel.userProfile().pubkeyHex,
|
||||
model = profilePicture,
|
||||
contentDescription = stringResource(id = R.string.profile_image),
|
||||
contentDescription = stringResource(id = R.string.your_profile_image),
|
||||
modifier = HeaderPictureModifier,
|
||||
contentScale = ContentScale.Crop,
|
||||
loadProfilePicture = automaticallyShowProfilePicture,
|
||||
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ fun routeToMessage(
|
||||
val withKey = ChatroomKey(persistentSetOf(user))
|
||||
accountViewModel.account.userProfile().createChatroom(withKey)
|
||||
return if (draftMessage != null) {
|
||||
"Room/${withKey.hashCode()}?message=$draftMessage"
|
||||
val encodedMessage = URLEncoder.encode(draftMessage, "utf-8")
|
||||
"Room/${withKey.hashCode()}?message=$encodedMessage"
|
||||
} else {
|
||||
"Room/${withKey.hashCode()}"
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ sealed class Route(
|
||||
val icon: Int,
|
||||
val notifSize: Modifier = Modifier.size(Size23dp),
|
||||
val iconSize: Modifier = Modifier.size(Size20dp),
|
||||
val contentDescriptor: Int = R.string.route,
|
||||
val hasNewItems: (Account, Set<com.vitorpamplona.amethyst.model.Note>) -> Boolean = { _, _ ->
|
||||
false
|
||||
},
|
||||
@@ -80,6 +81,7 @@ sealed class Route(
|
||||
},
|
||||
)
|
||||
.toImmutableList(),
|
||||
contentDescriptor = R.string.route_home,
|
||||
hasNewItems = { accountViewModel, newNotes ->
|
||||
HomeLatestItem.hasNewItems(accountViewModel, newNotes)
|
||||
},
|
||||
@@ -89,27 +91,31 @@ sealed class Route(
|
||||
Route(
|
||||
route = "Global",
|
||||
icon = R.drawable.ic_globe,
|
||||
contentDescriptor = R.string.route_global,
|
||||
)
|
||||
|
||||
object Search :
|
||||
Route(
|
||||
route = "Search",
|
||||
icon = R.drawable.ic_search,
|
||||
contentDescriptor = R.string.route_search,
|
||||
)
|
||||
|
||||
object Video :
|
||||
Route(
|
||||
route = "Video",
|
||||
icon = R.drawable.ic_video,
|
||||
contentDescriptor = R.string.route_video,
|
||||
)
|
||||
|
||||
object Discover :
|
||||
Route(
|
||||
route = "Discover",
|
||||
icon = R.drawable.ic_sensors,
|
||||
hasNewItems = { accountViewModel, newNotes ->
|
||||
DiscoverLatestItem.hasNewItems(accountViewModel, newNotes)
|
||||
},
|
||||
// hasNewItems = { accountViewModel, newNotes ->
|
||||
// DiscoverLatestItem.hasNewItems(accountViewModel, newNotes)
|
||||
// },
|
||||
contentDescriptor = R.string.route_discover,
|
||||
)
|
||||
|
||||
object Notification :
|
||||
@@ -119,6 +125,7 @@ sealed class Route(
|
||||
hasNewItems = { accountViewModel, newNotes ->
|
||||
NotificationLatestItem.hasNewItems(accountViewModel, newNotes)
|
||||
},
|
||||
contentDescriptor = R.string.route_notifications,
|
||||
)
|
||||
|
||||
object Message :
|
||||
@@ -128,18 +135,21 @@ sealed class Route(
|
||||
hasNewItems = { accountViewModel, newNotes ->
|
||||
MessagesLatestItem.hasNewItems(accountViewModel, newNotes)
|
||||
},
|
||||
contentDescriptor = R.string.route_messages,
|
||||
)
|
||||
|
||||
object BlockedUsers :
|
||||
Route(
|
||||
route = "BlockedUsers",
|
||||
icon = R.drawable.ic_security,
|
||||
contentDescriptor = R.string.route_security_filters,
|
||||
)
|
||||
|
||||
object Bookmarks :
|
||||
Route(
|
||||
route = "Bookmarks",
|
||||
icon = R.drawable.ic_bookmarks,
|
||||
contentDescriptor = R.string.route_home,
|
||||
)
|
||||
|
||||
object Profile :
|
||||
|
||||
@@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.screen.BadgeCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -162,7 +163,7 @@ fun BadgeCompose(
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
null,
|
||||
modifier = Modifier.size(15.dp),
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -78,6 +79,7 @@ import com.vitorpamplona.amethyst.ui.theme.ChatPaddingInnerQuoteModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.ChatPaddingModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChat
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||
@@ -491,8 +493,14 @@ private fun RenderReply(
|
||||
onWantsToReply: (Note) -> Unit,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val replyTo by remember { derivedStateOf { note.replyTo?.lastOrNull() } }
|
||||
replyTo?.let { note ->
|
||||
val replyTo =
|
||||
produceState(initialValue = note.replyTo?.lastOrNull()) {
|
||||
accountViewModel.unwrapIfNeeded(value) {
|
||||
value = it
|
||||
}
|
||||
}
|
||||
|
||||
replyTo.value?.let { note ->
|
||||
ChatroomMessageCompose(
|
||||
note,
|
||||
null,
|
||||
@@ -619,19 +627,18 @@ private fun RenderRegularTextNote(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
val tags = remember(note.event) { note.event?.tags()?.toImmutableListOfLists() ?: EmptyTagList }
|
||||
val modifier = remember { Modifier.padding(top = 5.dp) }
|
||||
|
||||
LoadDecryptedContentOrNull(note = note, accountViewModel = accountViewModel) { eventContent ->
|
||||
if (eventContent != null) {
|
||||
SensitivityWarning(
|
||||
note = note,
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
val tags = remember(note.event) { note.event?.tags()?.toImmutableListOfLists() ?: EmptyTagList }
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = eventContent!!,
|
||||
content = eventContent,
|
||||
canPreview = canPreview,
|
||||
modifier = modifier,
|
||||
modifier = HalfTopPadding,
|
||||
tags = tags,
|
||||
backgroundColor = backgroundBubbleColor,
|
||||
accountViewModel = accountViewModel,
|
||||
@@ -642,8 +649,8 @@ private fun RenderRegularTextNote(
|
||||
TranslatableRichTextViewer(
|
||||
content = stringResource(id = R.string.could_not_decrypt_the_message),
|
||||
canPreview = true,
|
||||
modifier = modifier,
|
||||
tags = tags,
|
||||
modifier = HalfTopPadding,
|
||||
tags = EmptyTagList,
|
||||
backgroundColor = backgroundBubbleColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
|
||||
@@ -152,7 +152,7 @@ fun LikeIcon(
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_like),
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.like_description),
|
||||
modifier = iconSizeModifier,
|
||||
tint = grayTint,
|
||||
)
|
||||
@@ -165,7 +165,7 @@ fun RepostedIcon(
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_retweeted),
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.boost_or_quote_description),
|
||||
modifier = modifier,
|
||||
tint = tint,
|
||||
)
|
||||
@@ -198,10 +198,11 @@ fun ZappedIcon(modifier: Modifier) {
|
||||
fun ZapIcon(
|
||||
modifier: Modifier,
|
||||
tint: Color = Color.Unspecified,
|
||||
contentDescriptor: Int = R.string.zap_description,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Bolt,
|
||||
contentDescription = stringResource(R.string.zaps),
|
||||
contentDescription = stringResource(contentDescriptor),
|
||||
tint = tint,
|
||||
modifier = modifier,
|
||||
)
|
||||
@@ -244,20 +245,26 @@ fun OpenInNewIcon(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExpandLessIcon(modifier: Modifier) {
|
||||
fun ExpandLessIcon(
|
||||
modifier: Modifier,
|
||||
contentDescriptor: Int,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ExpandLess,
|
||||
null,
|
||||
contentDescription = stringResource(id = contentDescriptor),
|
||||
modifier = modifier,
|
||||
tint = MaterialTheme.colorScheme.subtleButton,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExpandMoreIcon(modifier: Modifier) {
|
||||
fun ExpandMoreIcon(
|
||||
modifier: Modifier,
|
||||
contentDescriptor: Int,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ExpandMore,
|
||||
null,
|
||||
contentDescription = stringResource(id = contentDescriptor),
|
||||
modifier = modifier,
|
||||
tint = MaterialTheme.colorScheme.subtleButton,
|
||||
)
|
||||
@@ -270,7 +277,7 @@ fun CommentIcon(
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_comment),
|
||||
contentDescription = null,
|
||||
contentDescription = stringResource(id = R.string.reply_description),
|
||||
modifier = iconSizeModifier,
|
||||
tint = tint,
|
||||
)
|
||||
@@ -293,7 +300,7 @@ fun ViewCountIcon(
|
||||
fun PollIcon() {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_poll),
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.poll),
|
||||
modifier = Size20Modifier,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
@@ -303,7 +310,7 @@ fun PollIcon() {
|
||||
fun RegularPostIcon() {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_lists),
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.disable_poll),
|
||||
modifier = Size20Modifier,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
@@ -435,10 +442,10 @@ fun LinkIcon(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VerticalDotsIcon() {
|
||||
fun VerticalDotsIcon(contentDescriptor: Int? = null) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
null,
|
||||
contentDescription = contentDescriptor?.let { stringResource(id = it) },
|
||||
modifier = Size18Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
|
||||
@@ -542,11 +542,6 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture(
|
||||
author: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val automaticallyShowProfilePicture =
|
||||
remember {
|
||||
accountViewModel.settings.showProfilePictures.value
|
||||
}
|
||||
|
||||
WatchUserMetadata(author) { baseUserPicture ->
|
||||
// Crossfade(targetState = baseUserPicture) { userPicture ->
|
||||
RobohashFallbackAsyncImage(
|
||||
@@ -555,7 +550,7 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture(
|
||||
contentDescription = stringResource(id = R.string.profile_image),
|
||||
modifier = MaterialTheme.colorScheme.profile35dpModifier,
|
||||
contentScale = ContentScale.Crop,
|
||||
loadProfilePicture = automaticallyShowProfilePicture,
|
||||
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
||||
)
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -2658,7 +2658,7 @@ fun MoreOptionsButton(
|
||||
modifier = Size24Modifier,
|
||||
onClick = enablePopup,
|
||||
) {
|
||||
VerticalDotsIcon()
|
||||
VerticalDotsIcon(R.string.note_options)
|
||||
|
||||
NoteDropDownMenu(
|
||||
baseNote,
|
||||
|
||||
@@ -379,9 +379,9 @@ private fun RenderShowIndividualReactionsButton(wantsToSeeReactions: MutableStat
|
||||
label = "RenderShowIndividualReactionsButton",
|
||||
) {
|
||||
if (it) {
|
||||
ExpandLessIcon(modifier = Size22Modifier)
|
||||
ExpandLessIcon(modifier = Size22Modifier, R.string.close_all_reactions_to_this_post)
|
||||
} else {
|
||||
ExpandMoreIcon(modifier = Size22Modifier)
|
||||
ExpandMoreIcon(modifier = Size22Modifier, R.string.open_all_reactions_to_this_post)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
@@ -87,7 +89,7 @@ private fun ShowMoreRelaysButton(onClick: () -> Unit) {
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ExpandMore,
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.expand_relay_list),
|
||||
modifier = ShowMoreRelaysButtonIconModifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
|
||||
@@ -119,7 +119,7 @@ fun ChatRelayExpandButton(onClick: () -> Unit) {
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ChevronRight,
|
||||
null,
|
||||
contentDescription = stringResource(id = R.string.expand_relay_list),
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
@@ -156,7 +156,8 @@ fun RenderRelay(
|
||||
|
||||
val clickableModifier =
|
||||
remember(relay) {
|
||||
Modifier.padding(1.dp)
|
||||
Modifier
|
||||
.padding(1.dp)
|
||||
.size(Size15dp)
|
||||
.clickable(
|
||||
role = Role.Button,
|
||||
@@ -175,18 +176,21 @@ fun RenderRelay(
|
||||
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,
|
||||
@@ -223,7 +227,7 @@ fun RenderRelayIcon(
|
||||
RobohashFallbackAsyncImage(
|
||||
robot = displayUrl,
|
||||
model = iconUrl,
|
||||
contentDescription = stringResource(id = R.string.relay_icon),
|
||||
contentDescription = stringResource(id = R.string.relay_info, displayUrl),
|
||||
colorFilter = RelayIconFilter,
|
||||
modifier = iconModifier,
|
||||
loadProfilePicture = loadProfilePicture,
|
||||
|
||||
@@ -340,10 +340,11 @@ fun BaseUserPicture(
|
||||
val myIconSize by remember(size) { derivedStateOf { size.div(3.5f) } }
|
||||
|
||||
Box(outerModifier, contentAlignment = Alignment.TopEnd) {
|
||||
LoadUserProfilePicture(baseUser) { userProfilePicture ->
|
||||
LoadUserProfilePicture(baseUser) { userProfilePicture, userName ->
|
||||
InnerUserPicture(
|
||||
userHex = baseUser.pubkeyHex,
|
||||
userPicture = userProfilePicture,
|
||||
userName = userName,
|
||||
size = size,
|
||||
modifier = innerModifier,
|
||||
accountViewModel = accountViewModel,
|
||||
@@ -357,17 +358,18 @@ fun BaseUserPicture(
|
||||
@Composable
|
||||
fun LoadUserProfilePicture(
|
||||
baseUser: User,
|
||||
innerContent: @Composable (String?) -> Unit,
|
||||
innerContent: @Composable (String?, String?) -> Unit,
|
||||
) {
|
||||
val userProfile by baseUser.live().profilePictureChanges.observeAsState(baseUser.profilePicture())
|
||||
val userProfile by baseUser.live().userMetadataInfo.observeAsState(baseUser.info)
|
||||
|
||||
innerContent(userProfile)
|
||||
innerContent(userProfile?.profilePicture(), userProfile?.bestDisplayName() ?: userProfile?.bestDisplayName())
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InnerUserPicture(
|
||||
userHex: String,
|
||||
userPicture: String?,
|
||||
userName: String?,
|
||||
size: Dp,
|
||||
modifier: Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
@@ -386,7 +388,12 @@ fun InnerUserPicture(
|
||||
RobohashFallbackAsyncImage(
|
||||
robot = userHex,
|
||||
model = userPicture,
|
||||
contentDescription = stringResource(id = R.string.profile_image),
|
||||
contentDescription =
|
||||
if (userName != null) {
|
||||
stringResource(id = R.string.profile_image_of_user, userName)
|
||||
} else {
|
||||
stringResource(id = R.string.profile_image)
|
||||
},
|
||||
modifier = myImageModifier,
|
||||
contentScale = ContentScale.Crop,
|
||||
loadProfilePicture = automaticallyShowProfilePicture,
|
||||
|
||||
@@ -275,7 +275,7 @@ class UserReactionsViewModel(val account: Account) : ViewModel() {
|
||||
refreshChartModel()
|
||||
}
|
||||
|
||||
suspend fun addToStatsSuspend(newNotes: Set<Note>) {
|
||||
suspend fun addToStatsSuspend(newBlockNotes: Set<Set<Note>>) {
|
||||
checkNotInMainThread()
|
||||
|
||||
val currentUser = user.pubkeyHex
|
||||
@@ -287,39 +287,41 @@ class UserReactionsViewModel(val account: Account) : ViewModel() {
|
||||
val takenIntoAccount = this.takenIntoAccount.toMutableSet()
|
||||
var hasNewElements = false
|
||||
|
||||
newNotes.forEach {
|
||||
val noteEvent = it.event
|
||||
if (noteEvent != null && !takenIntoAccount.contains(noteEvent.id())) {
|
||||
if (noteEvent is ReactionEvent) {
|
||||
if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey != currentUser) {
|
||||
val netDate = formatDate(noteEvent.createdAt)
|
||||
reactions[netDate] = (reactions[netDate] ?: 0) + 1
|
||||
takenIntoAccount.add(noteEvent.id())
|
||||
hasNewElements = true
|
||||
}
|
||||
} else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) {
|
||||
if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey() != currentUser) {
|
||||
val netDate = formatDate(noteEvent.createdAt())
|
||||
boosts[netDate] = (boosts[netDate] ?: 0) + 1
|
||||
takenIntoAccount.add(noteEvent.id())
|
||||
hasNewElements = true
|
||||
}
|
||||
} else if (noteEvent is LnZapEvent) {
|
||||
if (
|
||||
noteEvent.isTaggedUser(currentUser)
|
||||
) { // && noteEvent.pubKey != currentUser User might be sending his own receipts
|
||||
val netDate = formatDate(noteEvent.createdAt)
|
||||
zaps[netDate] =
|
||||
(zaps[netDate] ?: BigDecimal.ZERO) + (noteEvent.amount ?: BigDecimal.ZERO)
|
||||
takenIntoAccount.add(noteEvent.id())
|
||||
hasNewElements = true
|
||||
}
|
||||
} else if (noteEvent is TextNoteEvent) {
|
||||
if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey != currentUser) {
|
||||
val netDate = formatDate(noteEvent.createdAt)
|
||||
replies[netDate] = (replies[netDate] ?: 0) + 1
|
||||
takenIntoAccount.add(noteEvent.id())
|
||||
hasNewElements = true
|
||||
newBlockNotes.forEach { newNotes ->
|
||||
newNotes.forEach {
|
||||
val noteEvent = it.event
|
||||
if (noteEvent != null && !takenIntoAccount.contains(noteEvent.id())) {
|
||||
if (noteEvent is ReactionEvent) {
|
||||
if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey != currentUser) {
|
||||
val netDate = formatDate(noteEvent.createdAt)
|
||||
reactions[netDate] = (reactions[netDate] ?: 0) + 1
|
||||
takenIntoAccount.add(noteEvent.id())
|
||||
hasNewElements = true
|
||||
}
|
||||
} else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) {
|
||||
if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey() != currentUser) {
|
||||
val netDate = formatDate(noteEvent.createdAt())
|
||||
boosts[netDate] = (boosts[netDate] ?: 0) + 1
|
||||
takenIntoAccount.add(noteEvent.id())
|
||||
hasNewElements = true
|
||||
}
|
||||
} else if (noteEvent is LnZapEvent) {
|
||||
if (
|
||||
noteEvent.isTaggedUser(currentUser)
|
||||
) { // && noteEvent.pubKey != currentUser User might be sending his own receipts
|
||||
val netDate = formatDate(noteEvent.createdAt)
|
||||
zaps[netDate] =
|
||||
(zaps[netDate] ?: BigDecimal.ZERO) + (noteEvent.amount ?: BigDecimal.ZERO)
|
||||
takenIntoAccount.add(noteEvent.id())
|
||||
hasNewElements = true
|
||||
}
|
||||
} else if (noteEvent is TextNoteEvent) {
|
||||
if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey != currentUser) {
|
||||
val netDate = formatDate(noteEvent.createdAt)
|
||||
replies[netDate] = (replies[netDate] ?: 0) + 1
|
||||
takenIntoAccount.add(noteEvent.id())
|
||||
hasNewElements = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,7 +424,7 @@ class UserReactionsViewModel(val account: Account) : ViewModel() {
|
||||
private val bundlerInsert = BundledInsert<Set<Note>>(250, Dispatchers.IO)
|
||||
|
||||
fun invalidateInsertData(newItems: Set<Note>) {
|
||||
bundlerInsert.invalidateList(newItems) { addToStatsSuspend(it.flatten().toSet()) }
|
||||
bundlerInsert.invalidateList(newItems) { addToStatsSuspend(it) }
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
||||
@@ -213,7 +213,10 @@ fun DrawPlayName(name: String) {
|
||||
@Composable
|
||||
fun DrawPlayNameIcon(onClick: () -> Unit) {
|
||||
IconButton(onClick = onClick, modifier = StdButtonSizeModifier) {
|
||||
PlayIcon(modifier = StdButtonSizeModifier, tint = MaterialTheme.colorScheme.placeholderText)
|
||||
PlayIcon(
|
||||
modifier = StdButtonSizeModifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,12 +105,7 @@ class ZapOptionstViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun value(): Long? {
|
||||
return try {
|
||||
customAmount.text.trim().toLongOrNull()
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
null
|
||||
}
|
||||
return customAmount.text.trim().toLongOrNull()
|
||||
}
|
||||
|
||||
fun cancel() {}
|
||||
@@ -285,7 +280,7 @@ fun ZapButton(
|
||||
onPost: () -> Unit,
|
||||
) {
|
||||
Button(
|
||||
onClick = { onPost() },
|
||||
onClick = { if (isActive) onPost() },
|
||||
shape = ButtonBorder,
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.ServiceManager
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.HttpClientManager
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.Nip19
|
||||
@@ -193,6 +194,42 @@ class AccountStateViewModel() : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun login(
|
||||
key: String,
|
||||
password: String,
|
||||
useProxy: Boolean,
|
||||
proxyPort: Int,
|
||||
loginWithExternalSigner: Boolean = false,
|
||||
packageName: String = "",
|
||||
onError: (String?) -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (key.startsWith("ncryptsec")) {
|
||||
val newKey =
|
||||
try {
|
||||
CryptoUtils.decryptNIP49(key, password)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
onError(e.message)
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (newKey == null) {
|
||||
onError("Could not decrypt key with provided password")
|
||||
Log.e("Login", "Could not decrypt ncryptsec")
|
||||
} else {
|
||||
loginSync(newKey, useProxy, proxyPort, loginWithExternalSigner, packageName) {
|
||||
onError(null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loginSync(key, useProxy, proxyPort, loginWithExternalSigner, packageName) {
|
||||
onError(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun login(
|
||||
key: String,
|
||||
useProxy: Boolean,
|
||||
@@ -202,13 +239,24 @@ class AccountStateViewModel() : ViewModel() {
|
||||
onError: () -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
loginAndStartUI(key, useProxy, proxyPort, loginWithExternalSigner, packageName)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("Login", "Could not sign in", e)
|
||||
onError()
|
||||
}
|
||||
loginSync(key, useProxy, proxyPort, loginWithExternalSigner, packageName, onError)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loginSync(
|
||||
key: String,
|
||||
useProxy: Boolean,
|
||||
proxyPort: Int,
|
||||
loginWithExternalSigner: Boolean = false,
|
||||
packageName: String = "",
|
||||
onError: () -> Unit,
|
||||
) {
|
||||
try {
|
||||
loginAndStartUI(key, useProxy, proxyPort, loginWithExternalSigner, packageName)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("Login", "Could not sign in", e)
|
||||
onError()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ 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 androidx.compose.ui.Modifier
|
||||
@@ -127,6 +128,8 @@ import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
@@ -284,26 +287,27 @@ fun Modifier.drawReplyLevel(
|
||||
color: Color,
|
||||
selected: Color,
|
||||
): Modifier =
|
||||
this.drawBehind {
|
||||
val paddingDp = 2
|
||||
val strokeWidthDp = 2
|
||||
val levelWidthDp = strokeWidthDp + 1
|
||||
this
|
||||
.drawBehind {
|
||||
val paddingDp = 2
|
||||
val strokeWidthDp = 2
|
||||
val levelWidthDp = strokeWidthDp + 1
|
||||
|
||||
val padding = paddingDp.dp.toPx()
|
||||
val strokeWidth = strokeWidthDp.dp.toPx()
|
||||
val levelWidth = levelWidthDp.dp.toPx()
|
||||
val padding = paddingDp.dp.toPx()
|
||||
val strokeWidth = strokeWidthDp.dp.toPx()
|
||||
val levelWidth = levelWidthDp.dp.toPx()
|
||||
|
||||
repeat(level) {
|
||||
this.drawLine(
|
||||
if (it == level - 1) selected else color,
|
||||
Offset(padding + it * levelWidth, 0f),
|
||||
Offset(padding + it * levelWidth, size.height),
|
||||
strokeWidth = strokeWidth,
|
||||
)
|
||||
repeat(level) {
|
||||
this.drawLine(
|
||||
if (it == level - 1) selected else color,
|
||||
Offset(padding + it * levelWidth, 0f),
|
||||
Offset(padding + it * levelWidth, size.height),
|
||||
strokeWidth = strokeWidth,
|
||||
)
|
||||
}
|
||||
|
||||
return@drawBehind
|
||||
}
|
||||
|
||||
return@drawBehind
|
||||
}
|
||||
.padding(start = (2 + (level * 3)).dp)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@@ -353,11 +357,14 @@ fun NoteMaster(
|
||||
)
|
||||
} else {
|
||||
Column(
|
||||
modifier.fillMaxWidth().padding(top = 10.dp),
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.padding(start = 12.dp, end = 12.dp)
|
||||
Modifier
|
||||
.padding(start = 12.dp, end = 12.dp)
|
||||
.clickable(onClick = { note.author?.let { nav("User/${it.pubkeyHex}") } }),
|
||||
) {
|
||||
NoteAuthorPicture(
|
||||
@@ -391,13 +398,13 @@ fun NoteMaster(
|
||||
)
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.then(Modifier.size(24.dp)),
|
||||
modifier = Size24Modifier,
|
||||
onClick = enablePopup,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
null,
|
||||
modifier = Modifier.size(15.dp),
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
|
||||
@@ -443,7 +450,8 @@ fun NoteMaster(
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.padding(horizontal = 12.dp)
|
||||
Modifier
|
||||
.padding(horizontal = 12.dp)
|
||||
.combinedClickable(
|
||||
onClick = {},
|
||||
onLongClick = { popupExpanded = true },
|
||||
@@ -669,7 +677,9 @@ private fun RenderClassifiedsReaderForThread(
|
||||
}
|
||||
|
||||
Row(
|
||||
Modifier.padding(start = 20.dp, end = 20.dp, bottom = 5.dp, top = 15.dp).fillMaxWidth(),
|
||||
Modifier
|
||||
.padding(start = 20.dp, end = 20.dp, bottom = 5.dp, top = 15.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
@@ -686,7 +696,9 @@ private fun RenderClassifiedsReaderForThread(
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.padding(start = 10.dp, end = 10.dp, bottom = 5.dp, top = 5.dp).fillMaxWidth(),
|
||||
Modifier
|
||||
.padding(start = 10.dp, end = 10.dp, bottom = 5.dp, top = 5.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
@@ -703,6 +715,7 @@ private fun RenderClassifiedsReaderForThread(
|
||||
}
|
||||
|
||||
var message by remember { mutableStateOf(TextFieldValue(msg)) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
TextField(
|
||||
value = message,
|
||||
@@ -725,7 +738,11 @@ private fun RenderClassifiedsReaderForThread(
|
||||
isActive = message.text.isNotBlank(),
|
||||
modifier = EditFieldTrailingIconModifier,
|
||||
) {
|
||||
note.author?.let { nav(routeToMessage(it, msg, accountViewModel)) }
|
||||
scope.launch(Dispatchers.IO) {
|
||||
note.author?.let {
|
||||
nav(routeToMessage(it, note.toNostrUri() + "\n\n" + msg, accountViewModel))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
colors =
|
||||
|
||||
+234
-6
@@ -36,23 +36,48 @@ 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.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Key
|
||||
import androidx.compose.material.icons.outlined.Visibility
|
||||
import androidx.compose.material.icons.outlined.VisibilityOff
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
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 androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.autofill.AutofillNode
|
||||
import androidx.compose.ui.autofill.AutofillType
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.layout.boundsInWindow
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.ClipboardManager
|
||||
import androidx.compose.ui.platform.LocalAutofill
|
||||
import androidx.compose.ui.platform.LocalAutofillTree
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
@@ -67,10 +92,14 @@ import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.note.authenticate
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.encoders.toNsec
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun AccountBackupDialog(
|
||||
accountViewModel: AccountViewModel,
|
||||
@@ -80,12 +109,23 @@ fun AccountBackupDialog(
|
||||
onDismissRequest = onClose,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
) {
|
||||
Surface(modifier = Modifier.fillMaxSize()) {
|
||||
Surface(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.background).fillMaxSize(),
|
||||
modifier =
|
||||
Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(10.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
@@ -93,7 +133,10 @@ fun AccountBackupDialog(
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 30.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 30.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
@@ -101,13 +144,105 @@ fun AccountBackupDialog(
|
||||
style = RichTextStyle().resolveDefaults(),
|
||||
) {
|
||||
Markdown(
|
||||
content = stringResource(R.string.account_backup_tips_md),
|
||||
content = stringResource(R.string.account_backup_tips2_md),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(30.dp))
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
NSecCopyButton(accountViewModel)
|
||||
|
||||
Spacer(modifier = Modifier.height(30.dp))
|
||||
|
||||
Material3RichText(
|
||||
style = RichTextStyle().resolveDefaults(),
|
||||
) {
|
||||
Markdown(
|
||||
content = stringResource(R.string.account_backup_tips3_md),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
val password = remember { mutableStateOf(TextFieldValue("")) }
|
||||
var errorMessage by remember { mutableStateOf("") }
|
||||
var showCharsPassword by remember { mutableStateOf(false) }
|
||||
|
||||
val autofillNode =
|
||||
AutofillNode(
|
||||
autofillTypes = listOf(AutofillType.Password),
|
||||
onFill = { password.value = TextFieldValue(it) },
|
||||
)
|
||||
val autofill = LocalAutofill.current
|
||||
LocalAutofillTree.current += autofillNode
|
||||
|
||||
OutlinedTextField(
|
||||
modifier =
|
||||
Modifier
|
||||
.onGloballyPositioned { coordinates ->
|
||||
autofillNode.boundingBox = coordinates.boundsInWindow()
|
||||
}
|
||||
.onFocusChanged { focusState ->
|
||||
autofill?.run {
|
||||
if (focusState.isFocused) {
|
||||
requestAutofillForNode(autofillNode)
|
||||
} else {
|
||||
cancelAutofillForNode(autofillNode)
|
||||
}
|
||||
}
|
||||
},
|
||||
value = password.value,
|
||||
onValueChange = {
|
||||
password.value = it
|
||||
if (errorMessage.isNotEmpty()) {
|
||||
errorMessage = ""
|
||||
}
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
autoCorrect = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Go,
|
||||
),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.ncryptsec_password),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
Row {
|
||||
IconButton(onClick = { showCharsPassword = !showCharsPassword }) {
|
||||
Icon(
|
||||
imageVector =
|
||||
if (showCharsPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
|
||||
contentDescription =
|
||||
if (showCharsPassword) {
|
||||
stringResource(R.string.show_password)
|
||||
} else {
|
||||
stringResource(
|
||||
R.string.hide_password,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
visualTransformation =
|
||||
if (showCharsPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
)
|
||||
|
||||
if (errorMessage.isNotBlank()) {
|
||||
Text(
|
||||
text = errorMessage,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
EncryptNSecCopyButton(accountViewModel, password)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,6 +295,56 @@ private fun NSecCopyButton(accountViewModel: AccountViewModel) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EncryptNSecCopyButton(
|
||||
accountViewModel: AccountViewModel,
|
||||
password: MutableState<TextFieldValue>,
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val keyguardLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
result: ActivityResult ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
encryptCopyNSec(password, context, scope, accountViewModel, clipboardManager)
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = {
|
||||
authenticate(
|
||||
title = context.getString(R.string.copy_my_secret_key),
|
||||
context = context,
|
||||
keyguardLauncher = keyguardLauncher,
|
||||
onApproved = { encryptCopyNSec(password, context, scope, accountViewModel, clipboardManager) },
|
||||
onError = { title, message -> accountViewModel.toast(title, message) },
|
||||
)
|
||||
},
|
||||
shape = ButtonBorder,
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
contentPadding = ButtonPadding,
|
||||
enabled = password.value.text.isNotBlank(),
|
||||
) {
|
||||
Icon(
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
imageVector = Icons.Default.Key,
|
||||
contentDescription =
|
||||
stringResource(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup),
|
||||
modifier = Modifier.padding(end = 5.dp),
|
||||
)
|
||||
Text(
|
||||
stringResource(id = R.string.encrypt_and_copy_my_secret_key),
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getFragmentActivity(): FragmentActivity? {
|
||||
var currentContext = this
|
||||
while (currentContext is ContextWrapper) {
|
||||
@@ -189,3 +374,46 @@ private fun copyNSec(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun encryptCopyNSec(
|
||||
password: MutableState<TextFieldValue>,
|
||||
context: Context,
|
||||
scope: CoroutineScope,
|
||||
accountViewModel: AccountViewModel,
|
||||
clipboardManager: ClipboardManager,
|
||||
) {
|
||||
if (password.value.text.isBlank()) {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.password_is_required),
|
||||
Toast.LENGTH_SHORT,
|
||||
)
|
||||
.show()
|
||||
}
|
||||
} else {
|
||||
accountViewModel.account.keyPair.privKey?.let {
|
||||
val key = CryptoUtils.encryptNIP49(it.toHexKey(), password.value.text)
|
||||
if (key != null) {
|
||||
clipboardManager.setText(AnnotatedString(key))
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.secret_key_copied_to_clipboard),
|
||||
Toast.LENGTH_SHORT,
|
||||
)
|
||||
.show()
|
||||
}
|
||||
} else {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.failed_to_encrypt_key),
|
||||
Toast.LENGTH_SHORT,
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
-1
@@ -73,6 +73,7 @@ import com.vitorpamplona.quartz.encoders.Nip19
|
||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||
import com.vitorpamplona.quartz.events.ChatroomKeyable
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.EventInterface
|
||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||
@@ -104,7 +105,11 @@ import kotlin.time.measureTimedValue
|
||||
|
||||
@Immutable class StringToastMsg(val title: String, val msg: String) : ToastMsg()
|
||||
|
||||
@Immutable class ResourceToastMsg(val titleResId: Int, val resourceId: Int) : ToastMsg()
|
||||
@Immutable class ResourceToastMsg(
|
||||
val titleResId: Int,
|
||||
val resourceId: Int,
|
||||
val params: Array<out String>? = null,
|
||||
) : ToastMsg()
|
||||
|
||||
@Stable
|
||||
class AccountViewModel(val account: Account, val settings: SettingsState) : ViewModel(), Dao {
|
||||
@@ -140,6 +145,14 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
|
||||
viewModelScope.launch { toasts.emit(ResourceToastMsg(titleResId, resourceId)) }
|
||||
}
|
||||
|
||||
fun toast(
|
||||
titleResId: Int,
|
||||
resourceId: Int,
|
||||
vararg params: String,
|
||||
) {
|
||||
viewModelScope.launch { toasts.emit(ResourceToastMsg(titleResId, resourceId, params)) }
|
||||
}
|
||||
|
||||
fun isWriteable(): Boolean {
|
||||
return account.isWriteable()
|
||||
}
|
||||
@@ -1175,6 +1188,55 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun unwrapIfNeeded(
|
||||
event: EventInterface?,
|
||||
onReady: (Note) -> Unit,
|
||||
) {
|
||||
when (event) {
|
||||
is GiftWrapEvent -> {
|
||||
event.cachedGift(account.signer) {
|
||||
val existingNote = LocalCache.getNoteIfExists(it.id)
|
||||
if (existingNote != null) {
|
||||
unwrapIfNeeded(existingNote.event, onReady)
|
||||
} else {
|
||||
LocalCache.verifyAndConsume(it, null)
|
||||
unwrapIfNeeded(it, onReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
is SealedGossipEvent -> {
|
||||
event.cachedGossip(account.signer) {
|
||||
val existingNote = LocalCache.getNoteIfExists(it.id)
|
||||
if (existingNote != null) {
|
||||
unwrapIfNeeded(existingNote.event, onReady)
|
||||
} else {
|
||||
// this is not verifiable
|
||||
LocalCache.justConsume(it, null)
|
||||
unwrapIfNeeded(it, onReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
event?.id()?.let {
|
||||
LocalCache.getNoteIfExists(it)?.let {
|
||||
onReady(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unwrapIfNeeded(
|
||||
note: Note?,
|
||||
onReady: (Note) -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
unwrapIfNeeded(note?.event) {
|
||||
onReady(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HasNotificationDot(bottomNavigationItems: ImmutableList<Route>) {
|
||||
|
||||
@@ -38,10 +38,12 @@ import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Cancel
|
||||
import androidx.compose.material.icons.filled.EditNote
|
||||
@@ -112,6 +114,7 @@ import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.ServerOption
|
||||
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
@@ -156,6 +159,7 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.events.EmptyTagList
|
||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_LIVE
|
||||
import com.vitorpamplona.quartz.events.Participant
|
||||
import com.vitorpamplona.quartz.events.findURLs
|
||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -268,7 +272,13 @@ fun ChannelScreen(
|
||||
val replyTo = remember { mutableStateOf<Note?>(null) }
|
||||
|
||||
Column(
|
||||
modifier = remember { Modifier.fillMaxHeight().padding(vertical = 0.dp).weight(1f, true) },
|
||||
modifier =
|
||||
remember {
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(vertical = 0.dp)
|
||||
.weight(1f, true)
|
||||
},
|
||||
) {
|
||||
if (channel is LiveActivitiesChannel) {
|
||||
ShowVideoStreaming(channel, accountViewModel)
|
||||
@@ -300,6 +310,10 @@ fun ChannelScreen(
|
||||
dao = accountViewModel,
|
||||
)
|
||||
tagger.run()
|
||||
|
||||
val urls = findURLs(tagger.message)
|
||||
val usedAttachments = newPostModel.nip94attachments.filter { it.urls().intersect(urls.toSet()).isNotEmpty() }
|
||||
|
||||
if (channel is PublicChatChannel) {
|
||||
accountViewModel.account.sendChannelMessage(
|
||||
message = tagger.message,
|
||||
@@ -307,6 +321,7 @@ fun ChannelScreen(
|
||||
replyTo = tagger.eTags,
|
||||
mentions = tagger.pTags,
|
||||
wantsToMarkAsSensitive = false,
|
||||
nip94attachments = usedAttachments,
|
||||
)
|
||||
} else if (channel is LiveActivitiesChannel) {
|
||||
accountViewModel.account.sendLiveMessage(
|
||||
@@ -315,6 +330,7 @@ fun ChannelScreen(
|
||||
replyTo = tagger.eTags,
|
||||
mentions = tagger.pTags,
|
||||
wantsToMarkAsSensitive = false,
|
||||
nip94attachments = usedAttachments,
|
||||
)
|
||||
}
|
||||
newPostModel.message = TextFieldValue("")
|
||||
@@ -333,7 +349,11 @@ fun DisplayReplyingToNote(
|
||||
onCancel: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
Modifier.padding(horizontal = 10.dp).animateContentSize(),
|
||||
Modifier
|
||||
.padding(horizontal = 10.dp)
|
||||
.heightIn(max = 100.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.animateContentSize(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (replyingNote != null) {
|
||||
@@ -356,7 +376,10 @@ fun DisplayReplyingToNote(
|
||||
Icon(
|
||||
imageVector = Icons.Default.Cancel,
|
||||
null,
|
||||
modifier = Modifier.padding(end = 5.dp).size(30.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(end = 5.dp)
|
||||
.size(30.dp),
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
@@ -372,13 +395,13 @@ fun EditFieldRow(
|
||||
accountViewModel: AccountViewModel,
|
||||
onSendNewMessage: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
Column(
|
||||
modifier = EditFieldModifier,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
ShowUserSuggestionList(channelScreenModel, accountViewModel)
|
||||
|
||||
MyTextField(
|
||||
value = channelScreenModel.message,
|
||||
onValueChange = { channelScreenModel.updateMessage(it) },
|
||||
@@ -387,7 +410,7 @@ fun EditFieldRow(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
shape = EditFieldBorder,
|
||||
modifier = Modifier.weight(1f, true),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.reply_here),
|
||||
@@ -424,6 +447,7 @@ fun EditFieldRow(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
visualTransformation = UrlUserTagTransformation(MaterialTheme.colorScheme.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -718,7 +742,11 @@ fun ShortChannelHeader(
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 10.dp).height(35.dp).weight(1f),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = 10.dp)
|
||||
.height(35.dp)
|
||||
.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
@@ -731,7 +759,10 @@ fun ShortChannelHeader(
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.height(Size35dp).padding(start = 5.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.height(Size35dp)
|
||||
.padding(start = 5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (channel is PublicChatChannel) {
|
||||
@@ -1021,7 +1052,12 @@ fun LiveFlag() {
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
modifier =
|
||||
remember { Modifier.clip(SmallBorder).background(Color.Red).padding(horizontal = 5.dp) },
|
||||
remember {
|
||||
Modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Red)
|
||||
.padding(horizontal = 5.dp)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1032,7 +1068,12 @@ fun EndedFlag() {
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier =
|
||||
remember { Modifier.clip(SmallBorder).background(Color.Black).padding(horizontal = 5.dp) },
|
||||
remember {
|
||||
Modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Black)
|
||||
.padding(horizontal = 5.dp)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1043,7 +1084,12 @@ fun OfflineFlag() {
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier =
|
||||
remember { Modifier.clip(SmallBorder).background(Color.Black).padding(horizontal = 5.dp) },
|
||||
remember {
|
||||
Modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Black)
|
||||
.padding(horizontal = 5.dp)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1057,7 +1103,12 @@ fun ScheduledFlag(starts: Long?) {
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier =
|
||||
remember { Modifier.clip(SmallBorder).background(Color.Black).padding(horizontal = 5.dp) },
|
||||
remember {
|
||||
Modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Black)
|
||||
.padding(horizontal = 5.dp)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1066,7 +1117,10 @@ private fun NoteCopyButton(note: Channel) {
|
||||
var popupExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp).width(50.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = { popupExpanded = true },
|
||||
shape = ButtonBorder,
|
||||
colors =
|
||||
@@ -1109,7 +1163,10 @@ private fun EditButton(
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp).width(50.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = { wantsToPost = true },
|
||||
contentPadding = ZeroPadding,
|
||||
) {
|
||||
|
||||
@@ -25,11 +25,13 @@ import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
@@ -92,6 +94,7 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.ServerOption
|
||||
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayRoomSubject
|
||||
@@ -115,6 +118,7 @@ import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||
import com.vitorpamplona.quartz.events.findURLs
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -324,6 +328,9 @@ fun ChatroomScreen(
|
||||
// LAST ROW
|
||||
PrivateMessageEditFieldRow(newPostModel, isPrivate = true, accountViewModel) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val urls = findURLs(newPostModel.message.text)
|
||||
val usedAttachments = newPostModel.nip94attachments.filter { it.urls().intersect(urls.toSet()).isNotEmpty() }
|
||||
|
||||
if (newPostModel.nip24 || room.users.size > 1 || replyTo.value?.event is ChatMessageEvent) {
|
||||
accountViewModel.account.sendNIP24PrivateMessage(
|
||||
message = newPostModel.message.text,
|
||||
@@ -331,6 +338,7 @@ fun ChatroomScreen(
|
||||
replyingTo = replyTo.value,
|
||||
mentions = null,
|
||||
wantsToMarkAsSensitive = false,
|
||||
nip94attachments = usedAttachments,
|
||||
)
|
||||
} else {
|
||||
accountViewModel.account.sendPrivateMessage(
|
||||
@@ -339,6 +347,7 @@ fun ChatroomScreen(
|
||||
replyingTo = replyTo.value,
|
||||
mentions = null,
|
||||
wantsToMarkAsSensitive = false,
|
||||
nip94attachments = usedAttachments,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -357,13 +366,13 @@ fun PrivateMessageEditFieldRow(
|
||||
accountViewModel: AccountViewModel,
|
||||
onSendNewMessage: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
Column(
|
||||
modifier = EditFieldModifier,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
ShowUserSuggestionList(channelScreenModel, accountViewModel)
|
||||
|
||||
MyTextField(
|
||||
value = channelScreenModel.message,
|
||||
onValueChange = { channelScreenModel.updateMessage(it) },
|
||||
@@ -372,7 +381,7 @@ fun PrivateMessageEditFieldRow(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
shape = EditFieldBorder,
|
||||
modifier = Modifier.weight(1f, true),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.reply_here),
|
||||
@@ -455,10 +464,36 @@ fun PrivateMessageEditFieldRow(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
visualTransformation = UrlUserTagTransformation(MaterialTheme.colorScheme.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShowUserSuggestionList(
|
||||
channelScreenModel: NewPostViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
modifier: Modifier = Modifier.heightIn(0.dp, 200.dp),
|
||||
) {
|
||||
val userSuggestions = channelScreenModel.userSuggestions
|
||||
if (userSuggestions.isNotEmpty()) {
|
||||
LazyColumn(
|
||||
contentPadding =
|
||||
PaddingValues(
|
||||
top = 10.dp,
|
||||
),
|
||||
modifier = modifier,
|
||||
) {
|
||||
itemsIndexed(
|
||||
userSuggestions,
|
||||
key = { _, item -> item.pubkeyHex },
|
||||
) { _, item ->
|
||||
UserLine(item, accountViewModel) { channelScreenModel.autocompleteWithUser(item) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NewFeatureNIP24AlertDialog(
|
||||
accountViewModel: AccountViewModel,
|
||||
|
||||
@@ -270,6 +270,7 @@ fun MainScreen(
|
||||
videoFeedViewModel.sendToTop()
|
||||
}
|
||||
Route.Discover.base -> {
|
||||
discoverMarketplaceFeedViewModel.sendToTop()
|
||||
discoveryLiveFeedViewModel.sendToTop()
|
||||
discoveryCommunityFeedViewModel.sendToTop()
|
||||
discoveryChatFeedViewModel.sendToTop()
|
||||
@@ -472,12 +473,22 @@ private fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
|
||||
openDialogMsg.value?.let { obj ->
|
||||
when (obj) {
|
||||
is ResourceToastMsg ->
|
||||
InformationDialog(
|
||||
context.getString(obj.titleResId),
|
||||
context.getString(obj.resourceId),
|
||||
) {
|
||||
accountViewModel.clearToasts()
|
||||
if (obj.params != null) {
|
||||
InformationDialog(
|
||||
context.getString(obj.titleResId),
|
||||
context.getString(obj.resourceId, *obj.params),
|
||||
) {
|
||||
accountViewModel.clearToasts()
|
||||
}
|
||||
} else {
|
||||
InformationDialog(
|
||||
context.getString(obj.titleResId),
|
||||
context.getString(obj.resourceId),
|
||||
) {
|
||||
accountViewModel.clearToasts()
|
||||
}
|
||||
}
|
||||
|
||||
is StringToastMsg ->
|
||||
InformationDialog(
|
||||
obj.title,
|
||||
|
||||
@@ -155,7 +155,9 @@ import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size25Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
@@ -390,7 +392,10 @@ private fun RenderSurface(
|
||||
var tabsSize by remember { mutableStateOf(IntSize.Zero) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().onSizeChanged { columnSize = it },
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.onSizeChanged { columnSize = it },
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val scrollState = rememberScrollState()
|
||||
@@ -403,7 +408,8 @@ private fun RenderSurface(
|
||||
Box(
|
||||
modifier =
|
||||
remember {
|
||||
Modifier.verticalScroll(scrollState)
|
||||
Modifier
|
||||
.verticalScroll(scrollState)
|
||||
.nestedScroll(
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(
|
||||
@@ -726,10 +732,17 @@ private fun ProfileHeader(
|
||||
DrawBanner(baseUser, accountViewModel)
|
||||
|
||||
Box(
|
||||
modifier = Modifier.padding(horizontal = 10.dp).size(40.dp).align(Alignment.TopEnd),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 10.dp)
|
||||
.size(40.dp)
|
||||
.align(Alignment.TopEnd),
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.size(30.dp).align(Alignment.Center),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(30.dp)
|
||||
.align(Alignment.Center),
|
||||
onClick = { popupExpanded = true },
|
||||
shape = ButtonBorder,
|
||||
colors =
|
||||
@@ -754,7 +767,11 @@ private fun ProfileHeader(
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp).padding(top = 75.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 10.dp)
|
||||
.padding(top = 75.dp),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
@@ -789,7 +806,10 @@ private fun ProfileHeader(
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.height(Size35dp).padding(bottom = 3.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.height(Size35dp)
|
||||
.padding(bottom = 3.dp),
|
||||
) {
|
||||
MessageButton(baseUser, accountViewModel, nav)
|
||||
|
||||
@@ -969,13 +989,16 @@ private fun DrawAdditionalInfo(
|
||||
)
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.size(25.dp).padding(start = 5.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(25.dp)
|
||||
.padding(start = 5.dp),
|
||||
onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ContentCopy,
|
||||
null,
|
||||
modifier = Modifier.size(15.dp),
|
||||
contentDescription = stringResource(id = R.string.copy_npub_to_clipboard),
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
@@ -995,13 +1018,13 @@ private fun DrawAdditionalInfo(
|
||||
}
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.size(25.dp),
|
||||
modifier = Size25Modifier,
|
||||
onClick = { dialogOpen = true },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_qrcode),
|
||||
null,
|
||||
modifier = Modifier.size(15.dp),
|
||||
contentDescription = stringResource(id = R.string.show_npub_as_a_qr_code),
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
@@ -1059,7 +1082,10 @@ private fun DrawAdditionalInfo(
|
||||
text = AnnotatedString(identity.identity),
|
||||
onClick = { runCatching { uri.openUri(identity.toProofUrl()) } },
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary),
|
||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp).weight(1f),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
||||
.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1131,7 +1157,10 @@ fun DisplayLNAddress(
|
||||
text = AnnotatedString(lud16),
|
||||
onClick = { zapExpanded = !zapExpanded },
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary),
|
||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp).weight(1f),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
||||
.weight(1f),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1223,12 +1252,21 @@ private fun WatchApp(
|
||||
|
||||
appLogo?.let {
|
||||
Box(
|
||||
remember { Modifier.size(Size35dp).clickable { nav("Note/${baseApp.idHex}") } },
|
||||
remember {
|
||||
Modifier
|
||||
.size(Size35dp)
|
||||
.clickable { nav("Note/${baseApp.idHex}") }
|
||||
},
|
||||
) {
|
||||
AsyncImage(
|
||||
model = appLogo,
|
||||
contentDescription = null,
|
||||
modifier = remember { Modifier.size(Size35dp).clip(shape = CircleShape) },
|
||||
modifier =
|
||||
remember {
|
||||
Modifier
|
||||
.size(Size35dp)
|
||||
.clip(shape = CircleShape)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1343,7 +1381,11 @@ fun BadgeThumb(
|
||||
onClick: ((String) -> Unit)? = null,
|
||||
) {
|
||||
Box(
|
||||
remember { Modifier.width(size).height(size) },
|
||||
remember {
|
||||
Modifier
|
||||
.width(size)
|
||||
.height(size)
|
||||
},
|
||||
) {
|
||||
WatchAndRenderBadgeImage(baseNote, loadProfilePicture, size, pictureModifier, onClick)
|
||||
}
|
||||
@@ -1373,7 +1415,13 @@ private fun WatchAndRenderBadgeImage(
|
||||
RobohashAsyncImage(
|
||||
robot = "authornotfound",
|
||||
contentDescription = stringResource(R.string.unknown_author),
|
||||
modifier = remember { pictureModifier.width(size).height(size).background(bgColor) },
|
||||
modifier =
|
||||
remember {
|
||||
pictureModifier
|
||||
.width(size)
|
||||
.height(size)
|
||||
.background(bgColor)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
RobohashFallbackAsyncImage(
|
||||
@@ -1418,7 +1466,8 @@ fun DrawBanner(
|
||||
contentDescription = stringResource(id = R.string.profile_image),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(125.dp)
|
||||
.combinedClickable(
|
||||
onClick = { zoomImageDialogOpen = true },
|
||||
@@ -1438,7 +1487,10 @@ fun DrawBanner(
|
||||
painter = painterResource(R.drawable.profile_banner),
|
||||
contentDescription = stringResource(id = R.string.profile_banner),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth().height(125.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(125.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1692,7 +1744,10 @@ private fun MessageButton(
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp).width(50.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) { accountViewModel.createChatRoomFor(user) { nav("Room/$it") } }
|
||||
},
|
||||
@@ -1727,7 +1782,10 @@ private fun InnerEditButtonPreview() {
|
||||
@Composable
|
||||
private fun InnerEditButton(onClick: () -> Unit) {
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp).width(50.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = onClick,
|
||||
contentPadding = ZeroPadding,
|
||||
) {
|
||||
|
||||
@@ -55,6 +55,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -90,10 +91,12 @@ import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.PackageUtils
|
||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
||||
import com.vitorpamplona.amethyst.ui.components.getActivity
|
||||
import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size40dp
|
||||
@@ -142,6 +145,16 @@ fun LoginPage(
|
||||
val scope = rememberCoroutineScope()
|
||||
var loginWithExternalSigner by remember { mutableStateOf(false) }
|
||||
|
||||
var processingLogin by remember { mutableStateOf(false) }
|
||||
|
||||
val password = remember { mutableStateOf(TextFieldValue("")) }
|
||||
val needsPassword =
|
||||
remember {
|
||||
derivedStateOf {
|
||||
key.value.text.startsWith("ncryptsec1")
|
||||
}
|
||||
}
|
||||
|
||||
if (loginWithExternalSigner) {
|
||||
val externalSignerLauncher = remember { ExternalSignerLauncher("", signerPackageName = "") }
|
||||
val id = remember { UUID.randomUUID().toString() }
|
||||
@@ -243,33 +256,47 @@ fun LoginPage(
|
||||
|
||||
Spacer(modifier = Modifier.height(40.dp))
|
||||
|
||||
var showPassword by remember { mutableStateOf(false) }
|
||||
var showCharsKey by remember { mutableStateOf(false) }
|
||||
var showCharsPassword by remember { mutableStateOf(false) }
|
||||
|
||||
val autofillNode =
|
||||
val autofillNodeKey =
|
||||
AutofillNode(
|
||||
autofillTypes = listOf(AutofillType.Password),
|
||||
onFill = { key.value = TextFieldValue(it) },
|
||||
)
|
||||
|
||||
val autofillNodePassword =
|
||||
AutofillNode(
|
||||
autofillTypes = listOf(AutofillType.Password),
|
||||
onFill = { key.value = TextFieldValue(it) },
|
||||
)
|
||||
|
||||
val autofill = LocalAutofill.current
|
||||
LocalAutofillTree.current += autofillNode
|
||||
LocalAutofillTree.current += autofillNodeKey
|
||||
LocalAutofillTree.current += autofillNodePassword
|
||||
|
||||
OutlinedTextField(
|
||||
modifier =
|
||||
Modifier
|
||||
.onGloballyPositioned { coordinates ->
|
||||
autofillNode.boundingBox = coordinates.boundsInWindow()
|
||||
autofillNodeKey.boundingBox = coordinates.boundsInWindow()
|
||||
}
|
||||
.onFocusChanged { focusState ->
|
||||
autofill?.run {
|
||||
if (focusState.isFocused) {
|
||||
requestAutofillForNode(autofillNode)
|
||||
requestAutofillForNode(autofillNodeKey)
|
||||
} else {
|
||||
cancelAutofillForNode(autofillNode)
|
||||
cancelAutofillForNode(autofillNodeKey)
|
||||
}
|
||||
}
|
||||
},
|
||||
value = key.value,
|
||||
onValueChange = { key.value = it },
|
||||
onValueChange = {
|
||||
key.value = it
|
||||
if (errorMessage.isNotEmpty()) {
|
||||
errorMessage = ""
|
||||
}
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
autoCorrect = false,
|
||||
@@ -284,12 +311,12 @@ fun LoginPage(
|
||||
},
|
||||
trailingIcon = {
|
||||
Row {
|
||||
IconButton(onClick = { showPassword = !showPassword }) {
|
||||
IconButton(onClick = { showCharsKey = !showCharsKey }) {
|
||||
Icon(
|
||||
imageVector =
|
||||
if (showPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
|
||||
if (showCharsKey) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
|
||||
contentDescription =
|
||||
if (showPassword) {
|
||||
if (showCharsKey) {
|
||||
stringResource(R.string.show_password)
|
||||
} else {
|
||||
stringResource(
|
||||
@@ -312,29 +339,42 @@ fun LoginPage(
|
||||
IconButton(onClick = { dialogOpen = true }) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_qrcode),
|
||||
null,
|
||||
contentDescription =
|
||||
stringResource(
|
||||
R.string.login_with_qr_code,
|
||||
),
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
},
|
||||
visualTransformation =
|
||||
if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
if (showCharsKey) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
keyboardActions =
|
||||
KeyboardActions(
|
||||
onGo = {
|
||||
if (!acceptedTerms.value) {
|
||||
termsAcceptanceIsRequired =
|
||||
context.getString(R.string.acceptance_of_terms_is_required)
|
||||
termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required)
|
||||
}
|
||||
|
||||
if (key.value.text.isBlank()) {
|
||||
errorMessage = context.getString(R.string.key_is_required)
|
||||
}
|
||||
|
||||
if (acceptedTerms.value && key.value.text.isNotBlank()) {
|
||||
accountStateViewModel.login(key.value.text, useProxy.value, proxyPort.value.toInt()) {
|
||||
errorMessage = context.getString(R.string.invalid_key)
|
||||
if (needsPassword.value && password.value.text.isBlank()) {
|
||||
errorMessage = context.getString(R.string.password_is_required)
|
||||
}
|
||||
|
||||
if (acceptedTerms.value && key.value.text.isNotBlank() && !(needsPassword.value && password.value.text.isBlank())) {
|
||||
processingLogin = true
|
||||
accountStateViewModel.login(key.value.text, password.value.text, useProxy.value, proxyPort.value.toInt()) {
|
||||
processingLogin = false
|
||||
errorMessage =
|
||||
if (it != null) {
|
||||
context.getString(R.string.invalid_key_with_message, it)
|
||||
} else {
|
||||
context.getString(R.string.invalid_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -350,39 +390,128 @@ fun LoginPage(
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
if (PackageUtils.isOrbotInstalled(context)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = useProxy.value,
|
||||
onCheckedChange = {
|
||||
if (it) {
|
||||
connectOrbotDialogOpen = true
|
||||
if (needsPassword.value) {
|
||||
OutlinedTextField(
|
||||
modifier =
|
||||
Modifier
|
||||
.onGloballyPositioned { coordinates ->
|
||||
autofillNodePassword.boundingBox = coordinates.boundsInWindow()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Text(stringResource(R.string.connect_via_tor))
|
||||
}
|
||||
|
||||
if (connectOrbotDialogOpen) {
|
||||
ConnectOrbotDialog(
|
||||
onClose = { connectOrbotDialogOpen = false },
|
||||
onPost = {
|
||||
connectOrbotDialogOpen = false
|
||||
useProxy.value = true
|
||||
},
|
||||
onError = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
it,
|
||||
Toast.LENGTH_LONG,
|
||||
.onFocusChanged { focusState ->
|
||||
autofill?.run {
|
||||
if (focusState.isFocused) {
|
||||
requestAutofillForNode(autofillNodePassword)
|
||||
} else {
|
||||
cancelAutofillForNode(autofillNodePassword)
|
||||
}
|
||||
}
|
||||
},
|
||||
value = password.value,
|
||||
onValueChange = {
|
||||
password.value = it
|
||||
if (errorMessage.isNotEmpty()) {
|
||||
errorMessage = ""
|
||||
}
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
autoCorrect = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Go,
|
||||
),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.ncryptsec_password),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
Row {
|
||||
IconButton(onClick = { showCharsPassword = !showCharsPassword }) {
|
||||
Icon(
|
||||
imageVector =
|
||||
if (showCharsPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
|
||||
contentDescription =
|
||||
if (showCharsPassword) {
|
||||
stringResource(R.string.show_password)
|
||||
} else {
|
||||
stringResource(
|
||||
R.string.hide_password,
|
||||
)
|
||||
},
|
||||
)
|
||||
.show()
|
||||
}
|
||||
},
|
||||
proxyPort,
|
||||
)
|
||||
}
|
||||
},
|
||||
visualTransformation =
|
||||
if (showCharsPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
keyboardActions =
|
||||
KeyboardActions(
|
||||
onGo = {
|
||||
if (!acceptedTerms.value) {
|
||||
termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required)
|
||||
}
|
||||
|
||||
if (key.value.text.isBlank()) {
|
||||
errorMessage = context.getString(R.string.key_is_required)
|
||||
}
|
||||
|
||||
if (needsPassword.value && password.value.text.isBlank()) {
|
||||
errorMessage = context.getString(R.string.password_is_required)
|
||||
}
|
||||
|
||||
if (acceptedTerms.value && key.value.text.isNotBlank() && !(needsPassword.value && password.value.text.isBlank())) {
|
||||
processingLogin = true
|
||||
accountStateViewModel.login(key.value.text, password.value.text, useProxy.value, proxyPort.value.toInt()) {
|
||||
processingLogin = false
|
||||
errorMessage =
|
||||
if (it != null) {
|
||||
context.getString(R.string.invalid_key_with_message, it)
|
||||
} else {
|
||||
context.getString(R.string.invalid_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
if (PackageUtils.isOrbotInstalled(context)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = useProxy.value,
|
||||
onCheckedChange = {
|
||||
if (it) {
|
||||
connectOrbotDialogOpen = true
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Text(stringResource(R.string.connect_via_tor))
|
||||
}
|
||||
|
||||
if (connectOrbotDialogOpen) {
|
||||
ConnectOrbotDialog(
|
||||
onClose = { connectOrbotDialogOpen = false },
|
||||
onPost = {
|
||||
connectOrbotDialogOpen = false
|
||||
useProxy.value = true
|
||||
},
|
||||
onError = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
it,
|
||||
Toast.LENGTH_LONG,
|
||||
)
|
||||
.show()
|
||||
}
|
||||
},
|
||||
proxyPort,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,19 +574,33 @@ fun LoginPage(
|
||||
errorMessage = context.getString(R.string.key_is_required)
|
||||
}
|
||||
|
||||
if (acceptedTerms.value && key.value.text.isNotBlank()) {
|
||||
accountStateViewModel.login(key.value.text, useProxy.value, proxyPort.value.toInt()) {
|
||||
errorMessage = context.getString(R.string.invalid_key)
|
||||
if (needsPassword.value && password.value.text.isBlank()) {
|
||||
errorMessage = context.getString(R.string.password_is_required)
|
||||
}
|
||||
|
||||
if (acceptedTerms.value && key.value.text.isNotBlank() && !(needsPassword.value && password.value.text.isBlank())) {
|
||||
processingLogin = true
|
||||
accountStateViewModel.login(key.value.text, password.value.text, useProxy.value, proxyPort.value.toInt()) {
|
||||
processingLogin = false
|
||||
errorMessage =
|
||||
if (it != null) {
|
||||
context.getString(R.string.invalid_key_with_message, it)
|
||||
} else {
|
||||
context.getString(R.string.invalid_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(Size35dp),
|
||||
modifier = Modifier.height(50.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.login),
|
||||
modifier = Modifier.padding(horizontal = 40.dp),
|
||||
)
|
||||
Row(modifier = Modifier.padding(horizontal = 40.dp)) {
|
||||
if (processingLogin) {
|
||||
LoadingAnimation()
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
}
|
||||
Text(stringResource(R.string.login))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ val Size19Modifier = Modifier.size(19.dp)
|
||||
val Size20Modifier = Modifier.size(20.dp)
|
||||
val Size22Modifier = Modifier.size(22.dp)
|
||||
val Size24Modifier = Modifier.size(24.dp)
|
||||
val Size25Modifier = Modifier.size(25.dp)
|
||||
val Size26Modifier = Modifier.size(26.dp)
|
||||
val Size30Modifier = Modifier.size(30.dp)
|
||||
val Size35Modifier = Modifier.size(35.dp)
|
||||
|
||||
@@ -85,9 +85,12 @@
|
||||
<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">أضف Relay</string>
|
||||
<string name="display_name">إسم العرض</string>
|
||||
<string name="my_display_name">إسم العرض الخاص بي</string>
|
||||
<string name="my_awesome_name">اسمي المميز</string>
|
||||
<string name="welcome">مرحبا بك!</string>
|
||||
<string name="username">اسم المستخدم</string>
|
||||
<string name="my_username">اسم المستخدم الخاص بي</string>
|
||||
<string name="about_me">ْعَنِّي</string>
|
||||
@@ -126,6 +129,7 @@
|
||||
<string name="send_a_direct_message">إرسال رسالة مباشرة</string>
|
||||
<string name="edits_the_user_s_metadata">القيام بتحرير 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>
|
||||
@@ -140,7 +144,14 @@
|
||||
<string name="terms_of_use">شروط الاستخدام</string>
|
||||
<string name="acceptance_of_terms_is_required">يجب قبول شروط الإستخدام</string>
|
||||
<string name="key_is_required">المفتاح مطلوب</string>
|
||||
<string name="name_is_required">يجب ادخال اسم</string>
|
||||
<string name="login">تسجيل دخول</string>
|
||||
<string name="sign_up">التسجيل</string>
|
||||
<string name="create_account">انشاء حساب</string>
|
||||
<string name="how_should_we_call_you">كيف ينبغي أن ندعوك؟</string>
|
||||
<string name="don_t_have_an_account">ليس لديك حساب؟</string>
|
||||
<string name="already_have_an_account">هل لديك حساب nostr بالفعل؟</string>
|
||||
<string name="create_a_new_account">أنشئ حسابًا جديدًا</string>
|
||||
<string name="generate_a_new_key">إنشاء مفتاح جديد</string>
|
||||
<string name="loading_feed">بإنتظار التحديثات</string>
|
||||
<string name="loading_account">جاري تحميل الحساب</string>
|
||||
@@ -182,12 +193,6 @@
|
||||
<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>
|
||||
@@ -249,6 +254,7 @@
|
||||
<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_explainer">السماح لمفتاحك السري بدفع ال zaps بدون الحاجة الى لخروج من التطبيق (دع مفتاحك السري ٱمن و استخدم خادم خاص إن امكن)</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>
|
||||
@@ -293,6 +299,7 @@
|
||||
<string name="zap_type_anonymous_explainer">المستقبل والجمهور لا يعرفان من الذي قام بإرسال الدفع</string>
|
||||
<string name="zap_type_nonzap_explainer">لا يوجد أثر في نوستر، فقط على شبكة الLightning</string>
|
||||
<string name="file_server">خادم الملفات</string>
|
||||
<string name="zap_forward_lnAddress"> user@ او Lnaddres </string>
|
||||
<string name="upload_server_relays_nip95">المرحلات الخاصة بك (NIP-95)</string>
|
||||
<string name="connect_via_tor_short">إعداد Tor/Orbot</string>
|
||||
<string name="connect_via_tor">الاتصال من خلال إعدادات Orbot الخاص بك</string>
|
||||
@@ -303,6 +310,9 @@
|
||||
<string name="follow_list_selection">قائمة المتابعة</string>
|
||||
<string name="follow_list_kind3follows">جميع المتابعات</string>
|
||||
<string name="follow_list_global">العالمي</string>
|
||||
<string name="follow_list_mute_list">قائمة الحسابات المكتومة</string>
|
||||
<string name="app_notification_dms_channel_name">الرسائل الخاصة</string>
|
||||
<string name="app_notification_dms_channel_description">التنبيه عند وصول رسالة خاصة</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>
|
||||
@@ -347,6 +357,8 @@
|
||||
<string name="auth">المصادقة</string>
|
||||
<string name="payment">الدفع</string>
|
||||
<string name="cashu">Cashu Token</string>
|
||||
<string name="cashu_copy_token">تسخ الtoken</string>
|
||||
<string name="copied_token_to_clipboard">نسخ الtoken من المحفظة</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">تسجيل الخروج سوف يؤدي الى جميع المعلومات المحلية الخاصة بك. تأكد من وجود نسخة احتياطية لمفاتحك السرية لتجنب فقدان حسابك.
|
||||
هل تريد الاستمرار؟</string>
|
||||
<string name="followed_tags">العلامات المُتابعة</string>
|
||||
@@ -416,6 +428,12 @@
|
||||
<string name="error_dialog_button_ok">موافق</string>
|
||||
<string name="relay_information_document_error_assemble_url">فشل الوصول إلى
|
||||
%1$s: %2$s</string>
|
||||
<string name="active_for_home">الصفحة الرئيسية</string>
|
||||
<string name="active_for_search">البحث</string>
|
||||
<string name="error_opening_external_signer">خطأ في فتح تطبيق الموقِّع (signer app)</string>
|
||||
<string name="hidden_words">الكلمات المخفية</string>
|
||||
<string name="hide_new_word_label">إخفاء كلمة أو جملة جديدة</string>
|
||||
<string name="select_an_option">حدد خيارا</string>
|
||||
<string name="cashu_successful_redemption_explainer">%1$s sats أرسلت الى محفظتك
|
||||
(الرسوم: sats %2$s ) </string>
|
||||
<string name="error_unable_to_fetch_invoice">تعذر جلب الinvoice من خوادم المستلم</string>
|
||||
@@ -436,4 +454,31 @@
|
||||
<string name="push_server_install_app_description">لاستخدام الإشعارات حمل أي تطبيق يدعم ال [Unified Push](https://unifiedpush.org/)
|
||||
مثل [Nfty](https://ntfy.sh/).
|
||||
بعد تثبيتك التطبيق قم باختياره من الإعدادات</string>
|
||||
<string name="classifieds_title">العنوان</string>
|
||||
<string name="classifieds_title_placeholder">iPhone 13</string>
|
||||
<string name="classifieds_condition">الشرط</string>
|
||||
<string name="classifieds_category">الفئة</string>
|
||||
<string name="classifieds_location">الموقع الجغرافيّ</string>
|
||||
<string name="classifieds_condition_like_new">كالجديد</string>
|
||||
<string name="classifieds_condition_like_new_explainer">مستعمل، لكن لا تظهر عليه أي ٱثار للاستعمال</string>
|
||||
<string name="classifieds_condition_good">بحالة جيدة</string>
|
||||
<string name="classifieds_condition_good_explainer">لديه بعض علامات الاستخدام السطحية</string>
|
||||
<string name="classifieds_condition_fair">جيد</string>
|
||||
<string name="classifieds_condition_fair_explainer">لا يزال في شكل مقبول وعملي</string>
|
||||
<string name="classifieds_category_clothing">ملابس</string>
|
||||
<string name="classifieds_category_accessories">اكسسوارات</string>
|
||||
<string name="classifieds_category_electronics">إلكترونيات</string>
|
||||
<string name="classifieds_category_furniture">أثاث</string>
|
||||
<string name="classifieds_category_books">كتب</string>
|
||||
<string name="classifieds_category_pets">حيوانات أليفة</string>
|
||||
<string name="classifieds_category_sports">الرياضة</string>
|
||||
<string name="classifieds_category_fitness">اللياقة البدنية</string>
|
||||
<string name="classifieds_category_art">فن</string>
|
||||
<string name="classifieds_category_crafts">الحرف</string>
|
||||
<string name="classifieds_category_home">الصفحة الرئيسية</string>
|
||||
<string name="classifieds_category_office">المكتب</string>
|
||||
<string name="classifieds_category_food">طعام</string>
|
||||
<string name="classifieds_category_other">أخرى</string>
|
||||
<string name="failed_to_upload_media_no_details">فشل في تحميل الوسائط</string>
|
||||
<string name="failed_to_upload_media">خطأ في التحميل: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -182,12 +182,6 @@
|
||||
<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>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<string name="point_to_the_qr_code">Nasměrujte na QR kód</string>
|
||||
<string name="show_qr">Zobrazit QR kód</string>
|
||||
<string name="profile_image">Profilový obrázek</string>
|
||||
<string name="your_profile_image">Profilový obrázek</string>
|
||||
<string name="scan_qr">Skenovat QR kód</string>
|
||||
<string name="show_anyway">Zobrazit přesto</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Příspěvek byl označen jako nevhodný uživatelem</string>
|
||||
@@ -138,12 +139,15 @@
|
||||
<string name="clear">Vymazat</string>
|
||||
<string name="app_logo">Logo aplikace</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec / npub / hex privátní klíč</string>
|
||||
<string name="ncryptsec_password">heslo pro otevření klíče</string>
|
||||
<string name="show_password">Zobrazit heslo</string>
|
||||
<string name="hide_password">Skrýt heslo</string>
|
||||
<string name="invalid_key">Neplatný klíč</string>
|
||||
<string name="invalid_key_with_message">Neplatný klíč: %1$s</string>
|
||||
<string name="i_accept_the">"Souhlasím s "</string>
|
||||
<string name="terms_of_use">podmínkami použití</string>
|
||||
<string name="acceptance_of_terms_is_required">Je vyžadováno přijetí podmínek</string>
|
||||
<string name="password_is_required">Heslo je vyžadováno</string>
|
||||
<string name="key_is_required">Klíč je povinný</string>
|
||||
<string name="name_is_required">Jméno je povinné</string>
|
||||
<string name="login">Přihlásit se</string>
|
||||
@@ -194,14 +198,17 @@
|
||||
<string name="mark_all_new_as_read">Označit všechny nové jako přečtené</string>
|
||||
<string name="mark_all_as_read">Označit všechny jako přečtené</string>
|
||||
<string name="backup_keys">Zálohovat klíče</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Tipy na zálohování a bezpečnost účtu
|
||||
\n\nVáš účet je zabezpečen tajným klíčem. Klíč je dlouhý náhodný řetězec začínající **nsec1**. Každý, kdo má přístup k vašemu tajnému klíči, může publikovat obsah pod vaší identitou.
|
||||
\n\n- **Nepoužívejte** svůj tajný klíč na webových stránkách nebo v softwaru, kterému nedůvěřujete.
|
||||
\n- Vývojáři aplikace Amethyst vás **nikdy** nebudou žádat o váš tajný klíč.
|
||||
\n- **Uchovávejte** si bezpečnou zálohu svého tajného klíče pro obnovení účtu. Doporučujeme používat správce hesel.
|
||||
</string>
|
||||
<string name="account_backup_tips2_md" tools:ignore="Typos"> ## Tipy pro zálohování klíčů a bezpečnost
|
||||
\n\nVaše konto je zabezpečeno tajným klíčem. Klíč je dlouhá posloupnost znaků začínající s **nsec1**. Kdo má přístup k tomuto tajnému klíči, může přispívat a měnit vaši identitu.
|
||||
\n\n- **Nedávejte** svůj tajný klíč na žádnou webovou stránku nebo do žádného softwaru, kterému nedůvěřujete.
|
||||
\n- Vývojáři Amethystu nikdy **nebudou** žádat o váš tajný klíč.
|
||||
\n- **Udržujte** bezpečnou zálohu vašeho tajného klíče pro obnovení účtu. Doporučujeme použití správce hesel.</string>
|
||||
<string name="account_backup_tips3_md" tools:ignore="Typos"> Pro další zabezpečení můžete svůj klíč zašifrovat heslem. Tento klíč začíná s **ncryptsec1** a nelze ho použít bez vašeho hesla.
|
||||
\n\nPokud zapomenete heslo, nebudete moci obnovit svůj klíč.</string>
|
||||
<string name="failed_to_encrypt_key">Nepodařilo se zašifrovat váš privátní klíč</string>
|
||||
<string name="secret_key_copied_to_clipboard">Tajný klíč (nsec) zkopírován do schránky</string>
|
||||
<string name="copy_my_secret_key">Zkopírovat můj tajný klíč</string>
|
||||
<string name="encrypt_and_copy_my_secret_key">Zašifrovat a zkopírovat můj tajný klíč</string>
|
||||
<string name="biometric_authentication_failed">Autentizace se nezdařila</string>
|
||||
<string name="biometric_authentication_failed_explainer">Biometrické údaje se nepodařilo ověřit vlastníka tohoto telefonu</string>
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Biometrické údaje se nepodařilo ověřit vlastníka tohoto telefonu. Chyba: %1$s</string>
|
||||
@@ -453,6 +460,7 @@
|
||||
<string name="new_feature_nip24_might_not_be_available_description">Aktivace tohoto režimu vyžaduje od Amethystu odeslání zprávy NIP-24 (GiftWrapped, Zapečetěné přímé a skupinové zprávy). NIP-24 je nový a většina klientů ho zatím neimplementovala. Ujistěte se, že příjemce používá kompatibilního klienta.</string>
|
||||
<string name="new_feature_nip24_activate">Aktivovat</string>
|
||||
<string name="messages_create_public_chat">Veřejné</string>
|
||||
<string name="messages_create_public_private_chat_desription">Nová veřejná nebo soukromá skupina</string>
|
||||
<string name="messages_new_message">Soukromé</string>
|
||||
<string name="messages_new_message_to">Pro</string>
|
||||
<string name="messages_new_message_subject">Předmět</string>
|
||||
@@ -469,6 +477,7 @@
|
||||
<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_to_clipboard">Kopírovat do schránky</string>
|
||||
<string name="copy_npub_to_clipboard">Kopírovat npub do schránky</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>
|
||||
@@ -597,4 +606,44 @@
|
||||
<string name="server_did_not_provide_a_url_after_uploading">Server po nahrání neposkytl URL</string>
|
||||
<string name="could_not_download_from_the_server">Nepodařilo se stáhnout nahraná média ze serveru</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">Nelze připravit místní soubor k nahrání: %1$s</string>
|
||||
<string name="login_with_qr_code">Přihlášení pomocí QR kódu</string>
|
||||
<string name="route">Trasa</string>
|
||||
<string name="route_home">Domů</string>
|
||||
<string name="route_search">Hledat</string>
|
||||
<string name="route_discover">Objevit</string>
|
||||
<string name="route_messages">Zprávy</string>
|
||||
<string name="route_notifications">Upozornění</string>
|
||||
<string name="route_global">Globální</string>
|
||||
<string name="route_video">Krátké</string>
|
||||
<string name="route_security_filters">Bezpečnostní filtry</string>
|
||||
<string name="new_post">Nový příspěvek</string>
|
||||
<string name="new_short">Nové Shorts: obrázky nebo videa</string>
|
||||
<string name="new_community_note">Nová poznámka komunity</string>
|
||||
<string name="open_all_reactions_to_this_post">Otevřít všechny reakce pro tento příspěvek</string>
|
||||
<string name="close_all_reactions_to_this_post">Zavřít všechny reakce na tento příspěvek</string>
|
||||
<string name="reply_description">Odpověď</string>
|
||||
<string name="boost_or_quote_description">Zvýšit nebo citovat</string>
|
||||
<string name="like_description">Olajkovat</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="profile_image_of_user">Obrázek profilu %1$s</string>
|
||||
<string name="relay_info">Relé %1$s</string>
|
||||
<string name="expand_relay_list">Rozbalit seznam relací</string>
|
||||
<string name="note_options">Volby poznámky</string>
|
||||
<string name="relay_list_selector">Výběr seznamu relé</string>
|
||||
<string name="poll">Anketa</string>
|
||||
<string name="disable_poll">Vypnout anketu</string>
|
||||
<string name="add_bitcoin_invoice">Bitcoinová faktura</string>
|
||||
<string name="cancel_bitcoin_invoice">Zrušit Bitcoinovou fakturu</string>
|
||||
<string name="cancel_classifieds">Zrušit prodej položky</string>
|
||||
<string name="add_zapraiser">Zap-sběr</string>
|
||||
<string name="cancel_zapraiser">Zrušit Zap-zběrku</string>
|
||||
<string name="add_location">Místo</string>
|
||||
<string name="remove_location">Odstranit umístění</string>
|
||||
<string name="add_zap_split">Zap splits</string>
|
||||
<string name="cancel_zap_split">Zrušit rozdělení Zap</string>
|
||||
<string name="add_content_warning">Přidat upozornění na obsah</string>
|
||||
<string name="remove_content_warning">Odstranit upozornění na obsah</string>
|
||||
<string name="show_npub_as_a_qr_code">Zobrazit npub jako QR kód</string>
|
||||
<string name="invalid_nip19_uri">Neplatná adresa</string>
|
||||
<string name="invalid_nip19_uri_description">Amethyst obdržel URI k otevření, ale tento URI byl neplatný: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<string name="point_to_the_qr_code">Zeigen Sie auf den QR Code</string>
|
||||
<string name="show_qr">QR Code anzeigen</string>
|
||||
<string name="profile_image">Profilbild</string>
|
||||
<string name="your_profile_image">Dein Profilbild</string>
|
||||
<string name="scan_qr">QR Code scannen</string>
|
||||
<string name="show_anyway">Trotzdem anzeigen</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Der Beitrag wurde als unangemessen gekennzeichnet von</string>
|
||||
@@ -140,14 +141,17 @@ erie gespeichert</string>
|
||||
<string name="clear">Löschen</string>
|
||||
<string name="app_logo">App-Logo</string>
|
||||
<string name="nsec_npub_hex_private_key">Nsec / Npub / Hex-Privatschlüssel</string>
|
||||
<string name="ncryptsec_password">Passwort zum Öffnen des Schlüssels</string>
|
||||
<string name="show_password">Passwort anzeigen</string>
|
||||
<string name="hide_password">Passwort ausblenden</string>
|
||||
<string name="invalid_key">Ungültiger Schlüssel</string>
|
||||
<string name="invalid_key_with_message">Ungültiger Schlüssel: %1$s</string>
|
||||
<string name="i_accept_the">"Ich akzeptiere die "</string>
|
||||
<string name="terms_of_use">Nutzungsbedingungen</string>
|
||||
<string name="acceptance_of_terms_is_required">Die Akzept
|
||||
|
||||
anz der Bedingungen ist erforderlich</string>
|
||||
<string name="password_is_required">Passwort ist erforderlich</string>
|
||||
<string name="key_is_required">Schlüssel ist erforderlich</string>
|
||||
<string name="name_is_required">Ein Name wird benötigt</string>
|
||||
<string name="login">Anmeldung</string>
|
||||
@@ -198,13 +202,17 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="mark_all_new_as_read">Alle als neu markieren</string>
|
||||
<string name="mark_all_as_read">Alle als gelesen markieren</string>
|
||||
<string name="backup_keys">Schlüssel sichern</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Sicherung und Sicherheitshinweise für Schlüssel
|
||||
\n\nIhr Konto ist durch einen geheimen Schlüssel geschützt. Der Schlüssel ist eine lange zufällige Zeichenfolge, die mit **nsec1** beginnt. Jeder, der Zugriff auf Ihren geheimen Schlüssel hat, kann Inhalte unter Verwendung Ihrer Identität veröffentlichen.
|
||||
\n\n- Geben Sie Ihren geheimen Schlüssel **nicht** auf Websites oder Software ein, denen Sie nicht vertrauen.
|
||||
<string name="account_backup_tips2_md" tools:ignore="Typos"> ## Schlüsselsicherheits-Tipps
|
||||
\n\nIhr Konto ist durch einen geheimen Schlüssel gesichert. Der Schlüssel ist eine lange Zeichenfolge, die mit **nsec1** beginnt. Jeder, der Zugriff auf diesen geheimen Schlüssel hat, kann Ihre Identität posten und ändern.
|
||||
\n\n- Legen Sie Ihren geheimen Schlüssel **nicht** auf eine Website oder in eine Software, der Sie nicht vertrauen.
|
||||
\n- Die Entwickler von Amethyst werden Sie **niemals** nach Ihrem geheimen Schlüssel fragen.
|
||||
\n- Bewahren Sie eine sichere Sicherungskopie Ihres geheimen Schlüssels zur Kontowiederherstellung auf. Wir empfehlen die Verwendung eines Passwort-Managers.</string>
|
||||
\n- **Bewahren Sie** eine sichere Sicherung Ihres geheimen Schlüssels zur Kontowiederherstellung auf. Wir empfehlen die Verwendung eines Passwort-Managers.</string>
|
||||
<string name="account_backup_tips3_md" tools:ignore="Typos"> Für zusätzliche Sicherheit können Sie Ihren Schlüssel mit einem Passwort verschlüsseln. Dieser Schlüssel beginnt mit **ncryptsec1** und kann ohne Ihr Passwort nicht verwendet werden.
|
||||
\n\nWenn Sie Ihr Passwort verlieren, können Sie Ihren Schlüssel nicht wiederherstellen.</string>
|
||||
<string name="failed_to_encrypt_key">Fehler beim Verschlüsseln Ihres privaten Schlüssels</string>
|
||||
<string name="secret_key_copied_to_clipboard">Geheimer Schlüssel (nsec) in die Zwischenablage kopiert</string>
|
||||
<string name="copy_my_secret_key">Meinen geheimen Schlüssel kopieren</string>
|
||||
<string name="encrypt_and_copy_my_secret_key">Verschlüsseln und kopieren Sie meinen geheimen Schlüssel</string>
|
||||
<string name="biometric_authentication_failed">Authentifizierung fehlgeschlagen</string>
|
||||
<string name="biometric_authentication_failed_explainer">Biometrie konnte den Besitzer dieses Telefons nicht authentifizieren</string>
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Biometrie konnte den Besitzer dieses Telefons nicht authentifizieren. Fehler: %1$s</string>
|
||||
@@ -457,6 +465,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_description">Um diesen Modus zu aktivieren, muss Amethyst eine NIP-24-Nachricht senden (GiftWrapped, Versiegelte Direkt- und Gruppennachrichten). NIP-24 ist neu und die meisten Clients haben es noch nicht implementiert. Stellen Sie sicher, dass der Empfänger einen kompatiblen Client verwendet.</string>
|
||||
<string name="new_feature_nip24_activate">Aktivieren</string>
|
||||
<string name="messages_create_public_chat">Öffentlich</string>
|
||||
<string name="messages_create_public_private_chat_desription">Neue öffentliche oder private Gruppe</string>
|
||||
<string name="messages_new_message">Privat</string>
|
||||
<string name="messages_new_message_to">An</string>
|
||||
<string name="messages_new_message_subject">Betreff</string>
|
||||
@@ -473,6 +482,7 @@ anz der Bedingungen ist erforderlich</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_to_clipboard">In Zwischenablage kopieren</string>
|
||||
<string name="copy_npub_to_clipboard">Npub in Zwischenablage kopieren</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>
|
||||
@@ -601,4 +611,44 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="server_did_not_provide_a_url_after_uploading">Der Server hat nach dem Hochladen keine URL angegeben</string>
|
||||
<string name="could_not_download_from_the_server">Hochgeladene Medien konnten nicht vom Server heruntergeladen werden</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">Lokale Datei konnte nicht zum Hochladen vorbereitet werden: %1$s</string>
|
||||
<string name="login_with_qr_code">Einloggen mit QR-Code</string>
|
||||
<string name="route">Route</string>
|
||||
<string name="route_home">Startseite</string>
|
||||
<string name="route_search">Suche</string>
|
||||
<string name="route_discover">Entdecken</string>
|
||||
<string name="route_messages">Nachrichten</string>
|
||||
<string name="route_notifications">Benachrichtigungen</string>
|
||||
<string name="route_global">Global</string>
|
||||
<string name="route_video">Kurzfilme</string>
|
||||
<string name="route_security_filters">Sicherheitsfilter</string>
|
||||
<string name="new_post">Neuer Beitrag</string>
|
||||
<string name="new_short">Neue Kurzfilme: Bilder oder Videos</string>
|
||||
<string name="new_community_note">Neue Community-Notiz</string>
|
||||
<string name="open_all_reactions_to_this_post">Alle Reaktionen auf diesen Beitrag öffnen</string>
|
||||
<string name="close_all_reactions_to_this_post">Alle Reaktionen auf diesen Beitrag schließen</string>
|
||||
<string name="reply_description">Antworten</string>
|
||||
<string name="boost_or_quote_description">Boosten oder Zitieren</string>
|
||||
<string name="like_description">Gefällt mir</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="profile_image_of_user">Profilbild von %1$s</string>
|
||||
<string name="relay_info">%1$s weiterleiten</string>
|
||||
<string name="expand_relay_list">Rela-Liste erweitern</string>
|
||||
<string name="note_options">Notizoptionen</string>
|
||||
<string name="relay_list_selector">Rela-Listenauswahl</string>
|
||||
<string name="poll">Umfrage</string>
|
||||
<string name="disable_poll">Umfrage deaktivieren</string>
|
||||
<string name="add_bitcoin_invoice">Bitcoin-Rechnung hinzufügen</string>
|
||||
<string name="cancel_bitcoin_invoice">Bitcoin-Rechnung abbrechen</string>
|
||||
<string name="cancel_classifieds">Verkauf eines Artikels abbrechen</string>
|
||||
<string name="add_zapraiser">Zapraiser hinzufügen</string>
|
||||
<string name="cancel_zapraiser">Zapraiser abbrechen</string>
|
||||
<string name="add_location">Ort hinzufügen</string>
|
||||
<string name="remove_location">Ort entfernen</string>
|
||||
<string name="add_zap_split">Zap-Aufteilung hinzufügen</string>
|
||||
<string name="cancel_zap_split">Zap-Aufteilung abbrechen</string>
|
||||
<string name="add_content_warning">Inhaltswarnung hinzufügen</string>
|
||||
<string name="remove_content_warning">Inhaltswarnung entfernen</string>
|
||||
<string name="show_npub_as_a_qr_code">Npub als QR-Code anzeigen</string>
|
||||
<string name="invalid_nip19_uri">Ungültige Adresse</string>
|
||||
<string name="invalid_nip19_uri_description">Amethyst hat eine URI zum Öffnen erhalten, aber diese URI war ungültig: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -185,12 +185,6 @@
|
||||
<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>
|
||||
|
||||
@@ -1,2 +1,15 @@
|
||||
<?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">Point to the QR Code</string>
|
||||
<string name="show_qr">Show QR</string>
|
||||
<string name="profile_image">Profile Image</string>
|
||||
<string name="scan_qr">Scan QR</string>
|
||||
<string name="show_anyway">Show Anyway</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Post was muted or reported by</string>
|
||||
<string name="post_not_found">Event is loading or can\'t be found in your relay list</string>
|
||||
<string name="channel_image">Channel Image</string>
|
||||
<string name="referenced_event_not_found">Referenced event not found</string>
|
||||
<string name="could_not_decrypt_the_message">Could not decrypt the message</string>
|
||||
<string name="group_picture">Group Picture</string>
|
||||
<string name="explicit_content">Explicit Content</string>
|
||||
</resources>
|
||||
|
||||
@@ -182,12 +182,6 @@
|
||||
<string name="mark_all_new_as_read">Marki ĉiujn Novajn legitaj</string>
|
||||
<string name="mark_all_as_read">Marki ĉiujn legitajn</string>
|
||||
<string name="backup_keys">Sekurkopii Ŝlosilojn</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Sekurkopio de Ŝlosiloj kaj Rekomendoj de Sekureco
|
||||
\n\nVia konto estas sekurigita per sekreta ŝlosilo. La ŝlosilo estas longa hazarda literĉeno komencata kun **nsec1**. Ĉiu, kiu havas aliron al via sekreta ŝlosilo, povas publikigi enhavon per via identeco.
|
||||
\n\n- **Ne** metu vian sekretan ŝlosilon en ajna retejo aŭ programaro, kiun vi ne fidas.
|
||||
\n- La programistoj de Amethyst **neniam** petos de vi vian sekretan ŝlosilon.
|
||||
\n- **Ja** konservu sekurkopion de via sekreta ŝlosilo por reakiro de konto. Ni rekomendas uzi pasvortmanaĝilon.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">Sekreta ŝlosilo (nsec) kopiita al tondujo</string>
|
||||
<string name="copy_my_secret_key">Kopii mian sekretan ŝlosilon</string>
|
||||
<string name="biometric_authentication_failed">Aŭtentikigo malsukcesis</string>
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
<string name="scan_qr">Escanear el QR</string>
|
||||
<string name="show_anyway">Mostrar de todos modos</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Post marcado como inapropiado por</string>
|
||||
<string name="post_not_found">El evento se está cargando o no se puede encontrar en la lista de relés</string>
|
||||
<string name="channel_image">Imagen del canal</string>
|
||||
<string name="referenced_event_not_found">Evento referenciado no encontrado</string>
|
||||
<string name="could_not_decrypt_the_message">No se pudo desencriptar el mensaje</string>
|
||||
<string name="group_picture">Imagen de grupo</string>
|
||||
<string name="explicit_content">Contenido explícito</string>
|
||||
<string name="spam">Spam</string>
|
||||
<string name="impersonation">Suplantación de identidad</string>
|
||||
<string name="illegal_behavior">Comportamiento ilegal</string>
|
||||
<string name="unknown">Desconocido</string>
|
||||
@@ -19,6 +22,8 @@
|
||||
<string name="copy_user_pubkey">Copiar PubKey del usuario</string>
|
||||
<string name="copy_note_id">Copiar ID de la nota</string>
|
||||
<string name="broadcast">Transmisión</string>
|
||||
<string name="request_deletion">Solicitar eliminación</string>
|
||||
<string name="block_report">Bloquear / Reportar</string>
|
||||
<string name="block_hide_user"><![CDATA[Bloquear y ocultar usuario]]></string>
|
||||
<string name="report_spam_scam">Reportar Spam / Estafa</string>
|
||||
<string name="report_impersonation">Reportar suplantación de identidad</string>
|
||||
@@ -29,8 +34,14 @@
|
||||
<string name="login_with_a_private_key_to_like_posts">Inicie sesión con una clave privada para dar me gusta a los posts</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">Falta la configuración de Zaps por defecto. Mantén pulsado unos segundos para cambiarla</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Inicie sesión con una clave privada para poder enviar Zaps</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder seguir a otros usuarios.</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder dejar de seguir a otros usuarios.</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_hide_word">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder ocultar una palabra o frase.</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_show_word">Estás usando una clave pública, que es solo de lectura. Inicia sesión con una clave privada para poder mostrar una palabra o frase.</string>
|
||||
<string name="zaps">Zaps</string>
|
||||
<string name="view_count">Total vistas</string>
|
||||
<string name="boost">Impulsar</string>
|
||||
<string name="boosted">Impulsada</string>
|
||||
<string name="quote">Cita</string>
|
||||
<string name="new_amount_in_sats">Nueva cantidad en Sats</string>
|
||||
<string name="add">Añadir</string>
|
||||
@@ -38,6 +49,8 @@
|
||||
<string name="and">" y "</string>
|
||||
<string name="in_channel">"en canal "</string>
|
||||
<string name="profile_banner">Banner de perfil</string>
|
||||
<string name="payment_successful">El pago se ha realizado correctamente</string>
|
||||
<string name="error_parsing_error_message">Error al analizar el mensaje de error</string>
|
||||
<string name="following">" Siguiendo"</string>
|
||||
<string name="followers">" Seguidores"</string>
|
||||
<string name="profile">Perfil</string>
|
||||
@@ -67,6 +80,7 @@
|
||||
<string name="failed_to_upload_the_image">No se pudo cargar la imagen</string>
|
||||
<string name="relay_address">Dirección del transmisor</string>
|
||||
<string name="posts">Publicaciones</string>
|
||||
<string name="bytes">Bytes</string>
|
||||
<string name="errors">Errores</string>
|
||||
<string name="home_feed">Tus noticias</string>
|
||||
<string name="private_message_feed">Fuente de mensajes privados</string>
|
||||
@@ -76,6 +90,8 @@
|
||||
<string name="add_a_relay">Anadir transmisor</string>
|
||||
<string name="display_name">Nombre para mostrar</string>
|
||||
<string name="my_display_name">Mi nombre para mostrar</string>
|
||||
<string name="my_awesome_name">Avestruz Maravillosa</string>
|
||||
<string name="welcome">¡Te damos la bienvenida, Avestruz!</string>
|
||||
<string name="username">Nombre de usuario</string>
|
||||
<string name="my_username">Mi nombre de usuario</string>
|
||||
<string name="about_me">Sobre mí</string>
|
||||
@@ -114,6 +130,7 @@
|
||||
<string name="send_a_direct_message">Enviar un mensaje directo</string>
|
||||
<string name="edits_the_user_s_metadata">Edita los metadatos del usuario</string>
|
||||
<string name="follow">Seguir</string>
|
||||
<string name="follow_back">Seguir también</string>
|
||||
<string name="unblock">Desbloquear</string>
|
||||
<string name="copy_user_id">Copiar ID de usuario</string>
|
||||
<string name="unblock_user">Desbloquear usuario</string>
|
||||
@@ -128,9 +145,17 @@
|
||||
<string name="terms_of_use">términos de uso</string>
|
||||
<string name="acceptance_of_terms_is_required">Se requiere la aceptación de los términos</string>
|
||||
<string name="key_is_required">Se requiere la clave</string>
|
||||
<string name="name_is_required">Se requiere un nombre</string>
|
||||
<string name="login">Acceso</string>
|
||||
<string name="sign_up">Registrarse</string>
|
||||
<string name="create_account">Crear cuenta</string>
|
||||
<string name="how_should_we_call_you">¿Cómo debemos llamarte?</string>
|
||||
<string name="don_t_have_an_account">¿No tienes una cuenta de Nostr?</string>
|
||||
<string name="already_have_an_account">¿Ya tienes una cuenta de Nostr?</string>
|
||||
<string name="create_a_new_account">Crear una cuenta nueva</string>
|
||||
<string name="generate_a_new_key">Generar una nueva clave</string>
|
||||
<string name="loading_feed">Cargando el tablón…</string>
|
||||
<string name="loading_account">Cargando cuenta</string>
|
||||
<string name="error_loading_replies">"Error al cargar las respuestas: "</string>
|
||||
<string name="try_again">Intentar otra vez</string>
|
||||
<string name="feed_is_empty">Tablón vacío</string>
|
||||
@@ -148,13 +173,18 @@
|
||||
<string name="public_chat">Chat público</string>
|
||||
<string name="posts_received">publicaciones recibidas</string>
|
||||
<string name="remove">Eliminar</string>
|
||||
<string name="translations_auto">Automático</string>
|
||||
<string name="translations_translated_from">traducido de</string>
|
||||
<string name="translations_to">a</string>
|
||||
<string name="translations_show_in_lang_first">Mostrar en %1$s primero</string>
|
||||
<string name="translations_always_translate_to_lang">Traducir siempre a %1$s</string>
|
||||
<string name="translations_never_translate_from_lang">Nunca traducir desde %1$s</string>
|
||||
<string name="nip_05">Dirección de Nostr</string>
|
||||
<string name="never">nunca</string>
|
||||
<string name="now">ahora</string>
|
||||
<string name="h">h</string>
|
||||
<string name="m">m</string>
|
||||
<string name="d">d</string>
|
||||
<string name="nudity">Desnudos</string>
|
||||
<string name="profanity_hateful_speech">Blasfemias / Discurso de odio</string>
|
||||
<string name="report_hateful_speech">Denuncia discurso de odio</string>
|
||||
@@ -167,6 +197,9 @@
|
||||
<string name="secret_key_copied_to_clipboard">Clave secreta (nsec) copiada al portapapeles</string>
|
||||
<string name="copy_my_secret_key">Copiar mi clave secreta</string>
|
||||
<string name="biometric_authentication_failed">Autenticación fallida</string>
|
||||
<string name="biometric_authentication_failed_explainer">El sistema biométrico no pudo autenticar al propietario de este teléfono</string>
|
||||
<string name="biometric_authentication_failed_explainer_with_error">El sistema biométrico no pudo autenticar al propietario de este teléfono. Error: %1$s</string>
|
||||
<string name="biometric_error">Error</string>
|
||||
<string name="badge_created_by">"Credo por %1$s"</string>
|
||||
<string name="badge_award_image_for">"Imagen del Badge para %1$s"</string>
|
||||
<string name="new_badge_award_notif">Has recibido un nuevo Badge</string>
|
||||
@@ -175,6 +208,15 @@
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">\@npub del autor copiado al portapapeles</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">ID de nota copiada (@note1) al portapapeles</string>
|
||||
<string name="select_text_dialog_top">Seleccionar texto</string>
|
||||
<string name="private_conversation_notification">"<Unable to decrypt private message>\n\nTe citaron en una conversación privada o encriptada entre %1$s y %2$s."</string>
|
||||
<string name="account_switch_add_account_dialog_title">Agregar cuenta nueva</string>
|
||||
<string name="drawer_accounts">Cuentas</string>
|
||||
<string name="account_switch_select_account">Seleccionar cuenta</string>
|
||||
<string name="account_switch_add_account_btn">Agregar cuenta nueva</string>
|
||||
<string name="account_switch_active_account">Cuenta activa</string>
|
||||
<string name="account_switch_has_private_key">Tiene clave privada</string>
|
||||
<string name="account_switch_pubkey_only">Solo de lectura, sin clave privada</string>
|
||||
<string name="back">Volver</string>
|
||||
<string name="quick_action_select">Selecciona</string>
|
||||
<string name="quick_action_share_browser_link">Compartir enlace del navegador</string>
|
||||
<string name="quick_action_share">Compartir</string>
|
||||
@@ -186,4 +228,130 @@
|
||||
<string name="quick_action_follow">Seguir</string>
|
||||
<string name="quick_action_request_deletion_alert_title">Solicitar eliminación</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst solicitará que se elimine su nota de los relays a los que está conectado actualmente. No hay garantía de que su nota se elimine permanentemente de esos relays, o de otros relays donde pueda almacenarse.</string>
|
||||
<string name="report_dialog_impersonation">Suplantación de identidad malintencionada</string>
|
||||
<string name="report_dialog_reminder_public">Todos los reportes publicados serán visibles públicamente.</string>
|
||||
<string name="report_dialog_select_reason_label">Motivo</string>
|
||||
<string name="report_dialog_select_reason_placeholder">Selecciona un motivo…</string>
|
||||
<string name="report_dialog_post_report_btn">Publicar reporte</string>
|
||||
<string name="report_dialog_title">Bloquear y reportar</string>
|
||||
<string name="block_only">Bloquear</string>
|
||||
<string name="bookmarks">Marcadores</string>
|
||||
<string name="private_bookmarks">Marcadores privados</string>
|
||||
<string name="public_bookmarks">Marcadores públicos</string>
|
||||
<string name="add_to_private_bookmarks">Agregar a marcadores privados</string>
|
||||
<string name="add_to_public_bookmarks">Agregar a marcadores públicos</string>
|
||||
<string name="remove_from_private_bookmarks">Eliminar de marcadores privados</string>
|
||||
<string name="remove_from_public_bookmarks">Eliminar de marcadores públicos</string>
|
||||
<string name="wallet_connect_service">Servicio de conexión a monedero</string>
|
||||
<string name="wallet_connect_service_explainer">Autoriza a un secreto de Nostr para pagar zaps sin salir de la app. Mantén el secreto seguro y usa un relé privado si es posible.</string>
|
||||
<string name="wallet_connect_service_pubkey">Clave pública de conexión a monedero</string>
|
||||
<string name="wallet_connect_service_relay">Relé de conexión a monedero</string>
|
||||
<string name="wallet_connect_service_secret">Secreto de conexión a monedero</string>
|
||||
<string name="wallet_connect_service_show_secret">Clave secreta de conexión a monedero</string>
|
||||
<string name="wallet_connect_service_secret_placeholder">clave privada nsec / hex</string>
|
||||
<string name="pledge_amount_in_sats">Destinar cantidad en sats</string>
|
||||
<string name="post_poll">Publicar encuesta</string>
|
||||
<string name="poll_heading_required">Campos obligatorios:</string>
|
||||
<string name="poll_zap_recipients">Destinatarios de zaps</string>
|
||||
<string name="poll_primary_description">Descripción de encuesta principal…</string>
|
||||
<string name="poll_option_index">Opción %s</string>
|
||||
<string name="poll_option_description">Descripción de opción de encuesta</string>
|
||||
<string name="poll_heading_optional">Campos opcionales:</string>
|
||||
<string name="poll_zap_value_min">Zap mínimo</string>
|
||||
<string name="poll_zap_value_max">Zap máximo</string>
|
||||
<string name="poll_consensus_threshold">Consenso</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="poll_closing_time">Cerrar después de</string>
|
||||
<string name="poll_closing_time_days">días</string>
|
||||
<string name="poll_unable_to_vote">No se puede votar</string>
|
||||
<string name="poll_is_closed_explainer">Encuesta cerrada para votos nuevos</string>
|
||||
<string name="poll_zap_amount">Monto del zap</string>
|
||||
<string name="one_vote_per_user_on_atomic_votes">Solo se permite un voto por usuario en este tipo de encuesta</string>
|
||||
<string name="looking_for_event">"Buscando evento %1$s"</string>
|
||||
<string name="custom_zaps_add_a_message">Agregar un mensaje público</string>
|
||||
<string name="custom_zaps_add_a_message_private">Agregar un mensaje privado</string>
|
||||
<string name="custom_zaps_add_a_message_nonzap">Agregar un mensaje con factura</string>
|
||||
<string name="custom_zaps_add_a_message_example">¡Gracias por tu trabajo!</string>
|
||||
<string name="lightning_create_and_add_invoice">Crear y añadir</string>
|
||||
<string name="poll_author_no_vote">Los autores de encuestas no pueden votar en ellas.</string>
|
||||
<string name="hash_verification_info_title">¿Qué significa esto?</string>
|
||||
<string name="hash_verification_passed">Este contenido es el mismo desde la publicación</string>
|
||||
<string name="hash_verification_failed">Este contenido ha cambiado. Es posible que el autor no haya visto o aprobado el cambio.</string>
|
||||
<string name="content_description_add_image">Añadir imagen</string>
|
||||
<string name="content_description_add_video">Añadir vídeo</string>
|
||||
<string name="content_description_add_document">Añadir documento</string>
|
||||
<string name="add_content">Añadir al mensaje</string>
|
||||
<string name="content_description">Descripción del contenido</string>
|
||||
<string name="content_description_example">Un bote azul en una playa de arena blanca al atardecer</string>
|
||||
<string name="zap_type">Tipo de zap</string>
|
||||
<string name="zap_type_explainer">Tipo de zap para todas las opciones</string>
|
||||
<string name="zap_type_public">Público</string>
|
||||
<string name="zap_type_public_explainer">Todos pueden ver la transacción y el mensaje</string>
|
||||
<string name="zap_type_private">Privado</string>
|
||||
<string name="zap_type_private_explainer">El remitente y el destinatario pueden verse entre sí y leer el mensaje</string>
|
||||
<string name="zap_type_nonzap">Non-Zap</string>
|
||||
<string name="zap_type_nonzap_explainer">No hay rastro en Nostr, solo en Lightning</string>
|
||||
<string name="follow_list_mute_list">Lista de silenciados</string>
|
||||
<string name="connect_through_your_orbot_setup_markdown"> ## Conéctate a través de Tor con Orbot
|
||||
\n\n1. Instala [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android).
|
||||
\n2. Inicia Orbot.
|
||||
\n3. En Orbot, comprueba el puerto Socks. La opción predeterminada es 9050.
|
||||
\n4. Si es necesario, cambia el puerto en Orbot.
|
||||
\n5. Configura el puerto Socks en la pantalla.
|
||||
\n6. Presiona y activa el botón para usar Orbot como proxy.
|
||||
</string>
|
||||
<string name="orbot_socks_port">Puerto Socks de Orbot</string>
|
||||
<string name="invalid_port_number">Número de puerto inválido</string>
|
||||
<string name="use_orbot">Usar Orbot</string>
|
||||
<string name="disconnect_from_your_orbot_setup">Desconectar Tor/Orbot</string>
|
||||
<string name="app_notification_dms_channel_name">Mensajes privados</string>
|
||||
<string name="app_notification_dms_channel_description">Te notifica cuando llega un mensaje privado</string>
|
||||
<string name="app_notification_zaps_channel_name">Zaps recibidos</string>
|
||||
<string name="app_notification_zaps_channel_description">Te notifica cuando alguien te zapea</string>
|
||||
<string name="app_notification_zaps_channel_message">%1$s sats</string>
|
||||
<string name="app_notification_zaps_channel_message_from">De %1$s</string>
|
||||
<string name="app_notification_zaps_channel_message_for">para %1$s</string>
|
||||
<string name="reply_notify">Notificar: </string>
|
||||
<string name="channel_list_join_conversation">Unirse a la conversación</string>
|
||||
<string name="zapraiser">Zapraiser</string>
|
||||
<string name="zapraiser_explainer">Añade una meta de sats para recaudar por esta publicación. Los clientes compatibles pueden mostrarla como una barra de progreso para incentivar las donaciones.</string>
|
||||
<string name="zapraiser_target_amount_in_sats">Cantidad objetivo en sats</string>
|
||||
<string name="sats_to_complete">Zapraiser en %1$s. %2$s sats hasta la meta</string>
|
||||
<string name="read_from_relay">Relé de lectura</string>
|
||||
<string name="write_to_relay">Relé de escritura</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Se ha producido un error al intentar obtener información sobre el relé de %1$s</string>
|
||||
<string name="owner">Propietario</string>
|
||||
<string name="version">Versión</string>
|
||||
<string name="software">Software</string>
|
||||
<string name="contact">Contacto</string>
|
||||
<string name="supports">NIP compatibles</string>
|
||||
<string name="admission_fees">Tasas de admisión</string>
|
||||
<string name="payments_url">URL de pagos</string>
|
||||
<string name="limitations">Limitaciones</string>
|
||||
<string name="cashu_redeem">Canjear</string>
|
||||
<string name="cashu_redeem_to_zap">Enviar a monedero de zaps</string>
|
||||
<string name="cashu_redeem_to_cashu">Abrir en monedero de Cashu</string>
|
||||
<string name="cashu_copy_token">Copiar token</string>
|
||||
<string name="no_lightning_address_set">La dirección de Lightning no se ha configurado</string>
|
||||
<string name="copied_token_to_clipboard">Token copiado al portapapeles</string>
|
||||
<string name="live_stream_live_tag">EN VIVO</string>
|
||||
<string name="live_stream_offline_tag">FUERA DE LÍNEA</string>
|
||||
<string name="live_stream_ended_tag">TERMINÓ</string>
|
||||
<string name="live_stream_planned_tag">PROGRAMADO</string>
|
||||
<string name="live_stream_is_offline">La transmisión en vivo está fuera de línea</string>
|
||||
<string name="live_stream_has_ended">La transmisión en vivo ha terminado</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">Al cerrar la sesión se borra toda tu información local. Asegúrate de tener una copia de seguridad de tus claves privadas para que no pierdas la cuenta. ¿Quieres continuar?</string>
|
||||
<string name="followed_tags">Etiquetas seguidas</string>
|
||||
<string name="relay_setup">Relés</string>
|
||||
<string name="dark">Oscuro</string>
|
||||
<string name="application_preferences">Preferencias de la aplicación</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="theme">Tema</string>
|
||||
<string name="automatically_load_images_gifs">Vista previa de imagen</string>
|
||||
<string name="automatically_play_videos">Reproducción de vídeo</string>
|
||||
<string name="automatically_show_url_preview">Vista previa de URL</string>
|
||||
<string name="automatically_hide_nav_bars">Desplazamiento inmersivo</string>
|
||||
<string name="automatically_hide_nav_bars_description">Ocultar barras de navegación al desplazarse</string>
|
||||
<string name="load_image">Cargar imagen</string>
|
||||
<string name="spamming_users">Spammers</string>
|
||||
</resources>
|
||||
|
||||
@@ -194,12 +194,6 @@
|
||||
<string name="mark_all_new_as_read">Marcar todos los nuevos como leídos</string>
|
||||
<string name="mark_all_as_read">Marcar todos como leídos</string>
|
||||
<string name="backup_keys">Respaldar claves</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Consejos sobre la protección y la copia de seguridad de las claves
|
||||
\n\nTu cuenta está protegida por una clave secreta. Esta clave es una cadena aleatoria y larga que empieza por **nsec1**. Cualquiera que tenga acceso a tu clave secreta puede publicar contenido usando tu identidad.
|
||||
\n\n- **No** guardes la clave secreta en ningún sitio web o software en los que no confíes.
|
||||
\n- Los desarrolladores de Amethyst **nunca** te pedirán la clave secreta.
|
||||
\n- **Sí** puedes realizar una copia de seguridad de la clave secreta para recuperar la cuenta. Para ello, te recomendamos usar un gestor de contraseñas.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">Clave secreta (nsec) copiada al portapapeles</string>
|
||||
<string name="copy_my_secret_key">Copiar mi clave secreta</string>
|
||||
<string name="biometric_authentication_failed">Error de autenticación</string>
|
||||
@@ -367,7 +361,7 @@
|
||||
<string name="zapraiser">Zapraiser</string>
|
||||
<string name="zapraiser_explainer">Agrega una meta de sats para recaudar por esta publicación. Los clientes compatibles pueden mostrarla como una barra de progreso para incentivar las donaciones.</string>
|
||||
<string name="zapraiser_target_amount_in_sats">Cantidad objetivo en sats</string>
|
||||
<string name="sats_to_complete">Zapraiser a %1$s. %2$s sats hasta la meta</string>
|
||||
<string name="sats_to_complete">Zapraiser en %1$s. %2$s sats hasta la meta</string>
|
||||
<string name="read_from_relay">Relé de lectura</string>
|
||||
<string name="write_to_relay">Relé de escritura</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Se produjo un error al intentar obtener información sobre el relé de %1$s</string>
|
||||
@@ -532,6 +526,7 @@
|
||||
<string name="could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup">No se pudo ensamblar la LNUrl desde la dirección de Lightning \"%1$s\". Comprueba la configuración del usuario.</string>
|
||||
<string name="the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct">El servicio de Lightning del destinatario en %1$s no está disponible. Se calculó a partir de la dirección de Lightning \"%2$s\". Error: %3$s. Comprueba si el servidor está activo y si la dirección de Lightning es correcta.</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">No se pudo resolver %1$s. Comprueba si tienes conexión, si el servidor está activo y si la dirección de Lightning \"%2$s\" es correcta.</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">No se pudo resolver %1$s. Comprueba si tienes conexión, si el servidor está activo y si la dirección de Lightning \"%2$s\" es correcta..\n\nLa excepción fue: %3$s</string>
|
||||
<string name="could_not_fetch_invoice_from">No se pudo obtener la factura de %1$s</string>
|
||||
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Error al analizar JSON desde la dirección de Lightning. Comprueba la configuración de Lightning del usuario.</string>
|
||||
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">No se encontró la URL de devolución de llamada en la configuración del servidor de la dirección de Lightning del usuario.</string>
|
||||
|
||||
@@ -194,12 +194,6 @@
|
||||
<string name="mark_all_new_as_read">Marcar todos los nuevos como leídos</string>
|
||||
<string name="mark_all_as_read">Marcar todos como leídos</string>
|
||||
<string name="backup_keys">Respaldar claves</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Consejos sobre la protección y la copia de seguridad de las claves
|
||||
\n\nTu cuenta está protegida por una clave secreta. Esta clave es una cadena aleatoria y larga que empieza por **nsec1**. Cualquiera que tenga acceso a tu clave secreta puede publicar contenido usando tu identidad.
|
||||
\n\n- **No** guardes la clave secreta en ningún sitio web o software en los que no confíes.
|
||||
\n- Los desarrolladores de Amethyst **nunca** te pedirán la clave secreta.
|
||||
\n- **Sí** puedes realizar una copia de seguridad de la clave secreta para recuperar la cuenta. Para ello, te recomendamos usar un gestor de contraseñas.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">Clave secreta (nsec) copiada al portapapeles</string>
|
||||
<string name="copy_my_secret_key">Copiar mi clave secreta</string>
|
||||
<string name="biometric_authentication_failed">Error de autenticación</string>
|
||||
@@ -367,7 +361,7 @@
|
||||
<string name="zapraiser">Zapraiser</string>
|
||||
<string name="zapraiser_explainer">Agrega una meta de sats para recaudar por esta publicación. Los clientes compatibles pueden mostrarla como una barra de progreso para incentivar las donaciones.</string>
|
||||
<string name="zapraiser_target_amount_in_sats">Cantidad objetivo en sats</string>
|
||||
<string name="sats_to_complete">Zapraiser a %1$s. %2$s sats hasta la meta</string>
|
||||
<string name="sats_to_complete">Zapraiser en %1$s. %2$s sats hasta la meta</string>
|
||||
<string name="read_from_relay">Relé de lectura</string>
|
||||
<string name="write_to_relay">Relé de escritura</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Se produjo un error al intentar obtener información sobre el relé de %1$s</string>
|
||||
@@ -429,7 +423,7 @@
|
||||
<string name="language">Idioma</string>
|
||||
<string name="theme">Tema</string>
|
||||
<string name="automatically_load_images_gifs">Vista previa de imagen</string>
|
||||
<string name="automatically_play_videos">Reproducción de video</string>
|
||||
<string name="automatically_play_videos">Reproducción de vídeo</string>
|
||||
<string name="automatically_show_url_preview">Vista previa de URL</string>
|
||||
<string name="automatically_hide_nav_bars">Desplazamiento inmersivo</string>
|
||||
<string name="automatically_hide_nav_bars_description">Ocultar barras de navegación al desplazarse</string>
|
||||
@@ -532,6 +526,7 @@
|
||||
<string name="could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup">No se pudo ensamblar la LNUrl desde la dirección de Lightning \"%1$s\". Comprueba la configuración del usuario.</string>
|
||||
<string name="the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct">El servicio de Lightning del destinatario en %1$s no está disponible. Se calculó a partir de la dirección de Lightning \"%2$s\". Error: %3$s. Comprueba si el servidor está activo y si la dirección de Lightning es correcta.</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">No se pudo resolver %1$s. Comprueba si tienes conexión, si el servidor está activo y si la dirección de Lightning \"%2$s\" es correcta.</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">No se pudo resolver %1$s. Comprueba si tienes conexión, si el servidor está activo y si la dirección de Lightning \"%2$s\" es correcta..\n\nLa excepción fue: %3$s</string>
|
||||
<string name="could_not_fetch_invoice_from">No se pudo obtener la factura de %1$s</string>
|
||||
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Error al analizar JSON desde la dirección de Lightning. Comprueba la configuración de Lightning del usuario.</string>
|
||||
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">No se encontró la URL de devolución de llamada en la configuración del servidor de la dirección de Lightning del usuario.</string>
|
||||
|
||||
@@ -90,6 +90,8 @@
|
||||
<string name="add_a_relay">افزودن رله</string>
|
||||
<string name="display_name">نمایش نام</string>
|
||||
<string name="my_display_name">نام من</string>
|
||||
<string name="my_awesome_name">استریچ باحالیان</string>
|
||||
<string name="welcome">خوش آمدی استریچ!</string>
|
||||
<string name="username">نام کاربری</string>
|
||||
<string name="my_username">نام کاربری من</string>
|
||||
<string name="about_me">درباره من</string>
|
||||
@@ -143,7 +145,14 @@
|
||||
<string name="terms_of_use">شرایط استفاده</string>
|
||||
<string name="acceptance_of_terms_is_required">پذیرش شرایط الازمیست</string>
|
||||
<string name="key_is_required">کلید الزامیست</string>
|
||||
<string name="name_is_required">نام الزامی است</string>
|
||||
<string name="login">ورود</string>
|
||||
<string name="sign_up">ثبت نام</string>
|
||||
<string name="create_account">ایجاد حساب کاربری</string>
|
||||
<string name="how_should_we_call_you">شما را چی صدا بزنیم؟</string>
|
||||
<string name="don_t_have_an_account">حساب کاربری ناستر ندارید؟</string>
|
||||
<string name="already_have_an_account">از قبل حساب کاربری ناستر دارید؟</string>
|
||||
<string name="create_a_new_account">ایجاد حساب کاربری جدید</string>
|
||||
<string name="generate_a_new_key">ساختن جفت کلید جدید</string>
|
||||
<string name="loading_feed">در حال بارگیری خبرنامه</string>
|
||||
<string name="loading_account">بارگذاری حساب کاربری</string>
|
||||
@@ -185,12 +194,6 @@
|
||||
<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- سازندگان آماتیست **هرگز** از شما کلید خصوصی تان را نمی خواهند.
|
||||
\n- حتما یک کپی از کلید خصوصی را برای بازیابی حساب کاربری نزد خود نگه دارد. ما استفاده از یک نرم افزار مدیریت پسورد را توصیه می نماییم.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">کلید خصوصی در کلیپبورد کپی شد</string>
|
||||
<string name="copy_my_secret_key">کلید خصوصیم را کپی کن</string>
|
||||
<string name="biometric_authentication_failed">احراز هویت انجام نشد</string>
|
||||
@@ -318,6 +321,7 @@
|
||||
<string name="follow_list_selection">لیست دنبال ها</string>
|
||||
<string name="follow_list_kind3follows">همه دنبال ها</string>
|
||||
<string name="follow_list_global">همگانی</string>
|
||||
<string name="follow_list_mute_list">لیست خموش</string>
|
||||
<string name="connect_through_your_orbot_setup_markdown"> ## از طریق Tor با Orbot متصل شوید
|
||||
\n\n1. نصب کنید [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)
|
||||
\n2. شروع Orbot
|
||||
@@ -385,6 +389,9 @@
|
||||
<string name="payment">پرداخت</string>
|
||||
<string name="cashu">توکن Cashu</string>
|
||||
<string name="cashu_redeem">بازپرداخت</string>
|
||||
<string name="cashu_redeem_to_zap">ارسال به کیف پول زپ</string>
|
||||
<string name="cashu_redeem_to_cashu">باز کردن در کیف پول Cashu</string>
|
||||
<string name="cashu_copy_token">کپی کردن توکن</string>
|
||||
<string name="no_lightning_address_set">آدرس لایتنینگ تنظیم نشده است</string>
|
||||
<string name="copied_token_to_clipboard">توکن به کلیپبورد کپی شد</string>
|
||||
<string name="live_stream_live_tag">زنده</string>
|
||||
@@ -396,6 +403,7 @@
|
||||
<string name="are_you_sure_you_want_to_log_out">خروج همه اطلاعات محلی شما را پاک می کند. مطمئن شوید که کلید خصوصی خود را بکاپ گرفته و ذخیره کرده اید تا حساب کاربری تان را از دست ندهید. می خواهید ادامه دهید؟</string>
|
||||
<string name="followed_tags">برچسب های دنبال شده</string>
|
||||
<string name="relay_setup">رله ها</string>
|
||||
<string name="discover_marketplace">بازار</string>
|
||||
<string name="discover_live">زنده</string>
|
||||
<string name="discover_community">انجمن</string>
|
||||
<string name="discover_chat">گپ</string>
|
||||
@@ -454,6 +462,7 @@
|
||||
<string name="automatically_play_videos_description">پخش خودکار ویدئوها و جیف ها</string>
|
||||
<string name="automatically_show_url_preview_description">نشان دادن پیش نمایش URL</string>
|
||||
<string name="load_image_description">هنگام بارگیری تصاویر</string>
|
||||
<string name="copy_to_clipboard">کپی به کلیپبورد</string>
|
||||
<string name="copy_url_to_clipboard">کپی URL به کلیپبورد</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">کپی شناسه یادداشت به کلیپبورد</string>
|
||||
<string name="created_at">ایجاد شده در</string>
|
||||
@@ -489,6 +498,9 @@
|
||||
<string name="paid">پرداخت شد</string>
|
||||
<string name="wallet_number">کیف پول %1$s</string>
|
||||
<string name="error_opening_external_signer">خطا در بازکردن اپلیکیشن امضا کننده</string>
|
||||
<string name="error_opening_external_signer_description">اپ امضا کننده یافت نشد. بررسی کنید که اپ حذف نشده باشد.</string>
|
||||
<string name="sign_request_rejected">اپ امضا کننده رد کرد</string>
|
||||
<string name="sign_request_rejected_description">مطمئن شوید که اپ امضا کننده این تراکنش را تایید کرده است</string>
|
||||
<string name="no_wallet_found_with_error">هیچ کیف پولی برای پرداخت صورتحساب لایتنینگ یافت نشد (خطا: %1$s). لطفا کیف پول لایتنینگی برای استفاده از زپ نصب کنید</string>
|
||||
<string name="no_wallet_found">هیچ کیف پولی برای پرداخت صورتحساب لایتنینگ یافت نشد. لطفا یک کیف پول لایتنینگی برای استفاده از زپ نصب کنید</string>
|
||||
<string name="hidden_words">کلمات پنهان</string>
|
||||
@@ -506,6 +518,7 @@
|
||||
<string name="cashu_failed_redemption_explainer_already_spent">توکن Cashu خرج شده است.</string>
|
||||
<string name="cashu_successful_redemption">Cashu دریافت شد</string>
|
||||
<string name="cashu_successful_redemption_explainer">%1$s ساتوشی به کیف پول شما ارسال شد. (کارمزد: %2$s ساتوشی)</string>
|
||||
<string name="cashu_no_wallet_found">هیچ کیف پول Cashu سازگاری در سیستم یافت نشد</string>
|
||||
<string name="error_unable_to_fetch_invoice">صورتحساب از سرور گیرنده دریافت نشد</string>
|
||||
<string name="wallet_connect_pay_invoice_error_error">ارائه دهنده اتصال کیف پول شما خطای روبرو را داد: %1$s</string>
|
||||
<string name="could_not_connect_to_tor">نمی توان به Tor وصل شد</string>
|
||||
@@ -513,6 +526,7 @@
|
||||
<string name="could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup">LNUrl از آدرس لایتنینگ \"%1$s\" بدست نیامد. تنظیمات کاربر را بررسی کنید.</string>
|
||||
<string name="the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct">خدمات لایتنینگ %1$s گیرنده دردسترس نیست. از آدرس لایتنینگ \"%2$s\" محاسبه شد. خطا: %3$s. بررسی کنید آیا سرور کار می کند و آیا آدرس لایتنینگ صحیح است.</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">مشکل %1$s حل نشد. بررسی کنید که به اینترنت متصل باشید، سرور کار کند و آدرس لایتنینگ %2$s صحیح باشد.</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">مشکل %1$s حل نشد. بررسی کنید که به اینترنت متصل باشید، سرور کار کند و آدرس لایتنینگ %2$s صحیح باشد. \n\nخطای %3$s</string>
|
||||
<string name="could_not_fetch_invoice_from">صورتحساب از %1$s گرفته نشد</string>
|
||||
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">خطا در تفسیر JSON از آدرس لایتنینگ. تنظیمات لایتنینگ کاربر را بررسی کنید.</string>
|
||||
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">URL بازخوانی در پیکربندی سرور آدرس لایتنینگ یافت نشد</string>
|
||||
@@ -532,4 +546,49 @@
|
||||
<string name="push_server_install_app_description">برای دریافت اعلان ها، می توانید هر اپلیکیشنی که از [Unified Push](https://unifiedpush.org/) پشتیبانی می کند نصب کنید، مانند[Nfty](https://ntfy.sh/).
|
||||
پس از نصب، اپلیکیشنی را که می خواهید استفاده کنید در تنظیمات انتخاب کنید.
|
||||
</string>
|
||||
<string name="payment_required_title">پیغام از طرف %1$s</string>
|
||||
<string name="thread_title">رسته</string>
|
||||
<string name="send_the_seller_a_message">به فروشنده پیغام دهید</string>
|
||||
<string name="hi_seller_is_this_still_available">سلام %1$s، هنوز موجوده؟</string>
|
||||
<string name="hi_there_is_this_still_available">سلام، هنوز موجوده؟</string>
|
||||
<string name="classifieds">چیزی بفروشید</string>
|
||||
<string name="classifieds_title">عنوان</string>
|
||||
<string name="classifieds_title_placeholder">آیفون 13</string>
|
||||
<string name="classifieds_condition">شرایط</string>
|
||||
<string name="classifieds_category">دسته بندی</string>
|
||||
<string name="classifieds_price">قیمت (به ساتوشی)</string>
|
||||
<string name="classifieds_price_placeholder">1000</string>
|
||||
<string name="classifieds_location">موقعیت مکانی</string>
|
||||
<string name="classifieds_location_placeholder">شهر، استان، کشور</string>
|
||||
<string name="classifieds_condition_new">جدید</string>
|
||||
<string name="classifieds_condition_new_explainer">این مورد نو هست، در بسته بندی اصلی</string>
|
||||
<string name="classifieds_condition_like_new">در حد نو</string>
|
||||
<string name="classifieds_condition_like_new_explainer">دست دوم است، اما ظاهرش تمیز است</string>
|
||||
<string name="classifieds_condition_good">خوب</string>
|
||||
<string name="classifieds_condition_good_explainer">کمی در ظاهر نشان می دهد که دست دوم است</string>
|
||||
<string name="classifieds_condition_fair">منصفانه</string>
|
||||
<string name="classifieds_condition_fair_explainer">هنوز مناسب است و کار می کند</string>
|
||||
<string name="classifieds_category_clothing">پوشاک</string>
|
||||
<string name="classifieds_category_accessories">لوازم جانبی</string>
|
||||
<string name="classifieds_category_electronics">الکترونیکی</string>
|
||||
<string name="classifieds_category_furniture">مبلمان</string>
|
||||
<string name="classifieds_category_collectibles">یادگاری</string>
|
||||
<string name="classifieds_category_books">کتاب</string>
|
||||
<string name="classifieds_category_pets">حیوانات خانگی</string>
|
||||
<string name="classifieds_category_sports">ورزش</string>
|
||||
<string name="classifieds_category_fitness">تناسب اندام</string>
|
||||
<string name="classifieds_category_art">هنر</string>
|
||||
<string name="classifieds_category_crafts">دست سازه</string>
|
||||
<string name="classifieds_category_home">خانه</string>
|
||||
<string name="classifieds_category_office">اداره</string>
|
||||
<string name="classifieds_category_food">خوراکی</string>
|
||||
<string name="classifieds_category_misc">گوناگون</string>
|
||||
<string name="classifieds_category_other">سایر</string>
|
||||
<string name="failed_to_upload_media_no_details">رسانه بارگذاری نشد</string>
|
||||
<string name="could_not_open_the_compressed_file">فایل فشرده باز نشد</string>
|
||||
<string name="error_when_compressing_media">خطا در فشرده سازی رسانه: %1$s</string>
|
||||
<string name="failed_to_upload_media">خطای بارگیری: %1$s</string>
|
||||
<string name="server_did_not_provide_a_url_after_uploading">سرور پس از بارگذاری URL نداد</string>
|
||||
<string name="could_not_download_from_the_server">فایل بارگذاری شده از سرور بارگیری نشد</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">فایل محلی برای بارگذاری آماده نشد: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -185,12 +185,6 @@
|
||||
<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>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<string name="point_to_the_qr_code">Pointer vers le QR code</string>
|
||||
<string name="show_qr">Montrer le QR code</string>
|
||||
<string name="profile_image">Image de profil</string>
|
||||
<string name="your_profile_image">Votre Photo de Profil</string>
|
||||
<string name="scan_qr">Scanner le QR code</string>
|
||||
<string name="show_anyway">Montrer quand même</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Post signalé inapproprié par</string>
|
||||
@@ -90,6 +91,8 @@
|
||||
<string name="add_a_relay">Ajouter un relais</string>
|
||||
<string name="display_name">Nom visible du public</string>
|
||||
<string name="my_display_name">Mon nom visible du public</string>
|
||||
<string name="my_awesome_name">Ostrich McGénial</string>
|
||||
<string name="welcome">Bienvenue Ostrich!</string>
|
||||
<string name="username">Nom d\'utilisateur</string>
|
||||
<string name="my_username">Mon nom d\'utilisateur</string>
|
||||
<string name="about_me">Biographie</string>
|
||||
@@ -136,12 +139,15 @@
|
||||
<string name="clear">Nettoyer</string>
|
||||
<string name="app_logo">Logo de l\'App</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec / npub / hex private key</string>
|
||||
<string name="ncryptsec_password">mot de passe pour ouvrir la clé</string>
|
||||
<string name="show_password">Révéler le mot de passe</string>
|
||||
<string name="hide_password">Cacher le mot de passe</string>
|
||||
<string name="invalid_key">Clé non valide</string>
|
||||
<string name="invalid_key_with_message">Clé invalide : %1$s</string>
|
||||
<string name="i_accept_the">"J'accepte les "</string>
|
||||
<string name="terms_of_use">conditions d\'utilisation</string>
|
||||
<string name="acceptance_of_terms_is_required">L\'acceptation des conditions est requise</string>
|
||||
<string name="password_is_required">Mot de passe requis</string>
|
||||
<string name="key_is_required">La clé est requise</string>
|
||||
<string name="name_is_required">Un nom est requis</string>
|
||||
<string name="login">Connexion</string>
|
||||
@@ -192,14 +198,19 @@
|
||||
<string name="mark_all_new_as_read">Marquer tous les Nouveaux comme lu</string>
|
||||
<string name="mark_all_as_read">Tout marquer comme lu</string>
|
||||
<string name="backup_keys">Clés de sauvegarde</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Sauvegarde des clés et conseils de sécurité
|
||||
\n\nVotre compte est sécurisé par une clé secrète. La clé est une longue chaîne aléatoire commençant par **nsec1**. Toute personne ayant accès à votre clé secrète peut publier du contenu en utilisant votre identité.
|
||||
\n\n- Ne communiquez **jamais** votre clé secrète à un site Web ou un logiciel auquel vous ne faites pas confiance.
|
||||
<string name="account_backup_tips2_md" tools:ignore="Typos"> ## Sauvegarde des Clés et Conseils de Sécurité
|
||||
\n\nVotre compte est sécurisé par une clé secrète. La clé est une longue séquence de caractères commençant par **nsec1**. Toute personne ayant accès à cette clé secrète peut publier et modifier votre identité.
|
||||
\n\n- Ne mettez **pas** votre clé secrète sur un site web ou un logiciel auquel vous ne faites pas confiance.
|
||||
\n- Les développeurs d\'Amethyst ne vous demanderont **jamais** votre clé secrète.
|
||||
\n- **Conservez** une sauvegarde sécurisée de votre clé secrète pour la récupération de compte. Nous vous recommandons d\'utiliser un gestionnaire de mots de passe.
|
||||
\n- **Faites** une sauvegarde sécurisée de votre clé secrète pour la récupération de votre compte. Nous vous recommandons d\'utiliser un gestionnaire de mots de passe.
|
||||
</string>
|
||||
<string name="account_backup_tips3_md" tools:ignore="Typos"> Pour plus de sécurité, vous pouvez chiffrer votre clé avec un mot de passe. Cette clé commence par **ncryptsec1** et ne peut être utilisée sans votre mot de passe.
|
||||
\n\nSi vous perdez votre mot de passe, vous ne pourrez pas récupérer votre clé.
|
||||
</string>
|
||||
<string name="failed_to_encrypt_key">Échec du chiffrement de votre clé privée</string>
|
||||
<string name="secret_key_copied_to_clipboard">Clé secrète (nsec) copiée dans le presse-papiers</string>
|
||||
<string name="copy_my_secret_key">Copier ma clé secrète</string>
|
||||
<string name="encrypt_and_copy_my_secret_key">Chiffrer et copier ma clé secrète</string>
|
||||
<string name="biometric_authentication_failed">Échec de l\'authentification</string>
|
||||
<string name="biometric_authentication_failed_explainer">La biométrie n\'a pas pu authentifier le propriétaire de ce téléphone</string>
|
||||
<string name="biometric_authentication_failed_explainer_with_error">La biométrie n\'a pas pu authentifier le propriétaire de ce téléphone. Erreur : %1$s</string>
|
||||
@@ -393,6 +404,8 @@
|
||||
<string name="payment">Paiement</string>
|
||||
<string name="cashu">Jeton Cashu</string>
|
||||
<string name="cashu_redeem">Échanger</string>
|
||||
<string name="cashu_redeem_to_zap">Envoyer vers le portefeuille Zap</string>
|
||||
<string name="cashu_redeem_to_cashu">Ouvrir dans le portefeuille Cashu</string>
|
||||
<string name="cashu_copy_token">Copier le Jeton</string>
|
||||
<string name="no_lightning_address_set">Adresse Lightning non définie</string>
|
||||
<string name="copied_token_to_clipboard">Jeton copié dans le presse-papiers</string>
|
||||
@@ -449,6 +462,7 @@
|
||||
<string name="new_feature_nip24_might_not_be_available_description">Pour activer ce mode, Amethyst doit envoyer un message NIP-24 (GiftWrapped, Sealed Direct et Group Messages). Le protocole NIP-24 est nouveau et la plupart des clients ne l\'ont pas encore mis en oeuvre. Assurez-vous que le destinataire utilise un client compatible.</string>
|
||||
<string name="new_feature_nip24_activate">Activer</string>
|
||||
<string name="messages_create_public_chat">Public</string>
|
||||
<string name="messages_create_public_private_chat_desription">Nouveau Groupe Public ou Privé</string>
|
||||
<string name="messages_new_message">Privé</string>
|
||||
<string name="messages_new_message_to">À</string>
|
||||
<string name="messages_new_message_subject">Sujet</string>
|
||||
@@ -465,6 +479,7 @@
|
||||
<string name="automatically_show_url_preview_description">Afficher la prévisualisation d\'URL</string>
|
||||
<string name="load_image_description">Quand charger les images</string>
|
||||
<string name="copy_to_clipboard">Copier dans le presse-papiers</string>
|
||||
<string name="copy_npub_to_clipboard">Copier npub dans le presse-papiers</string>
|
||||
<string name="copy_url_to_clipboard">Copier l\'URL dans le presse-papiers</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Copier l\'ID de la note dans le presse-papiers</string>
|
||||
<string name="created_at">Créé le</string>
|
||||
@@ -513,14 +528,22 @@
|
||||
<string name="error_dialog_pay_invoice_error">Impossible de payer la facture</string>
|
||||
<string name="error_dialog_pay_withdraw_error">Impossible de retirer</string>
|
||||
<string name="error_parsing_nip47_title">Impossible de configurer Wallet Connect</string>
|
||||
<string name="error_parsing_nip47">Erreur lors de l\'analyse de l\'événement de connexion NIP-47. Vérifiez si tout est correct avec le prestataire de votre Portefeuille : %1$s. Erreur : %2$s</string>
|
||||
<string name="error_parsing_nip47_no_error">Erreur lors de l\'analyse de l\'événement de connexion NIP-47. Vérifiez si tout est correct avec le prestataire de votre Portefeuille : %1$s.</string>
|
||||
<string name="cashu_failed_redemption">Impossible d\'utiliser ce Cashu</string>
|
||||
<string name="cashu_failed_redemption_explainer_error_msg">Le Mint a fourni le message d\'erreur suivant : %1$s</string>
|
||||
<string name="cashu_failed_redemption_explainer_already_spent">Les jetons de cashu sont déjà dépensés.</string>
|
||||
<string name="cashu_successful_redemption">Cashu reçu</string>
|
||||
<string name="cashu_successful_redemption_explainer">%1$s sats ont été envoyés à votre portefeuille. (Frais : %2$s sats)</string>
|
||||
<string name="cashu_no_wallet_found">Aucun portefeuille Cashu compatible trouvé dans le système</string>
|
||||
<string name="error_unable_to_fetch_invoice">Impossible de récupérer la facture depuis les serveurs du destinataire</string>
|
||||
<string name="wallet_connect_pay_invoice_error_error">Le fournisseur de connexion de votre portefeuille a retourné l\'erreur suivante : %1$s</string>
|
||||
<string name="could_not_connect_to_tor">Impossible de se connecter à Tor</string>
|
||||
<string name="unable_to_download_relay_document">Téléchargement du document relais indisponible</string>
|
||||
<string name="could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup">Impossible d\'assembler LNUrl à partir de l\'adresse Lightning \"%1$s\". Vérifiez la configuration de l\'utilisateur</string>
|
||||
<string name="the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct">Le service lightning du récepteur à %1$s n\'est pas disponible. Il a été calculé à partir de l\'adresse lightning \"%2$s\". Erreur: %3$s. Vérifiez si le serveur est en ligne et si l\'adresse lightning est correcte</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Impossible de résoudre %1$s. Vérifiez si vous êtes connecté, si le serveur est en ligne et si l\'adresse lightning %2$s est correcte</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Impossible de résoudre %1$s. Vérifiez si vous êtes connecté, si le serveur est actif et si l\'adresse Lightning %2$s est correcte.\n\nL\'erreur était : %3$s</string>
|
||||
<string name="could_not_fetch_invoice_from">Impossible de récupérer la facture depuis %1$s</string>
|
||||
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Erreur lors de l\'analyse du JSON à partir de l\'Adresse Lightning. Vérifiez la configuration lightning de l\'utilisateur</string>
|
||||
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">URL de callback introuvable dans la configuration serveur de l\'adresse lightning de l\'utilisateur</string>
|
||||
@@ -585,4 +608,44 @@
|
||||
<string name="server_did_not_provide_a_url_after_uploading">Le serveur n\'a pas fourni d\'URL après le téléversement</string>
|
||||
<string name="could_not_download_from_the_server">Impossible de télécharger le média depuis le serveur</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">Impossible de préparer le fichier local à téléverser: %1$s</string>
|
||||
<string name="login_with_qr_code">Se connecter avec un QR Code</string>
|
||||
<string name="route">Route</string>
|
||||
<string name="route_home">Accueil</string>
|
||||
<string name="route_search">Rechercher</string>
|
||||
<string name="route_discover">Découvrir</string>
|
||||
<string name="route_messages">Messages</string>
|
||||
<string name="route_notifications">Notifications</string>
|
||||
<string name="route_global">Général</string>
|
||||
<string name="route_video">Shorts</string>
|
||||
<string name="route_security_filters">Filtres de Sécurité</string>
|
||||
<string name="new_post">Nouveau Message</string>
|
||||
<string name="new_short">Nouveaux Shorts : images ou vidéos</string>
|
||||
<string name="new_community_note">Nouvelle Note Communautaire</string>
|
||||
<string name="open_all_reactions_to_this_post">Ouvrir toutes les réactions à ce message</string>
|
||||
<string name="close_all_reactions_to_this_post">Fermer toutes les réactions à ce message</string>
|
||||
<string name="reply_description">Répondre</string>
|
||||
<string name="boost_or_quote_description">Booster ou Citer</string>
|
||||
<string name="like_description">J\'aime</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="profile_image_of_user">Photo de Profil de %1$s</string>
|
||||
<string name="relay_info">Relais %1$s</string>
|
||||
<string name="expand_relay_list">Développer la liste des relais</string>
|
||||
<string name="note_options">Options de la note</string>
|
||||
<string name="relay_list_selector">Sélecteur de liste de relais</string>
|
||||
<string name="poll">Sondage</string>
|
||||
<string name="disable_poll">Désactiver le Sondage</string>
|
||||
<string name="add_bitcoin_invoice">Facture Bitcoin</string>
|
||||
<string name="cancel_bitcoin_invoice">Annuler la Facture Bitcoin</string>
|
||||
<string name="cancel_classifieds">Annuler la Vente d\'un Objet</string>
|
||||
<string name="add_zapraiser">Collecte de Zaps</string>
|
||||
<string name="cancel_zapraiser">Annuler la Collecte de Zaps</string>
|
||||
<string name="add_location">Emplacement</string>
|
||||
<string name="remove_location">Retirer l\'Emplacement</string>
|
||||
<string name="add_zap_split">Partage des Zaps</string>
|
||||
<string name="cancel_zap_split">Annuler le partage des Zaps</string>
|
||||
<string name="add_content_warning">Ajouter un avertissement de contenu</string>
|
||||
<string name="remove_content_warning">Retirer l\'avertissement de contenu</string>
|
||||
<string name="show_npub_as_a_qr_code">Afficher npub en tant que QR code</string>
|
||||
<string name="invalid_nip19_uri">Adresse invalide</string>
|
||||
<string name="invalid_nip19_uri_description">Amethyst a reçu une URI à ouvrir mais cette URI était invalide : %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<string name="point_to_the_qr_code">Mutass a QR kódra</string>
|
||||
<string name="show_qr">QR kód megjelenítése</string>
|
||||
<string name="profile_image">Profil kép</string>
|
||||
<string name="your_profile_image">Profilkép</string>
|
||||
<string name="scan_qr">QR kód beolvasása</string>
|
||||
<string name="show_anyway">Mutasd</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">A bejegyzést nem megfelelőként jelölte meg</string>
|
||||
@@ -194,12 +195,6 @@
|
||||
<string name="mark_all_new_as_read">Az összes új megjelölése olvasottként</string>
|
||||
<string name="mark_all_as_read">Összes megjelölése olvasottként</string>
|
||||
<string name="backup_keys">Biztonsági Kulcsok</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Kulcs- és biztonsági mentési tippek
|
||||
\n\nFiókját titkos kulcs védi. A kulcs egy hosszú véletlenszerű karakterlánc, amely **nsec1**-el kezdődik. Bárki, aki az Ön titkos kulcsához hozzáfér, az Ön személyazonosságának használatával bármilyen tartalmat közzétehet.
|
||||
\n\n- **Ne** helyezze el titkos kulcsát olyan webhelyen vagy szoftverben, amelyben nem bízik.
|
||||
\n- Az Amethyst fejlesztők a titkos kulcsodat **soha** nem fogják elkérni.
|
||||
\n- A fiók-helyreállításhoz, titkos kulcsáról **mindig** készítsen biztonságos biztonsági másolatot. Javasoljuk a jelszókezelő használatát.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">A titkos kulcs (nsec) a vágólapra másolva</string>
|
||||
<string name="copy_my_secret_key">A titkos kulcsom másolása</string>
|
||||
<string name="biometric_authentication_failed">Hitelesítés nem sikerült</string>
|
||||
@@ -453,6 +448,7 @@
|
||||
<string name="new_feature_nip24_might_not_be_available_description">Az Amethystnek ennek a módnak az aktiválásához NIP-24 üzenetet kell küldenie (GiftWrapped, Zárolt direkt és csoportos üzeneteket). A NIP-24 új, és a legtöbb kliens még nem implementálta. Győződj meg arról, hogy a fogadó fél kompatibilis klienst használ.</string>
|
||||
<string name="new_feature_nip24_activate">Aktiválás</string>
|
||||
<string name="messages_create_public_chat">Publikus</string>
|
||||
<string name="messages_create_public_private_chat_desription">Új nyilvános vagy privát csoport</string>
|
||||
<string name="messages_new_message">Privát</string>
|
||||
<string name="messages_new_message_to">Címzett</string>
|
||||
<string name="messages_new_message_subject">Téma</string>
|
||||
@@ -469,6 +465,7 @@
|
||||
<string name="automatically_show_url_preview_description">URL előnézetek megjelenítése</string>
|
||||
<string name="load_image_description">Mikor kell a képeket betölteni</string>
|
||||
<string name="copy_to_clipboard">Másolás a vágólapra</string>
|
||||
<string name="copy_npub_to_clipboard">Npub másolása a vágólapra</string>
|
||||
<string name="copy_url_to_clipboard">Az URL vágólapra másolása</string>
|
||||
<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>
|
||||
@@ -532,6 +529,7 @@
|
||||
<string name="could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup">Nem sikerült az LNUrl-t a(z) \"%1$s\" Lightning-címről összeállítani. Ellenőrizd a felhasználó beállítását</string>
|
||||
<string name="the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct">A fogadó lightning szolgáltatása itt: %1$s nem érhető el. A \"%2$s\" lightning címből lett kiszámítva. Hiba: %3$s. Ellenőrizd, hogy a szerver működik-e, és hogy a lightning cím helyes-e</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Nem sikerült megoldani: %1$s. Ellenőrizd, hogy csatlakozol-e, működik-e a szerver, és hogy a %2$s lightning cím helyes-e</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Nem sikerült megoldani: %1$s. Ellenőrizd, hogy csatlakozol-e, működik-e a szerver, és hogy a %2$s lightning cím helyes-e.\n\nKivétel: %3$s</string>
|
||||
<string name="could_not_fetch_invoice_from">Nem sikerült a számlát a következőtől: %1$s lekérni</string>
|
||||
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Hiba a Lightning-címből származó JSON elemzésekor. Ellenőrizd a felhasználó Lightning beállítását</string>
|
||||
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">A visszahívási URL a felhasználó Lightning címszerver konfigurációjában nem található</string>
|
||||
@@ -596,4 +594,44 @@
|
||||
<string name="server_did_not_provide_a_url_after_uploading">A szerver a feltöltés után nem adott URL-t</string>
|
||||
<string name="could_not_download_from_the_server">Nem sikerült a szerverről a feltöltött médiát letölteni</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">Nem sikerült előkészíteni a helyi fájlt a feltöltésre: %1$s</string>
|
||||
<string name="login_with_qr_code">Bejelentkezés QR kóddal</string>
|
||||
<string name="route">Útvonal</string>
|
||||
<string name="route_home">Főoldal</string>
|
||||
<string name="route_search">Keresés</string>
|
||||
<string name="route_discover">Felfedezés</string>
|
||||
<string name="route_messages">Üzenetek</string>
|
||||
<string name="route_notifications">Értesítések</string>
|
||||
<string name="route_global">Globális</string>
|
||||
<string name="route_video">Rövidfilmek</string>
|
||||
<string name="route_security_filters">Biztonsági szűrők</string>
|
||||
<string name="new_post">Új bejegyzés</string>
|
||||
<string name="new_short">Új rövidfilmek: képek vagy videók</string>
|
||||
<string name="new_community_note">Új közösségi bejegyzés</string>
|
||||
<string name="open_all_reactions_to_this_post">Nyissa meg a bejegyzésre adott összes reakciót</string>
|
||||
<string name="close_all_reactions_to_this_post">Zárja be a bejegyzésre adott összes reakciót</string>
|
||||
<string name="reply_description">Válasz</string>
|
||||
<string name="boost_or_quote_description">Megosztás vagy Idézés</string>
|
||||
<string name="like_description">Like</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="profile_image_of_user">%1$s profilképe</string>
|
||||
<string name="relay_info">%1$s csomópont</string>
|
||||
<string name="expand_relay_list">Csomópont lista kibontása</string>
|
||||
<string name="note_options">Bejegyzés opciók</string>
|
||||
<string name="relay_list_selector">Csomópont lista választó</string>
|
||||
<string name="poll">Szavazás</string>
|
||||
<string name="disable_poll">Szavazás kikapcsolása</string>
|
||||
<string name="add_bitcoin_invoice">Bitcoin számla</string>
|
||||
<string name="cancel_bitcoin_invoice">Bitcoin számla visszavonása</string>
|
||||
<string name="cancel_classifieds">Egy tétel eladásának visszavonása</string>
|
||||
<string name="add_zapraiser">ZapGyűjtés</string>
|
||||
<string name="cancel_zapraiser">ZapGyűjtés visszavonása</string>
|
||||
<string name="add_location">Pozíció</string>
|
||||
<string name="remove_location">Pozíció törlése</string>
|
||||
<string name="add_zap_split">Zap megosztások</string>
|
||||
<string name="cancel_zap_split">Zap megosztások visszavonása</string>
|
||||
<string name="add_content_warning">Tartalomra vonatkozó figyelmeztetés</string>
|
||||
<string name="remove_content_warning">Tartalomra vonatkozó figyelmeztetés visszavonása</string>
|
||||
<string name="show_npub_as_a_qr_code">Az npub megjelenítése QR-kódként</string>
|
||||
<string name="invalid_nip19_uri">Érvénytelen cím</string>
|
||||
<string name="invalid_nip19_uri_description">Az Amethyst kapott egy URI-t a megnyitáshoz, de az érvénytelen volt: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -185,12 +185,6 @@
|
||||
<string name="mark_all_new_as_read">Tandai semua yang baru sebagai telah dibaca</string>
|
||||
<string name="mark_all_as_read">Tandai semua sebagai telah dibaca</string>
|
||||
<string name="backup_keys">Kunci Cadangan</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Kiat Pencadangan dan Keamanan Kunci
|
||||
\n\nAkun Anda diamankan dengan kunci rahasia. Kunci ini berupa string acak panjang yang dimulai dengan **nsec1**. Siapa pun yang memiliki akses ke kunci rahasia Anda dapat mempublikasikan konten menggunakan identitas Anda.
|
||||
\n\n- **Jangan** memasukkan kunci rahasia Anda ke situs web atau aplikasi apa pun yang tidak Anda percayai.
|
||||
\n- Pengembang Amethyst **tidak akan pernah** meminta kunci rahasia Anda.
|
||||
\n- **Simpanlah** cadangan kunci rahasia Anda dengan aman untuk pemulihan akun. Kami sarankan menggunakan aplikasi pengelola kata sandi.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">Kunci rahasia (nsec) telah disalin ke papan klip</string>
|
||||
<string name="copy_my_secret_key">Salin kunci rahasia saya</string>
|
||||
<string name="biometric_authentication_failed">Autentikasi gagal</string>
|
||||
|
||||
@@ -182,7 +182,6 @@
|
||||
<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>
|
||||
|
||||
@@ -182,12 +182,6 @@
|
||||
<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>
|
||||
|
||||
@@ -177,12 +177,6 @@
|
||||
<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"> ## 鍵のバックアップと安全に利用するためのTips
|
||||
\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>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<string name="point_to_the_qr_code">Richt camera op de QR-code</string>
|
||||
<string name="show_qr">Toon QR</string>
|
||||
<string name="profile_image">Profielfoto</string>
|
||||
<string name="your_profile_image">Jouw profielfoto</string>
|
||||
<string name="scan_qr">Scan QR</string>
|
||||
<string name="show_anyway">Laat toch zien</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Bericht gemarkeerd als ongepast door</string>
|
||||
@@ -194,12 +195,6 @@
|
||||
<string name="mark_all_new_as_read">Nieuwe markeren als gelezen</string>
|
||||
<string name="mark_all_as_read">Alles markeren als gelezen</string>
|
||||
<string name="backup_keys">Back-up sleutels</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Sleutel back-up en veiligheidstips
|
||||
\n\nUw account is beveiligd met een privésleutel. De sleutel is een lange, willekeurige reeks die begint met **nsec1**. Iedereen die toegang heeft tot uw privésleutel kan inhoud publiceren met uw identiteit.
|
||||
\n\n- Voer uw privésleutel **nooit** in een website of software die u niet vertrouwt.
|
||||
\n- Amethyst ontwikkelaars zullen u **nooit** om uw privésleutel vragen.
|
||||
\n- **Bewaar** een veilige back-up van uw privésleutel voor accountherstel. Wij raden u aan een wachtwoordmanager te gebruiken.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">Privésleutel (nsec) gekopieerd naar klembord</string>
|
||||
<string name="copy_my_secret_key">Kopieer mijn privésleutel</string>
|
||||
<string name="biometric_authentication_failed">Authenticatie mislukt</string>
|
||||
@@ -453,6 +448,7 @@
|
||||
<string name="new_feature_nip24_might_not_be_available_description">Voor het activeren van deze modus is Amethyst nodig om een NIP-24 bericht te versturen. NIP-24 is nieuw en de meeste clients hebben deze nog niet geïmplementeerd. Zorg ervoor dat de ontvanger een compatibele client gebruikt.</string>
|
||||
<string name="new_feature_nip24_activate">Activeren</string>
|
||||
<string name="messages_create_public_chat">Publiek</string>
|
||||
<string name="messages_create_public_private_chat_desription">Nieuwe openbare of besloten groep</string>
|
||||
<string name="messages_new_message">Privé</string>
|
||||
<string name="messages_new_message_to">Aan</string>
|
||||
<string name="messages_new_message_subject">Onderwerp</string>
|
||||
@@ -469,6 +465,7 @@
|
||||
<string name="automatically_show_url_preview_description">Toon URL-voorbeelden</string>
|
||||
<string name="load_image_description">Wanneer afbeeldingen te laden</string>
|
||||
<string name="copy_to_clipboard">Kopiëren naar klembord</string>
|
||||
<string name="copy_npub_to_clipboard">Kopieer npub naar klembord</string>
|
||||
<string name="copy_url_to_clipboard">Kopieer URL naar klembord</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Note naar klembord kopiëren</string>
|
||||
<string name="created_at">Gemaakt op</string>
|
||||
@@ -532,6 +529,7 @@
|
||||
<string name="could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup">Kon LNUrl niet samenbrengen van Lightning Adress \"%1$s\". Controleer de instellingen van de gebruiker</string>
|
||||
<string name="the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct">De Lightning service van de ontvanger bij %1$s is niet beschikbaar. Het werd berekend op basis van het Lightning Adress \"%2$s\". Fout: %3$s. Controleer of de server klaar is en of het Lightning Adress juist is</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Kan %1$s niet oplossen. Controleer of u verbonden bent, of de server online is en of het Lightning Adress %2$s juist is</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Kon %1$s niet oplossen. Controleer of u verbonden bent, of de server online is en of het Lightning Adress %2$s juist is. \n\nUitzondering was: %3$s</string>
|
||||
<string name="could_not_fetch_invoice_from">Kan invoice niet ophalen van %1$s</string>
|
||||
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Fout bij het verwerken van JSON uit Lightning Adress. Controleer de Lightning setup van de gebruiker</string>
|
||||
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">Callback URL niet gevonden in de Lightning Adress configuratie van de gebruiker</string>
|
||||
@@ -596,4 +594,44 @@
|
||||
<string name="server_did_not_provide_a_url_after_uploading">Server heeft geen URL opgegeven na uploaden</string>
|
||||
<string name="could_not_download_from_the_server">Kan de geüploade media niet downloaden van de server</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">Kon lokaal bestand niet voorbereiden voor upload: %1$s</string>
|
||||
<string name="login_with_qr_code">Inloggen met QR-Code</string>
|
||||
<string name="route">Route</string>
|
||||
<string name="route_home">Beginscherm</string>
|
||||
<string name="route_search">Zoeken</string>
|
||||
<string name="route_discover">Ontdekken</string>
|
||||
<string name="route_messages">Berichten</string>
|
||||
<string name="route_notifications">Notificaties</string>
|
||||
<string name="route_global">Globaal</string>
|
||||
<string name="route_video">Shorts</string>
|
||||
<string name="route_security_filters">Beveiligingsfilters</string>
|
||||
<string name="new_post">Nieuw bericht</string>
|
||||
<string name="new_short">Nieuwe shorts: afbeeldingen of video\'s</string>
|
||||
<string name="new_community_note">Nieuwe community note</string>
|
||||
<string name="open_all_reactions_to_this_post">Open alle reacties op dit bericht</string>
|
||||
<string name="close_all_reactions_to_this_post">Sluit alle reacties op dit bericht</string>
|
||||
<string name="reply_description">Antwoorden</string>
|
||||
<string name="boost_or_quote_description">Boost of citaat</string>
|
||||
<string name="like_description">Vind ik leuk</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="profile_image_of_user">Profielfoto van %1$s</string>
|
||||
<string name="relay_info">Relay %1$s</string>
|
||||
<string name="expand_relay_list">Lijst van relays uitvouwen</string>
|
||||
<string name="note_options">Note opties</string>
|
||||
<string name="relay_list_selector">Relay selectiemenu</string>
|
||||
<string name="poll">Poll</string>
|
||||
<string name="disable_poll">Poll uitschakelen</string>
|
||||
<string name="add_bitcoin_invoice">Bitcoin invoice</string>
|
||||
<string name="cancel_bitcoin_invoice">Annuleer Bitcoin invoice</string>
|
||||
<string name="cancel_classifieds">Verkoop van een item annuleren</string>
|
||||
<string name="add_zapraiser">Zapraiser</string>
|
||||
<string name="cancel_zapraiser">Annuleer Zapraiser</string>
|
||||
<string name="add_location">Locatie</string>
|
||||
<string name="remove_location">Locatie verwijderen</string>
|
||||
<string name="add_zap_split">Zap splits</string>
|
||||
<string name="cancel_zap_split">Annuleer splitsing Zap</string>
|
||||
<string name="add_content_warning">Content waarschuwing toevoegen</string>
|
||||
<string name="remove_content_warning">Content waarschuwing verwijderen</string>
|
||||
<string name="show_npub_as_a_qr_code">Toon npub als een QR-code</string>
|
||||
<string name="invalid_nip19_uri">Ongeldig adres</string>
|
||||
<string name="invalid_nip19_uri_description">Amethist ontving een URI om te openen, maar die uri was ongeldig: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<string name="point_to_the_qr_code">Aponte para o código QR</string>
|
||||
<string name="show_qr">Mostrar QR</string>
|
||||
<string name="profile_image">Imagem de perfil</string>
|
||||
<string name="scan_qr">Escanear QR</string>
|
||||
<string name="your_profile_image">Sua Foto de Perfil</string>
|
||||
<string name="scan_qr">Ler QR</string>
|
||||
<string name="show_anyway">Mostrar de qualquer maneira</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">A postagem foi sinalizada como imprópria por</string>
|
||||
<string name="post_not_found">postagem não encontrada</string>
|
||||
@@ -69,7 +70,7 @@
|
||||
<string name="new_channel">Novo Canal</string>
|
||||
<string name="channel_name">Nome do Canal</string>
|
||||
<string name="my_awesome_group">Meu grupo</string>
|
||||
<string name="picture_url">Url da foto</string>
|
||||
<string name="picture_url">URL da foto</string>
|
||||
<string name="description">Descrição</string>
|
||||
<string name="about_us">"Sobre nós.. "</string>
|
||||
<string name="what_s_on_your_mind">O que você está pensando?</string>
|
||||
@@ -103,7 +104,7 @@
|
||||
<string name="image_saved_to_the_gallery">Imagem salva para a galeria</string>
|
||||
<string name="failed_to_save_the_image">Falha ao salvar a imagem</string>
|
||||
<string name="upload_image">Enviar Imagem</string>
|
||||
<string name="uploading">Enviando...</string>
|
||||
<string name="uploading">Enviando…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">Usuário não tem um endereço lightning configurado para receber sats</string>
|
||||
<string name="reply_here">"responda aqui.. "</string>
|
||||
<string name="copies_the_note_id_to_the_clipboard_for_sharing">Copia o ID do canal (note) para compartilhar</string>
|
||||
@@ -138,12 +139,15 @@
|
||||
<string name="clear">Limpar</string>
|
||||
<string name="app_logo">Logo do aplicativo</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec / npub / hex chave privada</string>
|
||||
<string name="ncryptsec_password">senha para abrir a chave</string>
|
||||
<string name="show_password">Mostrar senha</string>
|
||||
<string name="hide_password">Esconder senha</string>
|
||||
<string name="invalid_key">Chave inválida</string>
|
||||
<string name="invalid_key_with_message">Chave inválida: %1$s</string>
|
||||
<string name="i_accept_the">"Eu aceito os "</string>
|
||||
<string name="terms_of_use">termos de uso</string>
|
||||
<string name="acceptance_of_terms_is_required">É necessário aceitar os termos de uso</string>
|
||||
<string name="password_is_required">Senha é necessária</string>
|
||||
<string name="key_is_required">Chave é obrigatória</string>
|
||||
<string name="name_is_required">Um nome é necessário</string>
|
||||
<string name="login">Entrar</string>
|
||||
@@ -194,14 +198,17 @@
|
||||
<string name="mark_all_new_as_read">Marcar todas novas solicitações como lidas</string>
|
||||
<string name="mark_all_as_read">Marcar todas como lidas</string>
|
||||
<string name="backup_keys">Copia de segurança das chaves</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Backup de chaves e dicas de segurança
|
||||
\n\nSua conta é protegida por uma chave secreta. A chave é uma string aleatória longa começando com **nsec1**. Qualquer pessoa que tenha acesso à sua chave secreta pode publicar conteúdo usando sua identidade.
|
||||
\n\n- **Não** coloque sua chave secreta em qualquer site ou software em que não confie.
|
||||
\n- Os desenvolvedores do Amethyst **nunca** pedirão sua chave secreta.
|
||||
\n- **Faça** um backup seguro de sua chave secreta para recuperação de conta. Recomendamos o uso de um gerenciador de senhas.
|
||||
</string>
|
||||
<string name="account_backup_tips2_md" tools:ignore="Typos"> ## Dicas de Backup de Chave e Segurança
|
||||
\n\nSua conta é protegida por uma chave secreta. A chave é uma sequência longa de caracteres que começa com **nsec1**. Qualquer pessoa que tenha acesso a esta chave secreta pode postar e alterar sua identidade.
|
||||
\n\n- **Não** coloque sua chave secreta em nenhum site ou software que você não confie.
|
||||
\n- Os desenvolvedores da Amethyst **nunca** pedirão sua chave secreta.
|
||||
\n- **Mantenha** uma cópia de segurança segura de sua chave secreta para recuperação da conta. Recomendamos o uso de um gerenciador de senhas.</string>
|
||||
<string name="account_backup_tips3_md" tools:ignore="Typos"> Para segurança adicional, você pode criptografar sua chave com uma senha. Esta chave começa com **ncryptsec1** e não pode ser usada sem sua senha.
|
||||
\n\nSe você perder sua senha, não será possível recuperar sua chave.</string>
|
||||
<string name="failed_to_encrypt_key">Falha ao criptografar sua chave privada</string>
|
||||
<string name="secret_key_copied_to_clipboard">Chave secreta (nsec) copiada</string>
|
||||
<string name="copy_my_secret_key">Copiar minha chave secreta</string>
|
||||
<string name="encrypt_and_copy_my_secret_key">Criptografar e copiar minha chave secreta</string>
|
||||
<string name="biometric_authentication_failed">Autenticação falhou</string>
|
||||
<string name="biometric_authentication_failed_explainer">A autenticação biométrica falhou ao autenticar o proprietário deste telefone</string>
|
||||
<string name="biometric_authentication_failed_explainer_with_error">A autenticação biométrica falhou ao autenticar o proprietário deste telefone. Erro: %1$s</string>
|
||||
@@ -274,7 +281,7 @@
|
||||
<string name="post_poll">Postar enquete</string>
|
||||
<string name="poll_heading_required">Campos obrigatórios:</string>
|
||||
<string name="poll_zap_recipients">Destinatários Zap</string>
|
||||
<string name="poll_primary_description">Descrição da enquete principal...</string>
|
||||
<string name="poll_primary_description">Descrição da enquete principal…</string>
|
||||
<string name="poll_option_index">Opção %s</string>
|
||||
<string name="poll_option_description">Descrição da opção de enquete</string>
|
||||
<string name="poll_heading_optional">Campos opcionais:</string>
|
||||
@@ -321,7 +328,7 @@
|
||||
<string name="connect_via_tor_short">Configuração do Tor/Orbot</string>
|
||||
<string name="connect_via_tor">Conecte-se através da configuração do Orbot</string>
|
||||
<string name="do_you_really_want_to_disable_tor_title">Desconectar do Orbot/Tor?</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">Seus dados serão transferidos imediatamente na rede regular</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">Seus dados serão transferidos imediatamente na rede aberta</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="no">Não</string>
|
||||
<string name="follow_list_selection">Lista de seguidores</string>
|
||||
@@ -447,12 +454,13 @@
|
||||
<string name="zap_forward_title">Encaminhar Zaps para:</string>
|
||||
<string name="zap_forward_explainer">Os clientes que suportam encaminharão zaps para o endereço lightning ou perfil de usuário abaixo, em vez do seu</string>
|
||||
<string name="geohash_title">Expor localização como </string>
|
||||
<string name="geohash_explainer">Adiciona um Geohash da sua localização à postagem. O público saberá que você está a 5 km (3 milhas) do local atual</string>
|
||||
<string name="geohash_explainer">Adicione um geohash da sua localização à postagem. O público saberá que você está a 5 km (3 milhas) do local atual</string>
|
||||
<string name="add_sensitive_content_explainer">Adiciona aviso de conteúdo sensível antes de mostrar seu conteúdo. Isso é ideal para qualquer conteúdo NSFW ou conteúdo que algumas pessoas possam considerar ofensivo ou perturbador</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_title">Novo recurso</string>
|
||||
<string name="new_feature_nip24_might_not_be_available_description">Ativando este modo requer o Amethyst para enviar uma mensagem de NIP-24 (GiftWrapped, Sealed Direct and Group Messages). NIP-24 é novo e a maioria dos clientes ainda não o implementaram. Certifique-se de que o destinatário está usando um cliente compatível.</string>
|
||||
<string name="new_feature_nip24_activate">Ativar</string>
|
||||
<string name="messages_create_public_chat">Público</string>
|
||||
<string name="messages_create_public_private_chat_desription">Novo Grupo Público ou Privado</string>
|
||||
<string name="messages_new_message">Privado</string>
|
||||
<string name="messages_new_message_to">Para</string>
|
||||
<string name="messages_new_message_subject">Assunto</string>
|
||||
@@ -463,18 +471,19 @@
|
||||
<string name="messages_new_subject_message_placeholder">Mudando o nome dos novos objetivos.</string>
|
||||
<string name="paste_from_clipboard">Colar da área de transferência</string>
|
||||
<string name="language_description">Para a interface do aplicativo</string>
|
||||
<string name="theme_description">Tema Escuro, Claro ou Sistema</string>
|
||||
<string name="theme_description">Tema Escuro, Claro ou Padrão</string>
|
||||
<string name="automatically_load_images_gifs_description">Carregar automaticamente imagens e GIFs</string>
|
||||
<string name="automatically_play_videos_description">Reproduz automaticamente vídeos e GIFs</string>
|
||||
<string name="automatically_show_url_preview_description">Mostrar visualizações de URL</string>
|
||||
<string name="load_image_description">Quando carregar imagens</string>
|
||||
<string name="copy_to_clipboard">Copiar para a Área de Transferência</string>
|
||||
<string name="copy_npub_to_clipboard">Copiar npub para a área de transferência</string>
|
||||
<string name="copy_url_to_clipboard">Copiar URL para a área de transferência</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Copiar ID da nota para a área de transferência</string>
|
||||
<string name="created_at">Criado em</string>
|
||||
<string name="rules">Regras</string>
|
||||
<string name="login_with_external_signer">Login com Amber</string>
|
||||
<string name="status_update">Atualize seu status</string>
|
||||
<string name="status_update">O que você está fazendo?</string>
|
||||
<string name="lightning_wallets_not_found">Erro ao analisar mensagem de erro</string>
|
||||
<string name="poll_zap_value_min_max_explainer">Os votos são ponderados pelo valor do zap. Você pode definir um valor mínimo para evitar spammers e um valor máximo para evitar que grandes zappers assumam o controle da enquete. Use o mesmo valor em ambos os campos para garantir que cada voto tenha o mesmo valor. Deixe em branco para aceitar qualquer valor.</string>
|
||||
<string name="error_dialog_zap_error">Não foi possível enviar o Zap</string>
|
||||
@@ -597,4 +606,44 @@
|
||||
<string name="server_did_not_provide_a_url_after_uploading">O servidor não forneceu uma URL após o upload</string>
|
||||
<string name="could_not_download_from_the_server">Não foi possível baixar o arquivo de mídia carregado do servidor</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">Não foi possível preparar o arquivo local para enviar: %1$s</string>
|
||||
<string name="login_with_qr_code">Entrar com Código QR</string>
|
||||
<string name="route">Rota</string>
|
||||
<string name="route_home">Início</string>
|
||||
<string name="route_search">Buscar</string>
|
||||
<string name="route_discover">Descobrir</string>
|
||||
<string name="route_messages">Mensagens</string>
|
||||
<string name="route_notifications">Notificações</string>
|
||||
<string name="route_global">Global</string>
|
||||
<string name="route_video">Vídeos Curtos</string>
|
||||
<string name="route_security_filters">Filtros de Segurança</string>
|
||||
<string name="new_post">Novo Post</string>
|
||||
<string name="new_short">Novos Vídeos Curtos: imagens ou vídeos</string>
|
||||
<string name="new_community_note">Nova Nota da Comunidade</string>
|
||||
<string name="open_all_reactions_to_this_post">Abrir todas as reações a esta postagem</string>
|
||||
<string name="close_all_reactions_to_this_post">Fechar todas as reações a esta postagem</string>
|
||||
<string name="reply_description">Responder</string>
|
||||
<string name="boost_or_quote_description">Impulsionar ou Citar</string>
|
||||
<string name="like_description">Gostar</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="profile_image_of_user">Foto de perfil de %1$s</string>
|
||||
<string name="relay_info">Repassar %1$s</string>
|
||||
<string name="expand_relay_list">Expandir lista de repasse</string>
|
||||
<string name="note_options">Opções de nota</string>
|
||||
<string name="relay_list_selector">Seletor de lista de repasse</string>
|
||||
<string name="poll">Enquete</string>
|
||||
<string name="disable_poll">Desativar enquete</string>
|
||||
<string name="add_bitcoin_invoice">Fatura de Bitcoin</string>
|
||||
<string name="cancel_bitcoin_invoice">Cancelar Fatura de Bitcoin</string>
|
||||
<string name="cancel_classifieds">Cancelar Venda de Item</string>
|
||||
<string name="add_zapraiser">Avaliador Zap</string>
|
||||
<string name="cancel_zapraiser">Cancelar Avaliador Zap</string>
|
||||
<string name="add_location">Localização</string>
|
||||
<string name="remove_location">Remover Localização</string>
|
||||
<string name="add_zap_split">Divisão Zap</string>
|
||||
<string name="cancel_zap_split">Cancelar Divisão Zap</string>
|
||||
<string name="add_content_warning">Adicionar aviso de conteúdo</string>
|
||||
<string name="remove_content_warning">Remover aviso de conteúdo</string>
|
||||
<string name="show_npub_as_a_qr_code">Mostrar npub como um código QR</string>
|
||||
<string name="invalid_nip19_uri">Endereço inválido</string>
|
||||
<string name="invalid_nip19_uri_description">O Amethyst recebeu um URI para abrir, mas esse URI era inválido: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -183,12 +183,6 @@
|
||||
<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>
|
||||
|
||||
@@ -11,4 +11,26 @@
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<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="your_profile_image">Din profilbild</string>
|
||||
<string name="scan_qr">Scanna QR</string>
|
||||
<string name="show_anyway">Visa ändå</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Inlägget flaggades som olämpligt av</string>
|
||||
@@ -138,12 +139,15 @@
|
||||
<string name="clear">Rensa</string>
|
||||
<string name="app_logo">App Logo</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec / npub / hex privat nyckel</string>
|
||||
<string name="ncryptsec_password">lösenord för att öppna nyckeln</string>
|
||||
<string name="show_password">Visa lösenord</string>
|
||||
<string name="hide_password">Göm lösenord</string>
|
||||
<string name="invalid_key">Ogiltig nyckel</string>
|
||||
<string name="invalid_key_with_message">Ogiltig nyckel: %1$s</string>
|
||||
<string name="i_accept_the">"Jag accepterar "</string>
|
||||
<string name="terms_of_use">villkor</string>
|
||||
<string name="acceptance_of_terms_is_required">Godkännande av villkor krävs</string>
|
||||
<string name="password_is_required">Lösenord krävs</string>
|
||||
<string name="key_is_required">Nyckel krävs</string>
|
||||
<string name="name_is_required">Namn är obligatoriskt</string>
|
||||
<string name="login">Inloggning</string>
|
||||
@@ -194,13 +198,17 @@
|
||||
<string name="mark_all_new_as_read">Markera alla nya som lästa</string>
|
||||
<string name="mark_all_as_read">Markera allt som läst</string>
|
||||
<string name="backup_keys">Backup-nycklar</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos"> ## Nyckelsäkerhetskopiering och säkerhetstips
|
||||
\n\nDitt konto är skyddat av en hemlig nyckel. Nyckeln är en lång slumpmässig sträng som börjar med **nsec1**. Alla som har tillgång till din hemliga nyckel kan publicera innehåll med din identitet.
|
||||
\n\n- Lägg **inte** in din hemliga nyckel på någon webbplats eller programvara som du inte litar på.
|
||||
\n- Amethyst-utvecklare kommer **aldrig** att be dig om din hemliga nyckel.
|
||||
\n- **Behåll en säker säkerhetskopia av din hemliga nyckel för kontoåterställning. Vi rekommenderar att du använder en lösenordshanterare. </string>
|
||||
<string name="account_backup_tips2_md" tools:ignore="Typos"> ## Nyckelsäkerhetsråd
|
||||
\n\nDitt konto är säkrat med en hemlig nyckel. Nyckeln är en lång följd av tecken som börjar med **nsec1**. Den som har tillgång till denna hemliga nyckel kan posta och ändra din identitet.
|
||||
\n\n- **Lägg inte** din hemliga nyckel på någon webbplats eller i någon mjukvara som du inte litar på.
|
||||
\n- Utvecklarna av Amethyst kommer **aldrig** att be om din hemliga nyckel.
|
||||
\n- **Spara** en säkerhetskopia av din hemliga nyckel för återställning av kontot. Vi rekommenderar att du använder en lösenordshanterare.</string>
|
||||
<string name="account_backup_tips3_md" tools:ignore="Typos"> För extra säkerhet kan du kryptera din nyckel med ett lösenord. Denna nyckel börjar med **ncryptsec1** och kan inte användas utan ditt lösenord.
|
||||
\n\nOm du tappar bort ditt lösenord kommer du inte att kunna återställa din nyckel.</string>
|
||||
<string name="failed_to_encrypt_key">Misslyckades med att kryptera din privata nyckel</string>
|
||||
<string name="secret_key_copied_to_clipboard">Hemlig nyckel (nsec) kopierad till urklipp</string>
|
||||
<string name="copy_my_secret_key">Kopiera min hemliga nyckel</string>
|
||||
<string name="encrypt_and_copy_my_secret_key">Kryptera och kopiera min hemliga nyckel</string>
|
||||
<string name="biometric_authentication_failed">Autentisering misslyckades</string>
|
||||
<string name="biometric_authentication_failed_explainer">Biometri misslyckades med att autentisera ägaren av denna telefon</string>
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Biometri misslyckades med att autentisera ägaren av denna telefon. Fel: %1$s</string>
|
||||
@@ -451,6 +459,7 @@
|
||||
<string name="new_feature_nip24_might_not_be_available_description">För att aktivera denna funktion kräver det att Amethyst skickar ett NIP-24 meddelande (GiftWrapped, Förseglade Direkta och Gruppmeddelanden). NIP-24 är nytt och de flesta klienter har ännu inte implementerat det. Se till att mottagaren använder en kompatibel klient.</string>
|
||||
<string name="new_feature_nip24_activate">Aktivera</string>
|
||||
<string name="messages_create_public_chat">Publik</string>
|
||||
<string name="messages_create_public_private_chat_desription">Ny offentlig eller privat grupp</string>
|
||||
<string name="messages_new_message">Privat</string>
|
||||
<string name="messages_new_message_to">Till</string>
|
||||
<string name="messages_new_message_subject">Ämne</string>
|
||||
@@ -467,6 +476,7 @@
|
||||
<string name="automatically_show_url_preview_description">Visa förhandsgranskning av URL</string>
|
||||
<string name="load_image_description">När bilder ska laddas</string>
|
||||
<string name="copy_to_clipboard">Kopiera till Urklipp</string>
|
||||
<string name="copy_npub_to_clipboard">Kopiera npub till urklipp</string>
|
||||
<string name="copy_url_to_clipboard">Kopiera URL till urklipp</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Kopiera anteckningens ID till urklipp</string>
|
||||
<string name="created_at">Skapad den</string>
|
||||
@@ -595,4 +605,44 @@
|
||||
<string name="server_did_not_provide_a_url_after_uploading">Servern gav inte en URL efter uppladdning</string>
|
||||
<string name="could_not_download_from_the_server">Kunde inte ladda ner uppladdade medier från servern</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">Kunde inte förbereda lokal fil att ladda upp: %1$s</string>
|
||||
<string name="login_with_qr_code">Logga in med QR-kod</string>
|
||||
<string name="route">Rutt</string>
|
||||
<string name="route_home">Hem</string>
|
||||
<string name="route_search">Sök</string>
|
||||
<string name="route_discover">Upptäck</string>
|
||||
<string name="route_messages">Meddelanden</string>
|
||||
<string name="route_notifications">Aviseringar</string>
|
||||
<string name="route_global">Globalt</string>
|
||||
<string name="route_video">Kortfilmer</string>
|
||||
<string name="route_security_filters">Säkerhetsfilter</string>
|
||||
<string name="new_post">Nytt inlägg</string>
|
||||
<string name="new_short">Nya kort: bilder eller videor</string>
|
||||
<string name="new_community_note">Nytt Community-meddelande</string>
|
||||
<string name="open_all_reactions_to_this_post">Öppna alla reaktioner på detta inlägg</string>
|
||||
<string name="close_all_reactions_to_this_post">Stäng alla reaktioner på detta inlägg</string>
|
||||
<string name="reply_description">Svara</string>
|
||||
<string name="boost_or_quote_description">Boosta eller citera</string>
|
||||
<string name="like_description">Gilla</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
<string name="profile_image_of_user">%1$s Profilbild</string>
|
||||
<string name="relay_info">Relä %1$s</string>
|
||||
<string name="expand_relay_list">Expandera relälistan</string>
|
||||
<string name="note_options">Notalternativ</string>
|
||||
<string name="relay_list_selector">Relälistväljare</string>
|
||||
<string name="poll">Omröstning</string>
|
||||
<string name="disable_poll">Inaktivera omröstning</string>
|
||||
<string name="add_bitcoin_invoice">Bitcoin-faktura</string>
|
||||
<string name="cancel_bitcoin_invoice">Avbryt Bitcoin-faktura</string>
|
||||
<string name="cancel_classifieds">Avbryt Sälja en artikel</string>
|
||||
<string name="add_zapraiser">Zappraiser</string>
|
||||
<string name="cancel_zapraiser">Avbryt Zappraiser</string>
|
||||
<string name="add_location">Plats</string>
|
||||
<string name="remove_location">Ta bort plats</string>
|
||||
<string name="add_zap_split">Zap-split</string>
|
||||
<string name="cancel_zap_split">Avbryt Zap-split</string>
|
||||
<string name="add_content_warning">Lägg till varning för innehåll</string>
|
||||
<string name="remove_content_warning">Ta bort varning för innehåll</string>
|
||||
<string name="show_npub_as_a_qr_code">Visa npub som en QR-kod</string>
|
||||
<string name="invalid_nip19_uri">Ogiltig adress</string>
|
||||
<string name="invalid_nip19_uri_description">Amethyst fick en URI att öppna men den URI var ogiltig: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -182,12 +182,6 @@
|
||||
<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>
|
||||
|
||||
@@ -171,12 +171,6 @@
|
||||
<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- அமேதிஸ்ட் டெவலப்பர்கள் உங்கள் ரகசியசாவியை உங்களிடம் எப்போதும் கேட்க மாட்டார்கள்.
|
||||
\ n- கணக்கு மீட்டெடுப்பதற்கு உங்கள் ரகசியசாவியின் பாதுகாப்பான காப்புப்பிரதியை **தவறாமல்** வைத்திருங்கள். கடவுச்சொல் நிர்வாகியைப் பயன்படுத்த பரிந்துரைக்கிறோம்.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">ரகசிய சாவி (nsec) கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது</string>
|
||||
<string name="copy_my_secret_key">எனது ரகசிய சாவியை நகலெடுக்கவும்</string>
|
||||
<string name="biometric_authentication_failed">அங்கீகரிப்பு தோல்வியுற்றது</string>
|
||||
|
||||
@@ -185,12 +185,6 @@
|
||||
<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 บัญชีของคุณมีความปลอดภัยด้วยรหัสลับ key คือสตริงสุ่มยาวที่ขึ้นต้นด้วย **nsec1** ทุกคนที่มีสิทธิ์เข้าถึงรหัสลับของคุณสามารถเผยแพร่เนื้อหาโดยใช้บัญชีของคุณได้
|
||||
\n\n- **อย่า** ใส่รหัสลับของคุณในเว็บไซต์หรือซอฟต์แวร์ที่คุณไม่เชื่อถือ
|
||||
\n- นักพัฒนา Amethyst จะ **ไม่เคย** ขอรหัสลับของคุณ
|
||||
\n- **เก็บ** สําเนาสํารองคีย์ลับของคุณไว้อย่างปลอดภัยสําหรับการกู้คืนบัญชี เราขอแนะนําให้ใช้ password manager.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">คัดลอก Secret key (nsec) ลงคลิปบอร์ด</string>
|
||||
<string name="copy_my_secret_key">คัดลอก secret key ของฉัน</string>
|
||||
<string name="biometric_authentication_failed"> เกิดข้อผิดพลาดในการตรวจสอบ</string>
|
||||
|
||||
@@ -33,9 +33,14 @@
|
||||
<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="login_with_a_private_key_to_be_able_to_hide_word">Ви використовуєте відкритий ключ, і відкриті ключі доступні лише для читання. Увійдіть за допомогою закритого ключа, щоб мати можливість приховати слово або речення</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_show_word">Ви використовуєте відкритий ключ, і відкриті ключі доступні лише для читання. Увійдіть за допомогою закритого ключа, щоб мати можливість показати слово або речення</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">Нова сума в sat</string>
|
||||
<string name="add">Додати</string>
|
||||
@@ -43,6 +48,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>
|
||||
@@ -135,9 +142,17 @@
|
||||
<string name="terms_of_use">умови користування</string>
|
||||
<string name="acceptance_of_terms_is_required">Треба прийняти умови користування</string>
|
||||
<string name="key_is_required">Треба ввести ключ</string>
|
||||
<string name="name_is_required">Необхідно вказати ім\'я</string>
|
||||
<string name="login">Увійти</string>
|
||||
<string name="sign_up">Реєстрація</string>
|
||||
<string name="create_account">Створити обліковий запис</string>
|
||||
<string name="how_should_we_call_you">Як ми можемо до вас звертатися?</string>
|
||||
<string name="don_t_have_an_account">Не маєте облікового запису Nostr?</string>
|
||||
<string name="already_have_an_account">Вже маєте обліковий запис Nostr?</string>
|
||||
<string name="create_a_new_account">Створити новий обліковий запис</string>
|
||||
<string name="generate_a_new_key">Згенерувати ключ</string>
|
||||
<string name="loading_feed">Завантаження стрічки</string>
|
||||
<string name="loading_account">Завантаження облікового запису</string>
|
||||
<string name="error_loading_replies">"Не вдалося завантажити відповіді: "</string>
|
||||
<string name="try_again">Повторити</string>
|
||||
<string name="feed_is_empty">Стрічка порожня.</string>
|
||||
@@ -161,6 +176,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>
|
||||
@@ -175,15 +191,11 @@
|
||||
<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_authentication_failed_explainer">Не вдалося виконати біометрику для автентифікації власника цього телефону</string>
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Не вдалося виконати біометрику для автентифікації власника цього телефону</string>
|
||||
<string name="biometric_error">Помилка</string>
|
||||
<string name="badge_created_by">"Створено %1$s"</string>
|
||||
<string name="badge_award_image_for">"Картинка значка-нагороди %1$s"</string>
|
||||
@@ -260,8 +272,11 @@
|
||||
<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_unable_to_vote">Неможливо проголосувати</string>
|
||||
<string name="poll_is_closed_explainer">Опитування закрито для нових голосів</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>
|
||||
@@ -271,6 +286,7 @@
|
||||
<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_info_title">Що це значить?</string>
|
||||
<string name="hash_verification_passed">Фото не змінилося після публікації</string>
|
||||
<string name="hash_verification_failed">Фото змінено. Автор міг не помітити зміну</string>
|
||||
<string name="content_description_add_image">Додати фото</string>
|
||||
@@ -293,8 +309,24 @@
|
||||
<string name="zap_forward_lnAddress">LnAddress або @User</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\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="invalid_port_number">Невірний номер порту</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>
|
||||
@@ -302,4 +334,174 @@
|
||||
<string name="app_notification_zaps_channel_message">%1$s sat</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_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_explainer">Додає цільову суму сат, яку потрібно отримати для цієї посади. Клієнти, які підтримують, можуть відображати це як індикатор прогресу, щоб стимулювати пожертвування</string>
|
||||
<string name="owner">Власник</string>
|
||||
<string name="version">Версія</string>
|
||||
<string name="software">Програмне забезпечення</string>
|
||||
<string name="supports">Підтримувані NIP</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">Мінімальний PoW</string>
|
||||
<string name="payment">Оплата</string>
|
||||
<string name="cashu_redeem_to_zap">Надіслати до Zap Wallet</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="discover_marketplace">Торгівельний майданчик</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">Тільки через Wi-Fi</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="automatically_hide_nav_bars">Розширена прокрутка</string>
|
||||
<string name="automatically_hide_nav_bars_description">Приховати Nav панель під час прокручування</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_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">Переслати Zaps до:</string>
|
||||
<string name="zap_forward_explainer">Підтримання клієнтів переведе його на LNAddress або профіль користувача нижче замість ваших</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_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="paste_from_clipboard">Вставити з буферу</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_to_clipboard">Скопіювати в буфер</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="login_with_external_signer">Увійти з Amber</string>
|
||||
<string name="status_update">Оновіть свій статус</string>
|
||||
<string name="lightning_wallets_not_found">Помилка при обробці повідомлення про помилку</string>
|
||||
<string name="error_dialog_zap_error">Не вдалося надіслати файл Zap</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">DMs</string>
|
||||
<string name="active_for_chats">Чати</string>
|
||||
<string name="active_for_global">Глобально</string>
|
||||
<string name="active_for_search">Пошук</string>
|
||||
<string name="zap_split_search_and_add_user">Знайти та додати користувача</string>
|
||||
<string name="zap_split_search_and_add_user_placeholder">Ім\'я користувача або ім\'я для відображення</string>
|
||||
<string name="zap_split_weight_placeholder">25</string>
|
||||
<string name="paid">Сплачено</string>
|
||||
<string name="wallet_number">Гаманець: %1$s</string>
|
||||
<string name="error_opening_external_signer">Помилка при відкритті додатка для входу</string>
|
||||
<string name="error_opening_external_signer_description">Додаток для входу не знайдено. Перевірте чи не було видалено додаток</string>
|
||||
<string name="sign_request_rejected">Заявку на вхід відхилено</string>
|
||||
<string name="hide_new_word_label">Приховати нове слово або речення</string>
|
||||
<string name="automatically_show_profile_picture">Зображення профілю</string>
|
||||
<string name="automatically_show_profile_picture_description">Показувати зображення профілю</string>
|
||||
<string name="select_an_option">Вибрати опцію</string>
|
||||
<string name="error_dialog_pay_invoice_error">Не вдалося сплатити рахунок</string>
|
||||
<string name="error_dialog_pay_withdraw_error">Не вдалося зняти</string>
|
||||
<string name="error_parsing_nip47_title">Не вдалося налаштувати Wallet Connect</string>
|
||||
<string name="error_parsing_nip47">Помилка аналізу рядка підключення NIP-47. Перевірте, чи це правильно, у вашого постачальника Wallet: %1$s. Помилка: %2$s</string>
|
||||
<string name="error_parsing_nip47_no_error">Помилка аналізу рядка підключення NIP-47. Перевірте, чи це правильно, у вашого постачальника Wallet: %1$s.</string>
|
||||
<string name="cashu_failed_redemption_explainer_error_msg">Mint надав таке повідомлення про помилку: %1$s</string>
|
||||
<string name="cashu_failed_redemption_explainer_already_spent">Cashu токени вже витрачені.</string>
|
||||
<string name="cashu_successful_redemption">Отримано Cashu</string>
|
||||
<string name="cashu_successful_redemption_explainer">%1$s sats були відправлені в ваш гаманець. (Fees: %2$s sats)</string>
|
||||
<string name="cashu_no_wallet_found">У системі не знайдено сумісний гаманець Cashu</string>
|
||||
<string name="error_unable_to_fetch_invoice">Не вдалося отримати рахунок з серверів отримувача</string>
|
||||
<string name="wallet_connect_pay_invoice_error_error">Постачальник підключення гаманця повернув наступну помилку: %1$s</string>
|
||||
<string name="could_not_connect_to_tor">Не вдалося підключитися до Tor</string>
|
||||
<string name="classifieds_condition_new_explainer">Це абсолютно нова одиниця, в оригінальній коробці</string>
|
||||
<string name="classifieds_condition_like_new">Як нове</string>
|
||||
<string name="classifieds_condition_like_new_explainer">Він використовується, але немає ознак використання</string>
|
||||
<string name="classifieds_condition_good">Добре</string>
|
||||
<string name="classifieds_condition_good_explainer">Він має декілька поверхових знаків використання</string>
|
||||
<string name="classifieds_condition_fair">Непогано</string>
|
||||
<string name="classifieds_condition_fair_explainer">Він все ще знаходиться у прийнятній і функціональній формі</string>
|
||||
<string name="classifieds_category_clothing">Одяг</string>
|
||||
<string name="classifieds_category_accessories">Аксесуари</string>
|
||||
<string name="classifieds_category_electronics">Електроніка</string>
|
||||
<string name="classifieds_category_furniture">Меблі</string>
|
||||
<string name="classifieds_category_collectibles">Колекційні елементи</string>
|
||||
<string name="classifieds_category_books">Книги</string>
|
||||
<string name="classifieds_category_pets">Домашні тварини</string>
|
||||
<string name="classifieds_category_sports">Спорт</string>
|
||||
<string name="classifieds_category_fitness">Фітнес</string>
|
||||
<string name="classifieds_category_art">Мистецтво</string>
|
||||
<string name="classifieds_category_crafts">Ремесла</string>
|
||||
<string name="classifieds_category_other">Інше</string>
|
||||
<string name="failed_to_upload_media_no_details">Не вдалося завантажити фото</string>
|
||||
<string name="could_not_open_the_compressed_file">Не вдалося відкрити стиснутий файл</string>
|
||||
<string name="error_when_compressing_media">Помилка стискання медіафайлу: %1$s</string>
|
||||
<string name="failed_to_upload_media">Помилка завантаження: %1$s</string>
|
||||
<string name="server_did_not_provide_a_url_after_uploading">Сервер не задав URL після завантаження</string>
|
||||
<string name="could_not_download_from_the_server">Не вдалося завантажити завантажені медіафайли з сервера</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">Не вдалося підготувати локальний файл для завантаження: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<string name="point_to_the_qr_code">对准二维码</string>
|
||||
<string name="show_qr">显示二维码</string>
|
||||
<string name="profile_image">头像图片</string>
|
||||
<string name="your_profile_image">头像图片</string>
|
||||
<string name="scan_qr">扫描二维码</string>
|
||||
<string name="show_anyway">仍然显示</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">帖文被标记为不当</string>
|
||||
@@ -90,6 +91,8 @@
|
||||
<string name="add_a_relay">添加中继器</string>
|
||||
<string name="display_name">显示名称</string>
|
||||
<string name="my_display_name">我的显示名称</string>
|
||||
<string name="my_awesome_name">Ostrich McAwesome</string>
|
||||
<string name="welcome">欢迎!</string>
|
||||
<string name="username">用户名</string>
|
||||
<string name="my_username">我的用户名</string>
|
||||
<string name="about_me">关于我</string>
|
||||
@@ -143,7 +146,14 @@
|
||||
<string name="terms_of_use">使用条款</string>
|
||||
<string name="acceptance_of_terms_is_required">需要接受条款</string>
|
||||
<string name="key_is_required">需要密钥</string>
|
||||
<string name="name_is_required">名称必填</string>
|
||||
<string name="login">登录</string>
|
||||
<string name="sign_up">注册</string>
|
||||
<string name="create_account">创建帐户</string>
|
||||
<string name="how_should_we_call_you">我们应该如何称呼你?</string>
|
||||
<string name="don_t_have_an_account">还没有 Nostr 帐户?</string>
|
||||
<string name="already_have_an_account">已经有 Nostr 帐户?</string>
|
||||
<string name="create_a_new_account">创建新帐户</string>
|
||||
<string name="generate_a_new_key">生成新密钥</string>
|
||||
<string name="loading_feed">正在加载</string>
|
||||
<string name="loading_account">正在载入帐户</string>
|
||||
@@ -185,11 +195,6 @@
|
||||
<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>
|
||||
@@ -443,6 +448,7 @@
|
||||
<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_create_public_private_chat_desription">新建公开或私人群组</string>
|
||||
<string name="messages_new_message">私人</string>
|
||||
<string name="messages_new_message_to">至</string>
|
||||
<string name="messages_new_message_subject">主题</string>
|
||||
@@ -459,6 +465,7 @@
|
||||
<string name="automatically_show_url_preview_description">显示 URL 预览</string>
|
||||
<string name="load_image_description">何时加载图像</string>
|
||||
<string name="copy_to_clipboard">复制到剪贴板</string>
|
||||
<string name="copy_npub_to_clipboard">复制 npub 到剪贴板</string>
|
||||
<string name="copy_url_to_clipboard">复制链接到剪贴板</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">复制笔记 ID 到剪贴板</string>
|
||||
<string name="created_at">创建于</string>
|
||||
@@ -522,6 +529,7 @@
|
||||
<string name="could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup">无法从闪电地址集合LNURL\"%1$s\"。请检查用户设置</string>
|
||||
<string name="the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct">%1$s 的接收者闪电服务不可用。它是根据雷电地址\"%2$s\"计算的。 错误: %3$s。请检查服务器是否已经上线,闪电地址是否正确</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">无法解析 %1$s。请检查你是否已连接,服务器是否启动,以及闪电地址 %2$s 是否正确</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">无法解析 %1$s。请检查你是否已连接,服务器是否启动,以及闪电地址 %2$s 是否正确。\n\n异常为:%3$s</string>
|
||||
<string name="could_not_fetch_invoice_from">无法从 %1$s 获取发票</string>
|
||||
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">从闪电地址解析JSON出错。请检查用户的打闪设置</string>
|
||||
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">在用户闪电地址服务器配置中找不到回调URL</string>
|
||||
@@ -586,4 +594,44 @@
|
||||
<string name="server_did_not_provide_a_url_after_uploading">上传后服务器没有提供 URL</string>
|
||||
<string name="could_not_download_from_the_server">无法从服务器下载上传的媒体</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">无法准备要上传的本地文件:%1$s</string>
|
||||
<string name="login_with_qr_code">使用二维码登录</string>
|
||||
<string name="route">路径</string>
|
||||
<string name="route_home">主页</string>
|
||||
<string name="route_search">搜索</string>
|
||||
<string name="route_discover">发现</string>
|
||||
<string name="route_messages">消息</string>
|
||||
<string name="route_notifications">通知</string>
|
||||
<string name="route_global">全球</string>
|
||||
<string name="route_video">短篇</string>
|
||||
<string name="route_security_filters">安全滤镜</string>
|
||||
<string name="new_post">新帖子</string>
|
||||
<string name="new_short">新短篇媒体:图像或视频</string>
|
||||
<string name="new_community_note">新社区笔记</string>
|
||||
<string name="open_all_reactions_to_this_post">展开对此帖子的所有回应</string>
|
||||
<string name="close_all_reactions_to_this_post">收起对此帖子的所有回应</string>
|
||||
<string name="reply_description">回复</string>
|
||||
<string name="boost_or_quote_description">提升或引用</string>
|
||||
<string name="like_description">点赞</string>
|
||||
<string name="zap_description">打闪</string>
|
||||
<string name="profile_image_of_user">%1$s 的个人头像</string>
|
||||
<string name="relay_info">中继器 %1$s</string>
|
||||
<string name="expand_relay_list">展开中继列表</string>
|
||||
<string name="note_options">笔记选项</string>
|
||||
<string name="relay_list_selector">中继列表选择器</string>
|
||||
<string name="poll">投票</string>
|
||||
<string name="disable_poll">停用投票</string>
|
||||
<string name="add_bitcoin_invoice">比特币发票</string>
|
||||
<string name="cancel_bitcoin_invoice">取消比特币发票</string>
|
||||
<string name="cancel_classifieds">取消出售物品</string>
|
||||
<string name="add_zapraiser">Zapraiser</string>
|
||||
<string name="cancel_zapraiser">取消 Zapraiser</string>
|
||||
<string name="add_location">地点</string>
|
||||
<string name="remove_location">移除地点</string>
|
||||
<string name="add_zap_split">打闪拆分</string>
|
||||
<string name="cancel_zap_split">取消打闪拆分</string>
|
||||
<string name="add_content_warning">添加内容警告</string>
|
||||
<string name="remove_content_warning">移除内容警告</string>
|
||||
<string name="show_npub_as_a_qr_code">将 npub 显示为二维码</string>
|
||||
<string name="invalid_nip19_uri">地址无效</string>
|
||||
<string name="invalid_nip19_uri_description">Amethyst 收到了要打开的 URI,但该 uri 无效:%1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -172,12 +172,6 @@
|
||||
<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>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<string name="point_to_the_qr_code">對準 QR</string>
|
||||
<string name="show_qr">顯示 QR</string>
|
||||
<string name="profile_image">頭像圖片</string>
|
||||
<string name="your_profile_image">頭像圖片</string>
|
||||
<string name="scan_qr">掃描 QR</string>
|
||||
<string name="show_anyway">一直顯示</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">貼文被標記為不當</string>
|
||||
@@ -90,6 +91,8 @@
|
||||
<string name="add_a_relay">添加中繼器</string>
|
||||
<string name="display_name">顯示名稱</string>
|
||||
<string name="my_display_name">我的顯示名稱</string>
|
||||
<string name="my_awesome_name">Ostrich McAwesome</string>
|
||||
<string name="welcome">歡迎!</string>
|
||||
<string name="username">用户名</string>
|
||||
<string name="my_username">我的用户名</string>
|
||||
<string name="about_me">關於我</string>
|
||||
@@ -143,7 +146,14 @@
|
||||
<string name="terms_of_use">使用條款</string>
|
||||
<string name="acceptance_of_terms_is_required">需要接受條款</string>
|
||||
<string name="key_is_required">需要密鑰</string>
|
||||
<string name="name_is_required">名稱為必填</string>
|
||||
<string name="login">登錄</string>
|
||||
<string name="sign_up">註冊</string>
|
||||
<string name="create_account">建立帳戶</string>
|
||||
<string name="how_should_we_call_you">我們應該如何稱呼你?</string>
|
||||
<string name="don_t_have_an_account">還沒有 Nostr 帳戶?</string>
|
||||
<string name="already_have_an_account">已經有 Nostr 帳戶?</string>
|
||||
<string name="create_a_new_account">創建新帳戶</string>
|
||||
<string name="generate_a_new_key">生成新密鑰</string>
|
||||
<string name="loading_feed">正在加載</string>
|
||||
<string name="loading_account">正在加載帳戶</string>
|
||||
@@ -185,12 +195,6 @@
|
||||
<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>
|
||||
@@ -444,6 +448,7 @@
|
||||
<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_create_public_private_chat_desription">新建公開或私人群組</string>
|
||||
<string name="messages_new_message">私人</string>
|
||||
<string name="messages_new_message_to">至</string>
|
||||
<string name="messages_new_message_subject">主題</string>
|
||||
@@ -460,6 +465,7 @@
|
||||
<string name="automatically_show_url_preview_description">顯示鏈接預覽</string>
|
||||
<string name="load_image_description">何時加載圖像</string>
|
||||
<string name="copy_to_clipboard">複製至剪貼簿</string>
|
||||
<string name="copy_npub_to_clipboard">複製 npub 至剪貼簿</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>
|
||||
@@ -523,6 +529,7 @@
|
||||
<string name="could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup">無法從閃電地址集合 LNURL “%1$s”。請檢查用戶設置</string>
|
||||
<string name="the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct">%1$s 的接收方閃電服務不可用。它是根據閃電地址“%2$s”計算的。錯誤:%3$s。請檢查伺服器是否已上線,閃電地址是否正確</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">無法解析 %1$s。請檢查你是否已連接,伺服器是否啟動,以及閃電地址 %2$s 是否正確</string>
|
||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">無法解析 %1$s。請檢查你是否已連接,服務器是否啓動,以及閃電地址 %2$s 是否正確。\n\n異常爲:%3$s</string>
|
||||
<string name="could_not_fetch_invoice_from">無法從 %1$s 獲取發票</string>
|
||||
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">從閃電地址解析 JSON 發生錯誤。請檢查用戶的閃電設置</string>
|
||||
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">在用戶閃電地址伺服器配置中找不到回調 URL</string>
|
||||
@@ -587,4 +594,44 @@
|
||||
<string name="server_did_not_provide_a_url_after_uploading">上傳後伺服器沒有提供 URL</string>
|
||||
<string name="could_not_download_from_the_server">無法從伺服器下載上傳的媒體</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">無法準備要上傳的本地文件:%1$s</string>
|
||||
<string name="login_with_qr_code">使用二維碼登錄</string>
|
||||
<string name="route">路徑</string>
|
||||
<string name="route_home">主頁</string>
|
||||
<string name="route_search">搜索</string>
|
||||
<string name="route_discover">發現</string>
|
||||
<string name="route_messages">訊息</string>
|
||||
<string name="route_notifications">通知</string>
|
||||
<string name="route_global">全球</string>
|
||||
<string name="route_video">短篇</string>
|
||||
<string name="route_security_filters">安全濾鏡</string>
|
||||
<string name="new_post">新帖子</string>
|
||||
<string name="new_short">新短篇:圖像或視頻</string>
|
||||
<string name="new_community_note">新社區筆記</string>
|
||||
<string name="open_all_reactions_to_this_post">展開所有對此帖子的回應</string>
|
||||
<string name="close_all_reactions_to_this_post">收起所有對此帖子的回應</string>
|
||||
<string name="reply_description">回覆</string>
|
||||
<string name="boost_or_quote_description">提升或引用</string>
|
||||
<string name="like_description">點贊</string>
|
||||
<string name="zap_description">打閃</string>
|
||||
<string name="profile_image_of_user">%1$s 的頭像圖片</string>
|
||||
<string name="relay_info">中繼器 %1$s</string>
|
||||
<string name="expand_relay_list">展開中繼列表</string>
|
||||
<string name="note_options">筆記選項</string>
|
||||
<string name="relay_list_selector">中繼列表選擇器</string>
|
||||
<string name="poll">投票</string>
|
||||
<string name="disable_poll">停用投票</string>
|
||||
<string name="add_bitcoin_invoice">比特幣發票</string>
|
||||
<string name="cancel_bitcoin_invoice">取消比特幣發票</string>
|
||||
<string name="cancel_classifieds">取消出售物品</string>
|
||||
<string name="add_zapraiser">Zapraiser</string>
|
||||
<string name="cancel_zapraiser">取消 Zapraiser</string>
|
||||
<string name="add_location">地點</string>
|
||||
<string name="remove_location">移除地點</string>
|
||||
<string name="add_zap_split">打閃拆分</string>
|
||||
<string name="cancel_zap_split">取消打閃拆分</string>
|
||||
<string name="add_content_warning">添加內容警告</string>
|
||||
<string name="remove_content_warning">移除內容警告</string>
|
||||
<string name="show_npub_as_a_qr_code">將 npub 顯示爲二維碼</string>
|
||||
<string name="invalid_nip19_uri">地址無效</string>
|
||||
<string name="invalid_nip19_uri_description">Amethyst 收到了要打開的 URI,但該 URI 無效:%1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -176,11 +176,6 @@
|
||||
<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>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<string name="app_name_debug" translatable="false">Amethyst Debug</string>
|
||||
<string name="point_to_the_qr_code">Point to the QR Code</string>
|
||||
<string name="show_qr">Show QR</string>
|
||||
<string name="profile_image">Profile Image</string>
|
||||
<string name="profile_image">Profile Picture</string>
|
||||
<string name="your_profile_image">Your Profile Picture</string>
|
||||
<string name="scan_qr">Scan QR</string>
|
||||
<string name="show_anyway">Show Anyway</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Post was muted or reported by</string>
|
||||
@@ -141,12 +142,15 @@
|
||||
<string name="clear">Clear</string>
|
||||
<string name="app_logo">App Logo</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec.. or npub..</string>
|
||||
<string name="ncryptsec_password">password to open the key</string>
|
||||
<string name="show_password">Show Password</string>
|
||||
<string name="hide_password">Hide Password</string>
|
||||
<string name="invalid_key">Invalid key</string>
|
||||
<string name="invalid_key_with_message">Invalid key: %1$s</string>
|
||||
<string name="i_accept_the">"I accept the "</string>
|
||||
<string name="terms_of_use">terms of use</string>
|
||||
<string name="acceptance_of_terms_is_required">Acceptance of terms is required</string>
|
||||
<string name="password_is_required">Password is required</string>
|
||||
<string name="key_is_required">Key is required</string>
|
||||
<string name="name_is_required">A name is required</string>
|
||||
<string name="login">Login</string>
|
||||
@@ -199,15 +203,22 @@
|
||||
<string name="mark_all_new_as_read">Mark all New as read</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
<string name="backup_keys">Backup Keys</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos">
|
||||
<string name="account_backup_tips2_md" tools:ignore="Typos">
|
||||
## Key Backup and Safety Tips
|
||||
\n\nYour account is secured by a secret key. The key is long random string starting with **nsec1**. Anyone who has access to your secret key can publish content using your identity.
|
||||
\n\nYour account is secured by a secret key. The key is a long sequence of characters starting with **nsec1**. Anyone who has access to this secret key can post and change your identity.
|
||||
\n\n- Do **not** put your secret key in any website or software you do not trust.
|
||||
\n- Amethyst developers will **never** ask you for your secret key.
|
||||
\n- Amethyst developers will **never** ask for your secret key.
|
||||
\n- **Do** keep a secure backup of your secret key for account recovery. We recommend using a password manager.
|
||||
</string>
|
||||
<string name="account_backup_tips3_md" tools:ignore="Typos">
|
||||
For additional security, you can encrypt your key with a password. This key starts with **ncryptsec1** and cannot be used without your password.
|
||||
\n\nIf you lose your password, you will not be able to recover your key.
|
||||
</string>
|
||||
|
||||
<string name="failed_to_encrypt_key">Failed to encrypt your private key</string>
|
||||
<string name="secret_key_copied_to_clipboard">Secret key (nsec) copied to clipboard</string>
|
||||
<string name="copy_my_secret_key">Copy my secret key</string>
|
||||
<string name="encrypt_and_copy_my_secret_key">Encrypt and copy my secret key</string>
|
||||
<string name="biometric_authentication_failed">Authentication failed</string>
|
||||
<string name="biometric_authentication_failed_explainer">Biometrics failed to authenticate the owner of this phone</string>
|
||||
<string name="biometric_authentication_failed_explainer_with_error">Biometrics failed to authenticate the owner of this phone. Error: %1$s</string>
|
||||
@@ -523,6 +534,7 @@
|
||||
<string name="new_feature_nip24_activate">Activate</string>
|
||||
|
||||
<string name="messages_create_public_chat">Public</string>
|
||||
<string name="messages_create_public_private_chat_desription">New Public or Private Group</string>
|
||||
<string name="messages_new_message">Private</string>
|
||||
<string name="messages_new_message_to">To</string>
|
||||
<string name="messages_new_message_subject">Subject</string>
|
||||
@@ -543,6 +555,7 @@
|
||||
<string name="load_image_description">When to load images</string>
|
||||
|
||||
<string name="copy_to_clipboard">Copy to clipboard</string>
|
||||
<string name="copy_npub_to_clipboard">Copy npub to clipboard</string>
|
||||
<string name="copy_url_to_clipboard">Copy URL to clipboard</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Copy Note ID to clipboard</string>
|
||||
|
||||
@@ -702,4 +715,55 @@
|
||||
<string name="server_did_not_provide_a_url_after_uploading">Server did not provide a URL after uploading</string>
|
||||
<string name="could_not_download_from_the_server">Could not download uploaded media from the server</string>
|
||||
<string name="could_not_prepare_local_file_to_upload">Could not prepare local file to upload: %1$s</string>
|
||||
|
||||
<string name="login_with_qr_code">Login with QR Code</string>
|
||||
<string name="route">Route</string>
|
||||
<string name="route_home">Home</string>
|
||||
<string name="route_search">Search</string>
|
||||
<string name="route_discover">Discover</string>
|
||||
<string name="route_messages">Messages</string>
|
||||
<string name="route_notifications">Notifications</string>
|
||||
<string name="route_global">Global</string>
|
||||
<string name="route_video">Shorts</string>
|
||||
<string name="route_security_filters">Security Filters</string>
|
||||
|
||||
<string name="new_post">New Post</string>
|
||||
<string name="new_short">New Shorts: images or videos</string>
|
||||
<string name="new_community_note">New Community Note</string>
|
||||
|
||||
<string name="open_all_reactions_to_this_post">Open all reactions to this post</string>
|
||||
<string name="close_all_reactions_to_this_post">Close all reactions to this post</string>
|
||||
|
||||
<string name="reply_description">Reply</string>
|
||||
<string name="boost_or_quote_description">Boost Or Quote</string>
|
||||
<string name="like_description">Like</string>
|
||||
<string name="zap_description">Zap</string>
|
||||
|
||||
<string name="profile_image_of_user">Profile Picture of %1$s</string>
|
||||
<string name="relay_info">Relay %1$s</string>
|
||||
<string name="expand_relay_list">Expand relay list</string>
|
||||
<string name="note_options">Note options</string>
|
||||
<string name="relay_list_selector">Relay list selector</string>
|
||||
<string name="poll">Poll</string>
|
||||
<string name="disable_poll">Disable Poll</string>
|
||||
|
||||
<string name="add_bitcoin_invoice">Bitcoin Invoice</string>
|
||||
<string name="cancel_bitcoin_invoice">Cancel Bitcoin Invoice</string>
|
||||
<string name="cancel_classifieds">Cancel Sell an Item</string>
|
||||
|
||||
<string name="add_zapraiser">Zapraiser</string>
|
||||
<string name="cancel_zapraiser">Cancel Zapraiser</string>
|
||||
|
||||
<string name="add_location">Location</string>
|
||||
<string name="remove_location">Remove Location</string>
|
||||
|
||||
<string name="add_zap_split">Zap splits</string>
|
||||
<string name="cancel_zap_split">Cancel Zap split</string>
|
||||
|
||||
<string name="add_content_warning">Add content warning</string>
|
||||
<string name="remove_content_warning">Remove content warning</string>
|
||||
<string name="show_npub_as_a_qr_code">Show npub as a QR code</string>
|
||||
|
||||
<string name="invalid_nip19_uri">Invalid address</string>
|
||||
<string name="invalid_nip19_uri_description">Amethyst received a URI to open but that uri was invalid: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -52,8 +52,9 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.benchmark:benchmark-junit4:1.2.2'
|
||||
androidTestImplementation 'androidx.benchmark:benchmark-junit4:1.2.3'
|
||||
androidTestImplementation project(path: ':quartz')
|
||||
androidTestImplementation project(path: ':commons')
|
||||
|
||||
// Add your dependencies here. Note that you cannot benchmark code
|
||||
// in an app module this way - you will need to move any code you
|
||||
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright (c) 2023 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.benchmark
|
||||
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.amethyst.commons.ExpandableTextParser
|
||||
import com.vitorpamplona.amethyst.commons.nthIndexOf
|
||||
import junit.framework.TestCase
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExpandableViewComputationBenchmark {
|
||||
@get:Rule
|
||||
val benchmarkRule = BenchmarkRule()
|
||||
|
||||
@Test
|
||||
fun computeTestCase1() {
|
||||
benchmarkRule.measureRepeated {
|
||||
TestCase.assertEquals(
|
||||
293,
|
||||
testCase1.nthIndexOf('\n', 10),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeTestCase2() {
|
||||
benchmarkRule.measureRepeated {
|
||||
TestCase.assertEquals(
|
||||
423,
|
||||
testCase2.nthIndexOf('\n', 10),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeTestCase1All() {
|
||||
benchmarkRule.measureRepeated {
|
||||
TestCase.assertEquals(
|
||||
293,
|
||||
ExpandableTextParser.computeWhereToCutIfPostIsTooLong(testCase1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeTestCase2All() {
|
||||
benchmarkRule.measureRepeated {
|
||||
TestCase.assertEquals(
|
||||
355,
|
||||
ExpandableTextParser.computeWhereToCutIfPostIsTooLong(testCase2),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeTestCase3All() {
|
||||
benchmarkRule.measureRepeated {
|
||||
TestCase.assertEquals(
|
||||
65,
|
||||
ExpandableTextParser.computeWhereToCutIfPostIsTooLong(testCase3),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val testCase1 = """
|
||||
#Amethyst v0.83.10
|
||||
|
||||
تحديث جديد لـ Amethyst بإصدار 0.83.10 مع تعديلات وإضافات جديدة
|
||||
|
||||
: NIP-92 إصلاحات الأخطاء
|
||||
|
||||
الإضافات الجديدة:
|
||||
- يتضمن رابط المنتج في الرسالة الأولى من المشتري في السوق
|
||||
- يضيف دعمًا لـ NIP-92 في الرسائل العامة والرسائل المباشرة الجديدة (NIP-17). يبقى NIP-54 في NIP-04 DMs
|
||||
- إضافة التمرير الأفقي إلى أزرار الإجراءات في شاشة النشر الجديد لإصلاح الأزرار المخفية جزئيًا في الشاشات الصغيرة/الرفيعة.
|
||||
|
||||
اصلاحات الشوائب:
|
||||
- إصلاحات التعطل مع مبلغ Zap مخصص غير صالح
|
||||
- يعمل على إصلاح مشكلات إعادة اتصال التتابع عندما يقوم المرحل بإغلاق الاتصال
|
||||
- إصلاح الحشو العلوي للملاحظة المقتبسة في المنشور
|
||||
- تحسين استخدام الذاكرة للمستخدم المرئي وعلامة URL في المشاركات الجديدة
|
||||
|
||||
الترجمات المحدثة:
|
||||
- الفارسية بواسطة
|
||||
- الفرنسية والإنجليزية، المملكة المتحدة بواسطة
|
||||
- الأوكرانية
|
||||
- الإسبانية والإسبانية والمكسيك والإسبانية والولايات المتحدة بواسطة
|
||||
- العربية
|
||||
|
||||
تحسينات جودة الكود:
|
||||
- تحديثات لنظام Android Studio 2023.1.1 Patch 2
|
||||
|
||||
|
||||
|
||||
|
||||
nostr:nevent1qqszq7kl888sw0c5rpvepn8w373zt0jrw8864x8lkauxxw335s66rzgppemhxue69uhkummn9ekx7mp0qgsyvrp9u6p0mfur9dfdru3d853tx9mdjuhkphxuxgfwmryja7zsvhqrqsqqqqqpaax7m2
|
||||
"""
|
||||
|
||||
val testCase2 = """
|
||||
#Amethyst v0.83.10: NIP-92 and Bug Fixes
|
||||
|
||||
New Additions:
|
||||
- Includes a link to the product in the first message from the buyer in the marketplace
|
||||
- Adds support for NIP-92 in public messages and new DMs (NIP-17). NIP-54 stays in NIP-04 DMs
|
||||
- Adds Horizontal Scroll to the action buttons in the New Post screen to partially fix hidden buttons in small/thin screens.
|
||||
|
||||
Bugfixes:
|
||||
- Fixes crash with an invalid custom Zap Amount
|
||||
- Fixes relay re-connection issues when the relay closes a connection
|
||||
- Fixes the top padding of the quoted note in a post
|
||||
- Optimizes memory use of the visual user and url tagger in new posts
|
||||
|
||||
Updated translations:
|
||||
- Persian by nostr:npub1cpazafytvafazxkjn43zjfwtfzatfz508r54f6z6a3rf2ws8223qc3xxpk
|
||||
- French and English, United Kingdom by nostr:npub13qtw3yu0uc9r4yj5x0rhgy8nj5q0uyeq0pavkgt9ly69uuzxgkfqwvx23t
|
||||
- Ukrainian by crowdin.com/profile/liizzzz
|
||||
- Spanish, Spanish, Mexico and Spanish, United States by nostr:npub1luhyzgce7qtcs6r6v00ryjxza8av8u4dzh3avg0zks38tjktnmxspxq903
|
||||
- Arabic by nostr:npub13qtw3yu0uc9r4yj5x0rhgy8nj5q0uyeq0pavkgt9ly69uuzxgkfqwvx23t
|
||||
|
||||
Code Quality Improvements:
|
||||
- Updates to Android Studio 2023.1.1 Patch 2
|
||||
|
||||
Download:
|
||||
- [Play Edition](https://github.com/vitorpamplona/amethyst/releases/download/v0.83.10/amethyst-googleplay-universal-v0.83.10.apk )
|
||||
- [FOSS Edition - No translations](https://github.com/vitorpamplona/amethyst/releases/download/v0.83.10/amethyst-fdroid-universal-v0.83.10.apk )
|
||||
"""
|
||||
|
||||
val testCase3 = """#100aDayUntil100k
|
||||
Day 5 ✔️
|
||||
|
||||
Seems like they may be getting easier"""
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.benchmark
|
||||
package com.vitorpamplona.quartz.benchmark
|
||||
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.benchmark
|
||||
package com.vitorpamplona.quartz.benchmark
|
||||
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.benchmark
|
||||
package com.vitorpamplona.quartz.benchmark
|
||||
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.benchmark
|
||||
package com.vitorpamplona.quartz.benchmark
|
||||
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.benchmark
|
||||
package com.vitorpamplona.quartz.benchmark
|
||||
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.benchmark
|
||||
package com.vitorpamplona.quartz.benchmark
|
||||
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.benchmark
|
||||
package com.vitorpamplona.quartz.benchmark
|
||||
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.benchmark
|
||||
package com.vitorpamplona.quartz.benchmark
|
||||
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.benchmark
|
||||
package com.vitorpamplona.quartz.benchmark
|
||||
|
||||
import androidx.benchmark.junit4.BenchmarkRule
|
||||
import androidx.benchmark.junit4.measureRepeated
|
||||
+10
-10
@@ -4,15 +4,15 @@ buildscript {
|
||||
ext {
|
||||
fragment_version = "1.6.2"
|
||||
lifecycle_version = '2.7.0'
|
||||
compose_ui_version = '1.6.0'
|
||||
nav_version = '2.7.6'
|
||||
compose_ui_version = '1.6.1'
|
||||
nav_version = '2.7.7'
|
||||
room_version = "2.4.3"
|
||||
accompanist_version = '0.34.0'
|
||||
coil_version = '2.5.0'
|
||||
vico_version = '1.13.1'
|
||||
media3_version = '1.2.1'
|
||||
core_ktx_version = '1.12.0'
|
||||
material3_version = '1.1.2'
|
||||
material3_version = '1.2.0'
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.google.gms:google-services:4.4.0'
|
||||
@@ -20,13 +20,13 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.android.application' version '8.2.1' apply false
|
||||
id 'com.android.library' version '8.2.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.9.10' apply false
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.9.10' apply false
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.10' apply false
|
||||
id 'androidx.benchmark' version '1.1.1' apply false
|
||||
id 'com.diffplug.spotless' version '6.22.0' apply false
|
||||
id 'com.android.application' version '8.2.2' apply false
|
||||
id 'com.android.library' version '8.2.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.9.22' apply false
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.9.22' apply false
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.22' apply false
|
||||
id 'androidx.benchmark' version '1.2.3' apply false
|
||||
id 'com.diffplug.spotless' version '6.25.0' apply false
|
||||
}
|
||||
|
||||
subprojects {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,41 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.vitorpamplona.amethyst.commons'
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk 26
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
create("benchmark") {
|
||||
initWith(getByName("release"))
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
freeCompilerArgs += "-Xstring-concat=inline"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user