Turns the relay information dialog into a route
This commit is contained in:
@@ -80,6 +80,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.privacy.PrivacyOptionsScree
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.redirect.LoadRedirectScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.redirect.LoadRedirectScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.AllRelayListScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.AllRelayListScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScreen
|
||||||
@@ -135,6 +136,7 @@ fun AppNavigation(
|
|||||||
composableFromEndArgs<Route.Note> { ThreadScreen(it.id, accountViewModel, nav) }
|
composableFromEndArgs<Route.Note> { ThreadScreen(it.id, accountViewModel, nav) }
|
||||||
composableFromEndArgs<Route.Hashtag> { HashtagScreen(it, accountViewModel, nav) }
|
composableFromEndArgs<Route.Hashtag> { HashtagScreen(it, accountViewModel, nav) }
|
||||||
composableFromEndArgs<Route.Geohash> { GeoHashScreen(it, accountViewModel, nav) }
|
composableFromEndArgs<Route.Geohash> { GeoHashScreen(it, accountViewModel, nav) }
|
||||||
|
composableFromEndArgs<Route.RelayInfo> { RelayInformationScreen(it.url, accountViewModel, nav) }
|
||||||
composableFromEndArgs<Route.Community> { CommunityScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) }
|
composableFromEndArgs<Route.Community> { CommunityScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) }
|
||||||
composableFromEndArgs<Route.Room> { ChatroomScreen(it.id.toString(), it.message, it.replyId, it.draftId, accountViewModel, nav) }
|
composableFromEndArgs<Route.Room> { ChatroomScreen(it.id.toString(), it.message, it.replyId, it.draftId, accountViewModel, nav) }
|
||||||
composableFromEndArgs<Route.RoomByAuthor> { ChatroomByAuthorScreen(it.id, null, accountViewModel, nav) }
|
composableFromEndArgs<Route.RoomByAuthor> { ChatroomByAuthorScreen(it.id, null, accountViewModel, nav) }
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ sealed class Route {
|
|||||||
val id: String,
|
val id: String,
|
||||||
) : Route()
|
) : Route()
|
||||||
|
|
||||||
|
@Serializable data class RelayInfo(
|
||||||
|
val url: String,
|
||||||
|
) : Route()
|
||||||
|
|
||||||
@Serializable data class EphemeralChat(
|
@Serializable data class EphemeralChat(
|
||||||
val id: String,
|
val id: String,
|
||||||
val relayUrl: String,
|
val relayUrl: String,
|
||||||
@@ -197,6 +201,8 @@ fun getRouteWithArguments(navController: NavHostController): Route? {
|
|||||||
dest.hasRoute<Route.Geohash>() -> entry.toRoute<Route.Geohash>()
|
dest.hasRoute<Route.Geohash>() -> entry.toRoute<Route.Geohash>()
|
||||||
dest.hasRoute<Route.Community>() -> entry.toRoute<Route.Community>()
|
dest.hasRoute<Route.Community>() -> entry.toRoute<Route.Community>()
|
||||||
|
|
||||||
|
dest.hasRoute<Route.RelayInfo>() -> entry.toRoute<Route.RelayInfo>()
|
||||||
|
|
||||||
dest.hasRoute<Route.RoomByAuthor>() -> entry.toRoute<Route.RoomByAuthor>()
|
dest.hasRoute<Route.RoomByAuthor>() -> entry.toRoute<Route.RoomByAuthor>()
|
||||||
dest.hasRoute<Route.Channel>() -> entry.toRoute<Route.Channel>()
|
dest.hasRoute<Route.Channel>() -> entry.toRoute<Route.Channel>()
|
||||||
dest.hasRoute<Route.ChannelMetadataEdit>() -> entry.toRoute<Route.ChannelMetadataEdit>()
|
dest.hasRoute<Route.ChannelMetadataEdit>() -> entry.toRoute<Route.ChannelMetadataEdit>()
|
||||||
|
|||||||
@@ -53,13 +53,12 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header.loadRelayInfo
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header.loadRelayInfo
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog
|
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.LargeRelayIconModifier
|
import com.vitorpamplona.amethyst.ui.theme.LargeRelayIconModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
||||||
@@ -130,18 +129,6 @@ fun RenderRelay(
|
|||||||
) {
|
) {
|
||||||
val relayInfo by loadRelayInfo(relay, accountViewModel)
|
val relayInfo by loadRelayInfo(relay, accountViewModel)
|
||||||
|
|
||||||
var openRelayDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (openRelayDialog) {
|
|
||||||
RelayInformationDialog(
|
|
||||||
onClose = { openRelayDialog = false },
|
|
||||||
relayInfo = relayInfo,
|
|
||||||
relay = relay,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = nav,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
val clickableModifier =
|
val clickableModifier =
|
||||||
remember(relay) {
|
remember(relay) {
|
||||||
@@ -153,33 +140,7 @@ fun RenderRelay(
|
|||||||
onLongClick = {
|
onLongClick = {
|
||||||
clipboardManager.setText(AnnotatedString(relay.url))
|
clipboardManager.setText(AnnotatedString(relay.url))
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = { nav.nav(Route.RelayInfo(relay.url)) },
|
||||||
openRelayDialog = true
|
|
||||||
accountViewModel.retrieveRelayDocument(
|
|
||||||
relay,
|
|
||||||
onInfo = { },
|
|
||||||
onError = { url, errorCode, exceptionMessage ->
|
|
||||||
accountViewModel.toastManager.toast(
|
|
||||||
R.string.unable_to_download_relay_document,
|
|
||||||
when (errorCode) {
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL ->
|
|
||||||
R.string.relay_information_document_error_failed_to_assemble_url
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER ->
|
|
||||||
R.string.relay_information_document_error_failed_to_reach_server
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT ->
|
|
||||||
R.string.relay_information_document_error_failed_to_parse_response
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS ->
|
|
||||||
R.string.relay_information_document_error_failed_with_http
|
|
||||||
},
|
|
||||||
url.url,
|
|
||||||
exceptionMessage ?: errorCode.toString(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-42
@@ -52,16 +52,15 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.model.Constants
|
import com.vitorpamplona.amethyst.model.Constants
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header.loadRelayInfo
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header.loadRelayInfo
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog
|
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||||
@@ -241,18 +240,6 @@ fun RenderRelayLinePublicChat(
|
|||||||
@Suppress("ProduceStateDoesNotAssignValue")
|
@Suppress("ProduceStateDoesNotAssignValue")
|
||||||
val relayInfo by loadRelayInfo(relay, accountViewModel)
|
val relayInfo by loadRelayInfo(relay, accountViewModel)
|
||||||
|
|
||||||
var openRelayDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (openRelayDialog) {
|
|
||||||
RelayInformationDialog(
|
|
||||||
onClose = { openRelayDialog = false },
|
|
||||||
relayInfo = relayInfo,
|
|
||||||
relay = relay,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = nav,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
val clickableModifier =
|
val clickableModifier =
|
||||||
remember(relay) {
|
remember(relay) {
|
||||||
@@ -260,39 +247,13 @@ fun RenderRelayLinePublicChat(
|
|||||||
onLongClick = {
|
onLongClick = {
|
||||||
clipboardManager.setText(AnnotatedString(relay.url))
|
clipboardManager.setText(AnnotatedString(relay.url))
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = { nav.nav(Route.RelayInfo(relay.url)) },
|
||||||
openRelayDialog = true
|
|
||||||
accountViewModel.retrieveRelayDocument(
|
|
||||||
relay = relay,
|
|
||||||
onInfo = {},
|
|
||||||
onError = { relay, errorCode, exceptionMessage ->
|
|
||||||
accountViewModel.toastManager.toast(
|
|
||||||
R.string.unable_to_download_relay_document,
|
|
||||||
when (errorCode) {
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL ->
|
|
||||||
R.string.relay_information_document_error_failed_to_assemble_url
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER ->
|
|
||||||
R.string.relay_information_document_error_failed_to_reach_server
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT ->
|
|
||||||
R.string.relay_information_document_error_failed_to_parse_response
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS ->
|
|
||||||
R.string.relay_information_document_error_failed_with_http
|
|
||||||
},
|
|
||||||
relay.url,
|
|
||||||
exceptionMessage ?: errorCode.toString(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderRelayLine(
|
RenderRelayLine(
|
||||||
relay.displayUrl(),
|
relay.displayUrl(),
|
||||||
relayInfo?.icon,
|
relayInfo.icon,
|
||||||
clickableModifier,
|
clickableModifier,
|
||||||
showPicture = accountViewModel.settings.showProfilePictures.value,
|
showPicture = accountViewModel.settings.showProfilePictures.value,
|
||||||
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
||||||
|
|||||||
-439
@@ -1,439 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to use,
|
|
||||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
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.LocalContext
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
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.FeatureSetType
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.rememberExtendedNav
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton
|
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.LargeRelayIconModifier
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayDebugMessage
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
|
||||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
|
||||||
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun RelayInformationDialog(
|
|
||||||
onClose: () -> Unit,
|
|
||||||
relay: NormalizedRelayUrl,
|
|
||||||
relayInfo: Nip11RelayInformation,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
nav: INav,
|
|
||||||
) {
|
|
||||||
val newNav = rememberExtendedNav(nav, onClose)
|
|
||||||
|
|
||||||
val messages =
|
|
||||||
remember(relay) {
|
|
||||||
RelayStats
|
|
||||||
.get(url = relay)
|
|
||||||
.messages
|
|
||||||
.snapshot()
|
|
||||||
.values
|
|
||||||
.sortedByDescending { it.time }
|
|
||||||
.toImmutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog(
|
|
||||||
onDismissRequest = { onClose() },
|
|
||||||
properties =
|
|
||||||
DialogProperties(
|
|
||||||
usePlatformDefaultWidth = false,
|
|
||||||
dismissOnClickOutside = false,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
SetDialogToEdgeToEdge()
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
topBar = {
|
|
||||||
TopAppBar(
|
|
||||||
actions = {},
|
|
||||||
title = {
|
|
||||||
Text(relay.displayUrl())
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
Row {
|
|
||||||
Spacer(modifier = StdHorzSpacer)
|
|
||||||
BackButton(
|
|
||||||
onPress = { onClose() },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { pad ->
|
|
||||||
LazyColumn(
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(pad)
|
|
||||||
.consumeWindowInsets(pad)
|
|
||||||
.padding(bottom = Size10dp, start = Size10dp, end = Size10dp)
|
|
||||||
.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
modifier = StdPadding.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
RenderRelayIcon(
|
|
||||||
displayUrl = relay.displayUrl(),
|
|
||||||
iconUrl = relayInfo.icon,
|
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
|
||||||
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
|
||||||
RelayStats.get(relay).pingInMs,
|
|
||||||
iconModifier = LargeRelayIconModifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = DoubleHorzSpacer)
|
|
||||||
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Title(relayInfo.name?.trim() ?: "")
|
|
||||||
Spacer(modifier = HalfVertSpacer)
|
|
||||||
SubtitleContent(relay.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Section(stringRes(R.string.description))
|
|
||||||
|
|
||||||
SectionContent(relayInfo.description?.trim() ?: "")
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Section(stringRes(R.string.owner))
|
|
||||||
|
|
||||||
relayInfo.pubkey?.let {
|
|
||||||
DisplayOwnerInformation(it, accountViewModel, newNav)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Section(stringRes(R.string.contact))
|
|
||||||
|
|
||||||
Box(modifier = Modifier.padding(start = 10.dp)) {
|
|
||||||
relayInfo.contact?.let {
|
|
||||||
if (it.startsWith("https:")) {
|
|
||||||
ClickableUrl(urlText = it, url = it)
|
|
||||||
} else if (it.startsWith("mailto:") || it.contains('@')) {
|
|
||||||
ClickableEmail(it)
|
|
||||||
} else {
|
|
||||||
SectionContent(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Section(stringRes(R.string.software))
|
|
||||||
|
|
||||||
DisplaySoftwareInformation(relayInfo)
|
|
||||||
|
|
||||||
Section(stringRes(R.string.version))
|
|
||||||
|
|
||||||
SectionContent(relayInfo.version ?: "")
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Section(stringRes(R.string.supports))
|
|
||||||
|
|
||||||
DisplaySupportedNips(relayInfo)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
relayInfo.fees?.admission?.let {
|
|
||||||
if (it.isNotEmpty()) {
|
|
||||||
Section(stringRes(R.string.admission_fees))
|
|
||||||
|
|
||||||
it.forEach { item -> SectionContent("${item.amount?.div(1000) ?: 0} sats") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
relayInfo.payments_url?.let {
|
|
||||||
Section(stringRes(R.string.payments_url))
|
|
||||||
|
|
||||||
Box(modifier = Modifier.padding(start = 10.dp)) {
|
|
||||||
ClickableUrl(
|
|
||||||
urlText = it,
|
|
||||||
url = it,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
relayInfo.limitation?.let {
|
|
||||||
Section(stringRes(R.string.limitations))
|
|
||||||
val authRequiredText =
|
|
||||||
if (it.auth_required ?: false) stringRes(R.string.yes) else stringRes(R.string.no)
|
|
||||||
|
|
||||||
val paymentRequiredText =
|
|
||||||
if (it.payment_required ?: false) stringRes(R.string.yes) else stringRes(R.string.no)
|
|
||||||
|
|
||||||
val restrictedWritesText =
|
|
||||||
if (it.restricted_writes ?: false) stringRes(R.string.yes) else stringRes(R.string.no)
|
|
||||||
|
|
||||||
Column {
|
|
||||||
SectionContent(
|
|
||||||
"${stringRes(R.string.message_length)}: ${it.max_message_length ?: 0}",
|
|
||||||
)
|
|
||||||
SectionContent(
|
|
||||||
"${stringRes(R.string.subscriptions)}: ${it.max_subscriptions ?: 0}",
|
|
||||||
)
|
|
||||||
SectionContent("${stringRes(R.string.filters)}: ${it.max_filters ?: 0}")
|
|
||||||
SectionContent(
|
|
||||||
"${stringRes(R.string.subscription_id_length)}: ${it.max_subid_length ?: 0}",
|
|
||||||
)
|
|
||||||
SectionContent("${stringRes(R.string.minimum_prefix)}: ${it.min_prefix ?: 0}")
|
|
||||||
SectionContent(
|
|
||||||
"${stringRes(R.string.maximum_event_tags)}: ${it.max_event_tags ?: 0}",
|
|
||||||
)
|
|
||||||
SectionContent(
|
|
||||||
"${stringRes(R.string.content_length)}: ${it.max_content_length ?: 0}",
|
|
||||||
)
|
|
||||||
SectionContent(
|
|
||||||
"${stringRes(R.string.max_limit)}: ${it.max_limit ?: 0}",
|
|
||||||
)
|
|
||||||
SectionContent("${stringRes(R.string.minimum_pow)}: ${it.min_pow_difficulty ?: 0}")
|
|
||||||
SectionContent("${stringRes(R.string.auth)}: $authRequiredText")
|
|
||||||
SectionContent("${stringRes(R.string.payment)}: $paymentRequiredText")
|
|
||||||
SectionContent("${stringRes(R.string.restricted_writes)}: $restrictedWritesText")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
relayInfo.relay_countries?.let {
|
|
||||||
Section(stringRes(R.string.countries))
|
|
||||||
|
|
||||||
FlowRow { it.forEach { item -> SectionContent(item) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
relayInfo.language_tags?.let {
|
|
||||||
Section(stringRes(R.string.languages))
|
|
||||||
|
|
||||||
FlowRow { it.forEach { item -> SectionContent(item) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
relayInfo.tags?.let {
|
|
||||||
Section(stringRes(R.string.tags))
|
|
||||||
|
|
||||||
FlowRow { it.forEach { item -> SectionContent(item) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
relayInfo.posting_policy?.let {
|
|
||||||
Section(stringRes(R.string.posting_policy))
|
|
||||||
|
|
||||||
Box(Modifier.padding(10.dp)) {
|
|
||||||
ClickableUrl(
|
|
||||||
it,
|
|
||||||
it,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
Section(stringRes(R.string.relay_error_messages))
|
|
||||||
}
|
|
||||||
|
|
||||||
items(messages) { msg ->
|
|
||||||
Row {
|
|
||||||
RenderDebugMessage(msg, accountViewModel, newNav)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = StdVertSpacer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun RenderDebugMessage(
|
|
||||||
msg: RelayDebugMessage,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
newNav: INav,
|
|
||||||
) {
|
|
||||||
SelectionContainer {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val color =
|
|
||||||
remember {
|
|
||||||
mutableStateOf(Color.Transparent)
|
|
||||||
}
|
|
||||||
TranslatableRichTextViewer(
|
|
||||||
content =
|
|
||||||
remember {
|
|
||||||
"${timeAgo(msg.time, context)}, ${msg.type.name}: ${msg.message}"
|
|
||||||
},
|
|
||||||
canPreview = false,
|
|
||||||
quotesLeft = 0,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
tags = EmptyTagList,
|
|
||||||
backgroundColor = color,
|
|
||||||
id = msg.hashCode().toString(),
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = newNav,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
private fun DisplaySupportedNips(relayInfo: Nip11RelayInformation) {
|
|
||||||
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: Nip11RelayInformation) {
|
|
||||||
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: INav,
|
|
||||||
) {
|
|
||||||
LoadUser(baseUserHex = userHex, accountViewModel) {
|
|
||||||
CrossfadeIfEnabled(it, accountViewModel = accountViewModel) {
|
|
||||||
if (it != null) {
|
|
||||||
UserCompose(
|
|
||||||
baseUser = it,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = nav,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Title(text: String) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 24.sp,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SubtitleContent(text: String) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
+450
@@ -0,0 +1,450 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
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.LocalContext
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header.loadRelayInfo
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.LargeRelayIconModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayDebugMessage
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RelayInformationScreen(
|
||||||
|
relayUrl: String,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
RelayUrlNormalizer.normalizeOrNull(relayUrl)?.let {
|
||||||
|
RelayInformationScreen(
|
||||||
|
relay = it,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun RelayInformationScreen(
|
||||||
|
relay: NormalizedRelayUrl,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
actions = {},
|
||||||
|
title = {
|
||||||
|
Text(relay.displayUrl())
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
Row {
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
BackButton(
|
||||||
|
onPress = nav::popBack,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { pad ->
|
||||||
|
val relayInfo by loadRelayInfo(relay, accountViewModel)
|
||||||
|
|
||||||
|
val messages =
|
||||||
|
remember(relay) {
|
||||||
|
RelayStats
|
||||||
|
.get(url = relay)
|
||||||
|
.messages
|
||||||
|
.snapshot()
|
||||||
|
.values
|
||||||
|
.sortedByDescending { it.time }
|
||||||
|
.toImmutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(pad)
|
||||||
|
.consumeWindowInsets(pad)
|
||||||
|
.padding(bottom = Size10dp, start = Size10dp, end = Size10dp)
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
modifier = StdPadding.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
RenderRelayIcon(
|
||||||
|
displayUrl = relay.displayUrl(),
|
||||||
|
iconUrl = relayInfo.icon,
|
||||||
|
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
||||||
|
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
||||||
|
RelayStats.get(relay).pingInMs,
|
||||||
|
iconModifier = LargeRelayIconModifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Title(relayInfo.name?.trim() ?: "")
|
||||||
|
Spacer(modifier = HalfVertSpacer)
|
||||||
|
SubtitleContent(relay.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.description))
|
||||||
|
|
||||||
|
SectionContent(relayInfo.description?.trim() ?: stringRes(R.string.no_description))
|
||||||
|
}
|
||||||
|
relayInfo.pubkey?.let {
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.owner))
|
||||||
|
DisplayOwnerInformation(it, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relayInfo.contact?.let {
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.contact))
|
||||||
|
|
||||||
|
Box(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
|
if (it.startsWith("https:")) {
|
||||||
|
ClickableUrl(urlText = it, url = it)
|
||||||
|
} else if (it.startsWith("mailto:") || it.contains('@')) {
|
||||||
|
ClickableEmail(it)
|
||||||
|
} else {
|
||||||
|
SectionContent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relayInfo.software?.let {
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.software))
|
||||||
|
|
||||||
|
DisplaySoftwareInformation(it)
|
||||||
|
|
||||||
|
Section(stringRes(R.string.version))
|
||||||
|
|
||||||
|
SectionContent(relayInfo.version ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relayInfo.supported_nips?.let {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.supports))
|
||||||
|
|
||||||
|
DisplaySupportedNips(it, relayInfo.supported_nip_extensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relayInfo.fees?.admission?.let {
|
||||||
|
item {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
Section(stringRes(R.string.admission_fees))
|
||||||
|
|
||||||
|
it.forEach { item -> SectionContent("${item.amount?.div(1000) ?: 0} sats") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relayInfo.payments_url?.let {
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.payments_url))
|
||||||
|
|
||||||
|
Box(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
|
ClickableUrl(
|
||||||
|
urlText = it,
|
||||||
|
url = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relayInfo.limitation?.let {
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.limitations))
|
||||||
|
val authRequiredText =
|
||||||
|
if (it.auth_required ?: false) stringRes(R.string.yes) else stringRes(R.string.no)
|
||||||
|
|
||||||
|
val paymentRequiredText =
|
||||||
|
if (it.payment_required ?: false) stringRes(R.string.yes) else stringRes(R.string.no)
|
||||||
|
|
||||||
|
val restrictedWritesText =
|
||||||
|
if (it.restricted_writes ?: false) stringRes(R.string.yes) else stringRes(R.string.no)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
SectionContent(
|
||||||
|
"${stringRes(R.string.message_length)}: ${it.max_message_length ?: 0}",
|
||||||
|
)
|
||||||
|
SectionContent(
|
||||||
|
"${stringRes(R.string.subscriptions)}: ${it.max_subscriptions ?: 0}",
|
||||||
|
)
|
||||||
|
SectionContent("${stringRes(R.string.filters)}: ${it.max_filters ?: 0}")
|
||||||
|
SectionContent(
|
||||||
|
"${stringRes(R.string.subscription_id_length)}: ${it.max_subid_length ?: 0}",
|
||||||
|
)
|
||||||
|
SectionContent("${stringRes(R.string.minimum_prefix)}: ${it.min_prefix ?: 0}")
|
||||||
|
SectionContent(
|
||||||
|
"${stringRes(R.string.maximum_event_tags)}: ${it.max_event_tags ?: 0}",
|
||||||
|
)
|
||||||
|
SectionContent(
|
||||||
|
"${stringRes(R.string.content_length)}: ${it.max_content_length ?: 0}",
|
||||||
|
)
|
||||||
|
SectionContent(
|
||||||
|
"${stringRes(R.string.max_limit)}: ${it.max_limit ?: 0}",
|
||||||
|
)
|
||||||
|
SectionContent("${stringRes(R.string.minimum_pow)}: ${it.min_pow_difficulty ?: 0}")
|
||||||
|
SectionContent("${stringRes(R.string.auth)}: $authRequiredText")
|
||||||
|
SectionContent("${stringRes(R.string.payment)}: $paymentRequiredText")
|
||||||
|
SectionContent("${stringRes(R.string.restricted_writes)}: $restrictedWritesText")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relayInfo.relay_countries?.let {
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.countries))
|
||||||
|
|
||||||
|
FlowRow { it.forEach { item -> SectionContent(item) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relayInfo.language_tags?.let {
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.languages))
|
||||||
|
|
||||||
|
FlowRow { it.forEach { item -> SectionContent(item) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relayInfo.tags?.let {
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.tags))
|
||||||
|
|
||||||
|
FlowRow { it.forEach { item -> SectionContent(item) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relayInfo.posting_policy?.let {
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.posting_policy))
|
||||||
|
|
||||||
|
Box(Modifier.padding(10.dp)) {
|
||||||
|
ClickableUrl(
|
||||||
|
it,
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Section(stringRes(R.string.relay_error_messages))
|
||||||
|
}
|
||||||
|
|
||||||
|
items(messages) { msg ->
|
||||||
|
Row {
|
||||||
|
RenderDebugMessage(msg, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderDebugMessage(
|
||||||
|
msg: RelayDebugMessage,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
newNav: INav,
|
||||||
|
) {
|
||||||
|
SelectionContainer {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val color =
|
||||||
|
remember {
|
||||||
|
mutableStateOf(Color.Transparent)
|
||||||
|
}
|
||||||
|
TranslatableRichTextViewer(
|
||||||
|
content =
|
||||||
|
remember {
|
||||||
|
"${timeAgo(msg.time, context)}, ${msg.type.name}: ${msg.message}"
|
||||||
|
},
|
||||||
|
canPreview = false,
|
||||||
|
quotesLeft = 0,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
tags = EmptyTagList,
|
||||||
|
backgroundColor = color,
|
||||||
|
id = msg.hashCode().toString(),
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = newNav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
private fun DisplaySupportedNips(
|
||||||
|
supportedNips: List<Int>,
|
||||||
|
supportedNipExtensions: List<String>?,
|
||||||
|
) {
|
||||||
|
FlowRow {
|
||||||
|
supportedNips.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",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
supportedNipExtensions?.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(software: String) {
|
||||||
|
val url = software.replace("git+", "")
|
||||||
|
Box(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
|
ClickableUrl(
|
||||||
|
urlText = url,
|
||||||
|
url = url,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DisplayOwnerInformation(
|
||||||
|
userHex: String,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
LoadUser(baseUserHex = userHex, accountViewModel) {
|
||||||
|
CrossfadeIfEnabled(it, accountViewModel = accountViewModel) {
|
||||||
|
if (it != null) {
|
||||||
|
UserCompose(
|
||||||
|
baseUser = it,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Title(text: String) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SubtitleContent(text: String) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,
|
||||||
|
)
|
||||||
|
}
|
||||||
+2
-71
@@ -22,19 +22,11 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import com.vitorpamplona.amethyst.R
|
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.nav
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header.loadRelayInfo
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header.loadRelayInfo
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog
|
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BasicRelaySetupInfoDialog(
|
fun BasicRelaySetupInfoDialog(
|
||||||
@@ -43,75 +35,14 @@ fun BasicRelaySetupInfoDialog(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val relayInfo by loadRelayInfo(item.relay, accountViewModel)
|
val relayInfo by loadRelayInfo(item.relay, accountViewModel)
|
||||||
|
|
||||||
var openRelayDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (openRelayDialog) {
|
|
||||||
RelayInformationDialog(
|
|
||||||
onClose = { openRelayDialog = false },
|
|
||||||
relayInfo = relayInfo,
|
|
||||||
relay = item.relay,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = nav,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicRelaySetupInfoClickableRow(
|
BasicRelaySetupInfoClickableRow(
|
||||||
item = item,
|
item = item,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
||||||
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
||||||
onDelete = onDelete,
|
onDelete = onDelete,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
onClick = {
|
onClick = { nav.nav(Route.RelayInfo(item.relay.url)) },
|
||||||
openRelayDialog = true
|
|
||||||
accountViewModel.retrieveRelayDocument(
|
|
||||||
relay = item.relay,
|
|
||||||
onInfo = { },
|
|
||||||
onError = { relay, errorCode, exceptionMessage ->
|
|
||||||
val msg =
|
|
||||||
when (errorCode) {
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL ->
|
|
||||||
stringRes(
|
|
||||||
context,
|
|
||||||
R.string.relay_information_document_error_assemble_url,
|
|
||||||
relay.url,
|
|
||||||
exceptionMessage,
|
|
||||||
)
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER ->
|
|
||||||
stringRes(
|
|
||||||
context,
|
|
||||||
R.string.relay_information_document_error_assemble_url,
|
|
||||||
relay.url,
|
|
||||||
exceptionMessage,
|
|
||||||
)
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT ->
|
|
||||||
stringRes(
|
|
||||||
context,
|
|
||||||
R.string.relay_information_document_error_assemble_url,
|
|
||||||
relay.url,
|
|
||||||
exceptionMessage,
|
|
||||||
)
|
|
||||||
|
|
||||||
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS ->
|
|
||||||
stringRes(
|
|
||||||
context,
|
|
||||||
R.string.relay_information_document_error_assemble_url,
|
|
||||||
relay.url,
|
|
||||||
exceptionMessage,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
accountViewModel.toastManager.toast(
|
|
||||||
stringRes(context, R.string.unable_to_download_relay_document),
|
|
||||||
msg,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,6 +107,7 @@
|
|||||||
<string name="my_awesome_group">My Awesome Group</string>
|
<string name="my_awesome_group">My Awesome Group</string>
|
||||||
<string name="picture_url">Picture Url</string>
|
<string name="picture_url">Picture Url</string>
|
||||||
<string name="description">Description</string>
|
<string name="description">Description</string>
|
||||||
|
<string name="no_description">Description not found</string>
|
||||||
<string name="about_us">"About us.. "</string>
|
<string name="about_us">"About us.. "</string>
|
||||||
<string name="what_s_on_your_mind">What\'s on your mind?</string>
|
<string name="what_s_on_your_mind">What\'s on your mind?</string>
|
||||||
<string name="write_a_message">Write a message…</string>
|
<string name="write_a_message">Write a message…</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user