Improves layout of the Relay Monitor events.
This commit is contained in:
@@ -182,6 +182,8 @@ import com.vitorpamplona.quartz.nip64Chess.game.ChessGameEvent
|
||||
import com.vitorpamplona.quartz.nip64Chess.jester.JesterEvent
|
||||
import com.vitorpamplona.quartz.nip64Chess.move.LiveChessMoveEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.RelayDiscoveryEvent
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.RelayMonitorEvent
|
||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||
@@ -341,10 +343,10 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
}
|
||||
}.buffer(kotlinx.coroutines.channels.Channel.CONFLATED)
|
||||
|
||||
fun observeEvents(filter: Filter): Flow<List<Event>> =
|
||||
fun <T : Event> observeEvents(filter: Filter): Flow<List<T>> =
|
||||
callbackFlow {
|
||||
val cachedFilter =
|
||||
EventListMatchingFilter(filter, this@LocalCache::filter) {
|
||||
EventListMatchingFilter<T>(filter, this@LocalCache::filter) {
|
||||
trySend(it)
|
||||
}
|
||||
|
||||
@@ -358,7 +360,7 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
}.buffer(kotlinx.coroutines.channels.Channel.CONFLATED)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Event> observeLatestEvent(filter: Filter) = observeEvents(filter).map { it.firstNotNullOfOrNull { it as? T } }
|
||||
fun <T : Event> observeLatestEvent(filter: Filter) = observeEvents<T>(filter).map { it.firstOrNull() }
|
||||
|
||||
fun observeLatestNote(filter: Filter) = observeNotes(filter).map { it.firstOrNull() }
|
||||
|
||||
@@ -1349,6 +1351,18 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
wasVerified: Boolean,
|
||||
) = consumeRegularEvent(event, relay, wasVerified)
|
||||
|
||||
private fun consume(
|
||||
event: RelayDiscoveryEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
wasVerified: Boolean,
|
||||
) = consumeBaseReplaceable(event, relay, wasVerified)
|
||||
|
||||
private fun consume(
|
||||
event: RelayMonitorEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
wasVerified: Boolean,
|
||||
) = consumeBaseReplaceable(event, relay, wasVerified)
|
||||
|
||||
fun consume(
|
||||
event: StatusEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
@@ -3208,6 +3222,8 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
is ZapPollEvent -> consume(event, relay, wasVerified)
|
||||
is PollEvent -> consume(event, relay, wasVerified)
|
||||
is PollResponseEvent -> consume(event, relay, wasVerified)
|
||||
is RelayDiscoveryEvent -> consume(event, relay, wasVerified)
|
||||
is RelayMonitorEvent -> consume(event, relay, wasVerified)
|
||||
is ReactionEvent -> consume(event, relay, wasVerified)
|
||||
is ContactCardEvent -> consume(event, relay, wasVerified)
|
||||
is RelaySetEvent -> consume(event, relay, wasVerified)
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ class UserProfileFollowersUserFeedViewModel(
|
||||
@OptIn(kotlinx.coroutines.FlowPreview::class)
|
||||
val followersFlow: StateFlow<List<User>> =
|
||||
account.cache
|
||||
.observeEvents(followerFilter)
|
||||
.observeEvents<Event>(followerFilter)
|
||||
.sample(500)
|
||||
.map { followerContactLists ->
|
||||
followerContactLists.toNonHiddenOwners().sortedWith(sortingModel)
|
||||
|
||||
+2
-5
@@ -28,7 +28,6 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.utils.BigDecimal
|
||||
@@ -96,18 +95,16 @@ class UserProfileZapsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun List<Event>.sumAmountsByUser(): List<ZapAmount> {
|
||||
suspend fun List<LnZapEvent>.sumAmountsByUser(): List<ZapAmount> {
|
||||
val results = mutableMapOf<User, BigDecimal>()
|
||||
|
||||
this.forEach { zapEvent ->
|
||||
if (zapEvent is LnZapEvent) {
|
||||
val zapAmount = mapRequest(zapEvent)
|
||||
if (zapAmount != null) {
|
||||
val existingAmount = results[zapAmount.user] ?: BigDecimal.ZERO
|
||||
results[zapAmount.user] = existingAmount + zapAmount.amount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results.map { (user, amount) -> ZapAmount(user, amount) }.sortedWith(sortingModel)
|
||||
}
|
||||
@@ -115,7 +112,7 @@ class UserProfileZapsViewModel(
|
||||
@OptIn(kotlinx.coroutines.FlowPreview::class)
|
||||
val receivedZapAmountsByUser: StateFlow<List<ZapAmount>> =
|
||||
account.cache
|
||||
.observeEvents(zapsToUser)
|
||||
.observeEvents<LnZapEvent>(zapsToUser)
|
||||
.sample(500)
|
||||
.map { zapEvents ->
|
||||
zapEvents.sumAmountsByUser()
|
||||
|
||||
+12
-48
@@ -85,7 +85,6 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -102,6 +101,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.util.timeDiffAgoShortish
|
||||
@@ -115,6 +115,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.graspLink
|
||||
import com.vitorpamplona.amethyst.ui.note.nipLink
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgoNoDot
|
||||
@@ -283,6 +284,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
@@ -440,7 +442,7 @@ fun RelayInformationBody(
|
||||
|
||||
if (discoveryEvents.isNotEmpty()) {
|
||||
item { SectionHeader(stringRes(R.string.relay_monitor_reports)) }
|
||||
items(discoveryEvents, key = { it.id }) { event ->
|
||||
items(discoveryEvents, key = { it.addressTag() }) { event ->
|
||||
RelayMonitorReportCard(event, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
@@ -1575,21 +1577,19 @@ fun PoliciesCard(relay: Nip11RelayInformation) {
|
||||
|
||||
@Composable
|
||||
fun loadRelayDiscoveryEvents(relay: NormalizedRelayUrl): State<ImmutableList<RelayDiscoveryEvent>> =
|
||||
produceState<ImmutableList<RelayDiscoveryEvent>>(persistentListOf(), relay) {
|
||||
remember(relay) {
|
||||
LocalCache
|
||||
.observeEvents(
|
||||
.observeEvents<RelayDiscoveryEvent>(
|
||||
Filter(
|
||||
kinds = listOf(RelayDiscoveryEvent.KIND),
|
||||
tags = mapOf("d" to listOf(relay.url)),
|
||||
since = TimeUtils.oneWeekAgo(),
|
||||
limit = 3,
|
||||
),
|
||||
).collect { events ->
|
||||
value =
|
||||
events
|
||||
.filterIsInstance<RelayDiscoveryEvent>()
|
||||
.sortedByDescending { it.createdAt }
|
||||
.toImmutableList()
|
||||
}
|
||||
).map {
|
||||
it.toImmutableList()
|
||||
}
|
||||
}.collectAsStateWithLifecycle(persistentListOf())
|
||||
|
||||
@Composable
|
||||
private fun RelayMonitorReportCard(
|
||||
@@ -1622,11 +1622,7 @@ private fun RelayMonitorReportCard(
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
UserCompose(
|
||||
baseUser = user,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
UsernameDisplay(user, weight = Modifier.weight(1f), accountViewModel = accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1646,11 +1642,6 @@ private fun RelayMonitorReportCard(
|
||||
val rttWrite = event.rttWrite()
|
||||
|
||||
if (rttOpen != null || rttRead != null || rttWrite != null) {
|
||||
Text(
|
||||
stringRes(R.string.relay_monitor_rtt),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
@@ -1687,33 +1678,6 @@ private fun RelayMonitorReportCard(
|
||||
)
|
||||
}
|
||||
|
||||
// Supported NIPs
|
||||
val supportedNips = event.supportedNips()
|
||||
if (supportedNips.isNotEmpty()) {
|
||||
Text(
|
||||
stringRes(R.string.relay_monitor_supported_nips),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
val uri = LocalUriHandler.current
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
supportedNips.forEach { nip ->
|
||||
val nipStr = nip.toString().padStart(2, '0')
|
||||
SuggestionChip(
|
||||
onClick = {
|
||||
runCatching {
|
||||
uri.openUri(nipLink(nipStr))
|
||||
}
|
||||
},
|
||||
label = { Text(nipStr) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Requirements
|
||||
val requirements = event.requirements()
|
||||
if (requirements.isNotEmpty()) {
|
||||
|
||||
+18
-5
@@ -20,7 +20,9 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.model.observables
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import java.util.SortedSet
|
||||
@@ -31,18 +33,27 @@ import java.util.concurrent.ConcurrentSkipListSet
|
||||
* that is updated every time a new event that matches
|
||||
* the filter is received, including addressables.
|
||||
*/
|
||||
class EventListMatchingFilter(
|
||||
class EventListMatchingFilter<T : Event>(
|
||||
private val filter: Filter,
|
||||
private val atOnce: (filter: Filter) -> SortedSet<Note>,
|
||||
private val update: (List<Event>) -> Unit,
|
||||
private val update: (List<T>) -> Unit,
|
||||
) : Observable {
|
||||
// Keeping this here blocks it from being cleared from memory
|
||||
var currentResults: ConcurrentSkipListSet<Note> = ConcurrentSkipListSet(CreatedAtIdHexComparator)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun new(
|
||||
event: Event,
|
||||
note: Note,
|
||||
) {
|
||||
if (event is AddressableEvent && note !is AddressableNote) {
|
||||
// event update
|
||||
if (currentResults.contains(note)) {
|
||||
update(currentResults.mapNotNull { it.event as? T })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (filter.match(event)) {
|
||||
currentResults.add(note)
|
||||
val limit = filter.limit
|
||||
@@ -50,18 +61,20 @@ class EventListMatchingFilter(
|
||||
currentResults.remove(currentResults.last())
|
||||
}
|
||||
|
||||
update(currentResults.mapNotNull { it.event })
|
||||
update(currentResults.mapNotNull { it.event as? T })
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun remove(note: Note) {
|
||||
if (currentResults.remove(note)) {
|
||||
update(currentResults.mapNotNull { it.event })
|
||||
update(currentResults.mapNotNull { it.event as? T })
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun init() {
|
||||
currentResults = ConcurrentSkipListSet(atOnce(filter))
|
||||
update(currentResults.mapNotNull { it.event })
|
||||
update(currentResults.mapNotNull { it.event as? T })
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -20,7 +20,9 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.model.observables
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import java.util.SortedSet
|
||||
@@ -43,6 +45,8 @@ class NoteListMatchingFilter(
|
||||
event: Event,
|
||||
note: Note,
|
||||
) {
|
||||
if (event is AddressableEvent && note !is AddressableNote) return
|
||||
|
||||
if (filter.match(event)) {
|
||||
if (currentResults.add(note)) {
|
||||
val limit = filter.limit
|
||||
|
||||
Reference in New Issue
Block a user