Fix favorites timeline not showing events when switching
The favorites timeline was empty because: 1. The @StateObject filter in InnerTimelineView was captured once at init 2. Favorite events were mixed with follows events and got drowned out Fixed by: - Adding viewId parameter to TimelineView to force view recreation on switch - Creating separate favoriteEvents EventHolder for favorites - Adding dedicated subscribe_to_favorites() subscription that inserts directly into favoriteEvents when contact cards are loaded
This commit is contained in:
committed by
Daniel D’Aquino
parent
9a1ae6f9b5
commit
434c54f98e
@@ -461,7 +461,7 @@ struct ContentView: View {
|
||||
self.active_full_screen_item = item
|
||||
}
|
||||
.onReceive(handle_notify(.favoriteUpdated)) { _ in
|
||||
home.resubscribe(.following)
|
||||
home.subscribe_to_favorites()
|
||||
}
|
||||
.onReceive(handle_notify(.zapping)) { zap_ev in
|
||||
guard !zap_ev.is_custom else {
|
||||
|
||||
@@ -68,6 +68,7 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
||||
var should_debounce_dms = true
|
||||
|
||||
var homeHandlerTask: Task<Void, Never>?
|
||||
var favoritesHandlerTask: Task<Void, Never>?
|
||||
var notificationsHandlerTask: Task<Void, Never>?
|
||||
var generalHandlerTask: Task<Void, Never>?
|
||||
var dmsHandlerTask: Task<Void, Never>?
|
||||
@@ -81,6 +82,7 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
||||
var notifications = NotificationsModel()
|
||||
var notification_status = NotificationStatusModel()
|
||||
var events: EventHolder = EventHolder()
|
||||
var favoriteEvents: EventHolder = EventHolder()
|
||||
var already_reposted: Set<NoteId> = Set()
|
||||
var zap_button: ZapButtonModel = ZapButtonModel()
|
||||
|
||||
@@ -692,18 +694,6 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
||||
home_filters.append(hashtag_filter)
|
||||
}
|
||||
|
||||
// Add filter for favorited users who we dont follow
|
||||
if damus_state.settings.enable_favourites_feature {
|
||||
let all_favorites = damus_state.contactCards.favorites
|
||||
let favorited_not_followed = Array(all_favorites.subtracting(Set(friends)))
|
||||
if !favorited_not_followed.isEmpty {
|
||||
var favorites_filter = NostrFilter(kinds: home_filter_kinds)
|
||||
favorites_filter.authors = favorited_not_followed
|
||||
favorites_filter.limit = 500
|
||||
home_filters.append(favorites_filter)
|
||||
}
|
||||
}
|
||||
|
||||
self.homeHandlerTask?.cancel()
|
||||
self.homeHandlerTask = Task {
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
@@ -742,7 +732,43 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Subscribe to favorites - called when contact cards are loaded or favorites change
|
||||
func subscribe_to_favorites() {
|
||||
guard damus_state.settings.enable_favourites_feature else { return }
|
||||
|
||||
let all_favorites = Array(damus_state.contactCards.favorites)
|
||||
guard !all_favorites.isEmpty else { return }
|
||||
|
||||
var home_filter_kinds: [NostrKind] = [.text, .longform, .boost, .highlight]
|
||||
if !damus_state.settings.onlyzaps_mode {
|
||||
home_filter_kinds.append(.like)
|
||||
}
|
||||
|
||||
var favorites_filter = NostrFilter(kinds: home_filter_kinds)
|
||||
favorites_filter.authors = all_favorites
|
||||
favorites_filter.limit = 500
|
||||
|
||||
self.favoritesHandlerTask?.cancel()
|
||||
self.favoritesHandlerTask = Task {
|
||||
for await item in damus_state.nostrNetwork.reader.advancedStream(filters: [favorites_filter], streamMode: .ndbAndNetworkParallel(networkOptimization: .sinceOptimization)) {
|
||||
switch item {
|
||||
case .event(let lender):
|
||||
await lender.justUseACopy({ await self.insert_favorite_event($0) })
|
||||
case .eose, .ndbEose, .networkEose:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func insert_favorite_event(_ ev: NostrEvent) {
|
||||
guard should_show_event(state: damus_state, ev: ev) else { return }
|
||||
damus_state.events.insert(ev)
|
||||
favoriteEvents.insert(ev)
|
||||
}
|
||||
|
||||
/// Adapter pattern to make migration easier
|
||||
enum SubscriptionContext {
|
||||
case home
|
||||
|
||||
@@ -52,21 +52,21 @@ struct PostingTimelineView: View {
|
||||
func content_filter(_ fstate: FilterState) -> ((NostrEvent) -> Bool) {
|
||||
var filters = ContentFilters.defaults(damus_state: damus_state)
|
||||
filters.append(fstate.filter)
|
||||
|
||||
|
||||
// If favourites feature is disabled, always use follows
|
||||
let sourceToUse = damus_state.settings.enable_favourites_feature ? timeline_source : .follows
|
||||
|
||||
switch sourceToUse {
|
||||
case .follows:
|
||||
|
||||
// Only apply friend_filter for follows timeline
|
||||
// Favorites timeline uses a dedicated EventHolder (favoriteEvents) that already contains only favorited users' events
|
||||
if sourceToUse == .follows {
|
||||
filters.append(damus_state.contacts.friend_filter)
|
||||
case .favorites:
|
||||
filters.append(damus_state.contactCards.filter)
|
||||
}
|
||||
return ContentFilters(filters: filters).filter
|
||||
}
|
||||
|
||||
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
|
||||
TimelineView<AnyView>(events: home.events, loading: self.loading, headerHeight: $headerHeight, headerOffset: $headerOffset, damus: damus_state, show_friend_icon: false, filter: filter)
|
||||
let eventsSource = timeline_source == .favorites ? home.favoriteEvents : home.events
|
||||
return TimelineView<AnyView>(events: eventsSource, loading: self.loading, headerHeight: $headerHeight, headerOffset: $headerOffset, damus: damus_state, show_friend_icon: false, filter: filter, viewId: timeline_source)
|
||||
}
|
||||
|
||||
func HeaderView() -> some View {
|
||||
|
||||
@@ -21,8 +21,9 @@ struct TimelineView<Content: View>: View {
|
||||
let filter: (NostrEvent) -> Bool
|
||||
let content: Content?
|
||||
let apply_mute_rules: Bool
|
||||
let viewId: AnyHashable?
|
||||
|
||||
init(events: EventHolder, loading: Binding<Bool>, headerHeight: Binding<CGFloat>, headerOffset: Binding<CGFloat>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) {
|
||||
init(events: EventHolder, loading: Binding<Bool>, headerHeight: Binding<CGFloat>, headerOffset: Binding<CGFloat>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, viewId: AnyHashable? = nil, content: (() -> Content)? = nil) {
|
||||
self.events = events
|
||||
self._loading = loading
|
||||
self._headerHeight = headerHeight
|
||||
@@ -31,10 +32,11 @@ struct TimelineView<Content: View>: View {
|
||||
self.show_friend_icon = show_friend_icon
|
||||
self.filter = filter
|
||||
self.apply_mute_rules = apply_mute_rules
|
||||
self.viewId = viewId
|
||||
self.content = content?()
|
||||
}
|
||||
|
||||
init(events: EventHolder, loading: Binding<Bool>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) {
|
||||
init(events: EventHolder, loading: Binding<Bool>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, viewId: AnyHashable? = nil, content: (() -> Content)? = nil) {
|
||||
self.events = events
|
||||
self._loading = loading
|
||||
self._headerHeight = .constant(0.0)
|
||||
@@ -43,6 +45,7 @@ struct TimelineView<Content: View>: View {
|
||||
self.show_friend_icon = show_friend_icon
|
||||
self.filter = filter
|
||||
self.apply_mute_rules = apply_mute_rules
|
||||
self.viewId = viewId
|
||||
self.content = content?()
|
||||
}
|
||||
|
||||
@@ -71,6 +74,7 @@ struct TimelineView<Content: View>: View {
|
||||
.frame(height: 0)
|
||||
|
||||
InnerTimelineView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules)
|
||||
.id(viewId)
|
||||
.redacted(reason: loading ? .placeholder : [])
|
||||
.shimmer(loading)
|
||||
.disabled(loading)
|
||||
|
||||
Reference in New Issue
Block a user