feat: add account setting to forward kind 0 events to local relay
Adds a toggle in the Local Relays settings section that, when enabled, forwards all received kind 0 (profile/metadata) events to the user's configured local relays. - AccountSettings: add sendKind0EventsToLocalRelay MutableStateFlow + toggle method - LocalPreferences: persist the new setting - ForwardKind0ToLocalRelayState: uses EventCollector to intercept kind 0 events from any relay and publish them to local relays when enabled - Account: instantiate ForwardKind0ToLocalRelayState - AccountViewModel: expose toggleSendKind0ToLocalRelay - LocalRelayListView: add Switch toggle above the relay list - strings.xml: add label and description strings https://claude.ai/code/session_01Er9VV7nHyDb5pihrunb81F
This commit is contained in:
@@ -91,6 +91,7 @@ private object PrefKeys {
|
||||
const val NOSTR_PRIVKEY = "nostr_privkey"
|
||||
const val NOSTR_PUBKEY = "nostr_pubkey"
|
||||
const val LOCAL_RELAY_SERVERS = "localRelayServers"
|
||||
const val SEND_KIND0_TO_LOCAL_RELAY = "sendKind0ToLocalRelay"
|
||||
const val DEFAULT_FILE_SERVER = "defaultFileServer"
|
||||
const val STRIP_LOCATION_ON_UPLOAD = "stripLocationOnUpload"
|
||||
const val DEFAULT_HOME_FOLLOW_LIST = "defaultHomeFollowList"
|
||||
@@ -359,6 +360,8 @@ object LocalPreferences {
|
||||
remove(PrefKeys.LOCAL_RELAY_SERVERS)
|
||||
}
|
||||
|
||||
putBoolean(PrefKeys.SEND_KIND0_TO_LOCAL_RELAY, settings.sendKind0EventsToLocalRelay.value)
|
||||
|
||||
putOrRemove(PrefKeys.LATEST_MUTE_LIST, settings.backupMuteList)
|
||||
putOrRemove(PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST, settings.backupPrivateHomeRelayList)
|
||||
putOrRemove(PrefKeys.LATEST_APP_SPECIFIC_DATA, settings.backupAppSpecificData)
|
||||
@@ -474,6 +477,7 @@ object LocalPreferences {
|
||||
val hideNIP17WarningDialog = getBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, false)
|
||||
val hasDonatedInVersion = getStringSet(PrefKeys.HAS_DONATED_IN_VERSION, null) ?: setOf()
|
||||
val localRelayServers = getStringSet(PrefKeys.LOCAL_RELAY_SERVERS, null) ?: setOf()
|
||||
val sendKind0ToLocalRelay = getBoolean(PrefKeys.SEND_KIND0_TO_LOCAL_RELAY, false)
|
||||
|
||||
val defaultHomeFollowListStr = getString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, null)
|
||||
val defaultStoriesFollowListStr = getString(PrefKeys.DEFAULT_STORIES_FOLLOW_LIST, null)
|
||||
@@ -560,6 +564,7 @@ object LocalPreferences {
|
||||
transientAccount = false,
|
||||
externalSignerPackageName = externalSignerPackageName,
|
||||
localRelayServers = MutableStateFlow(localRelayServers),
|
||||
sendKind0EventsToLocalRelay = MutableStateFlow(sendKind0ToLocalRelay),
|
||||
defaultFileServer = defaultFileServer.await(),
|
||||
stripLocationOnUpload = stripLocationOnUpload,
|
||||
defaultHomeFollowList = MutableStateFlow(defaultHomeFollowList.await()),
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
import com.vitorpamplona.amethyst.logTime
|
||||
import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListDecryptionCache
|
||||
import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListState
|
||||
import com.vitorpamplona.amethyst.model.localRelays.ForwardKind0ToLocalRelayState
|
||||
import com.vitorpamplona.amethyst.model.localRelays.LocalRelayListState
|
||||
import com.vitorpamplona.amethyst.model.nip01UserMetadata.AccountHomeRelayState
|
||||
import com.vitorpamplona.amethyst.model.nip01UserMetadata.AccountOutboxRelayState
|
||||
@@ -262,6 +263,8 @@ class Account(
|
||||
|
||||
val userMetadata = UserMetadataState(signer, cache, scope, settings)
|
||||
|
||||
val forwardKind0ToLocalRelay = ForwardKind0ToLocalRelayState(client, localRelayList, settings)
|
||||
|
||||
override val nip47SignerState = NwcSignerState(signer, nwcFilterAssembler, cache, scope, settings.zapPaymentRequest)
|
||||
|
||||
val nip65RelayList = Nip65RelayListState(signer, cache, scope, settings)
|
||||
|
||||
@@ -159,6 +159,7 @@ class AccountSettings(
|
||||
val transientAccount: Boolean = false,
|
||||
var externalSignerPackageName: String? = null,
|
||||
var localRelayServers: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
||||
val sendKind0EventsToLocalRelay: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
var defaultFileServer: ServerName = DEFAULT_MEDIA_SERVERS[0],
|
||||
var stripLocationOnUpload: Boolean = true,
|
||||
val defaultHomeFollowList: MutableStateFlow<TopFilter> = MutableStateFlow(TopFilter.AllFollows),
|
||||
@@ -424,6 +425,13 @@ class AccountSettings(
|
||||
}
|
||||
}
|
||||
|
||||
fun changeSendKind0EventsToLocalRelay(send: Boolean) {
|
||||
if (sendKind0EventsToLocalRelay.value != send) {
|
||||
sendKind0EventsToLocalRelay.tryEmit(send)
|
||||
saveAccountSettings()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateUserMetadata(newMetadata: MetadataEvent?) {
|
||||
if (newMetadata == null) return
|
||||
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.model.localRelays
|
||||
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.EventCollector
|
||||
|
||||
class ForwardKind0ToLocalRelayState(
|
||||
val client: INostrClient,
|
||||
val localRelayList: LocalRelayListState,
|
||||
val settings: AccountSettings,
|
||||
) {
|
||||
private val eventCollector =
|
||||
EventCollector(client) { event, _ ->
|
||||
if (event is MetadataEvent && settings.sendKind0EventsToLocalRelay.value) {
|
||||
val localRelays = localRelayList.flow.value
|
||||
if (localRelays.isNotEmpty()) {
|
||||
client.publish(event, localRelays)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
eventCollector.destroy()
|
||||
}
|
||||
}
|
||||
+4
@@ -1144,6 +1144,10 @@ class AccountViewModel(
|
||||
|
||||
fun filterSpamFromStrangers() = account.settings.syncedSettings.security.filterSpamFromStrangers
|
||||
|
||||
fun toggleSendKind0ToLocalRelay(enabled: Boolean) {
|
||||
account.settings.changeSendKind0EventsToLocalRelay(enabled)
|
||||
}
|
||||
|
||||
fun updateWarnReports(warnReports: Boolean) = launchSigner { account.updateWarnReports(warnReports) }
|
||||
|
||||
fun updateFilterSpam(filterSpam: Boolean) =
|
||||
|
||||
+25
@@ -25,11 +25,16 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@@ -37,6 +42,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySet
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
|
||||
@@ -65,6 +71,25 @@ fun LazyListScope.renderLocalItems(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
item {
|
||||
val sendKind0 by accountViewModel.account.settings.sendKind0EventsToLocalRelay
|
||||
.collectAsStateWithLifecycle()
|
||||
var checked by remember(sendKind0) { mutableStateOf(sendKind0) }
|
||||
|
||||
SettingsRow(
|
||||
R.string.send_kind0_to_local_relay_title,
|
||||
R.string.send_kind0_to_local_relay_description,
|
||||
) {
|
||||
Switch(
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
accountViewModel.toggleSendKind0ToLocalRelay(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(feedState, key = { _, item -> "Local" + item.relay.url }) { index, item ->
|
||||
BasicRelaySetupInfoDialog(
|
||||
item,
|
||||
|
||||
@@ -1517,6 +1517,8 @@
|
||||
<string name="search_section_explainer">List of relays to use when searching content or users. Tagging and search will not work if no options are available. Make sure they implement NIP-50.</string>
|
||||
<string name="local_section">Local Relays</string>
|
||||
<string name="local_section_explainer">List of relays that are running in this device.</string>
|
||||
<string name="send_kind0_to_local_relay_title">Forward profiles to local relay</string>
|
||||
<string name="send_kind0_to_local_relay_description">Send all received profile (kind 0) events to your local relay</string>
|
||||
|
||||
<string name="trusted_relays_title">Trusted Relays</string>
|
||||
<string name="trusted_section">Trusted Relays</string>
|
||||
|
||||
Reference in New Issue
Block a user