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
+2 -2
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] Recommended Application Handlers (NIP-89)
- [x] Events with a Subject (NIP-14) - [x] Events with a Subject (NIP-14)
- [x] Generic Reposts (kind:16) - [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) - [ ] Marketplace (NIP-15)
- [ ] Image/Video Capture in the app - [ ] Image/Video Capture in the app
- [ ] Local Database - [ ] Local Database
- [ ] Bookmarks, Pinned Posts, Muted Events (NIP-51) - [ ] Bookmarks, Pinned Posts, Muted Events (NIP-51)
- [ ] Relay Pages (NIP-11)
- [ ] Proof of Work in the Phone (NIP-13, NIP-20) - [ ] Proof of Work in the Phone (NIP-13, NIP-20)
- [ ] Workspaces - [ ] Workspaces
- [ ] Expiration Support (NIP-40) - [ ] Expiration Support (NIP-40)
@@ -54,21 +54,14 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.RelayInformation import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.model.RelaySetupInfo 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.service.relays.FeedType
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request
import okhttp3.Response
import java.lang.Math.round import java.lang.Math.round
@Composable @Composable
@@ -242,17 +235,11 @@ fun ServerConfig(
var relayInfo: RelayInformation? by remember { mutableStateOf(null) } var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
if (relayInfo != null) { if (relayInfo != null) {
val user = LocalCache.getOrCreateUser(relayInfo!!.pubkey ?: "")
NostrUserProfileDataSource.loadUserProfile(user)
NostrUserProfileDataSource.start()
RelayInformationDialog( RelayInformationDialog(
onClose = { onClose = {
relayInfo = null relayInfo = null
NostrUserProfileDataSource.loadUserProfile(null)
NostrUserProfileDataSource.stop()
}, },
relayInfo = relayInfo!!, relayInfo = relayInfo!!,
user,
accountViewModel, accountViewModel,
nav nav
) )
@@ -286,48 +273,9 @@ fun ServerConfig(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.clickable { .clickable {
val client = HttpClient.getHttpClient() loadRelayInfo(item.url, context, scope) {
val url = item.url relayInfo = it
.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()
}
}
})
}, },
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.ui.actions 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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState 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.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.Surface
import androidx.compose.material.Text 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.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.Alignment
import androidx.compose.ui.Modifier 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.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.RelayInformation 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.ClickableEmail
import com.vitorpamplona.amethyst.ui.components.ClickableUrl import com.vitorpamplona.amethyst.ui.components.ClickableUrl
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.note.LoadUser
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.components.nip05VerificationAsAState
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisplayLNAddress import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.Nip05 import kotlinx.coroutines.CoroutineScope
import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.launch
import okhttp3.Call
@Composable import okhttp3.Callback
fun Section(text: String) { import okhttp3.Request
Spacer(modifier = Modifier.height(10.dp)) import okhttp3.Response
Text( import java.io.IOException
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
)
}
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun RelayInformationDialog(onClose: () -> Unit, relayInfo: RelayInformation, baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) { fun RelayInformationDialog(
val userState by baseUser.live().metadata.observeAsState() onClose: () -> Unit,
val user = remember(userState) { userState?.user } ?: return relayInfo: RelayInformation,
val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() } accountViewModel: AccountViewModel,
val lud16 = remember(userState) { user.info?.lud16?.trim() ?: user.info?.lud06?.trim() } nav: (String) -> Unit
val pubkeyHex = remember { baseUser.pubkeyHex } ) {
val uri = LocalUriHandler.current
val scrollState = rememberScrollState()
Dialog( Dialog(
onDismissRequest = { onClose() }, onDismissRequest = { onClose() },
properties = DialogProperties( properties = DialogProperties(
@@ -95,6 +58,8 @@ fun RelayInformationDialog(onClose: () -> Unit, relayInfo: RelayInformation, bas
) )
) { ) {
Surface { Surface {
val scrollState = rememberScrollState()
Column( Column(
modifier = Modifier modifier = Modifier
.padding(10.dp) .padding(10.dp)
@@ -112,141 +77,28 @@ fun RelayInformationDialog(onClose: () -> Unit, relayInfo: RelayInformation, bas
} }
Row( 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)) Section(stringResource(R.string.owner))
Row { relayInfo.pubkey?.let {
UserPicture( DisplayOwnerInformation(it, accountViewModel, nav)
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
)
}
}
}
} }
Section(stringResource(R.string.software)) Section(stringResource(R.string.software))
val url = (relayInfo.software ?: "").replace("git+", "") DisplaySoftwareInformation(relayInfo)
Box(modifier = Modifier.padding(start = 10.dp)) {
ClickableUrl(
urlText = url,
url = url
)
}
Section(stringResource(R.string.version)) Section(stringResource(R.string.version))
@@ -260,27 +112,7 @@ fun RelayInformationDialog(onClose: () -> Unit, relayInfo: RelayInformation, bas
Section(stringResource(R.string.supports)) Section(stringResource(R.string.supports))
FlowRow { DisplaySupportedNips(relayInfo)
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"
)
}
}
}
relayInfo.fees?.admission?.let { relayInfo.fees?.admission?.let {
if (it.isNotEmpty()) { 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()
}
}
}
)
}
@@ -33,6 +33,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
@@ -52,11 +52,14 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists 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.actions.toImmutableListOfLists
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
@@ -433,6 +436,7 @@ private fun MessageBubbleLines(
StatusRow( StatusRow(
baseNote = baseNote, baseNote = baseNote,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav,
onWantsToReply = onWantsToReply onWantsToReply = onWantsToReply
) )
} }
@@ -545,12 +549,13 @@ private fun ConstrainedStatusRow(
private fun StatusRow( private fun StatusRow(
baseNote: Note, baseNote: Note,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit,
onWantsToReply: (Note) -> Unit onWantsToReply: (Note) -> Unit
) { ) {
Column { Column {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
ChatTimeAgo(baseNote) ChatTimeAgo(baseNote)
RelayBadges(baseNote) RelayBadges(baseNote, accountViewModel, nav = nav)
Spacer(modifier = DoubleHorzSpacer) Spacer(modifier = DoubleHorzSpacer)
} }
} }
@@ -800,7 +805,7 @@ data class RelayBadgesState(
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
private fun RelayBadges(baseNote: Note) { private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteRelaysState by baseNote.live().relays.observeAsState() val noteRelaysState by baseNote.live().relays.observeAsState()
val state: RelayBadgesState by remember(noteRelaysState) { val state: RelayBadgesState by remember(noteRelaysState) {
@@ -821,7 +826,7 @@ private fun RelayBadges(baseNote: Note) {
FlowRow(Modifier.padding(start = 10.dp)) { FlowRow(Modifier.padding(start = 10.dp)) {
relaysToDisplay.forEach { relaysToDisplay.forEach {
RenderRelay(it) RenderRelay(it, accountViewModel, nav)
} }
} }
@@ -841,22 +846,37 @@ private fun RelayBadges(baseNote: Note) {
} }
@Composable @Composable
fun RenderRelay(dirtyUrl: String) { fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val uri = LocalUriHandler.current
val website = remember(dirtyUrl) {
val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/")
"https://$cleanUrl"
}
val iconUrl = remember(dirtyUrl) { val iconUrl = remember(dirtyUrl) {
val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/") val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/")
"https://$cleanUrl/favicon.ico" "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) { val clickableModifier = remember(dirtyUrl) {
Modifier Modifier
.padding(1.dp) .padding(1.dp)
.size(15.dp) .size(15.dp)
.clickable(onClick = { uri.openUri(website) }) .clickable(onClick = {
loadRelayInfo(dirtyUrl, context, scope) {
relayInfo = it
}
})
} }
val backgroundColor = MaterialTheme.colors.background val backgroundColor = MaterialTheme.colors.background
@@ -1872,10 +1872,10 @@ private fun DrawAuthorImages(baseNote: Note, accountViewModel: AccountViewModel,
baseNote.replyTo?.lastOrNull() baseNote.replyTo?.lastOrNull()
} }
baseReply?.let { baseReply?.let {
RelayBadges(it) RelayBadges(it, accountViewModel, nav)
} }
} else { } else {
RelayBadges(baseNote) RelayBadges(baseNote, accountViewModel, nav)
} }
} }
} }
@@ -2691,7 +2691,7 @@ private fun CreateImageHeader(
} }
@Composable @Composable
private fun RelayBadges(baseNote: Note) { private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
var showShowMore by remember { mutableStateOf(false) } var showShowMore by remember { mutableStateOf(false) }
@@ -2714,9 +2714,9 @@ private fun RelayBadges(baseNote: Note) {
Spacer(DoubleVertSpacer) Spacer(DoubleVertSpacer)
if (expanded) { if (expanded) {
VerticalRelayPanelWithFlow(lazyRelayList) VerticalRelayPanelWithFlow(lazyRelayList, accountViewModel, nav)
} else { } else {
VerticalRelayPanelWithFlow(shortRelayList) VerticalRelayPanelWithFlow(shortRelayList, accountViewModel, nav)
} }
if (showShowMore && !expanded) { if (showShowMore && !expanded) {
@@ -2745,12 +2745,14 @@ private fun WatchRelayLists(baseNote: Note, onListChanges: (ImmutableList<String
@Composable @Composable
@Stable @Stable
private fun VerticalRelayPanelWithFlow( private fun VerticalRelayPanelWithFlow(
relays: ImmutableList<String> relays: ImmutableList<String>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) { ) {
// FlowRow Seems to be a lot faster than LazyVerticalGrid // FlowRow Seems to be a lot faster than LazyVerticalGrid
FlowRow() { FlowRow() {
relays.forEach { url -> relays.forEach { url ->
RenderRelay(url) RenderRelay(url, accountViewModel, nav)
} }
} }
} }
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider import androidx.compose.material.Divider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -16,12 +17,15 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Composable @Composable
fun UserCompose( fun UserCompose(
baseUser: User, baseUser: User,
overallModifier: Modifier = Modifier overallModifier: Modifier = remember {
.padding( Modifier
start = 12.dp, .padding(
end = 12.dp, start = 12.dp,
top = 10.dp end = 12.dp,
), top = 10.dp
)
},
showDiviser: Boolean = true,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
@@ -50,9 +54,11 @@ fun UserCompose(
} }
} }
Divider( if (showDiviser) {
modifier = Modifier.padding(top = 10.dp), Divider(
thickness = 0.25.dp modifier = Modifier.padding(top = 10.dp),
) thickness = 0.25.dp
)
}
} }
} }
@@ -319,7 +319,7 @@ private fun RenderVideoOrPicture(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 2.dp) modifier = Modifier.padding(top = 2.dp)
) { ) {
RelayBadges(baseNote = note) RelayBadges(baseNote = note, accountViewModel, nav)
} }
} }
} }
@@ -354,7 +354,7 @@ private fun VideoUserOptionAction(
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
private fun RelayBadges(baseNote: Note) { private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteRelaysState by baseNote.live().relays.observeAsState() val noteRelaysState by baseNote.live().relays.observeAsState()
val noteRelays = remember(noteRelaysState) { val noteRelays = remember(noteRelaysState) {
noteRelaysState?.note?.relays ?: emptySet() noteRelaysState?.note?.relays ?: emptySet()
@@ -362,7 +362,7 @@ private fun RelayBadges(baseNote: Note) {
FlowRow() { FlowRow() {
noteRelays.forEach { dirtyUrl -> noteRelays.forEach { dirtyUrl ->
RenderRelay(dirtyUrl) RenderRelay(dirtyUrl, accountViewModel, nav)
} }
} }
} }
+1 -1
View File
@@ -430,7 +430,7 @@
<string name="version">Version</string> <string name="version">Version</string>
<string name="software">Software</string> <string name="software">Software</string>
<string name="contact">Contact</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="admission_fees">Admission Fees</string>
<string name="payments_url">Payments url</string> <string name="payments_url">Payments url</string>
<string name="limitations">Limitations</string> <string name="limitations">Limitations</string>