- Unifies All Badge rendering

- Constraints badge layouts on Notes
- Creates a flow cache to speed up Note relay layout
- Speeds up NoteQuickActionMenus
- Optimizes NIP-11 pings
- Better error messages when NIP-11 fails
pull/916/head
Vitor Pamplona 2024-06-13 16:50:19 -04:00
rodzic 6bc305dbe6
commit 2503cbaeac
11 zmienionych plików z 240 dodań i 200 usunięć

Wyświetl plik

@ -418,7 +418,7 @@ open class Note(
fun addRelay(relay: Relay) {
if (relay.brief !in relays) {
addRelaySync(relay.brief)
liveSet?.innerRelays?.invalidateData()
flowSet?.relays?.invalidateData()
}
}
@ -847,12 +847,17 @@ class NoteFlowSet(
// Observers line up here.
val metadata = NoteBundledRefresherFlow(u)
val reports = NoteBundledRefresherFlow(u)
val relays = NoteBundledRefresherFlow(u)
fun isInUse(): Boolean = metadata.stateFlow.subscriptionCount.value > 0 || reports.stateFlow.subscriptionCount.value > 0
fun isInUse(): Boolean =
metadata.stateFlow.subscriptionCount.value > 0 ||
reports.stateFlow.subscriptionCount.value > 0 ||
relays.stateFlow.subscriptionCount.value > 0
fun destroy() {
metadata.destroy()
reports.destroy()
relays.destroy()
}
}
@ -865,7 +870,6 @@ class NoteLiveSet(
val innerReactions = NoteBundledRefresherLiveData(u)
val innerBoosts = NoteBundledRefresherLiveData(u)
val innerReplies = NoteBundledRefresherLiveData(u)
val innerRelays = NoteBundledRefresherLiveData(u)
val innerZaps = NoteBundledRefresherLiveData(u)
val innerOts = NoteBundledRefresherLiveData(u)
val innerModifications = NoteBundledRefresherLiveData(u)
@ -874,7 +878,6 @@ class NoteLiveSet(
val reactions = innerReactions.map { it }
val boosts = innerBoosts.map { it }
val replies = innerReplies.map { it }
val relays = innerRelays.map { it }
val zaps = innerZaps.map { it }
val hasEvent = innerMetadata.map { it.note.event != null }.distinctUntilChanged()
@ -900,8 +903,6 @@ class NoteLiveSet(
val boostCount = innerBoosts.map { it.note.boosts.size }.distinctUntilChanged()
val relayInfo = innerRelays.map { it.note.relays }
val content = innerMetadata.map { it.note.event?.content() ?: "" }
fun isInUse(): Boolean =
@ -909,7 +910,6 @@ class NoteLiveSet(
reactions.hasObservers() ||
boosts.hasObservers() ||
replies.hasObservers() ||
relays.hasObservers() ||
zaps.hasObservers() ||
hasEvent.hasObservers() ||
hasReactions.hasObservers() ||
@ -924,7 +924,6 @@ class NoteLiveSet(
innerReactions.destroy()
innerBoosts.destroy()
innerReplies.destroy()
innerRelays.destroy()
innerZaps.destroy()
innerOts.destroy()
innerModifications.destroy()
@ -936,6 +935,7 @@ class NoteBundledRefresherFlow(
val note: Note,
) {
// Refreshes observers in batches.
// TODO: Replace the bundler for .sample
private val bundler = BundledUpdate(500, Dispatchers.IO)
val stateFlow = MutableStateFlow(NoteState(note))
@ -1015,11 +1015,12 @@ object RelayBriefInfoCache {
val cache = LruCache<String, RelayBriefInfo?>(50)
@Immutable
data class RelayBriefInfo(
class RelayBriefInfo(
val url: String,
val displayUrl: String = RelayUrlFormatter.displayUrl(url).intern(),
val favIcon: String = "https://$displayUrl/favicon.ico".intern(),
)
) {
val displayUrl: String = RelayUrlFormatter.displayUrl(url).intern()
val favIcon: String = "https://$displayUrl/favicon.ico".intern()
}
fun get(url: String): RelayBriefInfo {
val info = cache[url]

Wyświetl plik

@ -87,13 +87,11 @@ fun RobohashFallbackAsyncImage(
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality,
loadProfilePicture: Boolean,
) {
val context = LocalContext.current
val isLightTheme = MaterialTheme.colorScheme.isLight
if (model != null && loadProfilePicture) {
val isBase64 by remember { derivedStateOf { model.startsWith("data:image/jpeg;base64,") } }
if (isBase64) {
val context = LocalContext.current
val base64Painter =
rememberAsyncImagePainter(
model = Base64Requester.imageRequest(context, model),
@ -110,7 +108,7 @@ fun RobohashFallbackAsyncImage(
} else {
val painter =
rememberVectorPainter(
image = CachedRobohash.get(robot, isLightTheme),
image = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight),
)
AsyncImage(
@ -128,7 +126,7 @@ fun RobohashFallbackAsyncImage(
)
}
} else {
val robotPainter = CachedRobohash.get(robot, isLightTheme)
val robotPainter = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight)
Image(
imageVector = robotPainter,
@ -145,9 +143,12 @@ object Base64Requester {
fun imageRequest(
context: Context,
message: String,
): ImageRequest {
return ImageRequest.Builder(context).data(message).fetcherFactory(Base64Fetcher.Factory).build()
}
): ImageRequest =
ImageRequest
.Builder(context)
.data(message)
.fetcherFactory(Base64Fetcher.Factory)
.build()
}
@Stable
@ -179,8 +180,6 @@ class Base64Fetcher(
data: Uri,
options: Options,
imageLoader: ImageLoader,
): Fetcher {
return Base64Fetcher(options, data)
}
): Fetcher = Base64Fetcher(options, data)
}
}

Wyświetl plik

@ -190,10 +190,9 @@ fun NormalChatNote(
nav("User/${it.pubkeyHex}")
}
},
actionMenu = { popupExpanded, onDismiss ->
actionMenu = { onDismiss ->
NoteQuickActionMenu(
note = note,
popupExpanded = popupExpanded,
onDismiss = onDismiss,
onWantsToEditDraft = { onWantsToEditDraft(note) },
accountViewModel = accountViewModel,
@ -256,7 +255,7 @@ fun ChatBubbleLayout(
parentBackgroundColor: MutableState<Color>? = null,
onClick: () -> Boolean,
onAuthorClick: () -> Unit,
actionMenu: @Composable (popupExpanded: Boolean, onDismiss: () -> Unit) -> Unit,
actionMenu: @Composable (onDismiss: () -> Unit) -> Unit,
detailRow: @Composable () -> Unit,
drawAuthorLine: @Composable () -> Unit,
inner: @Composable (MutableState<Color>) -> Unit,
@ -294,7 +293,7 @@ fun ChatBubbleLayout(
modifier = if (innerQuote) ChatPaddingInnerQuoteModifier else ChatPaddingModifier,
horizontalArrangement = alignment,
) {
var popupExpanded by remember { mutableStateOf(false) }
var popupExpanded = remember { mutableStateOf(false) }
val modif2 = if (innerQuote) Modifier else ChatBubbleMaxSizeModifier
@ -319,7 +318,7 @@ fun ChatBubbleLayout(
}
}
},
onLongClick = { popupExpanded = true },
onLongClick = { popupExpanded.value = true },
)
}
@ -357,8 +356,10 @@ fun ChatBubbleLayout(
}
}
actionMenu(popupExpanded) {
popupExpanded = false
if (popupExpanded.value) {
actionMenu {
popupExpanded.value = false
}
}
}
}
@ -381,7 +382,7 @@ private fun BubblePreview() {
parentBackgroundColor = backgroundBubbleColor,
onClick = { false },
onAuthorClick = {},
actionMenu = { popupExpanded, onDismiss ->
actionMenu = { onDismiss ->
},
drawAuthorLine = {
UserDisplayNameLayout(
@ -411,7 +412,7 @@ private fun BubblePreview() {
parentBackgroundColor = backgroundBubbleColor,
onClick = { false },
onAuthorClick = {},
actionMenu = { popupExpanded, onDismiss ->
actionMenu = { onDismiss ->
},
drawAuthorLine = {
UserDisplayNameLayout(

Wyświetl plik

@ -40,7 +40,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
@ -121,6 +120,7 @@ import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.HalfEndPadding
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp
import com.vitorpamplona.amethyst.ui.theme.Size25dp
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
import com.vitorpamplona.amethyst.ui.theme.Size34dp
@ -179,7 +179,6 @@ import com.vitorpamplona.quartz.events.VideoEvent
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
import com.vitorpamplona.quartz.events.VideoVerticalEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent
import kotlinx.coroutines.launch
@Composable
fun NoteCompose(
@ -416,22 +415,18 @@ fun ClickableNote(
nav: (String) -> Unit,
content: @Composable () -> Unit,
) {
val scope = rememberCoroutineScope()
val updatedModifier =
remember(baseNote, backgroundColor.value) {
modifier
.combinedClickable(
onClick = {
scope.launch {
val redirectToNote =
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
baseNote.replyTo?.lastOrNull() ?: baseNote
} else {
baseNote
}
routeFor(redirectToNote, accountViewModel.userProfile())?.let { nav(it) }
}
val redirectToNote =
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
baseNote.replyTo?.lastOrNull() ?: baseNote
} else {
baseNote
}
routeFor(redirectToNote, accountViewModel.userProfile())?.let { nav(it) }
},
onLongClick = showPopup,
).background(backgroundColor.value)
@ -465,9 +460,7 @@ fun InnerNoteWithReactions(
},
) {
if (notBoostedNorQuote) {
Column(WidthAuthorPictureModifier) {
AuthorAndRelayInformation(baseNote, accountViewModel, nav)
}
AuthorAndRelayInformation(baseNote, accountViewModel, nav)
Spacer(modifier = DoubleHorzSpacer)
}
@ -1064,12 +1057,14 @@ private fun AuthorAndRelayInformation(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
// Draws the boosted picture outside the boosted card.
Box(modifier = Size55Modifier, contentAlignment = Alignment.BottomEnd) {
RenderAuthorImages(baseNote, nav, accountViewModel)
}
Column(WidthAuthorPictureModifier, verticalArrangement = RowColSpacing5dp) {
// Draws the boosted picture outside the boosted card.
Box(modifier = Size55Modifier, contentAlignment = Alignment.BottomEnd) {
RenderAuthorImages(baseNote, nav, accountViewModel)
}
BadgeBox(baseNote, accountViewModel, nav)
BadgeBox(baseNote, accountViewModel, nav)
}
}
@Composable

Wyświetl plik

@ -137,23 +137,22 @@ fun LongPressToQuickAction(
content: @Composable (() -> Unit) -> Unit,
) {
val popupExpanded = remember { mutableStateOf(false) }
val showPopup = remember { { popupExpanded.value = true } }
content(showPopup)
content { popupExpanded.value = true }
NoteQuickActionMenu(
note = baseNote,
popupExpanded = popupExpanded.value,
onDismiss = { popupExpanded.value = false },
accountViewModel = accountViewModel,
nav = {},
)
if (popupExpanded.value) {
NoteQuickActionMenu(
note = baseNote,
onDismiss = { popupExpanded.value = false },
accountViewModel = accountViewModel,
nav = {},
)
}
}
@Composable
fun NoteQuickActionMenu(
note: Note,
popupExpanded: Boolean,
onDismiss: () -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
@ -173,7 +172,6 @@ fun NoteQuickActionMenu(
NoteQuickActionMenu(
note = note,
popupExpanded = popupExpanded,
onDismiss = onDismiss,
onWantsToEditDraft = { editDraftDialog.value = true },
accountViewModel = accountViewModel,
@ -184,7 +182,6 @@ fun NoteQuickActionMenu(
@Composable
fun NoteQuickActionMenu(
note: Note,
popupExpanded: Boolean,
onDismiss: () -> Unit,
onWantsToEditDraft: () -> Unit,
accountViewModel: AccountViewModel,
@ -195,17 +192,15 @@ fun NoteQuickActionMenu(
val showBlockAlertDialog = remember { mutableStateOf(false) }
val showReportDialog = remember { mutableStateOf(false) }
if (popupExpanded) {
RenderMainPopup(
accountViewModel,
note,
onDismiss,
showBlockAlertDialog,
showDeleteAlertDialog,
showReportDialog,
onWantsToEditDraft,
)
}
RenderMainPopup(
accountViewModel,
note,
onDismiss,
showBlockAlertDialog,
showDeleteAlertDialog,
showReportDialog,
onWantsToEditDraft,
)
if (showSelectTextDialog.value) {
val decryptedNote = remember { mutableStateOf<String?>(null) }
@ -267,12 +262,12 @@ private fun RenderMainPopup(
val showToast = { stringResource: Int ->
scope.launch {
Toast.makeText(
context,
context.getString(stringResource),
Toast.LENGTH_SHORT,
)
.show()
Toast
.makeText(
context,
context.getString(stringResource),
Toast.LENGTH_SHORT,
).show()
}
}

Wyświetl plik

@ -20,6 +20,7 @@
*/
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -40,10 +41,8 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
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.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -51,41 +50,91 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
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 kotlinx.coroutines.flow.map
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun RelayBadges(
baseNote: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
val expanded = remember { mutableStateOf(false) }
val relayList by baseNote.live().relayInfo.observeAsState(baseNote.relays)
Spacer(StdVertSpacer)
// FlowRow Seems to be a lot faster than LazyVerticalGrid
Box(modifier = Modifier.fillMaxWidth().padding(start = 2.dp, end = 1.dp)) {
FlowRow(modifier = Modifier.fillMaxWidth()) {
if (expanded) {
relayList?.forEach { RenderRelay(it, accountViewModel, nav) }
} else {
relayList?.getOrNull(0)?.let { RenderRelay(it, accountViewModel, nav) }
relayList?.getOrNull(1)?.let { RenderRelay(it, accountViewModel, nav) }
relayList?.getOrNull(2)?.let { RenderRelay(it, accountViewModel, nav) }
Crossfade(expanded.value, modifier = noteComposeRelayBox, label = "RelayBadges") {
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 } }
}
}
}
}
if (relayList.size > 3 && !expanded) {
ShowMoreRelaysButton { expanded = true }
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun RenderAllRelayList(
baseNote: Note,
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val noteRelays by baseNote
.flow()
.relays.stateFlow
.collectAsStateWithLifecycle()
FlowRow(modifier, verticalArrangement = verticalArrangement) {
noteRelays.note.relays.forEach { RenderRelay(it, accountViewModel, nav) }
}
}
@Composable
fun RenderClosedRelayList(
baseNote: Note,
modifier: Modifier = Modifier,
verticalAlignment: Alignment.Vertical = Alignment.Top,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
Row(modifier, verticalAlignment = verticalAlignment) {
WatchAndRenderRelay(baseNote, 0, accountViewModel, nav)
WatchAndRenderRelay(baseNote, 1, accountViewModel, nav)
WatchAndRenderRelay(baseNote, 2, accountViewModel, nav)
}
}
@Composable
fun WatchAndRenderRelay(
baseNote: Note,
relayIndex: Int,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val noteRelays by baseNote
.flow()
.relays.stateFlow
.map {
it.note.relays.getOrNull(relayIndex)
}.collectAsStateWithLifecycle(
baseNote.relays.getOrNull(relayIndex),
)
Crossfade(targetState = noteRelays, label = "RenderRelay", modifier = Size17Modifier) {
if (it != null) {
RenderRelay(it, accountViewModel, nav)
}
}
}
@ -97,46 +146,79 @@ fun RelayIconLayoutPreview() {
Spacer(StdVertSpacer)
// FlowRow Seems to be a lot faster than LazyVerticalGrid
Box(modifier = Modifier.fillMaxWidth().padding(start = 2.dp, end = 1.dp)) {
Box(
modifier =
Modifier
.fillMaxWidth()
.padding(start = 2.dp, end = 1.dp),
) {
FlowRow {
Box(
modifier = Modifier.size(17.dp),
contentAlignment = Alignment.Center,
) {
Box(
Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black),
Modifier
.size(13.dp)
.clip(shape = CircleShape)
.background(Color.Black),
)
}
Box(
modifier = Modifier.size(17.dp),
contentAlignment = Alignment.Center,
) {
Box(Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black))
Box(
Modifier
.size(13.dp)
.clip(shape = CircleShape)
.background(Color.Black),
)
}
Box(
modifier = Modifier.size(17.dp),
contentAlignment = Alignment.Center,
) {
Box(Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black))
Box(
Modifier
.size(13.dp)
.clip(shape = CircleShape)
.background(Color.Black),
)
}
Box(
modifier = Modifier.size(17.dp),
contentAlignment = Alignment.Center,
) {
Box(Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black))
Box(
Modifier
.size(13.dp)
.clip(shape = CircleShape)
.background(Color.Black),
)
}
Box(
modifier = Modifier.size(17.dp),
contentAlignment = Alignment.Center,
) {
Box(Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black))
Box(
Modifier
.size(13.dp)
.clip(shape = CircleShape)
.background(Color.Black),
)
}
Box(
modifier = Modifier.size(17.dp),
contentAlignment = Alignment.Center,
) {
Box(Modifier.size(13.dp).clip(shape = CircleShape).background(Color.Black))
Box(
Modifier
.size(13.dp)
.clip(shape = CircleShape)
.background(Color.Black),
)
}
}
}

Wyświetl plik

@ -21,32 +21,24 @@
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.lifecycle.map
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
@ -70,42 +62,25 @@ public fun RelayBadgesHorizontal(
) {
val expanded = remember { mutableStateOf(false) }
RenderRelayList(baseNote, expanded, accountViewModel, nav)
RenderExpandButton(baseNote, expanded) { ChatRelayExpandButton { expanded.value = true } }
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun RenderRelayList(
baseNote: Note,
expanded: MutableState<Boolean>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val noteRelays by baseNote.live().relayInfo.observeAsState(baseNote.relays)
FlowRow(StdStartPadding, verticalArrangement = Arrangement.Center) {
if (expanded.value) {
noteRelays?.forEach { RenderRelay(it, accountViewModel, nav) }
} else {
noteRelays?.getOrNull(0)?.let { RenderRelay(it, accountViewModel, nav) }
noteRelays?.getOrNull(1)?.let { RenderRelay(it, accountViewModel, nav) }
noteRelays?.getOrNull(2)?.let { RenderRelay(it, accountViewModel, nav) }
if (expanded.value) {
RenderAllRelayList(baseNote, StdStartPadding, verticalArrangement = Arrangement.Center, accountViewModel, nav)
} else {
RenderClosedRelayList(baseNote, StdStartPadding, verticalAlignment = Alignment.CenterVertically, accountViewModel = accountViewModel, nav = nav)
ShouldShowExpandButton(baseNote, accountViewModel) {
ChatRelayExpandButton { expanded.value = true }
}
}
}
@Composable
fun RenderExpandButton(
fun ShouldShowExpandButton(
baseNote: Note,
expanded: MutableState<Boolean>,
accountViewModel: AccountViewModel,
content: @Composable () -> Unit,
) {
val showExpandButton by
baseNote.live().relays.map { it.note.relays.size > 3 }.observeAsState(baseNote.relays.size > 3)
val showExpandButton by accountViewModel.createMustShowExpandButtonFlows(baseNote).collectAsStateWithLifecycle()
if (showExpandButton && !expanded.value) {
if (showExpandButton) {
content()
}
}
@ -159,19 +134,11 @@ fun RenderRelay(
)
}
val context = LocalContext.current
val interactionSource = remember { MutableInteractionSource() }
val ripple = rememberRipple(bounded = false, radius = Size17dp)
val clickableModifier =
remember(relay) {
Modifier
.size(Size17dp)
.clickable(
role = Role.Button,
interactionSource = interactionSource,
indication = ripple,
onClick = {
accountViewModel.retrieveRelayDocument(
relay.url,
@ -179,40 +146,23 @@ fun RenderRelay(
openRelayDialog = true
},
onError = { url, errorCode, exceptionMessage ->
val msg =
accountViewModel.toast(
R.string.unable_to_download_relay_document,
when (errorCode) {
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL ->
context.getString(
R.string.relay_information_document_error_assemble_url,
url,
exceptionMessage,
)
R.string.relay_information_document_error_failed_to_assemble_url
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER ->
context.getString(
R.string.relay_information_document_error_assemble_url,
url,
exceptionMessage,
)
R.string.relay_information_document_error_failed_to_reach_server
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT ->
context.getString(
R.string.relay_information_document_error_assemble_url,
url,
exceptionMessage,
)
R.string.relay_information_document_error_failed_to_parse_response
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS ->
context.getString(
R.string.relay_information_document_error_assemble_url,
url,
exceptionMessage,
)
}
accountViewModel.toast(
context.getString(R.string.unable_to_download_relay_document),
msg,
R.string.relay_information_document_error_failed_with_http
},
url,
exceptionMessage ?: errorCode.toString(),
)
},
)

Wyświetl plik

@ -111,6 +111,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
@ -322,6 +323,23 @@ class AccountViewModel(
noteIsHiddenFlows.put(note, it)
}
val noteMustShowExpandButtonFlows = LruCache<Note, StateFlow<Boolean>>(300)
fun createMustShowExpandButtonFlows(note: Note): StateFlow<Boolean> =
noteMustShowExpandButtonFlows.get(note)
?: note
.flow()
.relays
.stateFlow
.map { it.note.relays.size > 3 }
.stateIn(
viewModelScope,
SharingStarted.Eagerly,
note.relays.size > 3,
).also {
noteMustShowExpandButtonFlows.put(note, it)
}
fun hasReactedTo(
baseNote: Note,
reaction: String,

Wyświetl plik

@ -26,8 +26,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi
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.fillMaxHeight
@ -49,7 +47,6 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@ -74,7 +71,7 @@ import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport
import com.vitorpamplona.amethyst.ui.note.LikeReaction
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
import com.vitorpamplona.amethyst.ui.note.RenderRelay
import com.vitorpamplona.amethyst.ui.note.RenderAllRelayList
import com.vitorpamplona.amethyst.ui.note.ReplyReaction
import com.vitorpamplona.amethyst.ui.note.ViewCountReaction
import com.vitorpamplona.amethyst.ui.note.ZapReaction
@ -312,7 +309,9 @@ private fun RenderAuthorInformation(
Spacer(modifier = DoubleHorzSpacer)
Column(
Modifier.height(65.dp).weight(1f),
Modifier
.height(65.dp)
.weight(1f),
verticalArrangement = Arrangement.Center,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
@ -332,7 +331,7 @@ private fun RenderAuthorInformation(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 2.dp),
) {
RelayBadges(baseNote = note, accountViewModel, nav)
RenderAllRelayList(baseNote = note, accountViewModel = accountViewModel, nav = nav)
}
}
}
@ -369,18 +368,6 @@ private fun VideoUserOptionAction(
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun RelayBadges(
baseNote: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val noteRelays by baseNote.live().relayInfo.observeAsState(baseNote.relays)
FlowRow { noteRelays?.forEach { relayInfo -> RenderRelay(relayInfo, accountViewModel, nav) } }
}
@Composable
fun ReactionsColumn(
baseNote: Note,

Wyświetl plik

@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@ -167,7 +168,7 @@ val HeaderPictureModifier = Modifier.size(34.dp).clip(shape = CircleShape)
val ShowMoreRelaysButtonIconButtonModifier = Modifier.size(15.dp)
val ShowMoreRelaysButtonIconModifier = Modifier.size(20.dp)
val ShowMoreRelaysButtonBoxModifer = Modifier.fillMaxWidth().height(17.dp)
val ShowMoreRelaysButtonBoxModifer = Modifier.width(55.dp).height(17.dp)
val ChatBubbleMaxSizeModifier = Modifier.fillMaxWidth(0.85f)
@ -194,7 +195,8 @@ val ButtonPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
val ChatPaddingInnerQuoteModifier = Modifier.padding(top = 10.dp)
val ChatPaddingModifier =
Modifier.fillMaxWidth(1f)
Modifier
.fillMaxWidth(1f)
.padding(
start = 12.dp,
end = 12.dp,
@ -217,7 +219,8 @@ val imageHeaderBannerSize = Modifier.fillMaxWidth().height(150.dp)
val authorNotePictureForImageHeader = Modifier.size(75.dp).padding(10.dp)
val normalWithTopMarginNoteModifier =
Modifier.fillMaxWidth()
Modifier
.fillMaxWidth()
.padding(
start = 12.dp,
end = 12.dp,
@ -225,7 +228,8 @@ val normalWithTopMarginNoteModifier =
)
val boostedNoteModifier =
Modifier.fillMaxWidth()
Modifier
.fillMaxWidth()
.padding(
start = 0.dp,
end = 0.dp,
@ -258,3 +262,5 @@ val incognitoIconModifier =
.size(14.dp)
val hashVerifierMark = Modifier.width(40.dp).height(40.dp).padding(10.dp)
val noteComposeRelayBox = Modifier.width(55.dp).heightIn(min = 17.dp).padding(start = 2.dp, end = 1.dp)

Wyświetl plik

@ -600,6 +600,12 @@
<string name="error_dialog_button_ok">OK</string>
<string name="relay_information_document_error_assemble_url">Failed to reach %1$s: %2$s</string>
<string name="relay_information_document_error_failed_to_assemble_url">Failed to assemble NIP-11 url for %1$s: %2$s</string>
<string name="relay_information_document_error_failed_to_reach_server">Failed to reach %1$s: %2$s</string>
<string name="relay_information_document_error_failed_to_parse_response">Failed to parse response from %1$s: %2$s</string>
<string name="relay_information_document_error_failed_with_http">Relay declined requrest %1$s: %2$s</string>
<string name="relay_information_document_error_reach_server">Failed to reach %1$s: %2$s</string>
<string name="relay_information_document_error_parse_result">Failed to parse result from %1$s: %2$s</string>
<string name="relay_information_document_error_http_status">%1$s failed with code %2$s</string>