fix(interest-sets): consume kind 30015 locally + empty-state polish

- LocalCache.consume dispatcher now handles InterestSetEvent via
  consumeBaseReplaceable, so the event created by the user is stored
  and flows through newEventBundles → interestSets.newNotes → listFeedFlow
  refresh. Without this, the just-signed event sat in the sendMyPublicAndPrivateOutbox
  call but the UI never saw the update.
- List screen: icon + centered text for the empty state and dividers
  between rows.
This commit is contained in:
Claude
2026-04-20 14:11:06 +00:00
parent 198e950f37
commit 4c82011a25
2 changed files with 38 additions and 9 deletions
@@ -153,6 +153,7 @@ import com.vitorpamplona.quartz.nip51Lists.favoriteAlgoFeedsList.FavoriteAlgoFee
import com.vitorpamplona.quartz.nip51Lists.followList.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListEvent
import com.vitorpamplona.quartz.nip51Lists.interestSet.InterestSetEvent
import com.vitorpamplona.quartz.nip51Lists.labeledBookmarkList.LabeledBookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
@@ -2642,6 +2643,7 @@ object LocalCache : ILocalCache, ICacheProvider {
is InteractiveStoryPrologueEvent -> consumeBaseReplaceable(event, relay, wasVerified)
is InteractiveStorySceneEvent -> consumeBaseReplaceable(event, relay, wasVerified)
is InteractiveStoryReadingStateEvent -> consumeBaseReplaceable(event, relay, wasVerified)
is InterestSetEvent -> consumeBaseReplaceable(event, relay, wasVerified)
is LabeledBookmarkListEvent -> consumeBaseReplaceable(event, relay, wasVerified)
is LiveActivitiesEvent -> consume(event, relay, wasVerified)
is LiveActivitiesChatMessageEvent -> consume(event, relay, wasVerified)
@@ -24,13 +24,17 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
import androidx.compose.material.icons.outlined.Tag
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
@@ -39,6 +43,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
@@ -46,6 +52,8 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@Composable
fun ListOfInterestSetsScreen(
@@ -71,19 +79,15 @@ fun ListOfInterestSetsScreen(
).fillMaxHeight(),
) {
if (sets.isEmpty()) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(stringRes(R.string.interest_sets_empty))
}
EmptyInterestSets()
} else {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = FeedPadding,
) {
items(sets, key = { it.identifier }) { set ->
itemsIndexed(sets, key = { _, it -> it.identifier }) { _, set ->
InterestSetItem(
modifier = Modifier.fillMaxSize().animateItem(),
interestSet = set,
onClick = { nav.nav(Route.InterestSetView(set.identifier)) },
onRename = { nav.nav(Route.InterestSetMetadataEdit(set.identifier)) },
@@ -105,6 +109,7 @@ fun ListOfInterestSetsScreen(
}
},
)
HorizontalDivider(thickness = DividerThickness)
}
}
}
@@ -112,6 +117,28 @@ fun ListOfInterestSetsScreen(
}
}
@Composable
private fun EmptyInterestSets() {
Column(
modifier = Modifier.fillMaxSize().padding(32.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
imageVector = Icons.Outlined.Tag,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(64.dp),
)
Text(
text = stringRes(R.string.interest_sets_empty),
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
)
}
}
@Composable
fun InterestSetFab(onAdd: () -> Unit) {
ExtendedFloatingActionButton(