mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
Merge pull request #3624 from vitorpamplona/claude/relay-icons-reaction-gallery-x4uzmk
Move relay badges from author column to reaction gallery
This commit is contained in:
@@ -1626,9 +1626,9 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
): Boolean {
|
||||
val note = getOrCreateNote(event.id)
|
||||
|
||||
// Approval notes are badge-rendered directly in community feeds
|
||||
// (BadgeBox has no repost-style indirection for them), so without
|
||||
// this attribution their relay list stays empty forever.
|
||||
// Approval notes render their own relay list directly in community feeds
|
||||
// (there is no repost-style indirection to a replyTo for them), so without
|
||||
// this attribution the "accepted by relays" gallery line stays empty forever.
|
||||
if (relay != null) {
|
||||
getOrCreateUser(event.pubKey).addRelayBeingUsed(relay, event.createdAt)
|
||||
note.addRelay(relay)
|
||||
|
||||
@@ -54,8 +54,6 @@ private data class NoteLayoutPx(
|
||||
val authorWidth: Int,
|
||||
/** Horizontal gap between author column and content column: 10dp */
|
||||
val authorContentGap: Int,
|
||||
/** Vertical gap between author picture and relay badges: 5dp */
|
||||
val authorBadgeGap: Int,
|
||||
/** Vertical spacer between header rows and note content: 4dp */
|
||||
val contentSpacer: Int,
|
||||
/** Left padding for the content area: 12dp */
|
||||
@@ -87,9 +85,6 @@ private fun NoteComposeLayoutPreviewCard() {
|
||||
.background(Color(0xFF9575CD)),
|
||||
)
|
||||
},
|
||||
relayBadges = {
|
||||
Text("R1 R2 R3", fontSize = 10.sp, color = Color.Gray)
|
||||
},
|
||||
firstRow = {
|
||||
Text(
|
||||
"Alice @alice 2h ...",
|
||||
@@ -127,7 +122,6 @@ private fun NoteComposeLayoutBoostedPreview() {
|
||||
addPadding = false,
|
||||
showAuthorColumn = false,
|
||||
authorPicture = {},
|
||||
relayBadges = {},
|
||||
firstRow = {
|
||||
Text("Bob boosted 2h ...")
|
||||
},
|
||||
@@ -157,10 +151,8 @@ private fun NoteComposeLayoutBoostedPreview() {
|
||||
* │ │author│ gap │ firstRow │ │
|
||||
* │ │ 55dp │ │ secondRow (optional) │ │
|
||||
* │ │ │ │ 4dp spacer (optional) │ │
|
||||
* │ ├──────┤ 5dp │ noteContent │ │
|
||||
* │ │relay │ gap │ │ │
|
||||
* │ │badges│ │ │ │
|
||||
* │ └──────┘ └────────────────────────────┘ │
|
||||
* │ └──────┘ │ noteContent │ │
|
||||
* │ └────────────────────────────┘ │
|
||||
* ├───────────────────────────────────────────────┤
|
||||
* │ reactionsRow (full width, no side padding) │
|
||||
* └───────────────────────────────────────────────┘
|
||||
@@ -200,11 +192,10 @@ private fun NoteComposeLayoutBoostedPreview() {
|
||||
*
|
||||
* The `contents` list and `allMeasurables` indices are:
|
||||
* - 0: [authorPicture] - 0 or 1 measurables
|
||||
* - 1: [relayBadges] - 0 or 1 measurables
|
||||
* - 2: [firstRow] - exactly 1 measurable
|
||||
* - 3: [secondRow] - 0 or 1 measurables
|
||||
* - 4: [noteContent] - 1+ measurables (stacked vertically)
|
||||
* - 5: [reactionsRow] - 0+ measurables (stacked vertically)
|
||||
* - 1: [firstRow] - exactly 1 measurable
|
||||
* - 2: [secondRow] - 0 or 1 measurables
|
||||
* - 3: [noteContent] - 1+ measurables (stacked vertically)
|
||||
* - 4: [reactionsRow] - 0+ measurables (stacked vertically)
|
||||
*
|
||||
* ## Performance
|
||||
*
|
||||
@@ -212,7 +203,7 @@ private fun NoteComposeLayoutBoostedPreview() {
|
||||
* - Eliminates 3 layout node levels (Row, author Column, content Column)
|
||||
* - Eliminates Row's two-pass measurement (measure author first, then content)
|
||||
* - Pre-computes all pixel dimensions once via [remember], cached across recompositions
|
||||
* - Remaining per-frame allocations: ~48 bytes for `listOf(6 lambdas)` +
|
||||
* - Remaining per-frame allocations: ~40 bytes for `listOf(5 lambdas)` +
|
||||
* ~24 bytes per `arrayOfNulls` for multi-child slots
|
||||
*
|
||||
* The caller should use `drawBehind { drawRect(color) }` instead of
|
||||
@@ -231,8 +222,6 @@ private fun NoteComposeLayoutBoostedPreview() {
|
||||
* note content. False for repost events.
|
||||
* @param authorPicture Slot for the author's profile picture (55x55dp area).
|
||||
* Should emit nothing when [showAuthorColumn] is false to skip composition.
|
||||
* @param relayBadges Slot for relay indicator icons below the author picture.
|
||||
* Should emit nothing when [showAuthorColumn] is false.
|
||||
* @param firstRow Slot for the primary header: author name, time, more options.
|
||||
* Always present.
|
||||
* @param secondRow Slot for the secondary header: NIP-05, location, PoW, OTS.
|
||||
@@ -250,7 +239,6 @@ fun NoteComposeLayout(
|
||||
showSecondRow: Boolean = false,
|
||||
showContentSpacer: Boolean = true,
|
||||
authorPicture: @Composable () -> Unit,
|
||||
relayBadges: @Composable () -> Unit,
|
||||
firstRow: @Composable () -> Unit,
|
||||
secondRow: @Composable () -> Unit,
|
||||
noteContent: @Composable () -> Unit,
|
||||
@@ -263,7 +251,6 @@ fun NoteComposeLayout(
|
||||
NoteLayoutPx(
|
||||
authorWidth = Size55dp.roundToPx(),
|
||||
authorContentGap = 10.dp.roundToPx(),
|
||||
authorBadgeGap = 5.dp.roundToPx(),
|
||||
contentSpacer = 4.dp.roundToPx(),
|
||||
padStart = 12.dp.roundToPx(),
|
||||
padEnd = 12.dp.roundToPx(),
|
||||
@@ -277,11 +264,10 @@ fun NoteComposeLayout(
|
||||
contents =
|
||||
listOf(
|
||||
authorPicture, // 0
|
||||
relayBadges, // 1
|
||||
firstRow, // 2
|
||||
secondRow, // 3
|
||||
noteContent, // 4
|
||||
reactionsRow, // 5
|
||||
firstRow, // 1
|
||||
secondRow, // 2
|
||||
noteContent, // 3
|
||||
reactionsRow, // 4
|
||||
),
|
||||
modifier = modifier,
|
||||
) { allMeasurables, constraints ->
|
||||
@@ -308,14 +294,13 @@ fun NoteComposeLayout(
|
||||
// Single-child slots: firstOrNull() returns null for empty slots (hidden),
|
||||
// skipping measurement entirely.
|
||||
val authorPlaceable = allMeasurables[0].firstOrNull()?.measure(authorConstraints)
|
||||
val relayPlaceable = allMeasurables[1].firstOrNull()?.measure(authorConstraints)
|
||||
|
||||
val firstRowPlaceable = allMeasurables[2].firstOrNull()?.measure(contentConstraints)
|
||||
val firstRowPlaceable = allMeasurables[1].firstOrNull()?.measure(contentConstraints)
|
||||
val secondRowPlaceable =
|
||||
if (showSecondRow) allMeasurables[3].firstOrNull()?.measure(contentConstraints) else null
|
||||
if (showSecondRow) allMeasurables[2].firstOrNull()?.measure(contentConstraints) else null
|
||||
|
||||
// Multi-child slots: measured into pre-sized arrays to avoid List allocation.
|
||||
val contentMeasurables = allMeasurables[4]
|
||||
val contentMeasurables = allMeasurables[3]
|
||||
val contentPlaceables = arrayOfNulls<Placeable>(contentMeasurables.size)
|
||||
var contentStackHeight = 0
|
||||
for (i in contentMeasurables.indices) {
|
||||
@@ -324,7 +309,7 @@ fun NoteComposeLayout(
|
||||
contentStackHeight += placeable.height
|
||||
}
|
||||
|
||||
val reactionsMeasurables = allMeasurables[5]
|
||||
val reactionsMeasurables = allMeasurables[4]
|
||||
val reactionsPlaceables = arrayOfNulls<Placeable>(reactionsMeasurables.size)
|
||||
var reactionsHeight = 0
|
||||
for (i in reactionsMeasurables.indices) {
|
||||
@@ -344,7 +329,7 @@ fun NoteComposeLayout(
|
||||
|
||||
val authorColumnHeight =
|
||||
if (showAuthorColumn && authorPlaceable != null) {
|
||||
authorPlaceable.height + px.authorBadgeGap + (relayPlaceable?.height ?: 0)
|
||||
authorPlaceable.height
|
||||
} else {
|
||||
0
|
||||
}
|
||||
@@ -357,10 +342,6 @@ fun NoteComposeLayout(
|
||||
// Author column (inside padding area)
|
||||
if (showAuthorColumn) {
|
||||
authorPlaceable?.placeRelative(padStart, padTop)
|
||||
relayPlaceable?.placeRelative(
|
||||
padStart,
|
||||
padTop + (authorPlaceable?.height ?: 0) + px.authorBadgeGap,
|
||||
)
|
||||
}
|
||||
|
||||
// Content column (to the right of author, inside padding area)
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.note
|
||||
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.MediumRelayIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.NotificationIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.sample
|
||||
|
||||
/**
|
||||
* Observes the relays the note was seen on, throttled and de-duplicated for the
|
||||
* "accepted by relays" gallery line. Exposed as State so the caller can both gate
|
||||
* the gallery's visibility on it and feed it to [RenderAcceptedByRelaysGallery]
|
||||
* from a single subscription.
|
||||
*/
|
||||
@OptIn(FlowPreview::class)
|
||||
@Composable
|
||||
internal fun observeNoteRelays(baseNote: Note): State<ImmutableList<NormalizedRelayUrl>> {
|
||||
// Cold wrapper: `flow()` must be re-resolved on every collection start. A memory
|
||||
// trim destroys the NoteFlowSet while the lifecycle is stopped; a stateFlow
|
||||
// captured in remember would then be orphaned and never see another relay update.
|
||||
// Sampled at 500ms because relay arrivals churn while a note is actively fanning out.
|
||||
val flow =
|
||||
remember(baseNote) {
|
||||
flow { emitAll(baseNote.flow().relays.stateFlow) }
|
||||
.sample(500)
|
||||
.map { it.note.relays.toImmutableList() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
val initial = remember(baseNote) { baseNote.relays.toImmutableList() }
|
||||
return flow.collectAsStateWithLifecycle(initial)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight "does this note have any relays" observer for the reaction row's
|
||||
* expand-button gate, which runs for every note in the feed. Unlike
|
||||
* [observeNoteRelays] it maps to a Boolean (no per-emission list allocation) and
|
||||
* needs no throttling, since the emptiness flips at most a couple of times.
|
||||
*/
|
||||
@Composable
|
||||
internal fun observeNoteHasRelays(baseNote: Note): State<Boolean> {
|
||||
val flow =
|
||||
remember(baseNote) {
|
||||
flow { emitAll(baseNote.flow().relays.stateFlow) }
|
||||
.map { it.note.relays.isNotEmpty() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(baseNote.relays.isNotEmpty())
|
||||
}
|
||||
|
||||
/**
|
||||
* The "accepted by relays" line of the reaction gallery: the favicon of every relay
|
||||
* the note was seen on. This is the same relay-pill set that Complete UI Mode used to
|
||||
* paint below the author avatar; it now lives in the expanded reaction gallery so it
|
||||
* is available to everyone, not just Complete mode.
|
||||
*/
|
||||
@Composable
|
||||
internal fun RenderAcceptedByRelaysGallery(
|
||||
relays: ImmutableList<NormalizedRelayUrl>,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
// NotificationIconModifier (55dp wide, 5dp end padding) matches the category-icon
|
||||
// column of the zap/like/nutzap gallery rows so the Dns icon lines up with them.
|
||||
Box(modifier = NotificationIconModifier) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.Dns,
|
||||
contentDescription = stringRes(id = R.string.accepted_by_relays),
|
||||
modifier = Modifier.size(Size20dp).align(Alignment.TopEnd),
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
|
||||
RelayGallery(relays, nav, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun RelayGallery(
|
||||
relays: ImmutableList<NormalizedRelayUrl>,
|
||||
nav: INav,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Column(modifier = StdStartPadding) {
|
||||
FlowRow {
|
||||
relays.forEach { relay ->
|
||||
// Match the 35dp author pictures of the sibling zap/boost/reaction lines.
|
||||
RenderRelay(
|
||||
relay = relay,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
boxSize = Size35dp,
|
||||
iconModifier = MediumRelayIconModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -745,11 +745,6 @@ fun InnerNoteWithReactions(
|
||||
}
|
||||
}
|
||||
},
|
||||
relayBadges = {
|
||||
if (notBoostedNorQuote) {
|
||||
BadgeBox(baseNote, accountViewModel, nav)
|
||||
}
|
||||
},
|
||||
firstRow = {
|
||||
FirstUserInfoRow(
|
||||
baseNote = baseNote,
|
||||
@@ -2075,21 +2070,6 @@ fun observeEdits(
|
||||
return editState
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BadgeBox(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
if (accountViewModel.settings.isCompleteUIMode()) {
|
||||
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
|
||||
baseNote.replyTo?.lastOrNull()?.let { RelayBadges(it, accountViewModel, nav) }
|
||||
} else {
|
||||
RelayBadges(baseNote, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderAuthorImages(
|
||||
baseNote: Note,
|
||||
|
||||
@@ -523,10 +523,13 @@ private fun WatchReactionsZapsBoostsAndDisplayIfExists(
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val hasReactions by observeNoteReferences(baseNote, accountViewModel)
|
||||
val hasRelays by observeNoteHasRelays(baseNote)
|
||||
|
||||
val hasZapraiser = (baseNote.event?.zapraiserAmount() ?: 0) > 0
|
||||
|
||||
if (hasReactions || hasZapraiser) {
|
||||
// The gallery always carries an "accepted by relays" line when the note has been
|
||||
// seen on any relay, so the expand button must be reachable in that case too.
|
||||
if (hasReactions || hasZapraiser || hasRelays) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
@@ -564,8 +567,13 @@ private fun ReactionDetailGallery(
|
||||
val backgroundColor = remember { mutableStateOf(defaultBackgroundColor) }
|
||||
|
||||
val hasReactions by observeNoteReferences(baseNote, accountViewModel)
|
||||
val relays by observeNoteRelays(baseNote)
|
||||
|
||||
if (hasReactions) {
|
||||
// The gallery shows whenever there is anything to display: the "accepted by relays"
|
||||
// line (relays are almost always present once a note is seen) or any zap/boost/
|
||||
// reaction line. Guarding here keeps the padded Row from rendering an empty strip
|
||||
// for a note that has neither.
|
||||
if (hasReactions || relays.isNotEmpty()) {
|
||||
Row(
|
||||
verticalAlignment = CenterVertically,
|
||||
modifier = Modifier.padding(start = 10.dp, top = 5.dp),
|
||||
@@ -576,6 +584,9 @@ private fun ReactionDetailGallery(
|
||||
WatchOnchainZapsAndRenderGallery(baseNote, nav, accountViewModel)
|
||||
WatchBoostsAndRenderGallery(baseNote, nav, accountViewModel)
|
||||
WatchReactionsAndRenderGallery(baseNote, nav, accountViewModel)
|
||||
if (relays.isNotEmpty()) {
|
||||
RenderAcceptedByRelaysGallery(relays, nav, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
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
|
||||
@@ -57,7 +56,6 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size17Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.noteComposeRelayBox
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
@@ -67,26 +65,6 @@ import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.sample
|
||||
|
||||
@Composable
|
||||
fun RelayBadges(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
|
||||
CrossfadeIfEnabled(expanded.value, modifier = noteComposeRelayBox, label = "RelayBadges", accountViewModel = accountViewModel) {
|
||||
if (it) {
|
||||
RenderAllRelayList(baseNote, Modifier.fillMaxWidth(), accountViewModel = accountViewModel, nav = nav)
|
||||
} else {
|
||||
Column {
|
||||
RenderClosedRelayList(baseNote, Modifier.fillMaxWidth(), accountViewModel = accountViewModel, nav = nav)
|
||||
ShouldShowExpandButton(baseNote, accountViewModel) { ShowMoreRelaysButton { expanded.value = true } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun RenderAllRelayList(
|
||||
|
||||
@@ -43,6 +43,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@@ -128,15 +129,17 @@ fun RenderRelay(
|
||||
relay: NormalizedRelayUrl,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
boxSize: Dp = Size17dp,
|
||||
iconModifier: Modifier = MaterialTheme.colorScheme.relayIconModifier,
|
||||
) {
|
||||
val relayInfo by loadRelayInfo(relay)
|
||||
|
||||
val clipboardManager = LocalClipboard.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val clickableModifier =
|
||||
remember(relay) {
|
||||
remember(relay, boxSize) {
|
||||
Modifier
|
||||
.size(Size17dp)
|
||||
.size(boxSize)
|
||||
.combinedClickable(
|
||||
indication = ripple24dp,
|
||||
interactionSource = MutableInteractionSource(),
|
||||
@@ -159,6 +162,7 @@ fun RenderRelay(
|
||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||
pingInMs = 0,
|
||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||
iconModifier = iconModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3123,6 +3123,7 @@
|
||||
|
||||
<string name="profile_image_of_user">Profile Picture of %1$s</string>
|
||||
<string name="relay_info">Relay %1$s</string>
|
||||
<string name="accepted_by_relays">Accepted by relays</string>
|
||||
<string name="expand_relay_list">Expand relay list</string>
|
||||
<string name="note_options">Note options</string>
|
||||
<string name="poll">Poll</string>
|
||||
|
||||
Reference in New Issue
Block a user