Merge pull request #3994 from greenart7c3/fix/deleted-list-uuid-in-top-bar

fix(lists): stop deleted lists showing their UUID in the feed filter picker
This commit is contained in:
Vitor Pamplona
2026-08-26 20:48:13 -04:00
committed by GitHub
5 changed files with 99 additions and 1 deletions
@@ -788,6 +788,62 @@ class AccountSettings(
// list names
// ---
/**
* All per-screen persisted feed filters paired with their factory default.
* Deleting a list (NIP-51 people list / follow pack) must reset any screen whose
* filter still points at the deleted address — otherwise the screen keeps a
* dangling [TopFilter.PeopleList] that re-creates an empty AddressableNote shell
* on every start and shows the list's dTag/UUID in the top bar instead of a name.
*/
private val feedFiltersWithDefaults: List<Pair<MutableStateFlow<TopFilter>, TopFilter>> =
listOf(
defaultHomeFollowList to TopFilter.AllFollows,
defaultStoriesFollowList to TopFilter.Global,
defaultNotificationFollowList to TopFilter.Selected,
defaultDiscoveryFollowList to TopFilter.Global,
defaultPollsFollowList to TopFilter.Global,
defaultPicturesFollowList to TopFilter.Global,
defaultNappletsFollowList to TopFilter.Global,
defaultNsitesFollowList to TopFilter.Global,
defaultWorkoutsFollowList to TopFilter.Global,
defaultGitRepositoriesFollowList to TopFilter.Global,
defaultHighlightsFollowList to TopFilter.Global,
defaultCalendarsFollowList to TopFilter.Global,
defaultProductsFollowList to TopFilter.AroundMe,
defaultShortsFollowList to TopFilter.Global,
defaultPublicChatsFollowList to TopFilter.Global,
defaultLiveStreamsFollowList to TopFilter.Global,
defaultNestsFollowList to TopFilter.Global,
defaultLongsFollowList to TopFilter.Global,
defaultArticlesFollowList to TopFilter.AllFollows,
defaultMusicTracksFollowList to TopFilter.Global,
defaultMusicPlaylistsFollowList to TopFilter.Global,
defaultPodcastEpisodesFollowList to TopFilter.Global,
defaultPodcastsFollowList to TopFilter.Global,
defaultSoftwareAppsFollowList to TopFilter.Global,
defaultBadgesFollowList to TopFilter.Mine,
defaultBrowseEmojiSetsFollowList to TopFilter.Global,
defaultCommunitiesFollowList to TopFilter.AllFollows,
defaultFollowPacksFollowList to TopFilter.Global,
defaultAppRecommendationsFollowList to TopFilter.Global,
defaultRelayGroupsDiscoveryFollowList to TopFilter.Mine,
)
/** Resets every persisted feed filter that points at the deleted list's address. */
fun resetFeedFiltersPointingTo(address: Address) {
var changed = false
feedFiltersWithDefaults.forEach { (flow, default) ->
val current = flow.value
if (current is TopFilter.AddressableTopFilter && current.address == address) {
flow.tryEmit(default)
changed = true
}
}
if (changed) saveAccountSettings()
}
fun changeDefaultHomeFollowList(name: FeedDefinition) {
changeDefaultHomeFollowList(name.code)
}
@@ -825,6 +825,12 @@ object LocalCache : ILocalCache, ICacheProvider, Dao {
false
}
/**
* Checks if a kind-5 event from the addressable's own author has deleted this
* address. Works for empty addressable shells whose event is not loaded yet.
*/
fun hasBeenDeleted(address: Address): Boolean = deletionIndex.hasBeenDeleted(address, address.pubKeyHex)
fun getOrAddAliasNote(
idHex: String,
note: Note,
@@ -71,7 +71,15 @@ class FollowListsState(
) {
val user = cache.getOrCreateUser(signer.pubKey)
fun existingPeopleListNotes() = cache.addressables.filter(FollowListEvent.KIND, user.pubkeyHex)
// Hides shells that a kind-5 deletion event from the list's author has already
// deleted (e.g. a persisted TopFilter re-creates an empty shell for the deleted
// address after a restart, and its name falls back to the dTag/UUID). Shells that
// are merely not loaded yet stay in the list so the UI can subscribe and fetch
// them from relays.
fun existingPeopleListNotes() =
cache.addressables
.filter(FollowListEvent.KIND, user.pubkeyHex)
.filter { it.event != null || !cache.hasBeenDeleted(it.address) }
val followListVersions = MutableStateFlow(0)
@@ -255,6 +263,10 @@ class FollowListsState(
val followListEvent = getPeopleList(identifierTag)
val deletionEvent = account.signer.sign(DeletionEvent.build(listOf(followListEvent)))
account.sendMyPublicAndPrivateOutbox(deletionEvent)
// Any screen whose persisted feed filter still points at this follow pack would
// keep re-creating an empty shell for its address (and render the dTag/UUID in
// the top bar) — reset those filters to their default.
account.settings.resetFeedFiltersPointingTo(followListEvent.address())
}
suspend fun addUserToSet(
@@ -69,10 +69,17 @@ class PeopleListsState(
) {
val user = cache.getOrCreateUser(signer.pubKey)
// Hides the fixed-dTag block-list shell when it is not loaded (it has no
// meaningful name until it exists) and shells that a kind-5 deletion event from
// the list's author has already deleted (e.g. a persisted TopFilter re-creates an
// empty shell for the deleted address after a restart, and its name falls back to
// the dTag/UUID). Shells that are merely not loaded yet stay in the list so the UI
// can subscribe and fetch them from relays.
fun existingPeopleListNotes() =
cache.addressables
.filter(PeopleListEvent.KIND, user.pubkeyHex)
.filter { it.dTag() != PeopleListEvent.BLOCK_LIST_D_TAG || it.event != null }
.filter { it.event != null || !cache.hasBeenDeleted(it.address) }
val peopleListVersions = MutableStateFlow(0)
@@ -262,6 +269,10 @@ class PeopleListsState(
val followListEvent = getPeopleList(identifierTag)
val deletionEvent = account.signer.sign(DeletionEvent.build(listOf(followListEvent)))
account.sendMyPublicAndPrivateOutbox(deletionEvent)
// Any screen whose persisted feed filter still points at this list would keep
// re-creating an empty shell for its address (and render the dTag/UUID in the
// top bar) — reset those filters to their default.
account.settings.resetFeedFiltersPointingTo(followListEvent.address())
}
suspend fun addUserToSet(
@@ -20,6 +20,7 @@
*/
package com.vitorpamplona.quartz.nip09Deletions
import com.vitorpamplona.quartz.nip01Core.core.Address
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@@ -115,6 +116,18 @@ class DeletionIndex {
return false
}
/**
* Checks if a kind-5 event signed by [pubKey] deleted the addressable at [address].
* Used when the addressable's event is not loaded yet (e.g. an empty shell
* re-created after a restart), so there is no event id or createdAt to compare
* against. A re-published version would arrive as a loaded event and is checked
* by [hasBeenDeleted] instead.
*/
fun hasBeenDeleted(
address: Address,
pubKey: HexKey,
): Boolean = hasBeenDeleted(DeletionRequest(address.toValue(), pubKey))
private fun hasBeenDeleted(key: DeletionRequest) = deletedReferencesBefore.containsKey(key)
private fun hasBeenDeleted(