mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 00:16:59 +00:00
feat(concord): rich, clickable relay rows in the community create/edit form
The Concord create/edit screens showed each community relay as a bare URL string with a remove button — far more basic than every other relay list in the app. Extract a shared ConcordRelayListEditor that renders each relay the way the Relay Settings / Marmot screens do: the relay's NIP-11 favicon, its advertised name, and its host, with the row tapping through to the full relay-info page (Route.RelayInfo) and long-press copying the URL. Both screens now call the one editor (also removes the duplicated relay block). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CzJ2Cwo8tg4oZq43oRa3ig
This commit is contained in:
+4
-15
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.conco
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -42,7 +41,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -50,11 +48,9 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
import kotlinx.coroutines.launch
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon
|
||||
|
||||
@@ -109,17 +105,10 @@ fun ConcordCreateScreen(
|
||||
title = stringRes(com.vitorpamplona.amethyst.R.string.concord_create_relays),
|
||||
description = stringRes(com.vitorpamplona.amethyst.R.string.concord_create_relays_desc),
|
||||
)
|
||||
relays.forEach { relay ->
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(relay.displayUrl(), Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
|
||||
IconButton(onClick = { relays.remove(relay) }) {
|
||||
SymbolIcon(symbol = MaterialSymbols.Close, contentDescription = stringRes(com.vitorpamplona.amethyst.R.string.remove))
|
||||
}
|
||||
}
|
||||
}
|
||||
RelayUrlEditField(
|
||||
onNewRelay = { if (it !in relays) relays.add(it) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
ConcordRelayListEditor(
|
||||
relays = relays,
|
||||
onRemove = { relays.remove(it) },
|
||||
onAdd = { if (it !in relays) relays.add(it) },
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
+4
-15
@@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.conco
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -33,7 +32,6 @@ import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
@@ -54,12 +52,10 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.datasource.ConcordChannelSubscription
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer
|
||||
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 kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon
|
||||
@@ -152,17 +148,10 @@ fun ConcordEditScreen(
|
||||
title = stringRes(R.string.concord_create_relays),
|
||||
description = stringRes(R.string.concord_edit_relays_desc),
|
||||
)
|
||||
relays.forEach { relay ->
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(relay.displayUrl(), Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
|
||||
IconButton(onClick = { relays.remove(relay) }) {
|
||||
SymbolIcon(symbol = MaterialSymbols.Close, contentDescription = stringRes(R.string.remove))
|
||||
}
|
||||
}
|
||||
}
|
||||
RelayUrlEditField(
|
||||
onNewRelay = { if (it !in relays) relays.add(it) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
ConcordRelayListEditor(
|
||||
relays = relays,
|
||||
onRemove = { relays.remove(it) },
|
||||
onAdd = { if (it !in relays) relays.add(it) },
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
+89
@@ -24,16 +24,20 @@ import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
@@ -52,18 +56,29 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.MediumRelayIconModifier
|
||||
import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
import kotlinx.coroutines.launch
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon
|
||||
|
||||
@@ -268,3 +283,77 @@ private fun ConcordBannerHero(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The community's bootstrap-relay list editor, shared by the create and edit screens. Each relay is
|
||||
* shown the same way the Relay Settings screens show them — the relay's NIP-11 favicon, its
|
||||
* advertised name, and its host — and tapping the row opens the full relay-info page ([Route.RelayInfo]),
|
||||
* so a community relay is a first-class, inspectable relay rather than a bare URL string. A trailing
|
||||
* ✕ removes it; the [RelayUrlEditField] below adds one. State is owned by the caller.
|
||||
*/
|
||||
@Composable
|
||||
fun ConcordRelayListEditor(
|
||||
relays: List<NormalizedRelayUrl>,
|
||||
onRemove: (NormalizedRelayUrl) -> Unit,
|
||||
onAdd: (NormalizedRelayUrl) -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
relays.forEach { relay ->
|
||||
ConcordRelayRow(relay, { onRemove(relay) }, accountViewModel, nav)
|
||||
}
|
||||
RelayUrlEditField(
|
||||
onNewRelay = onAdd,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun ConcordRelayRow(
|
||||
relay: NormalizedRelayUrl,
|
||||
onRemove: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
// The NIP-11 relay-info doc (icon + display name), fetched + cached exactly like the settings rows.
|
||||
val relayInfo by loadRelayInfo(relay)
|
||||
val clipboard = LocalClipboard.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
RenderRelayIcon(
|
||||
displayUrl = relayInfo.id ?: relay.displayUrl(),
|
||||
iconUrl = relayInfo.icon,
|
||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||
pingInMs = 0,
|
||||
iconModifier = MediumRelayIconModifier,
|
||||
)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.combinedClickable(
|
||||
onClick = { nav.nav(Route.RelayInfo(relay.url)) },
|
||||
onLongClick = { scope.launch { clipboard.setText(relay.url) } },
|
||||
),
|
||||
) {
|
||||
relayInfo.name?.takeIf { it.isNotBlank() }?.let { name ->
|
||||
Text(name, style = MaterialTheme.typography.bodyMedium, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
Text(
|
||||
text = relay.displayUrl(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.MiddleEllipsis,
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onRemove) {
|
||||
SymbolIcon(symbol = MaterialSymbols.Close, contentDescription = stringRes(R.string.remove))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user