mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-24 18:43:59 +02:00
Updates the User Profile's Relay List to an outbox version
This commit is contained in:
@@ -309,7 +309,9 @@ object LocalCache : ILocalCache {
|
||||
require(isValidHex(key = key)) { "$key is not a valid hex" }
|
||||
|
||||
return users.getOrCreate(key) {
|
||||
User(it)
|
||||
val nip65RelayListNote = getOrCreateAddressableNoteInternal(AdvertisedRelayListEvent.createAddress(key))
|
||||
val dmRelayListNote = getOrCreateAddressableNoteInternal(ChatMessageRelayListEvent.createAddress(key))
|
||||
User(it, nip65RelayListNote, dmRelayListNote)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -50,6 +50,8 @@ import java.math.BigDecimal
|
||||
@Stable
|
||||
class User(
|
||||
val pubkeyHex: String,
|
||||
val nip65RelayListNote: Note,
|
||||
val dmRelayListNote: Note,
|
||||
) {
|
||||
var info: UserMetadata? = null
|
||||
|
||||
@@ -72,9 +74,9 @@ class User(
|
||||
|
||||
fun pubkeyDisplayHex() = pubkeyNpub().toShortDisplay()
|
||||
|
||||
fun dmInboxRelayList() = (LocalCache.getAddressableNoteIfExists(ChatMessageRelayListEvent.createAddressTag(pubkeyHex))?.event as? ChatMessageRelayListEvent)
|
||||
fun dmInboxRelayList() = dmRelayListNote.event as? ChatMessageRelayListEvent
|
||||
|
||||
fun authorRelayList() = (LocalCache.getAddressableNoteIfExists(AdvertisedRelayListEvent.createAddressTag(pubkeyHex))?.event as? AdvertisedRelayListEvent)
|
||||
fun authorRelayList() = nip65RelayListNote.event as? AdvertisedRelayListEvent
|
||||
|
||||
fun toNProfile() = NProfile.create(pubkeyHex, relayHints())
|
||||
|
||||
@@ -139,8 +141,6 @@ class User(
|
||||
?.followers
|
||||
?.invalidateData()
|
||||
}
|
||||
|
||||
flowSet?.relays?.invalidateData()
|
||||
}
|
||||
|
||||
fun addReport(note: Note) {
|
||||
@@ -227,7 +227,7 @@ class User(
|
||||
here.counter++
|
||||
}
|
||||
|
||||
flowSet?.relayInfo?.invalidateData()
|
||||
flowSet?.usedRelays?.invalidateData()
|
||||
}
|
||||
|
||||
fun updateUserInfo(
|
||||
@@ -339,20 +339,18 @@ class UserFlowSet(
|
||||
// Observers line up here.
|
||||
val metadata = UserBundledRefresherFlow(u)
|
||||
val follows = UserBundledRefresherFlow(u)
|
||||
val relays = UserBundledRefresherFlow(u)
|
||||
val followers = UserBundledRefresherFlow(u)
|
||||
val reports = UserBundledRefresherFlow(u)
|
||||
val relayInfo = UserBundledRefresherFlow(u)
|
||||
val usedRelays = UserBundledRefresherFlow(u)
|
||||
val zaps = UserBundledRefresherFlow(u)
|
||||
val statuses = UserBundledRefresherFlow(u)
|
||||
|
||||
fun isInUse(): Boolean =
|
||||
metadata.hasObservers() ||
|
||||
relays.hasObservers() ||
|
||||
follows.hasObservers() ||
|
||||
followers.hasObservers() ||
|
||||
reports.hasObservers() ||
|
||||
relayInfo.hasObservers() ||
|
||||
usedRelays.hasObservers() ||
|
||||
zaps.hasObservers() ||
|
||||
statuses.hasObservers()
|
||||
}
|
||||
|
@@ -43,7 +43,6 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
@@ -634,61 +633,18 @@ fun observeUserStatuses(
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeUserRelayIntoList(
|
||||
user: User,
|
||||
relayUrl: NormalizedRelayUrl,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Boolean> {
|
||||
// Subscribe in the relay for changes in the metadata of this user.
|
||||
UserFinderFilterAssemblerSubscription(user, accountViewModel)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(user) {
|
||||
user
|
||||
.flow()
|
||||
.relayInfo
|
||||
.stateFlow
|
||||
.sample(1000)
|
||||
.mapLatest { userState ->
|
||||
userState.user.latestContactList
|
||||
?.relays()
|
||||
?.none { it.key == relayUrl } == true
|
||||
remember(accountViewModel) {
|
||||
accountViewModel.account.trustedRelays.flow
|
||||
.mapLatest { relays ->
|
||||
relayUrl in relays
|
||||
}.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.Default)
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(false)
|
||||
}
|
||||
|
||||
data class RelayUsage(
|
||||
val relays: List<NormalizedRelayUrl> = emptyList(),
|
||||
val userRelayList: List<NormalizedRelayUrl> = emptyList(),
|
||||
)
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
@Composable
|
||||
fun observeUserRelaysUsing(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<RelayUsage> {
|
||||
// Subscribe in the relay for changes in the metadata of this user.
|
||||
UserFinderFilterAssemblerSubscription(user, accountViewModel)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(user) {
|
||||
combine(user.flow().relays.stateFlow, user.flow().relayInfo.stateFlow) { relays, relayInfo ->
|
||||
val userRelaysBeingUsed = relays.user.relaysBeingUsed.map { it.key }
|
||||
val currentUserRelays =
|
||||
relayInfo.user.latestContactList
|
||||
?.relays()
|
||||
?.map { it.key } ?: emptyList()
|
||||
|
||||
RelayUsage(userRelaysBeingUsed, currentUserRelays)
|
||||
}.sample(1000)
|
||||
.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.Default)
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(RelayUsage())
|
||||
}
|
||||
|
@@ -45,6 +45,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
@@ -67,7 +68,7 @@ fun RelayCompose(
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
verticalArrangement = Arrangement.spacedBy(Size5dp),
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
|
@@ -388,7 +388,7 @@ private fun RelayOptionsAction(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val isCurrentlyOnTheUsersList by observeUserRelayIntoList(accountViewModel.userProfile(), relay, accountViewModel)
|
||||
val isCurrentlyOnTheUsersList by observeUserRelayIntoList(relay, accountViewModel)
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
if (isCurrentlyOnTheUsersList) {
|
||||
|
@@ -20,23 +20,29 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.relays
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.RelayInfo
|
||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.note.RelayCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
|
||||
@Composable
|
||||
fun RelayFeedView(
|
||||
@@ -45,16 +51,45 @@ fun RelayFeedView(
|
||||
nav: INav,
|
||||
enablePullRefresh: Boolean = true,
|
||||
) {
|
||||
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
|
||||
val outboxListState by viewModel.nip65OutboxFlow.collectAsStateWithLifecycle()
|
||||
val inboxListState by viewModel.nip65InboxFlow.collectAsStateWithLifecycle()
|
||||
val dmListState by viewModel.dmInboxFlow.collectAsStateWithLifecycle()
|
||||
|
||||
RefresheableBox(viewModel, enablePullRefresh) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = FeedPadding,
|
||||
contentPadding = PaddingValues(top = 10.dp, bottom = 20.dp),
|
||||
state = listState,
|
||||
) {
|
||||
itemsIndexed(feedState, key = { _, item -> item.url.url }) { _, item ->
|
||||
item {
|
||||
SettingsCategory(
|
||||
stringRes(R.string.public_home_section),
|
||||
stringRes(R.string.public_home_section_explainer_profile),
|
||||
Modifier.padding(top = 10.dp, bottom = 8.dp, start = 10.dp, end = 10.dp),
|
||||
)
|
||||
}
|
||||
itemsIndexed(outboxListState, key = { _, item -> "outbox" + item.url.url }) { _, item ->
|
||||
RenderRelayRow(item, accountViewModel, nav)
|
||||
}
|
||||
item {
|
||||
SettingsCategory(
|
||||
stringRes(R.string.public_notif_section),
|
||||
stringRes(R.string.public_notif_section_explainer_profile),
|
||||
Modifier.padding(top = 24.dp, bottom = 8.dp, start = 10.dp, end = 10.dp),
|
||||
)
|
||||
}
|
||||
itemsIndexed(inboxListState, key = { _, item -> "inbox" + item.url.url }) { _, item ->
|
||||
RenderRelayRow(item, accountViewModel, nav)
|
||||
}
|
||||
item {
|
||||
SettingsCategory(
|
||||
stringRes(R.string.private_inbox_section),
|
||||
stringRes(R.string.private_inbox_section_explainer_profile),
|
||||
Modifier.padding(top = 24.dp, bottom = 8.dp, start = 10.dp, end = 10.dp),
|
||||
)
|
||||
}
|
||||
itemsIndexed(dmListState, key = { _, item -> "dminbox" + item.url.url }) { _, item ->
|
||||
RenderRelayRow(item, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
|
@@ -29,18 +29,24 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.model.RelayInfo
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
|
||||
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ReadWrite
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.collections.map
|
||||
|
||||
@Stable
|
||||
class RelayFeedViewModel :
|
||||
@@ -51,11 +57,90 @@ class RelayFeedViewModel :
|
||||
.thenByDescending { it.counter }
|
||||
.thenBy { it.url.url }
|
||||
|
||||
private val _feedContent = MutableStateFlow<List<RelayInfo>>(emptyList())
|
||||
val feedContent = _feedContent.asStateFlow()
|
||||
var currentUser: MutableStateFlow<User?> = MutableStateFlow(null)
|
||||
|
||||
var currentUser: User? = null
|
||||
var currentJob: Job? = null
|
||||
fun convert(
|
||||
relays: Set<NormalizedRelayUrl>?,
|
||||
user: User?,
|
||||
): List<RelayInfo> {
|
||||
if (relays == null || user == null) return emptyList()
|
||||
return relays
|
||||
.map { relay ->
|
||||
user.relaysBeingUsed[relay] ?: RelayInfo(relay, 0, 0)
|
||||
}.sortedWith(order)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val nip65OutboxFlow =
|
||||
currentUser
|
||||
.transformLatest { user ->
|
||||
if (user != null) {
|
||||
emitAll(
|
||||
combine(
|
||||
user.nip65RelayListNote
|
||||
.flow()
|
||||
.metadata.stateFlow,
|
||||
user.flow().usedRelays.stateFlow,
|
||||
) { nip65, userState ->
|
||||
val relays = (nip65.note.event as? AdvertisedRelayListEvent)?.writeRelaysNorm()?.toSet() ?: emptySet()
|
||||
convert(relays, userState.user)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
emit(emptyList<RelayInfo>())
|
||||
}
|
||||
}.onStart {
|
||||
emit(convert((currentUser.value?.nip65RelayListNote?.event as? AdvertisedRelayListEvent)?.writeRelaysNorm()?.toSet(), currentUser.value))
|
||||
}.flowOn(Dispatchers.Default)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val nip65InboxFlow =
|
||||
currentUser
|
||||
.transformLatest { user ->
|
||||
if (user != null) {
|
||||
emitAll(
|
||||
combine(
|
||||
user.nip65RelayListNote
|
||||
.flow()
|
||||
.metadata.stateFlow,
|
||||
user.flow().usedRelays.stateFlow,
|
||||
) { nip65, userState ->
|
||||
val relays = (nip65.note.event as? AdvertisedRelayListEvent)?.readRelaysNorm()?.toSet() ?: emptySet()
|
||||
convert(relays, userState.user)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
emit(emptyList<RelayInfo>())
|
||||
}
|
||||
}.onStart {
|
||||
emit(convert((currentUser.value?.nip65RelayListNote?.event as? AdvertisedRelayListEvent)?.readRelaysNorm()?.toSet(), currentUser.value))
|
||||
}.flowOn(Dispatchers.Default)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val dmInboxFlow =
|
||||
currentUser
|
||||
.transformLatest { user ->
|
||||
if (user != null) {
|
||||
emitAll(
|
||||
combine(
|
||||
user.dmRelayListNote
|
||||
.flow()
|
||||
.metadata.stateFlow,
|
||||
user.flow().usedRelays.stateFlow,
|
||||
) { nip65, userState ->
|
||||
val relays = (nip65.note.event as? ChatMessageRelayListEvent)?.relays()?.toSet() ?: emptySet()
|
||||
convert(relays, userState.user)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
emit(emptyList<RelayInfo>())
|
||||
}
|
||||
}.onStart {
|
||||
emit(convert((currentUser.value?.nip65RelayListNote?.event as? ChatMessageRelayListEvent)?.relays()?.toSet(), currentUser.value))
|
||||
}.flowOn(Dispatchers.Default)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||
|
||||
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
|
||||
|
||||
@@ -65,80 +150,36 @@ class RelayFeedViewModel :
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshSuspended() {
|
||||
suspend fun refreshSuspended() {
|
||||
try {
|
||||
isRefreshing.value = true
|
||||
|
||||
currentUser?.let {
|
||||
val newList = mergeRelays(it.relaysBeingUsed, it.latestContactList?.relays())
|
||||
_feedContent.update { newList }
|
||||
}
|
||||
delay(1000)
|
||||
} finally {
|
||||
isRefreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun mergeRelays(
|
||||
relaysBeingUsed: Map<NormalizedRelayUrl, RelayInfo>,
|
||||
relays: Map<NormalizedRelayUrl, ReadWrite>?,
|
||||
): List<RelayInfo> {
|
||||
val userRelaysBeingUsed = relaysBeingUsed.map { it.value }
|
||||
|
||||
val currentUserRelays =
|
||||
relays?.mapNotNull {
|
||||
val url = it.key
|
||||
if (url !in relaysBeingUsed) {
|
||||
RelayInfo(url, 0, 0)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: emptyList()
|
||||
|
||||
return (userRelaysBeingUsed + currentUserRelays).sortedWith(order)
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
fun subscribeTo(user: User) {
|
||||
if (currentUser != user) {
|
||||
currentUser = user
|
||||
|
||||
currentJob?.cancel()
|
||||
currentJob =
|
||||
viewModelScope.launch {
|
||||
combine(currentUser!!.flow().relays.stateFlow, currentUser!!.flow().relayInfo.stateFlow) { relays, relayInfo ->
|
||||
mergeRelays(relays.user.relaysBeingUsed, relayInfo.user.latestContactList?.relays())
|
||||
}.debounce(1000)
|
||||
.collect { newList ->
|
||||
_feedContent.update { newList }
|
||||
}
|
||||
}
|
||||
|
||||
invalidateData()
|
||||
currentUser.tryEmit(user)
|
||||
}
|
||||
}
|
||||
|
||||
fun unsubscribeTo(user: User) {
|
||||
if (currentUser == user) {
|
||||
currentUser = null
|
||||
currentJob?.cancel()
|
||||
currentUser.tryEmit(null)
|
||||
invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
private val bundler = BundledUpdate(250, Dispatchers.IO)
|
||||
|
||||
override fun invalidateData(ignoreIfDoing: Boolean) {
|
||||
bundler.invalidate(ignoreIfDoing) {
|
||||
// adds the time to perform the refresh into this delay
|
||||
// holding off new updates in case of heavy refresh routines.
|
||||
refreshSuspended()
|
||||
}
|
||||
currentUser.tryEmit(currentUser.value)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
Log.d("Init", "OnCleared: ${this.javaClass.simpleName}")
|
||||
bundler.cancel()
|
||||
currentJob?.cancel()
|
||||
super.onCleared()
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserRelaysUsing
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@@ -34,9 +33,5 @@ fun RelaysTabHeader(
|
||||
baseUser: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val userState by observeUserRelaysUsing(baseUser, accountViewModel)
|
||||
|
||||
Text(text = "${sizeAsString(userState.userRelayList.size)} / ${sizeAsString(userState.relays.size)} ${stringRes(R.string.relays)}")
|
||||
Text(text = stringRes(R.string.relays))
|
||||
}
|
||||
|
||||
private fun sizeAsString(count: Int) = if (count > 0) count.toString() else "--"
|
||||
|
@@ -1084,10 +1084,13 @@
|
||||
<string name="dm_upload">DM Upload</string>
|
||||
<string name="relay_settings">Relay Settings</string>
|
||||
<string name="public_home_section">Public Outbox/Home Relays</string>
|
||||
<string name="public_home_section_explainer_profile">User is posting his content to these relays</string>
|
||||
<string name="public_home_section_explainer">This relay type stores all your content. Amethyst will send your posts here and others will use these relays to find your content. Insert between 1–3 relays. They can be personal relays, paid relays or public relays.</string>
|
||||
<string name="public_notif_section">Public Inbox Relays</string>
|
||||
<string name="public_notif_section_explainer_profile">User is receiving notifications on these relays</string>
|
||||
<string name="public_notif_section_explainer">This relay type receives all replies, comments, likes and zaps to your posts. They can be paid or free relays. Limits set by the relay operator can limit the notifications you receive for the good and for the bad. For example, if you are being attacked by comment spam, paid relays can filter the spam out. Insert between 1–3 relays.</string>
|
||||
<string name="private_inbox_section">DM Inbox Relays</string>
|
||||
<string name="private_inbox_section_explainer_profile">User receives DMs on these relays</string>
|
||||
<string name="private_inbox_section_explainer">Insert between 1–3 relays to serve as your private inbox. Others will use these relays to send DMs to you. DM Inbox relays should accept any message from anyone, but only allow you to download them. Good options are:\n - inbox.nostr.wine (paid)\n - auth.nostr1.com (free)\n - you.nostr1.com (personal relays - paid)</string>
|
||||
<string name="private_outbox_section">Private Home Relays</string>
|
||||
<string name="private_outbox_section_explainer">Insert between 1–3 relays to store events no one else can see, like your Drafts and/or app settings. Ideally, these relays are either local or require authentication before downloading each user\'s content.</string>
|
||||
|
Reference in New Issue
Block a user