Reorganizing Relay Info Page
This commit is contained in:
@@ -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
|
||||||
|
|||||||
+168
-202
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user