Compare commits

...
25 Commits
Author SHA1 Message Date
Vitor Pamplona c128e43fe5 v0.89.4 2024-07-19 17:10:22 -04:00
Vitor Pamplona f9ddf76686 action bar fix 2024-07-19 17:09:56 -04:00
Vitor Pamplona b6cb839fe7 v0.89.3 2024-07-19 16:44:30 -04:00
Vitor Pamplona 2b6a48f391 Fixes issue with super large follow lists. 2024-07-19 16:40:46 -04:00
Vitor Pamplona d1da863d3c Making sure an empty relay list uses the default list from Amethyst 2024-07-19 16:14:14 -04:00
Vitor Pamplona 15ab955a7d Fixes alt of NIP-96 server lists 2024-07-19 11:55:46 -04:00
Vitor Pamplona 9176151971 Fixes auth infinite loop with nostr.wine 2024-07-19 10:45:06 -04:00
Vitor Pamplona eba5ecfd91 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-07-19 09:47:04 -04:00
Vitor Pamplona 3e43a75acd Fixes the keyboard overriding parts of the screen 2024-07-19 09:46:58 -04:00
Vitor PamplonaandGitHub 60ba7786a0 Merge pull request #984 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-07-18 10:54:01 -04:00
Crowdin Bot 1773ad5a88 New Crowdin translations by GitHub Action 2024-07-18 14:30:23 +00:00
Vitor Pamplona 26faa2bc69 Migrating to edge to edge in preparation for Android 15 2024-07-18 10:28:19 -04:00
Vitor Pamplona 75c7d40f4e v0.89.2 2024-07-17 19:34:56 -04:00
Vitor Pamplona aa4a3fe9af Doesn't show people lists that have 0 people in it. 2024-07-17 19:33:47 -04:00
Vitor Pamplona a32f907d6c Fixes incorrectly marking EOSEs for addressables 2024-07-17 19:26:46 -04:00
Vitor Pamplona d30e613b17 Fixes TopNav people lists not loading 2024-07-17 17:24:06 -04:00
Vitor PamplonaandGitHub 0477f390c2 Merge pull request #983 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-07-17 16:54:43 -04:00
Crowdin Bot 15e5e2834a New Crowdin translations by GitHub Action 2024-07-17 20:39:46 +00:00
Vitor Pamplona b1399d1c13 Separates init and restart from observables. 2024-07-17 16:35:48 -04:00
Vitor Pamplona 45446b4125 Fixes bug on not updating observablesByKindAndAuthor 2024-07-17 16:35:03 -04:00
Vitor PamplonaandGitHub 094d7e71e2 Merge pull request #982 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-07-17 09:16:29 -04:00
Crowdin Bot 75423eaaab New Crowdin translations by GitHub Action 2024-07-17 13:13:57 +00:00
Vitor Pamplona 5e42fa5346 Adds additional logs for the size of observables in the Local Cache 2024-07-17 09:09:38 -04:00
Vitor Pamplona bc6d5060cb Moves frame dropping logs from 20ms to 33ms give than many videos make the UI run at 30fps 2024-07-17 09:09:19 -04:00
Vitor Pamplona e578327c5b Fixes the inability to click on the relay tab on User Profiles 2024-07-17 09:08:41 -04:00
24 changed files with 306 additions and 103 deletions
+2 -2
View File
@@ -15,8 +15,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk libs.versions.android.minSdk.get().toInteger()
targetSdk libs.versions.android.targetSdk.get().toInteger()
versionCode 387
versionName "0.89.1"
versionCode 390
versionName "0.89.4"
buildConfigField "String", "RELEASE_NOTES_ID", "\"1130badb252bdf62aaf93cb65bfa2bb09e7600c7d6764894f2954555b3b73018\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -183,7 +183,8 @@ object ChoreographerHelper {
return
}
val diff = (frameTimeNanos - lastFrameTimeNanos) / 1000000
if (diff > 20) {
// only report after 30ms because videos play at 30fps
if (diff > 35) {
// Follow the frame number
val droppedCount = (diff / 16.6).toInt()
Log.w("block-canary", "Dropped $droppedCount frames. Skipped $diff ms")
@@ -2761,19 +2761,22 @@ class Account(
// Takes a User's relay list and adds the types of feeds they are active for.
fun activeRelays(): Array<RelaySetupInfo>? {
val usersRelayList =
userProfile().latestContactList?.relays()?.map {
val url = RelayUrlFormatter.normalize(it.key)
userProfile()
.latestContactList
?.relays()
?.map {
val url = RelayUrlFormatter.normalize(it.key)
val localFeedTypes =
localRelays.firstOrNull { localRelay -> RelayUrlFormatter.normalize(localRelay.url) == url }?.feedTypes?.minus(setOf(FeedType.SEARCH, FeedType.WALLET_CONNECT))
?: Constants.defaultRelays
.filter { defaultRelay -> defaultRelay.url == url }
.firstOrNull()
?.feedTypes
?: Constants.activeTypesGlobalChats
val localFeedTypes =
localRelays.firstOrNull { localRelay -> RelayUrlFormatter.normalize(localRelay.url) == url }?.feedTypes?.minus(setOf(FeedType.SEARCH, FeedType.WALLET_CONNECT))
?: Constants.defaultRelays
.filter { defaultRelay -> defaultRelay.url == url }
.firstOrNull()
?.feedTypes
?: Constants.activeTypesGlobalChats
RelaySetupInfo(url, it.value.read, it.value.write, localFeedTypes)
} ?: return null
RelaySetupInfo(url, it.value.read, it.value.write, localFeedTypes)
}?.ifEmpty { null } ?: return null
return usersRelayList.toTypedArray()
}
@@ -149,8 +149,8 @@ object LocalCache {
val deletionIndex = DeletionIndex()
private val observablesByKindAndETag = ConcurrentHashMap<Int, ConcurrentHashMap<HexKey, LatestByKindWithETag<Event>>>(10)
private val observablesByKindAndAuthor = ConcurrentHashMap<Int, ConcurrentHashMap<HexKey, LatestByKindAndAuthor<Event>>>(10)
val observablesByKindAndETag = ConcurrentHashMap<Int, ConcurrentHashMap<HexKey, LatestByKindWithETag<Event>>>(10)
val observablesByKindAndAuthor = ConcurrentHashMap<Int, ConcurrentHashMap<HexKey, LatestByKindAndAuthor<Event>>>(10)
fun <T : Event> observeETag(
kind: Int,
@@ -211,9 +211,10 @@ object LocalCache {
}
private fun updateObservables(event: Event) {
val observablesOfKind = observablesByKindAndETag[event.kind] ?: return
event.forEachTaggedEvent {
observablesOfKind[it]?.updateIfMatches(event)
observablesByKindAndETag[event.kind]?.let { observablesOfKind ->
event.forEachTaggedEvent {
observablesOfKind[it]?.updateIfMatches(event)
}
}
observablesByKindAndAuthor[event.kind]?.get(event.pubKey)?.updateIfMatches(event)
@@ -46,7 +46,7 @@ class LatestByKindAndAuthor<T : Event>(
fun canDelete(): Boolean = _latest.subscriptionCount.value == 0
suspend fun init() {
fun restart() {
val latestNote =
if ((kind in 10000..19999) || (kind in 30000..39999)) {
LocalCache.addressables
@@ -70,6 +70,12 @@ class LatestByKindAndAuthor<T : Event>(
)?.event as? T
}
_latest.tryEmit(latestNote)
if (_latest.value != latestNote) {
_latest.tryEmit(latestNote)
}
}
suspend fun init() {
restart()
}
}
@@ -45,7 +45,7 @@ class LatestByKindWithETag<T : Event>(
fun canDelete(): Boolean = _latest.subscriptionCount.value == 0
suspend fun init() {
fun restart() {
val latestNote =
LocalCache.notes
.maxOrNullOf(
@@ -57,6 +57,12 @@ class LatestByKindWithETag<T : Event>(
comparator = CreatedAtComparator,
)?.event as? T
_latest.tryEmit(latestNote)
if (_latest.value != latestNote) {
_latest.tryEmit(latestNote)
}
}
suspend fun init() {
restart()
}
}
@@ -112,7 +112,7 @@ object NostrHomeDataSource : AmethystNostrDataSource("HomeFeed") {
fun createFollowMetadataAndReleaseFilter(): TypedFilter? {
val follows = account.liveHomeFollowLists.value?.users
val followSet = follows?.plus(account.userProfile().pubkeyHex)?.toList()?.ifEmpty { null }
val followSet = follows?.plus(account.userProfile().pubkeyHex)?.shuffled()?.ifEmpty { null }
return if (followSet != null) {
TypedFilter(
@@ -124,7 +124,7 @@ object NostrHomeDataSource : AmethystNostrDataSource("HomeFeed") {
MetadataEvent.KIND,
AdvertisedRelayListEvent.KIND,
),
authors = followSet,
authors = followSet.take(500),
since =
latestEOSEs.users[account.userProfile()]
?.followList
@@ -44,21 +44,24 @@ import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.TextNoteModificationEvent
object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
private var eventsToWatch = setOf<Note>()
private var addressesToWatch = setOf<Note>()
private var nextEventsToWatch = setOf<Note>()
private var nextAddressesToWatch = setOf<Note>()
private var eventsToWatchInProd = setOf<Note>()
private var addressesToWatchInProd = setOf<Note>()
private fun createReactionsToWatchInAddressFilter(): List<TypedFilter>? {
val addressesToWatch =
val myAddressesToWatch =
(
eventsToWatch.filter { it.address() != null } +
addressesToWatch.filter { it.address() != null }
eventsToWatchInProd.filter { it.address() != null } +
addressesToWatchInProd.filter { it.address() != null }
).toSet()
if (addressesToWatch.isEmpty()) {
if (myAddressesToWatch.isEmpty()) {
return null
}
return groupByEOSEPresence(addressesToWatch)
return groupByEOSEPresence(myAddressesToWatch)
.map {
listOf(
TypedFilter(
@@ -102,13 +105,13 @@ object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
}
private fun createAddressFilter(): List<TypedFilter>? {
val addressesToWatch = addressesToWatch.filter { it.event == null }
val myAddressesToWatch = addressesToWatchInProd.filter { it.event == null }
if (addressesToWatch.isEmpty()) {
if (myAddressesToWatch.isEmpty()) {
return null
}
return addressesToWatch.mapNotNull {
return myAddressesToWatch.mapNotNull {
it.address()?.let { aTag ->
if (aTag.kind < 25000 && aTag.dTag.isBlank()) {
TypedFilter(
@@ -137,11 +140,11 @@ object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
}
private fun createRepliesAndReactionsFilter(): List<TypedFilter>? {
if (eventsToWatch.isEmpty()) {
if (eventsToWatchInProd.isEmpty()) {
return null
}
return groupByEOSEPresence(eventsToWatch)
return groupByEOSEPresence(eventsToWatchInProd)
.map {
listOf(
TypedFilter(
@@ -187,11 +190,11 @@ object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
}
private fun createQuotesFilter(): List<TypedFilter>? {
if (eventsToWatch.isEmpty()) {
if (eventsToWatchInProd.isEmpty()) {
return null
}
return groupByEOSEPresence(eventsToWatch)
return groupByEOSEPresence(eventsToWatchInProd)
.map {
listOf(
TypedFilter(
@@ -210,10 +213,10 @@ object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
}
fun createLoadEventsIfNotLoadedFilter(): List<TypedFilter>? {
val directEventsToLoad = eventsToWatch.filter { it.event == null }
val directEventsToLoad = eventsToWatchInProd.filter { it.event == null }
val threadingEventsToLoad =
eventsToWatch
eventsToWatchInProd
.mapNotNull { it.replyTo }
.flatten()
.filter { it !is AddressableNote && it.event == null }
@@ -243,7 +246,7 @@ object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
checkNotInMainThread()
eventsToWatch.forEach {
eventsToWatchInProd.forEach {
val eose = it.lastReactionsDownloadTime[relayUrl]
if (eose == null) {
it.lastReactionsDownloadTime += Pair(relayUrl, EOSETime(time))
@@ -252,7 +255,7 @@ object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
}
}
addressesToWatch.forEach {
addressesToWatchInProd.forEach {
val eose = it.lastReactionsDownloadTime[relayUrl]
if (eose == null) {
it.lastReactionsDownloadTime += Pair(relayUrl, EOSETime(time))
@@ -267,6 +270,9 @@ object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
}
override fun updateChannelFilters() {
addressesToWatchInProd = nextAddressesToWatch
eventsToWatchInProd = nextEventsToWatch
val reactions = createRepliesAndReactionsFilter()
val missing = createLoadEventsIfNotLoadedFilter()
val addresses = createAddressFilter()
@@ -278,29 +284,29 @@ object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
}
fun add(eventId: Note) {
if (!eventsToWatch.contains(eventId)) {
eventsToWatch = eventsToWatch.plus(eventId)
if (!nextEventsToWatch.contains(eventId)) {
nextEventsToWatch = nextEventsToWatch.plus(eventId)
invalidateFilters()
}
}
fun remove(eventId: Note) {
if (eventsToWatch.contains(eventId)) {
eventsToWatch = eventsToWatch.minus(eventId)
if (nextEventsToWatch.contains(eventId)) {
nextEventsToWatch = nextEventsToWatch.minus(eventId)
invalidateFilters()
}
}
fun addAddress(addressableNote: Note) {
if (!addressesToWatch.contains(addressableNote)) {
addressesToWatch = addressesToWatch.plus(addressableNote)
if (!nextAddressesToWatch.contains(addressableNote)) {
nextAddressesToWatch = nextAddressesToWatch.plus(addressableNote)
invalidateFilters()
}
}
fun removeAddress(addressableNote: Note) {
if (addressesToWatch.contains(addressableNote)) {
addressesToWatch = addressesToWatch.minus(addressableNote)
if (nextAddressesToWatch.contains(addressableNote)) {
nextAddressesToWatch = nextAddressesToWatch.minus(addressableNote)
invalidateFilters()
}
}
@@ -325,8 +331,8 @@ fun groupByEOSEPresence(users: Iterable<User>): Collection<List<User>> =
fun findMinimumEOSEs(notes: List<Note>): Map<String, EOSETime> {
val minLatestEOSEs = mutableMapOf<String, EOSETime>()
notes.forEach {
it.lastReactionsDownloadTime.forEach {
notes.forEach { note ->
note.lastReactionsDownloadTime.forEach {
val minEose = minLatestEOSEs[it.key]
if (minEose == null) {
minLatestEOSEs.put(it.key, EOSETime(it.value.time))
@@ -30,6 +30,7 @@ import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
@@ -72,6 +73,7 @@ class MainActivity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
Log.d("Lifetime Event", "MainActivity.onCreate")
@@ -27,6 +27,10 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.BottomAppBarDefaults.windowInsets
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -43,11 +47,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavBackStackEntry
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Size0dp
import com.vitorpamplona.amethyst.ui.theme.Size10Modifier
@@ -126,11 +130,17 @@ private fun RenderBottomMenu(
navEntryState: State<NavBackStackEntry?>,
nav: (Route, Boolean) -> Unit,
) {
Column(modifier = BottomTopHeight) {
Column(
modifier =
Modifier
.fillMaxWidth()
.windowInsetsPadding(windowInsets)
.height(50.dp),
) {
HorizontalDivider(
thickness = DividerThickness,
)
NavigationBar(tonalElevation = Size0dp, containerColor = Color.Transparent) {
NavigationBar(tonalElevation = Size0dp) {
bottomNavigationItems.forEach { item ->
HasNewItemsIcon(item, accountViewModel, navEntryState, nav)
}
@@ -114,8 +114,8 @@ fun AppNavigation(
NavHost(
navController,
startDestination = Route.Home.route,
enterTransition = { fadeIn(animationSpec = tween(200)) },
exitTransition = { fadeOut(animationSpec = tween(200)) },
enterTransition = { fadeIn(animationSpec = tween(400)) },
exitTransition = { fadeOut(animationSpec = tween(400)) },
) {
Route.Home.let { route ->
composable(
@@ -219,7 +219,6 @@ fun AppNavigation(
composable(Route.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, nav) })
composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, nav) })
composable(Route.Drafts.route, content = { DraftListScreen(accountViewModel, nav) })
Route.ContentDiscovery.let { route ->
@@ -130,7 +130,6 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.Size22Modifier
@@ -152,6 +151,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
@@ -699,8 +700,33 @@ class FollowListViewModel(
)
val defaultLists = persistentListOf(kind3Follow, globalFollow, muteListFollow)
val livePeopleListsFlow: Flow<List<CodeName>> by lazy {
flow {
checkNotInMainThread()
emit(getPeopleLists())
emitAll(livePeopleListsFlowObservable)
}
}
fun getPeopleLists(): List<CodeName> =
LocalCache.addressables
.mapNotNull { _, addressableNote ->
val event = (addressableNote.event as? PeopleListEvent)
// Has to have an list
if (
event != null &&
event.pubKey == account.userProfile().pubkeyHex &&
(event.hasAnyTaggedUser() || event.publicAndPrivateUserCache?.isNotEmpty() == true)
) {
CodeName(event.address().toTag(), PeopleListName(addressableNote), CodeNameType.PEOPLE_LIST)
} else {
null
}
}.sortedBy { it.name.name() }
@OptIn(ExperimentalCoroutinesApi::class)
val livePeopleListsFlow: Flow<List<CodeName>> =
val livePeopleListsFlowObservable: Flow<List<CodeName>> =
LocalCache.live.newEventBundles.transformLatest { newNotes ->
val hasNewList =
newNotes.any {
@@ -728,30 +754,13 @@ class FollowListViewModel(
}
if (hasNewList) {
val newFollowLists =
LocalCache.addressables
.mapNotNull { _, addressableNote ->
val event = (addressableNote.event as? PeopleListEvent)
// Has to have an list
if (
event != null &&
event.pubKey == account.userProfile().pubkeyHex &&
(event.tags.size > 1 || event.content.length > 50)
) {
CodeName(event.address().toTag(), PeopleListName(addressableNote), CodeNameType.PEOPLE_LIST)
} else {
null
}
}.sortedBy { it.name.name() }
emit(newFollowLists)
emit(getPeopleLists())
}
}
@OptIn(ExperimentalCoroutinesApi::class)
val liveKind3FollowsFlow: Flow<List<CodeName>> =
account.userProfile().flow().follows.stateFlow.transformLatest {
val communities =
it.user.cachedFollowingCommunitiesSet().mapNotNull {
LocalCache.checkGetOrCreateAddressableNote(it)?.let { communityNote ->
@@ -977,7 +986,6 @@ fun FlexibleTopBarWithBackButton(
navigationIcon = { IconButton(onClick = popBack) { ArrowBackIcon() } },
actions = {},
)
Spacer(modifier = HalfVertSpacer)
HorizontalDivider(thickness = DividerThickness)
}
}
@@ -1071,6 +1079,18 @@ fun debugState(context: Context) {
" / " +
LocalCache.users.size(),
)
Log.d(
"STATE DUMP",
"Deletion Events: " +
LocalCache.deletionIndex.size(),
)
Log.d(
"STATE DUMP",
"Observable Events: " +
LocalCache.observablesByKindAndETag.size +
" / " +
LocalCache.observablesByKindAndAuthor.size,
)
Log.d(
"STATE DUMP",
@@ -35,6 +35,7 @@ import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.DrawerValue
@@ -485,13 +486,7 @@ private fun MainScaffold(
}
},
) {
Column(
modifier =
Modifier.padding(
top = it.calculateTopPadding(),
bottom = it.calculateBottomPadding(),
),
) {
Column(modifier = Modifier.padding(it).imePadding()) {
AppNavigation(
homeFeedViewModel = homeFeedViewModel,
repliesFeedViewModel = repliesFeedViewModel,
@@ -493,7 +493,7 @@ private fun RenderScreen(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val pagerState = rememberPagerState { 9 }
val pagerState = rememberPagerState { 10 }
Column {
ProfileHeader(baseUser, appRecommendations, nav, accountViewModel)
@@ -65,8 +65,8 @@ private val DarkColorPalette =
primary = Purple200,
secondary = Teal200,
tertiary = Teal200,
background = Color(0xFF000000),
surface = Color(0xFF000000),
background = Color(red = 0, green = 0, blue = 0),
surface = Color(red = 0, green = 0, blue = 0),
surfaceVariant = Color(red = 29, green = 26, blue = 34),
)
@@ -451,14 +451,13 @@ fun AmethystTheme(
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
val insetsController = WindowCompat.getInsetsController(window, view)
if (darkTheme) {
window.statusBarColor = colors.background.toArgb()
} else {
window.statusBarColor = colors.primary.toArgb()
}
val insets = WindowCompat.getInsetsController(window, view)
insets.isAppearanceLightNavigationBars = !darkTheme
insets.isAppearanceLightStatusBars = !darkTheme
window.statusBarColor = colors.background.toArgb()
window.navigationBarColor = colors.background.toArgb()
insetsController.isAppearanceLightNavigationBars = !darkTheme
}
}
}
@@ -9,6 +9,7 @@
<string name="post_was_hidden">Ce message a été masqué car il mentionne vos utilisateurs ou mots cachés</string>
<string name="post_was_flagged_as_inappropriate_by">Post signalé inapproprié par</string>
<string name="post_not_found">L\'événement est en cours de chargement ou ne peut pas être trouvé dans votre liste de relais</string>
<string name="post_not_found_short">👀</string>
<string name="channel_image">Image du canal</string>
<string name="referenced_event_not_found">référence de l\'évènement non trouvée</string>
<string name="could_not_decrypt_the_message">Impossible de déchiffrer le message</string>
@@ -132,6 +133,7 @@
<string name="conversations">Conversations</string>
<string name="notes">Notes</string>
<string name="replies">Réponses</string>
<string name="gallery">Galerie</string>
<string name="follows">"Suivis"</string>
<string name="reports">"Signalements"</string>
<string name="more_options">Plus d\'options</string>
@@ -255,6 +257,8 @@
<string name="quick_action_delete">Supprimer</string>
<string name="quick_action_unfollow">Ne plus suivre</string>
<string name="quick_action_follow">Suivre</string>
<string name="quick_action_request_deletion_gallery_title">Supprimer de la galerie</string>
<string name="quick_action_request_deletion_gallery_alert_body">Retirer ce média de votre galerie, vous pourrez le réajouter plus tard</string>
<string name="quick_action_request_deletion_alert_title">Demande de Suppression</string>
<string name="quick_action_request_deletion_alert_body">Amethyst demandera que votre note soit supprimée des relais auxquels vous êtes actuellement connecté. Il n\'y a aucune garantie que votre note sera définitivement supprimée de ces relais, ou d\'autres relais où elle peut être stockée.</string>
<string name="quick_action_block_dialog_btn">Bloquer</string>
@@ -517,6 +521,7 @@
<string name="share_or_save">Partager ou Enregistrer</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="add_media_to_gallery">Ajouter un média à la galerie</string>
<string name="created_at">Créé le</string>
<string name="rules">Règles</string>
<string name="login_with_external_signer">Se connecter avec Amber</string>
@@ -720,6 +725,8 @@
<string name="private_outbox_section_explainer">Insérez entre 1 et 3 relais pour stocker des événements que personne d\'autre ne peut voir, comme vos Brouillons et/ou paramètres d\'application. Idéalement, ces relais sont soit locaux soit nécessitent une authentification avant de télécharger le contenu de chaque utilisateur.</string>
<string name="kind_3_section">Relais généraux</string>
<string name="kind_3_section_description">Amethyst utilise ces relais pour télécharger des messages pour vous.</string>
<string name="kind_3_recommended_section">Relais recommandés</string>
<string name="kind_3_recommended_section_description">Ajoutez les relais suivants à votre liste de relais généraux afin de recevoir les messages des utilisateurs listés.</string>
<string name="search_section">Relais de recherche</string>
<string name="search_section_explainer">Liste des relais à utiliser pour la recherche et l\'étiquetage des utilisateurs. L\'étiquetage et la recherche ne fonctionneront pas si aucune option n\'est disponible.</string>
<string name="local_section">Relais locaux</string>
@@ -766,5 +773,34 @@
<string name="dvm_requesting_job">Tâche demandée par DVM</string>
<string name="nwc_payment_request">Demande de paiement envoyée, en attente de confirmation depuis votre portefeuille</string>
<string name="dvm_waiting_to_confirm_payment">En attente que DVM confirme le paiement ou envoie les résultats</string>
<string name="http_status_400">Mauvaise requête - Le serveur ne peut pas ou ne veut pas traiter la requête.</string>
<string name="http_status_401">Non autorisé - L\'utilisateur n\'a pas d\'identifiants d\'authentification valides</string>
<string name="http_status_402">Paiement requis - Le serveur requiert un paiement pour compléter la requête</string>
<string name="http_status_403">Interdit - L\'utilisateur n\'a pas les droits d\'accès pour effectuer la requête</string>
<string name="http_status_404">Non Trouvé - Le serveur ne peut pas trouver l\'adresse demandée</string>
<string name="http_status_405">Méthode non autorisée - Le serveur supporte la méthode de requête, mais pas la ressource cible</string>
<string name="http_status_406">Non Acceptable - Le serveur ne trouve aucun contenu qui satisfasse la requête.</string>
<string name="http_status_407">Authentification du proxy requise - L\'utilisateur n\'a pas d\'identifiants d\'authentification valides</string>
<string name="http_status_408">Expiration de la requête - Le serveur a expiré en attente de quelqu\'un d\'autre</string>
<string name="http_status_409">Conflit - Le serveur ne peut pas répondre à la requête car il y a un conflit avec la ressource</string>
<string name="http_status_410">Parti - Le contenu demandé a été définitivement supprimé du serveur et ne sera pas rétabli</string>
<string name="http_status_411">Longueur requise - Le serveur rejette la requête car elle nécessite une définition</string>
<string name="http_status_412">La précondition a échoué - Conditions préalables de la demande dans les champs de l\'entête que le serveur ne respecte pas</string>
<string name="http_status_413">Données trop grandes - La requête est supérieure aux limites définies par le serveur, et le serveur refuse de la traiter</string>
<string name="http_status_414">URI trop longue - L\'URL demandée par le client est trop longue pour que le serveur puisse la traiter. </string>
<string name="http_status_415">Type de média non pris en charge - La requête utilise un format média que le serveur ne prend pas en charge</string>
<string name="http_status_416">Plage non satisfaisante - Le serveur ne peut pas remplir la valeur indiquée dans le champ d\'entête Range de requête. </string>
<string name="http_status_417">Attente échouée - Le serveur ne peut pas répondre aux exigences indiquées par le champ d\'entête Expect de la requête</string>
<string name="http_status_426">Mise à jour requise - Le serveur refuse de traiter la requête en utilisant le protocole courant, à moins que le client ne passe à un autre protocole.</string>
<string name="http_status_500">Erreur interne du serveur - Le serveur a rencontré une erreur inattendue et ne peut pas compléter la requête</string>
<string name="http_status_501">Non implémenté - Le serveur ne peut pas répondre à la requête ou ne reconnaît pas la méthode de requête</string>
<string name="http_status_502">Mauvaise passerelle - Le serveur agit comme passerelle et a reçu une réponse invalide de l\'hôte</string>
<string name="http_status_503">Service indisponible - Ceci se produit souvent lorsqu\'un serveur est surchargé ou stoppé pour maintenance</string>
<string name="http_status_504">Délai d\'attente de la passerelle - Le serveur agissait comme une passerelle ou un proxy et a expiré, en attente d\'une réponse</string>
<string name="http_status_505">Version HTTP non prise en charge - Le serveur ne prend pas en charge la version HTTP de la requête</string>
<string name="http_status_506">Variant Also Negotiates - Le serveur a une erreur de configuration interne</string>
<string name="http_status_507">Stockage insuffisant - Le serveur n\'a pas assez d\'espace pour traiter la demande avec succès</string>
<string name="http_status_508">Boucle détectée - Le serveur détecte une boucle infinie lors du traitement de la requête</string>
<string name="http_status_511">Authentification réseau requise - Le client doit être authentifié pour accéder au réseau</string>
<string name="add_a_nip96_server">Ajouter un serveur NIP-96</string>
</resources>
@@ -9,11 +9,13 @@
<string name="post_was_hidden">Ez a bejegyzés el lett rejtve, mert említ rejtett felhasználóidat vagy szavaidat</string>
<string name="post_was_flagged_as_inappropriate_by">A bejegyzést nem megfelelőként jelölte meg</string>
<string name="post_not_found">Bejegyzés nem található</string>
<string name="post_not_found_short">👀</string>
<string name="channel_image">Csatorna Kép</string>
<string name="referenced_event_not_found">A hivatkozott esemény nem található</string>
<string name="could_not_decrypt_the_message">Az üzenet dekódolása sikertelen</string>
<string name="group_picture">Csoport Kép</string>
<string name="explicit_content">Szókimondó tartalom</string>
<string name="spam">Spam</string>
<string name="spam_description">Ebből a csomópontból érkező spam események száma</string>
<string name="impersonation">Megszemélyesítés</string>
<string name="illegal_behavior">Illegális viselkedés</string>
@@ -35,6 +37,7 @@
<string name="report_impersonation">Megszemélyesítés bejelentés</string>
<string name="report_explicit_content">Szókimondó tartalom bejelentés</string>
<string name="report_illegal_behaviour">Illegális viselkedés bejelentés</string>
<string name="report_malware">Vírus bejelentése</string>
<string name="login_with_a_private_key_to_be_able_to_reply">Jelentkezzen be privát kulccsal, hogy válaszolni tudjon</string>
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Jelentkezzen be privát kulccsal a bejegyzések megosztásához</string>
<string name="login_with_a_private_key_to_like_posts">Jelentkezzen be privát kulccsal a bejegyzések kedveléséhez</string>
@@ -52,6 +55,7 @@
<string name="edited_number">szerkesztés #%1$s</string>
<string name="original">eredeti</string>
<string name="quote">Idézet</string>
<string name="fork">Tükrözés</string>
<string name="propose_an_edit">Szerkesztési javaslat</string>
<string name="new_amount_in_sats">Új összeg sats-ban</string>
<string name="add">Hozzáad</string>
@@ -129,6 +133,7 @@
<string name="conversations">Beszélgetések</string>
<string name="notes">Bejegyzések</string>
<string name="replies">Válaszok</string>
<string name="gallery">Galéria</string>
<string name="follows">"Követek"</string>
<string name="reports">"Jelentések"</string>
<string name="more_options">Több beállítás</string>
@@ -251,6 +256,8 @@
<string name="quick_action_delete">Törlés</string>
<string name="quick_action_unfollow">Kikövetés</string>
<string name="quick_action_follow">Követem</string>
<string name="quick_action_request_deletion_gallery_title">Törlés a Galériából</string>
<string name="quick_action_request_deletion_gallery_alert_body">Távolítsa el ezt a médiát a galériából, később elolvashatod</string>
<string name="quick_action_request_deletion_alert_title">Törlés Kérése</string>
<string name="quick_action_request_deletion_alert_body">Az Amethyst kérni fogja, hogy töröljék a bejegyzésed azokról a csomópontokról, amelyekhez jelenleg csatlakozol. Nincs garancia arra, hogy a bejegyzésed ezekről a csomópontokról vagy más közvetítőkből (ahol tárolni lehet) véglegesen törlődik.</string>
<string name="quick_action_block_dialog_btn">Tiltás</string>
@@ -264,6 +271,7 @@
<string name="report_dialog_impersonation">Rosszindulatú megszemélyesítés</string>
<string name="report_dialog_nudity">Meztelenség vagy pornográf tartalom</string>
<string name="report_dialog_illegal">Illegális viselkedés</string>
<string name="report_dialog_malware">Vírus</string>
<string name="report_dialog_blocking_a_user">Az alkalmazásban egy felhasználó letiltása minden hozzá köthető tartalmat elrejt. Jegyzeteid továbbra is nyilvánosan megtekinthetők, beleértve a letiltott személyek számára is. A letiltott felhasználók a Biztonsági szűrők felületen jelennek meg.</string>
<string name="report_dialog_block_hide_user_btn"><![CDATA[Felhasználó Tiltása és Elrejtése]]></string>
<string name="report_dialog_report_btn">Visszaélés jelentése</string>
@@ -301,6 +309,7 @@
<string name="poll_zap_value_min">Minimum Zap</string>
<string name="poll_zap_value_max">Maximum Zap</string>
<string name="poll_consensus_threshold">Konszenzus</string>
<string name="poll_consensus_threshold_percent">(0100)%</string>
<string name="poll_closing_time">Szavazás lezárása</string>
<string name="poll_closing_time_days">napok</string>
<string name="poll_unable_to_vote">Nem lehet szavazni</string>
@@ -335,6 +344,14 @@
<string name="zap_type_nonzap_explainer">Nostr-ban nyoma sincs, csak a Lightning-ben</string>
<string name="file_server">Fájl Szerver</string>
<string name="zap_forward_lnAddress">LnCím vagy @Felhasználó</string>
<string name="media_servers">Média Szerverek</string>
<string name="set_preferred_media_servers">Állítsd be a kívánt médiafeltöltő szervereket.</string>
<string name="no_media_server_message">Nincs beállítva egyéni médiaszerver. Használhatod az Amethyst listáját, vagy alább hozzáadhatsz egyet ↓</string>
<string name="built_in_media_servers_title">Beépített médiaszerverek</string>
<string name="built_in_servers_description">Az Amethist alapértelmezett listája. Hozzáadhatod őket egyenként, vagy hozzáadhatod az egész listát.</string>
<string name="use_default_servers">Az alapértelmezett lista használata</string>
<string name="add_media_server">Médiaszerver hozzáadása</string>
<string name="delete_media_server">Médiaszerver törlése</string>
<string name="upload_server_relays_nip95">Saját csomópontjaid (NIP-95)</string>
<string name="upload_server_relays_nip95_explainer">A fájlokat a csomópontokra töltik fel és ott tárolják. Rögzített URL-től mentesek (harmadik féltől való függőség). Győződj meg róla, hogy legalább egy NIP-95 csomópont a csomópontlistában szerepel</string>
<string name="connect_via_tor_short">Tor/Orbot beállítása</string>
@@ -363,6 +380,7 @@
<string name="app_notification_dms_channel_description">Értesítést küld, amikor privát üzeneted érkezik</string>
<string name="app_notification_zaps_channel_name">Zap-et kaptál</string>
<string name="app_notification_zaps_channel_description">Értesítést küld, amikor valaki neked Zap-et küldött</string>
<string name="app_notification_zaps_channel_message">%1$s sats</string>
<string name="app_notification_zaps_channel_message_from">%1$s -tól</string>
<string name="app_notification_zaps_channel_message_for">%1$s -ért</string>
<string name="reply_notify">Értesítés: </string>
@@ -414,6 +432,7 @@
<string name="minimum_pow">Minimális PoW</string>
<string name="auth">Azonosítás</string>
<string name="payment">Fizetés</string>
<string name="cashu">Cashu Token</string>
<string name="cashu_redeem">Beváltás</string>
<string name="cashu_redeem_to_zap">Küldje a Zap tárcába</string>
<string name="cashu_redeem_to_cashu">Nyissa meg a Cashu tárcában</string>
@@ -442,6 +461,7 @@
<string name="settings">Beállítások</string>
<string name="connectivity_type_always">Mindig</string>
<string name="connectivity_type_wifi_only">Csak WIFI</string>
<string name="connectivity_type_unmetered_wifi_only">Korlátlan WiFi</string>
<string name="connectivity_type_never">Soha</string>
<string name="ui_feature_set_type_complete">Teljes</string>
<string name="ui_feature_set_type_simplified">Egyszerűsített</string>
@@ -500,6 +520,7 @@
<string name="share_or_save">Megosztás vagy Mentés</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="add_media_to_gallery">Média hozzáadása a galériához</string>
<string name="created_at">Létrehozva</string>
<string name="rules">Szabályok</string>
<string name="login_with_external_signer">Bejelentkezés Amber-el</string>
@@ -530,6 +551,7 @@
<string name="missing_lud16">Hiányzó lightning beállítás</string>
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">A %1$s felhasználó a sat fogadáshoz nem rendelkezik LN cím beállítással</string>
<string name="zap_split_weight">Százalék</string>
<string name="zap_split_weight_placeholder">25</string>
<string name="splitting_zaps_with">Zap-ek megosztása</string>
<string name="forwarding_zaps_to">Zap-ek továbbítása</string>
<string name="lightning_wallets_not_found2">Lightning tárca nem található</string>
@@ -596,9 +618,11 @@
<string name="hi_there_is_this_still_available">Szia, ez még aktuális?</string>
<string name="classifieds">Elem eladása</string>
<string name="classifieds_title">Cím</string>
<string name="classifieds_title_placeholder">iPhone 13</string>
<string name="classifieds_condition">Állapot</string>
<string name="classifieds_category">Kategória</string>
<string name="classifieds_price">Ár (satoshiban)</string>
<string name="classifieds_price_placeholder">1000</string>
<string name="classifieds_location">Helyszín</string>
<string name="classifieds_location_placeholder">Város, állam, ország</string>
<string name="classifieds_condition_new">Új</string>
@@ -617,6 +641,7 @@
<string name="classifieds_category_books">Könyvek</string>
<string name="classifieds_category_pets">Háziállatok</string>
<string name="classifieds_category_sports">Sport</string>
<string name="classifieds_category_fitness">Fitnesz</string>
<string name="classifieds_category_art">Művészet</string>
<string name="classifieds_category_crafts">Iparművészet</string>
<string name="classifieds_category_home">Otthon</string>
@@ -631,6 +656,8 @@
<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="failed_to_upload_with_message">Nem sikerült feltölteni: %1$s</string>
<string name="failed_to_delete_with_message">Nem sikerült törölni: %1$s</string>
<string name="edit_draft">Piszkozat szerkesztése</string>
<string name="login_with_qr_code">Bejelentkezés QR kóddal</string>
<string name="route">Útvonal</string>
@@ -649,6 +676,9 @@
<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="change_reaction">Változtasd meg a gyors reakciókat</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>
@@ -694,6 +724,8 @@
<string name="private_outbox_section_explainer">Adj hozzá 13 csomópontot, hogy olyan eseményeket tároljon, amelyeket senki más nem láthat, például a piszkozataidat és/vagy az alkalmazásbeállításaidat. Ideális esetben ezek a csomópontok vagy helyiek, vagy az egyes felhasználók tartalmának letöltése előtt hitelesítést igényelnek.</string>
<string name="kind_3_section">Általános Csomópontok</string>
<string name="kind_3_section_description">Az Amethyst ezeket a csomópontokat a bejegyzések letöltésére használja.</string>
<string name="kind_3_recommended_section">Ajánlott csomópontok</string>
<string name="kind_3_recommended_section_description">Add hozzá a következő csomópontokat az Általános Csomópontok listájához, hogy a felsorolt felhasználóktól üzeneteket kapj.</string>
<string name="search_section">Kereső Csomópontok</string>
<string name="search_section_explainer">A tartalom vagy a felhasználók kereséséhez használt csomópontok listája. A címkézés és a keresés nem működik, ha itt nincsenek megadott lehetőségek. Győződj meg arról, hogy alkalmazzák a NIP-50-et.</string>
<string name="local_section">Helyi Csomópontok</string>
@@ -705,10 +737,14 @@
<string name="this_version_brought_to_you_by">Ezt a verziót hozta neked:</string>
<string name="version_name">%1$s verzió</string>
<string name="thank_you">Köszönöm!</string>
<string name="max_limit">Max Limit</string>
<string name="restricted_writes">Korlátozott írások</string>
<string name="forked_from">Fork-olva innen</string>
<string name="forked_tag">FORK</string>
<string name="git_repository">Git-tár: %1$s</string>
<string name="git_web_address">Web:</string>
<string name="git_clone_address">Klónoz:</string>
<string name="existed_since">OTS: %1$s</string>
<string name="ots_info_title">Időbélyeg igazolás</string>
<string name="ots_info_description">Bizonyíték van arra, hogy ezt a bejegyzést valamikor %1$s előtt írták alá. A bizonyítékot ezen a napon és időpontban a Bitcoin blokkláncába bélyegezték.</string>
<string name="edit_post">Bejegyzés szerkesztése</string>
@@ -736,4 +772,34 @@
<string name="dvm_requesting_job">Feladatot kér a DVM-től</string>
<string name="nwc_payment_request">Fizetési kérelem elküldve, megerősítésére vár a tárcádtól</string>
<string name="dvm_waiting_to_confirm_payment">Várakozás a DVM-re a fizetés megerősítésére vagy az eredmények elküldésére</string>
<string name="http_status_400">Hibás kérés A szerver nem tudja vagy nem akarja a kérést feldolgozni.</string>
<string name="http_status_401">Jogosulatlan A felhasználó nem rendelkezik érvényes hitelesítési adatokkal</string>
<string name="http_status_402">Fizetés szükséges A szerver fizetséget kér a kérés teljesítéséhez</string>
<string name="http_status_403">Tiltott A felhasználónak nincs hozzáférési joga a kérelem benyújtásához</string>
<string name="http_status_404">Nem található A szerver a kért címet nem találja</string>
<string name="http_status_405">Módszer Nem Engedélyezett A kiszolgáló támogatja a kérési módszert, de a célerőforrás nem</string>
<string name="http_status_406">Nem elfogadható A szerver nem talál olyan tartalmat, amely a kérést kielégítené.</string>
<string name="http_status_407">Proxy hitelesítés szükséges A felhasználó nem rendelkezik érvényes hitelesítési adatokkal</string>
<string name="http_status_408">Kérelem időtúllépése A szerver időtúllépés miatt valaki másra vár</string>
<string name="http_status_409">Ütközés A szerver nem tudja teljesíteni a kérést, mert az erőforrással konfliktus van</string>
<string name="http_status_410">Eltűnt A kért tartalmat a szerverről véglegesen törölték, és nem lesz visszaállítva</string>
<string name="http_status_411">Kötelező hossz A szerver elutasítja a kérést, mert nincs kellően definiálva</string>
<string name="http_status_412">Előfeltétel nem sikerült A fejlécben lévő kérés előfeltételeit, a szerver nem teljesíti</string>
<string name="http_status_413">A kérés túl nagy A kérés nagyobb, mint a szerver által meghatározott korlátok, és a szerver nem hajlandó feldolgozni</string>
<string name="http_status_414">Túl hosszú URI Az ügyfél által kért URL túl hosszú ahhoz, hogy a szerver feldolgozza. </string>
<string name="http_status_415">Nem támogatott médiatípus A kérés olyan médiaformátumot használ, amelyet a szerver nem támogat</string>
<string name="http_status_416">Tartomány nem teljesíthető A szerver nem tudja teljesíteni a kérés Tartomány fejlécében jelzett értéket. </string>
<string name="http_status_417">Az elvárás nem sikerült A szerver nem tudja teljesíteni a Várható kérés fejlécében jelzett követelményeket</string>
<string name="http_status_426">Frissítés szükséges A szerver megtagadja a kérés feldolgozását az aktuális protokoll használatával, hacsak az ügyfél nem frissít egy másik protokollra.</string>
<string name="http_status_500">Belső szerverhiba A szerver váratlan hibát észlelt, és nem tudja a kérést teljesíteni</string>
<string name="http_status_501">Nincs végrehajtva A szerver nem tudja a kérést teljesíteni, vagy nem ismeri fel a kérés módszerét</string>
<string name="http_status_502">Rossz átjáró A szerver átjáróként működik, és érvénytelen választ kapott a gazdagéptől</string>
<string name="http_status_503">A szolgáltatás nem elérhető Ez gyakran akkor fordul elő, ha a kiszolgáló túlterhelt vagy karbantartás miatt leáll</string>
<string name="http_status_504">Átjáró Időtúllépés A szerver átjáróként vagy proxyként működött, és a válaszra várva időtúllépés történt</string>
<string name="http_status_505">A HTTP-verzió nem támogatott A szerver a kérésben szereplő HTTP-verziót nem támogatja</string>
<string name="http_status_506">Váltózó porbléma A kiszolgálón belső konfigurációs hiba lépett fel</string>
<string name="http_status_507">Elégtelen tárhely A szervernek nincs elég tárhelye a kérés sikeres feldolgozásához</string>
<string name="http_status_508">Hurok észlelve A szerver a kérés feldolgozása közben végtelen hurkot észlel</string>
<string name="http_status_511">Hálózati hitelesítés szükséges A klienst a hálózat eléréséhez hitelesíteni kell</string>
<string name="add_a_nip96_server">Adj hozzá egy NIP-96 szervert</string>
</resources>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.Amethyst" parent="Theme.AppCompat.Light.NoActionBar">
<style name="Theme.Amethyst" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:statusBarColor">@color/purple_700</item>
<item name="android:windowBackground">@color/black</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:ignore="NewApi">shortEdges</item>
@@ -258,7 +258,7 @@
<string name="quick_action_unfollow">取关</string>
<string name="quick_action_follow">关注</string>
<string name="quick_action_request_deletion_gallery_title">从相册中删除</string>
<string name="quick_action_request_deletion_gallery_alert_body">图库中删除此媒体,但然可供浏览</string>
<string name="quick_action_request_deletion_gallery_alert_body">相册中删除此媒体,但然可供浏览</string>
<string name="quick_action_request_deletion_alert_title">请求删贴</string>
<string name="quick_action_request_deletion_alert_body">Amethyst 将请求你的记录从当前连接的中继器中删除。不能保证你发布的笔记将被永久从那些中继器或其他存储笔记的中继器中删除。</string>
<string name="quick_action_block_dialog_btn">阻止</string>
@@ -725,6 +725,7 @@
<string name="private_outbox_section_explainer">设置 1 ~ 3 个中继用于保存其他人无法看到的事件,例如您的草稿和软件设置。理想情况下,这些应该是本地中继,或者是在获取用户内容时需要经过身份验证的中继。</string>
<string name="kind_3_section">通用中继</string>
<string name="kind_3_section_description">Amethyst 会通过这些中继为您获取帖子。</string>
<string name="kind_3_recommended_section">推荐的中继器</string>
<string name="search_section">搜索中继</string>
<string name="search_section_explainer">用于关键词和标签检索的中继列表。如果没有可用选项,也就无法使用关键词和标签检索。需要确保它们支持 NIP-50。</string>
<string name="local_section">本地中继</string>
@@ -9,6 +9,7 @@
<string name="post_was_hidden">此帖被隱藏,因爲它提到了你隱藏的用戶或單詞</string>
<string name="post_was_flagged_as_inappropriate_by">貼文被標記為不當</string>
<string name="post_not_found">事件正在加載或無法在你的中繼器列表中找到</string>
<string name="post_not_found_short">👀</string>
<string name="channel_image">頻道圖片</string>
<string name="referenced_event_not_found">未找到相關事件</string>
<string name="could_not_decrypt_the_message">無法解密消息</string>
@@ -36,6 +37,7 @@
<string name="report_impersonation">舉報冒充</string>
<string name="report_explicit_content">舉報露骨內容</string>
<string name="report_illegal_behaviour">舉報非法行為</string>
<string name="report_malware">舉報惡意軟體</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>
@@ -111,6 +113,7 @@
<string name="website_url">網站鏈接</string>
<string name="ln_address">LN 地址</string>
<string name="ln_url_outdated">LN 鏈接(過期)</string>
<string name="save_to_gallery">保存到相冊</string>
<string name="image_saved_to_the_gallery">圖片已保存到相冊</string>
<string name="failed_to_save_the_image">保存圖片失敗</string>
<string name="upload_image">上傳圖片</string>
@@ -128,6 +131,7 @@
<string name="conversations">會話</string>
<string name="notes">動態</string>
<string name="replies">回覆</string>
<string name="gallery">相冊</string>
<string name="follows">"關注"</string>
<string name="reports">"舉報"</string>
<string name="more_options">更多選項</string>
@@ -251,6 +255,8 @@
<string name="quick_action_delete">刪除</string>
<string name="quick_action_unfollow">取關</string>
<string name="quick_action_follow">關注</string>
<string name="quick_action_request_deletion_gallery_title">從相冊中刪除</string>
<string name="quick_action_request_deletion_gallery_alert_body">從相冊中刪除此媒體,但仍然可供瀏覽</string>
<string name="quick_action_request_deletion_alert_title">請求刪貼</string>
<string name="quick_action_request_deletion_alert_body">Amethyst 將請求您的記錄從當前連接的中繼器中刪除。不能保證您發佈的筆記將被永久從那些中繼器或其他存儲筆記的中繼器中刪除。</string>
<string name="quick_action_block_dialog_btn">屏蔽</string>
@@ -264,6 +270,7 @@
<string name="report_dialog_impersonation">惡意冒充</string>
<string name="report_dialog_nudity">暴露裸體或噁心內容</string>
<string name="report_dialog_illegal">非法行為</string>
<string name="report_dialog_malware">惡意軟體</string>
<string name="report_dialog_blocking_a_user">屏蔽一位用戶將會在你的應用中隱藏他的內容。你的筆記仍然是公開可見的,包括對被你屏蔽的用戶。屏蔽的用戶將會被列在安全過濾器屏幕上。</string>
<string name="report_dialog_block_hide_user_btn"><![CDATA[阻止並隱藏用户]]></string>
<string name="report_dialog_report_btn">舉報違規</string>
@@ -301,6 +308,7 @@
<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">(0100)%</string>
<string name="poll_closing_time">後關閉</string>
<string name="poll_closing_time_days"></string>
<string name="poll_unable_to_vote">無法投票</string>
@@ -335,6 +343,14 @@
<string name="zap_type_nonzap_explainer">Nostr 上沒有痕跡,僅在閃電上</string>
<string name="file_server">文檔服務器</string>
<string name="zap_forward_lnAddress">LnAddress 或 @User</string>
<string name="media_servers">媒體服務器</string>
<string name="set_preferred_media_servers">設置首選媒體上傳服務器</string>
<string name="no_media_server_message">尚未設置自定義媒體服務器。可以使用默認的內置服務器列表或自行添加 ↓</string>
<string name="built_in_media_servers_title">內置媒體服務器</string>
<string name="built_in_servers_description">Amethyst 的默認列表。可單獨添加或添加整個列表。</string>
<string name="use_default_servers">使用默認列表</string>
<string name="add_media_server">添加媒體服務器</string>
<string name="delete_media_server">刪除媒體服務器</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>
@@ -383,6 +399,7 @@
<string name="warn_when_posts_have_reports_from_your_follows">當帖子有你的關注的報告時警告</string>
<string name="new_reaction_symbol">新回應符號</string>
<string name="no_reaction_type_setup_long_press_to_change">未選擇回應類型。長按可更改</string>
<string name="zapraiser">Zapraiser</string>
<string name="zapraiser_explainer">為這個帖子添加目標聰金額。支持的客戶端可以將此顯示為獎勵捐贈的進度條</string>
<string name="zapraiser_target_amount_in_sats">目標聰金額</string>
<string name="sats_to_complete">Zapraiser 位於 %1$s。距離目標%2$s聰</string>
@@ -403,6 +420,7 @@
<string name="languages">語言</string>
<string name="tags">標籤</string>
<string name="posting_policy">發佈政策</string>
<string name="relay_error_messages">該中繼的錯誤和通知</string>
<string name="message_length">消息長度</string>
<string name="subscriptions">訂閱</string>
<string name="filters">篩選器</string>
@@ -442,6 +460,7 @@
<string name="settings">設置</string>
<string name="connectivity_type_always">始終</string>
<string name="connectivity_type_wifi_only">僅限 WiFi</string>
<string name="connectivity_type_unmetered_wifi_only">未計量的 WiFi</string>
<string name="connectivity_type_never">永不</string>
<string name="ui_feature_set_type_complete">完整版</string>
<string name="ui_feature_set_type_simplified">簡化版</string>
@@ -496,8 +515,10 @@
<string name="load_image_description">何時加載圖像</string>
<string name="copy_to_clipboard">複製至剪貼簿</string>
<string name="copy_npub_to_clipboard">複製 npub 至剪貼簿</string>
<string name="share_or_save">分享或保存</string>
<string name="copy_url_to_clipboard">將 URL 複製到剪貼簿</string>
<string name="copy_the_note_id_to_the_clipboard">將筆記 ID 複製到剪貼簿</string>
<string name="add_media_to_gallery">將媒體添加到相冊</string>
<string name="created_at">創建於</string>
<string name="rules">規則</string>
<string name="login_with_external_signer">使用 Amber 登錄</string>
@@ -508,6 +529,9 @@
<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_failed_to_reach_server">無法連接 %1$s%2$s</string>
<string name="relay_information_document_error_failed_to_parse_response">無法解析來自 %1$s 的結果:%2$s</string>
<string name="relay_information_document_error_failed_with_http">中继拒绝请求 %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>
@@ -524,6 +548,7 @@
<string name="missing_lud16">閃電設置缺失</string>
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">用户 %1$s 尚未設置接收聰的閃電地址</string>
<string name="zap_split_weight">百分比</string>
<string name="zap_split_weight_placeholder">25</string>
<string name="splitting_zaps_with">與此用戶拆分打閃</string>
<string name="forwarding_zaps_to">將打閃轉發到</string>
<string name="lightning_wallets_not_found2">找不到閃電錢包</string>
@@ -590,9 +615,11 @@
<string name="hi_there_is_this_still_available">你好,這個還能買到嗎?</string>
<string name="classifieds">出售物品</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_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>
@@ -626,6 +653,8 @@
<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="failed_to_upload_with_message">上傳失敗:%1$s</string>
<string name="failed_to_delete_with_message">刪除失敗:%1$s</string>
<string name="edit_draft">編輯草稿</string>
<string name="login_with_qr_code">使用二維碼登錄</string>
<string name="route">路徑</string>
@@ -646,6 +675,7 @@
<string name="boost_or_quote_description">提升或引用</string>
<string name="like_description">點贊</string>
<string name="zap_description">打閃</string>
<string name="change_reaction">修改快速回應</string>
<string name="profile_image_of_user">%1$s 的頭像圖片</string>
<string name="relay_info">中繼器 %1$s</string>
<string name="expand_relay_list">展開中繼列表</string>
@@ -656,6 +686,7 @@
<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>
@@ -690,6 +721,7 @@
<string name="private_outbox_section_explainer">輸入 1-3 個中繼用於存儲別人看不到的事件,例如你的草稿和/或應用設置。理想情況下,這些中繼要么是本地的,要么需要在下載每個用戶的內容之前進行驗證。</string>
<string name="kind_3_section">一般中繼器</string>
<string name="kind_3_section_description">Amethyst 使用這些中繼爲你下載帖子。</string>
<string name="kind_3_recommended_section">推薦的中繼器</string>
<string name="search_section">搜索中繼器</string>
<string name="search_section_explainer">搜尋內容或用戶時使用的中繼列表。如果沒有可用選項,標記和搜索將無法運作。確保他們實施 NIP-50。</string>
<string name="local_section">本地中繼器</string>
@@ -703,7 +735,9 @@
<string name="thank_you">謝謝!</string>
<string name="max_limit">最大上限</string>
<string name="restricted_writes">寫入限制</string>
<string name="git_repository">Git 倉庫:%1$s</string>
<string name="git_web_address">網址</string>
<string name="git_clone_address">克隆:</string>
<string name="existed_since">OTS%1$s</string>
<string name="ots_info_title">時間戳證明</string>
<string name="ots_info_description">有在 %1$s 之前的某個時間簽署了此帖子的證明。此證明是在那個日期和時間在比特幣區塊鏈中記錄的時間戳。</string>
@@ -732,4 +766,5 @@
<string name="dvm_requesting_job">正在向 DVM 申請數據作業</string>
<string name="nwc_payment_request">付款請求已發送,正在等待來自你的錢包的確認</string>
<string name="dvm_waiting_to_confirm_payment">正在等待 DVM 確認付款或返回結果</string>
<string name="add_a_nip96_server">添加 NIP-96 服務器</string>
</resources>
@@ -75,6 +75,7 @@ class Relay(
private var afterEOSEPerSubscription = mutableMapOf<String, Boolean>()
private val authResponse = mutableMapOf<HexKey, Boolean>()
private val authChallengesSent = mutableSetOf<String>()
private val outboxCache = mutableMapOf<HexKey, EventInterface>()
fun register(listener: Listener) {
@@ -372,6 +373,9 @@ class Relay(
fun resetEOSEStatuses() {
afterEOSEPerSubscription = LinkedHashMap(afterEOSEPerSubscription.size)
authResponse.clear()
authChallengesSent.clear()
}
fun sendFilter(
@@ -492,8 +496,19 @@ class Relay(
}
private fun sendAuth(signedEvent: RelayAuthEvent) {
authResponse.put(signedEvent.id, false)
writeToSocket("""["AUTH",${signedEvent.toJson()}]""")
val challenge = signedEvent.challenge() ?: ""
// only send replies to new challenges to avoid infinite loop:
// 1. Auth is sent
// 2. auth is rejected
// 3. auth is requested
// 4. auth is sent
// ...
if (!authChallengesSent.contains(challenge)) {
authResponse.put(signedEvent.id, false)
authChallengesSent.add(challenge)
writeToSocket("""["AUTH",${signedEvent.toJson()}]""")
}
}
private fun sendEvent(signedEvent: EventInterface) {
@@ -110,4 +110,6 @@ class DeletionIndex {
val deletionEvent = deletedReferencesBefore.get(key)
return deletionEvent != null && createdAt <= deletionEvent.createdAt
}
fun size() = deletedReferencesBefore.size()
}
@@ -59,7 +59,7 @@ class FileServersEvent(
servers
.map {
arrayOf("server", it)
}.plusElement(arrayOf("alt", "Relay list to use for Search"))
}.plusElement(arrayOf("alt", ALT))
.toTypedArray()
fun updateRelayList(
@@ -54,7 +54,7 @@ abstract class GeneralListEvent(
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
fun nameOrTitle() = name() ?: title()
fun nameOrTitle() = name()?.ifBlank { null } ?: title()?.ifBlank { null }
fun cachedPrivateTags(): Array<Array<String>>? = privateTagsCache