Reorganizing Relay Info Page

This commit is contained in:
Vitor Pamplona 2023-06-21 14:51:44 -04:00
parent 54462f8d26
commit 157802142c
8 changed files with 233 additions and 291 deletions

View File

@ -65,12 +65,12 @@ Or get the latest APK from the [Releases Section](https://github.com/vitorpamplo
- [x] Recommended Application Handlers (NIP-89)
- [x] Events with a Subject (NIP-14)
- [x] Generic Reposts (kind:16)
- [x] Live Activities (NIP-102)
- [x] Live Activities & Live Chats (NIP-102)
- [x] Relay Pages (NIP-11)
- [ ] Marketplace (NIP-15)
- [ ] Image/Video Capture in the app
- [ ] Local Database
- [ ] Bookmarks, Pinned Posts, Muted Events (NIP-51)
- [ ] Relay Pages (NIP-11)
- [ ] Proof of Work in the Phone (NIP-13, NIP-20)
- [ ] Workspaces
- [ ] Expiration Support (NIP-40)

View File

@ -54,21 +54,14 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.model.RelaySetupInfo
import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.coroutines.launch
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request
import okhttp3.Response
import java.lang.Math.round
@Composable
@ -242,17 +235,11 @@ fun ServerConfig(
var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
if (relayInfo != null) {
val user = LocalCache.getOrCreateUser(relayInfo!!.pubkey ?: "")
NostrUserProfileDataSource.loadUserProfile(user)
NostrUserProfileDataSource.start()
RelayInformationDialog(
onClose = {
relayInfo = null
NostrUserProfileDataSource.loadUserProfile(null)
NostrUserProfileDataSource.stop()
},
relayInfo = relayInfo!!,
user,
accountViewModel,
nav
)
@ -286,48 +273,9 @@ fun ServerConfig(
modifier = Modifier
.weight(1f)
.clickable {
val client = HttpClient.getHttpClient()
val url = item.url
.replace("wss://", "https://")
.replace("ws://", "http://")
val request: Request = Request
.Builder()
.header("Accept", "application/nostr+json")
.url(url)
.build()
client
.newCall(request)
.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
response.use {
if (it.isSuccessful) {
relayInfo =
RelayInformation.fromJson(it.body.string())
} else {
scope.launch {
Toast
.makeText(
context,
context.getString(R.string.an_error_ocurred_trying_to_get_relay_information),
Toast.LENGTH_SHORT
).show()
}
}
}
}
override fun onFailure(call: Call, e: java.io.IOException) {
e.printStackTrace()
scope.launch {
Toast
.makeText(
context,
context.getString(R.string.an_error_ocurred_trying_to_get_relay_information),
Toast.LENGTH_SHORT
).show()
}
}
})
loadRelayInfo(item.url, context, scope) {
relayInfo = it
}
},
maxLines = 1,
overflow = TextOverflow.Ellipsis

View File

@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.ui.actions
import androidx.compose.foundation.border
import android.content.Context
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -10,83 +11,45 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Downloading
import androidx.compose.material.icons.filled.Report
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.components.nip05VerificationAsAState
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.LoadUser
import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisplayLNAddress
import com.vitorpamplona.amethyst.ui.theme.Nip05
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@Composable
fun Section(text: String) {
Spacer(modifier = Modifier.height(10.dp))
Text(
text = text,
fontWeight = FontWeight.Bold,
fontSize = 25.sp
)
Spacer(modifier = Modifier.height(10.dp))
}
@Composable
fun SectionContent(text: String) {
Text(
modifier = Modifier.padding(start = 10.dp),
text = text
)
}
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request
import okhttp3.Response
import java.io.IOException
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun RelayInformationDialog(onClose: () -> Unit, relayInfo: RelayInformation, baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val userState by baseUser.live().metadata.observeAsState()
val user = remember(userState) { userState?.user } ?: return
val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
val lud16 = remember(userState) { user.info?.lud16?.trim() ?: user.info?.lud06?.trim() }
val pubkeyHex = remember { baseUser.pubkeyHex }
val uri = LocalUriHandler.current
val scrollState = rememberScrollState()
fun RelayInformationDialog(
onClose: () -> Unit,
relayInfo: RelayInformation,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
Dialog(
onDismissRequest = { onClose() },
properties = DialogProperties(
@ -95,6 +58,8 @@ fun RelayInformationDialog(onClose: () -> Unit, relayInfo: RelayInformation, bas
)
) {
Surface {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.padding(10.dp)
@ -112,141 +77,28 @@ fun RelayInformationDialog(onClose: () -> Unit, relayInfo: RelayInformation, bas
}
Row(
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Section(relayInfo.name ?: "")
Title(relayInfo.name ?: "")
}
SectionContent(relayInfo.description ?: "")
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
SectionContent(relayInfo.description ?: "")
}
Section(stringResource(R.string.owner))
Row {
UserPicture(
baseUser = user,
accountViewModel = accountViewModel,
size = 100.dp,
modifier = Modifier.border(
3.dp,
MaterialTheme.colors.background,
CircleShape
),
onClick = {
nav("User/${user.pubkeyHex}")
}
)
Column(Modifier.padding(start = 10.dp)) {
(user.bestDisplayName() ?: user.bestUsername())?.let {
Row(verticalAlignment = Alignment.Bottom, modifier = Modifier.padding(top = 7.dp)) {
CreateTextWithEmoji(
text = it,
tags = tags,
fontWeight = FontWeight.Bold,
fontSize = 25.sp
)
}
}
if (user.bestDisplayName() != null) {
user.bestUsername()?.let {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp)
) {
CreateTextWithEmoji(
text = "@$it",
tags = tags,
color = MaterialTheme.colors.placeholderText
)
}
}
}
user.nip05()?.let { nip05 ->
if (nip05.split("@").size == 2) {
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
Row(verticalAlignment = Alignment.CenterVertically) {
if (nip05Verified == null) {
Icon(
tint = Color.Yellow,
imageVector = Icons.Default.Downloading,
contentDescription = "Downloading",
modifier = Modifier.size(16.dp)
)
} else if (nip05Verified == true) {
Icon(
painter = painterResource(R.drawable.ic_verified),
"NIP-05 Verified",
tint = Nip05,
modifier = Modifier.size(16.dp)
)
} else {
Icon(
tint = Color.Red,
imageVector = Icons.Default.Report,
contentDescription = "Invalid Nip05",
modifier = Modifier.size(16.dp)
)
}
var domainPadStart = 5.dp
if (nip05.split("@")[0] != "_") {
Text(
text = AnnotatedString(nip05.split("@")[0] + "@"),
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
domainPadStart = 0.dp
}
ClickableText(
text = AnnotatedString(nip05.split("@")[1]),
onClick = { nip05.let { runCatching { uri.openUri("https://${it.split("@")[1]}") } } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = domainPadStart),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
DisplayLNAddress(lud16, pubkeyHex, accountViewModel.account)
user.info?.about?.let {
Row(
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
) {
val defaultBackground = MaterialTheme.colors.background
val background = remember {
mutableStateOf(defaultBackground)
}
TranslatableRichTextViewer(
content = it,
canPreview = false,
tags = remember { ImmutableListOfLists(emptyList()) },
backgroundColor = background,
accountViewModel = accountViewModel,
nav = nav
)
}
}
}
relayInfo.pubkey?.let {
DisplayOwnerInformation(it, accountViewModel, nav)
}
Section(stringResource(R.string.software))
val url = (relayInfo.software ?: "").replace("git+", "")
Box(modifier = Modifier.padding(start = 10.dp)) {
ClickableUrl(
urlText = url,
url = url
)
}
DisplaySoftwareInformation(relayInfo)
Section(stringResource(R.string.version))
@ -260,27 +112,7 @@ fun RelayInformationDialog(onClose: () -> Unit, relayInfo: RelayInformation, bas
Section(stringResource(R.string.supports))
FlowRow {
relayInfo.supported_nips?.forEach { item ->
val text = item.toString().padStart(2, '0')
Box(Modifier.padding(10.dp)) {
ClickableUrl(
urlText = "Nip-$text",
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
)
}
}
relayInfo.supported_nip_extensions?.forEach { item ->
val text = item.padStart(2, '0')
Box(Modifier.padding(10.dp)) {
ClickableUrl(
urlText = "Nip-$text",
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
)
}
}
}
DisplaySupportedNips(relayInfo)
relayInfo.fees?.admission?.let {
if (it.isNotEmpty()) {
@ -368,3 +200,137 @@ fun RelayInformationDialog(onClose: () -> Unit, relayInfo: RelayInformation, bas
}
}
}
@Composable
@OptIn(ExperimentalLayoutApi::class)
private fun DisplaySupportedNips(relayInfo: RelayInformation) {
FlowRow {
relayInfo.supported_nips?.forEach { item ->
val text = item.toString().padStart(2, '0')
Box(Modifier.padding(10.dp)) {
ClickableUrl(
urlText = "$text",
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
)
}
}
relayInfo.supported_nip_extensions?.forEach { item ->
val text = item.padStart(2, '0')
Box(Modifier.padding(10.dp)) {
ClickableUrl(
urlText = "$text",
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
)
}
}
}
}
@Composable
private fun DisplaySoftwareInformation(relayInfo: RelayInformation) {
val url = (relayInfo.software ?: "").replace("git+", "")
Box(modifier = Modifier.padding(start = 10.dp)) {
ClickableUrl(
urlText = url,
url = url
)
}
}
@Composable
private fun DisplayOwnerInformation(
userHex: String,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
LoadUser(baseUserHex = userHex) {
UserCompose(baseUser = it, accountViewModel = accountViewModel, showDiviser = false, nav = nav)
}
}
@Composable
fun Title(text: String) {
Spacer(modifier = DoubleVertSpacer)
Text(
text = text,
fontWeight = FontWeight.Bold,
fontSize = 24.sp
)
Spacer(modifier = DoubleVertSpacer)
}
@Composable
fun Section(text: String) {
Spacer(modifier = DoubleVertSpacer)
Text(
text = text,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
Spacer(modifier = DoubleVertSpacer)
}
@Composable
fun SectionContent(text: String) {
Text(
modifier = Modifier.padding(start = 10.dp),
text = text
)
}
fun loadRelayInfo(
dirtyUrl: String,
context: Context,
scope: CoroutineScope,
onInfo: (RelayInformation) -> Unit
) {
val url = if (dirtyUrl.contains("://")) {
dirtyUrl
.replace("wss://", "https://")
.replace("ws://", "http://")
} else {
"https://$dirtyUrl"
}
val request: Request = Request
.Builder()
.header("Accept", "application/nostr+json")
.url(url)
.build()
HttpClient.getHttpClient()
.newCall(request)
.enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
response.use {
if (it.isSuccessful) {
onInfo(RelayInformation.fromJson(it.body.string()))
} else {
scope.launch {
Toast
.makeText(
context,
context.getString(R.string.an_error_ocurred_trying_to_get_relay_information),
Toast.LENGTH_SHORT
).show()
}
}
}
}
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
scope.launch {
Toast
.makeText(
context,
context.getString(R.string.an_error_ocurred_trying_to_get_relay_information),
Toast.LENGTH_SHORT
).show()
}
}
}
)
}

View File

@ -33,6 +33,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
@ -44,7 +45,6 @@ import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntSize
@ -52,11 +52,14 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
import com.vitorpamplona.amethyst.ui.actions.loadRelayInfo
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
@ -433,6 +436,7 @@ private fun MessageBubbleLines(
StatusRow(
baseNote = baseNote,
accountViewModel = accountViewModel,
nav = nav,
onWantsToReply = onWantsToReply
)
}
@ -545,12 +549,13 @@ private fun ConstrainedStatusRow(
private fun StatusRow(
baseNote: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
onWantsToReply: (Note) -> Unit
) {
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
ChatTimeAgo(baseNote)
RelayBadges(baseNote)
RelayBadges(baseNote, accountViewModel, nav = nav)
Spacer(modifier = DoubleHorzSpacer)
}
}
@ -800,7 +805,7 @@ data class RelayBadgesState(
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun RelayBadges(baseNote: Note) {
private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteRelaysState by baseNote.live().relays.observeAsState()
val state: RelayBadgesState by remember(noteRelaysState) {
@ -821,7 +826,7 @@ private fun RelayBadges(baseNote: Note) {
FlowRow(Modifier.padding(start = 10.dp)) {
relaysToDisplay.forEach {
RenderRelay(it)
RenderRelay(it, accountViewModel, nav)
}
}
@ -841,22 +846,37 @@ private fun RelayBadges(baseNote: Note) {
}
@Composable
fun RenderRelay(dirtyUrl: String) {
val uri = LocalUriHandler.current
val website = remember(dirtyUrl) {
val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/")
"https://$cleanUrl"
}
fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val iconUrl = remember(dirtyUrl) {
val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/")
"https://$cleanUrl/favicon.ico"
}
var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
if (relayInfo != null) {
RelayInformationDialog(
onClose = {
relayInfo = null
},
relayInfo = relayInfo!!,
accountViewModel,
nav
)
}
val context = LocalContext.current
val scope = rememberCoroutineScope()
val clickableModifier = remember(dirtyUrl) {
Modifier
.padding(1.dp)
.size(15.dp)
.clickable(onClick = { uri.openUri(website) })
.clickable(onClick = {
loadRelayInfo(dirtyUrl, context, scope) {
relayInfo = it
}
})
}
val backgroundColor = MaterialTheme.colors.background

View File

@ -1872,10 +1872,10 @@ private fun DrawAuthorImages(baseNote: Note, accountViewModel: AccountViewModel,
baseNote.replyTo?.lastOrNull()
}
baseReply?.let {
RelayBadges(it)
RelayBadges(it, accountViewModel, nav)
}
} else {
RelayBadges(baseNote)
RelayBadges(baseNote, accountViewModel, nav)
}
}
}
@ -2691,7 +2691,7 @@ private fun CreateImageHeader(
}
@Composable
private fun RelayBadges(baseNote: Note) {
private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var expanded by remember { mutableStateOf(false) }
var showShowMore by remember { mutableStateOf(false) }
@ -2714,9 +2714,9 @@ private fun RelayBadges(baseNote: Note) {
Spacer(DoubleVertSpacer)
if (expanded) {
VerticalRelayPanelWithFlow(lazyRelayList)
VerticalRelayPanelWithFlow(lazyRelayList, accountViewModel, nav)
} else {
VerticalRelayPanelWithFlow(shortRelayList)
VerticalRelayPanelWithFlow(shortRelayList, accountViewModel, nav)
}
if (showShowMore && !expanded) {
@ -2745,12 +2745,14 @@ private fun WatchRelayLists(baseNote: Note, onListChanges: (ImmutableList<String
@Composable
@Stable
private fun VerticalRelayPanelWithFlow(
relays: ImmutableList<String>
relays: ImmutableList<String>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
// FlowRow Seems to be a lot faster than LazyVerticalGrid
FlowRow() {
relays.forEach { url ->
RenderRelay(url)
RenderRelay(url, accountViewModel, nav)
}
}
}

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@ -16,12 +17,15 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Composable
fun UserCompose(
baseUser: User,
overallModifier: Modifier = Modifier
.padding(
start = 12.dp,
end = 12.dp,
top = 10.dp
),
overallModifier: Modifier = remember {
Modifier
.padding(
start = 12.dp,
end = 12.dp,
top = 10.dp
)
},
showDiviser: Boolean = true,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
@ -50,9 +54,11 @@ fun UserCompose(
}
}
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
if (showDiviser) {
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
}
}

View File

@ -319,7 +319,7 @@ private fun RenderVideoOrPicture(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 2.dp)
) {
RelayBadges(baseNote = note)
RelayBadges(baseNote = note, accountViewModel, nav)
}
}
}
@ -354,7 +354,7 @@ private fun VideoUserOptionAction(
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun RelayBadges(baseNote: Note) {
private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteRelaysState by baseNote.live().relays.observeAsState()
val noteRelays = remember(noteRelaysState) {
noteRelaysState?.note?.relays ?: emptySet()
@ -362,7 +362,7 @@ private fun RelayBadges(baseNote: Note) {
FlowRow() {
noteRelays.forEach { dirtyUrl ->
RenderRelay(dirtyUrl)
RenderRelay(dirtyUrl, accountViewModel, nav)
}
}
}

View File

@ -430,7 +430,7 @@
<string name="version">Version</string>
<string name="software">Software</string>
<string name="contact">Contact</string>
<string name="supports">Supports</string>
<string name="supports">Supported NIPs</string>
<string name="admission_fees">Admission Fees</string>
<string name="payments_url">Payments url</string>
<string name="limitations">Limitations</string>