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
|
self.active_full_screen_item = item
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.favoriteUpdated)) { _ in
|
.onReceive(handle_notify(.favoriteUpdated)) { _ in
|
||||||
home.resubscribe(.following)
|
home.subscribe_to_favorites()
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.zapping)) { zap_ev in
|
.onReceive(handle_notify(.zapping)) { zap_ev in
|
||||||
guard !zap_ev.is_custom else {
|
guard !zap_ev.is_custom else {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
var should_debounce_dms = true
|
var should_debounce_dms = true
|
||||||
|
|
||||||
var homeHandlerTask: Task<Void, Never>?
|
var homeHandlerTask: Task<Void, Never>?
|
||||||
|
var favoritesHandlerTask: Task<Void, Never>?
|
||||||
var notificationsHandlerTask: Task<Void, Never>?
|
var notificationsHandlerTask: Task<Void, Never>?
|
||||||
var generalHandlerTask: Task<Void, Never>?
|
var generalHandlerTask: Task<Void, Never>?
|
||||||
var dmsHandlerTask: Task<Void, Never>?
|
var dmsHandlerTask: Task<Void, Never>?
|
||||||
@@ -81,6 +82,7 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
var notifications = NotificationsModel()
|
var notifications = NotificationsModel()
|
||||||
var notification_status = NotificationStatusModel()
|
var notification_status = NotificationStatusModel()
|
||||||
var events: EventHolder = EventHolder()
|
var events: EventHolder = EventHolder()
|
||||||
|
var favoriteEvents: EventHolder = EventHolder()
|
||||||
var already_reposted: Set<NoteId> = Set()
|
var already_reposted: Set<NoteId> = Set()
|
||||||
var zap_button: ZapButtonModel = ZapButtonModel()
|
var zap_button: ZapButtonModel = ZapButtonModel()
|
||||||
|
|
||||||
@@ -692,18 +694,6 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
home_filters.append(hashtag_filter)
|
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?.cancel()
|
||||||
self.homeHandlerTask = Task {
|
self.homeHandlerTask = Task {
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
@@ -743,6 +733,42 @@ 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
|
/// Adapter pattern to make migration easier
|
||||||
enum SubscriptionContext {
|
enum SubscriptionContext {
|
||||||
case home
|
case home
|
||||||
|
|||||||
@@ -56,17 +56,17 @@ struct PostingTimelineView: View {
|
|||||||
// If favourites feature is disabled, always use follows
|
// If favourites feature is disabled, always use follows
|
||||||
let sourceToUse = damus_state.settings.enable_favourites_feature ? timeline_source : .follows
|
let sourceToUse = damus_state.settings.enable_favourites_feature ? timeline_source : .follows
|
||||||
|
|
||||||
switch sourceToUse {
|
// Only apply friend_filter for follows timeline
|
||||||
case .follows:
|
// Favorites timeline uses a dedicated EventHolder (favoriteEvents) that already contains only favorited users' events
|
||||||
|
if sourceToUse == .follows {
|
||||||
filters.append(damus_state.contacts.friend_filter)
|
filters.append(damus_state.contacts.friend_filter)
|
||||||
case .favorites:
|
|
||||||
filters.append(damus_state.contactCards.filter)
|
|
||||||
}
|
}
|
||||||
return ContentFilters(filters: filters).filter
|
return ContentFilters(filters: filters).filter
|
||||||
}
|
}
|
||||||
|
|
||||||
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
|
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 {
|
func HeaderView() -> some View {
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ struct TimelineView<Content: View>: View {
|
|||||||
let filter: (NostrEvent) -> Bool
|
let filter: (NostrEvent) -> Bool
|
||||||
let content: Content?
|
let content: Content?
|
||||||
let apply_mute_rules: Bool
|
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.events = events
|
||||||
self._loading = loading
|
self._loading = loading
|
||||||
self._headerHeight = headerHeight
|
self._headerHeight = headerHeight
|
||||||
@@ -31,10 +32,11 @@ struct TimelineView<Content: View>: View {
|
|||||||
self.show_friend_icon = show_friend_icon
|
self.show_friend_icon = show_friend_icon
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
self.apply_mute_rules = apply_mute_rules
|
self.apply_mute_rules = apply_mute_rules
|
||||||
|
self.viewId = viewId
|
||||||
self.content = content?()
|
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.events = events
|
||||||
self._loading = loading
|
self._loading = loading
|
||||||
self._headerHeight = .constant(0.0)
|
self._headerHeight = .constant(0.0)
|
||||||
@@ -43,6 +45,7 @@ struct TimelineView<Content: View>: View {
|
|||||||
self.show_friend_icon = show_friend_icon
|
self.show_friend_icon = show_friend_icon
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
self.apply_mute_rules = apply_mute_rules
|
self.apply_mute_rules = apply_mute_rules
|
||||||
|
self.viewId = viewId
|
||||||
self.content = content?()
|
self.content = content?()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +74,7 @@ struct TimelineView<Content: View>: View {
|
|||||||
.frame(height: 0)
|
.frame(height: 0)
|
||||||
|
|
||||||
InnerTimelineView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules)
|
InnerTimelineView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules)
|
||||||
|
.id(viewId)
|
||||||
.redacted(reason: loading ? .placeholder : [])
|
.redacted(reason: loading ? .placeholder : [])
|
||||||
.shimmer(loading)
|
.shimmer(loading)
|
||||||
.disabled(loading)
|
.disabled(loading)
|
||||||
|
|||||||
Reference in New Issue
Block a user