From e5c82ec64b5dcf3fc408c966f238863fae9741c5 Mon Sep 17 00:00:00 2001 From: Terry Yiu Date: Sun, 17 Aug 2025 17:36:15 -0700 Subject: [PATCH] WIP pinned notes --- damus.xcodeproj/project.pbxproj | 24 ++++++ damus/Models/HomeModel.swift | 2 + damus/Models/ProfileModel.swift | 75 +++++++++++++++---- damus/Nostr/NostrKind.swift | 1 + damus/Views/BookmarksView.swift | 2 +- damus/Views/EventView.swift | 8 +- .../Events/FollowPack/FollowPackView.swift | 2 +- .../Views/Events/Pinned/PinnedEventView.swift | 28 +++++++ .../Events/Pinned/PinnedHeaderView.swift | 33 ++++++++ damus/Views/LoadableNostrEventView.swift | 2 +- damus/Views/Profile/ProfileView.swift | 6 +- damus/Views/Timeline/InnerTimelineView.swift | 10 ++- damus/Views/TimelineView.swift | 2 +- 13 files changed, 168 insertions(+), 27 deletions(-) create mode 100644 damus/Views/Events/Pinned/PinnedEventView.swift create mode 100644 damus/Views/Events/Pinned/PinnedHeaderView.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 259fdf9b..b8bae60e 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -14,6 +14,12 @@ 31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; }; 3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 3A0A30BA2C21397A00F8C9BC /* EmojiPicker */; }; 3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */; }; + 3A28D3A12E2F3FB5003C6F82 /* PinnedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A28D3A02E2F3FB5003C6F82 /* PinnedEventView.swift */; }; + 3A28D3A22E2F3FB5003C6F82 /* PinnedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A28D3A02E2F3FB5003C6F82 /* PinnedEventView.swift */; }; + 3A28D3A32E2F3FB5003C6F82 /* PinnedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A28D3A02E2F3FB5003C6F82 /* PinnedEventView.swift */; }; + 3A28D3A62E2F4082003C6F82 /* PinnedHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A28D3A52E2F4082003C6F82 /* PinnedHeaderView.swift */; }; + 3A28D3A72E2F4082003C6F82 /* PinnedHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A28D3A52E2F4082003C6F82 /* PinnedHeaderView.swift */; }; + 3A28D3A82E2F4082003C6F82 /* PinnedHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A28D3A52E2F4082003C6F82 /* PinnedHeaderView.swift */; }; 3A2BAC5A2DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BAC592DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift */; }; 3A2BAC5B2DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BAC592DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift */; }; 3A2BAC5C2DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BAC592DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift */; }; @@ -1869,6 +1875,8 @@ 3A25EF132992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/InfoPlist.strings"; sourceTree = ""; }; 3A25EF142992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = ""; }; 3A25EF152992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "el-GR"; path = "el-GR.lproj/Localizable.stringsdict"; sourceTree = ""; }; + 3A28D3A02E2F3FB5003C6F82 /* PinnedEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventView.swift; sourceTree = ""; }; + 3A28D3A52E2F4082003C6F82 /* PinnedHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedHeaderView.swift; sourceTree = ""; }; 3A2B8B0A296A8982009CC16D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-US"; path = "en-US.lproj/Localizable.stringsdict"; sourceTree = ""; }; 3A2BAC592DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05DomainTimelineHeaderView.swift; sourceTree = ""; }; 3A2BAC5D2DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05DomainPubkeysView.swift; sourceTree = ""; }; @@ -2784,6 +2792,15 @@ path = "Empty Views"; sourceTree = ""; }; + 3A28D3A42E2F4053003C6F82 /* Pinned */ = { + isa = PBXGroup; + children = ( + 3A28D3A02E2F3FB5003C6F82 /* PinnedEventView.swift */, + 3A28D3A52E2F4082003C6F82 /* PinnedHeaderView.swift */, + ); + path = Pinned; + sourceTree = ""; + }; 3A515C4E2DF4E0E6002D3B34 /* Tips */ = { isa = PBXGroup; children = ( @@ -3685,6 +3702,7 @@ 4CC7AAEE297F11B300430951 /* Events */ = { isa = PBXGroup; children = ( + 3A28D3A42E2F4053003C6F82 /* Pinned */, 5C4FA7FA2DC29C3800CE658C /* FollowPack */, 5CC852A02BDED9970039FFC5 /* Highlight */, 4CA927682A290F8F0098A105 /* Components */, @@ -4698,6 +4716,7 @@ 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */, D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */, 504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */, + 3A28D3A32E2F3FB5003C6F82 /* PinnedEventView.swift in Sources */, 5C4D9EA72C042FA5005EA0F7 /* HighlightDraftContentView.swift in Sources */, 3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */, D7FD12262BD345A700CF195B /* FirstAidSettingsView.swift in Sources */, @@ -5002,6 +5021,7 @@ 4CC14FF92A741939007AEB17 /* Referenced.swift in Sources */, 4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */, 4CE1399429F0669900AC6A0B /* BigButton.swift in Sources */, + 3A28D3A62E2F4082003C6F82 /* PinnedHeaderView.swift in Sources */, D7EFBA372CC322F300F45588 /* DamusVideoControlsView.swift in Sources */, 7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */, D706C5AF2D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */, @@ -5332,6 +5352,7 @@ 82D6FB012CD99F7900C925F4 /* Block.swift in Sources */, 82D6FB022CD99F7900C925F4 /* MigratedTypes.swift in Sources */, 82D6FB032CD99F7900C925F4 /* DamusDuration.swift in Sources */, + 3A28D3A22E2F3FB5003C6F82 /* PinnedEventView.swift in Sources */, 82D6FB042CD99F7900C925F4 /* SwipeToDismiss.swift in Sources */, 82D6FB052CD99F7900C925F4 /* MusicController.swift in Sources */, 82D6FB062CD99F7900C925F4 /* UserStatusView.swift in Sources */, @@ -5490,6 +5511,7 @@ D74EA08E2D2E271E002290DD /* ErrorView.swift in Sources */, 82D6FB952CD99F7900C925F4 /* TranslationService.swift in Sources */, 82D6FB962CD99F7900C925F4 /* DeepLPlan.swift in Sources */, + 3A28D3A72E2F4082003C6F82 /* PinnedHeaderView.swift in Sources */, 82D6FB972CD99F7900C925F4 /* ZapsModel.swift in Sources */, 82D6FB982CD99F7900C925F4 /* DraftsModel.swift in Sources */, 82D6FB992CD99F7900C925F4 /* NotificationsModel.swift in Sources */, @@ -5754,6 +5776,7 @@ D73E5F8B2C6AA6A2007EB227 /* UserStatusSheet.swift in Sources */, D73E5E282C6A97F4007EB227 /* LoginNotify.swift in Sources */, D73E5E292C6A97F4007EB227 /* LogoutNotify.swift in Sources */, + 3A28D3A12E2F3FB5003C6F82 /* PinnedEventView.swift in Sources */, D73E5E2A2C6A97F4007EB227 /* OnlyZapsNotify.swift in Sources */, D73E5E2B2C6A97F4007EB227 /* PostNotify.swift in Sources */, D73E5E2C2C6A97F4007EB227 /* PresentSheetNotify.swift in Sources */, @@ -5776,6 +5799,7 @@ D73E5E3A2C6A97F4007EB227 /* SwipeToDismiss.swift in Sources */, D73E5E3B2C6A97F4007EB227 /* MusicController.swift in Sources */, D73E5E3C2C6A97F4007EB227 /* UserStatusView.swift in Sources */, + 3A28D3A82E2F4082003C6F82 /* PinnedHeaderView.swift in Sources */, D74EA08F2D2E271E002290DD /* ErrorView.swift in Sources */, D73E5E3E2C6A97F4007EB227 /* SearchHeaderView.swift in Sources */, D73E5E3F2C6A97F4007EB227 /* DamusGradient.swift in Sources */, diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift index d9812c60..49cbdd86 100644 --- a/damus/Models/HomeModel.swift +++ b/damus/Models/HomeModel.swift @@ -231,6 +231,8 @@ class HomeModel: ContactsDelegate { break case .interest_list: break // Don't care for now + case .pinned_notes: + break // FIXME(tyiu) } } diff --git a/damus/Models/ProfileModel.swift b/damus/Models/ProfileModel.swift index a5ca2ee4..9c5b1d00 100644 --- a/damus/Models/ProfileModel.swift +++ b/damus/Models/ProfileModel.swift @@ -22,8 +22,17 @@ class ProfileModel: ObservableObject, Equatable { } return nil } - + + @Published var pinned_notes_list: NostrEvent? = nil + var pinned_note_ids: Set { + if let pinned_notes_list { + return Set(pinned_notes_list.referenced_noterefs.map { $0.note_id }) + } + return [] + } + var events: EventHolder + var pinned_events: EventHolder let pubkey: Pubkey let damus: DamusState @@ -32,6 +41,7 @@ class ProfileModel: ObservableObject, Equatable { var prof_subid = UUID().description var conversations_subid = UUID().description var findRelay_subid = UUID().description + var pinned_subid = UUID().description var conversation_events: Set = Set() init(pubkey: Pubkey, damus: DamusState) { @@ -40,6 +50,9 @@ class ProfileModel: ObservableObject, Equatable { self.events = EventHolder(on_queue: { ev in preload_events(state: damus, events: [ev]) }) + self.pinned_events = EventHolder(on_queue: { ev in + preload_events(state: damus, events: [ev]) + }) } func follows(pubkey: Pubkey) -> Bool { @@ -74,20 +87,18 @@ class ProfileModel: ObservableObject, Equatable { } } - func subscribe() { - var text_filter = NostrFilter(kinds: [.text, .longform, .highlight]) - var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost]) - var relay_list_filter = NostrFilter(kinds: [.relay_list], authors: [pubkey]) + let textKinds: [NostrKind] = [.text, .longform, .highlight] - profile_filter.authors = [pubkey] - - text_filter.authors = [pubkey] - text_filter.limit = 500 + func subscribe() { + let text_filter = NostrFilter(kinds: textKinds, limit: 500, authors: [pubkey]) + let profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost], authors: [pubkey]) + let relay_list_filter = NostrFilter(kinds: [.relay_list], authors: [pubkey]) + let pinned_notes_filter = NostrFilter(kinds: [.pinned_notes], authors: [pubkey]) print("subscribing to textlike events from profile \(pubkey) with sub_id \(sub_id)") //print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]]) damus.nostrNetwork.pool.subscribe(sub_id: sub_id, filters: [text_filter], handler: handle_event) - damus.nostrNetwork.pool.subscribe(sub_id: prof_subid, filters: [profile_filter, relay_list_filter], handler: handle_event) + damus.nostrNetwork.pool.subscribe(sub_id: prof_subid, filters: [profile_filter, relay_list_filter, pinned_notes_filter], handler: handle_event) subscribe_to_conversations() } @@ -98,7 +109,7 @@ class ProfileModel: ObservableObject, Equatable { return } - let conversation_kinds: [NostrKind] = [.text, .longform, .highlight] + let conversation_kinds: [NostrKind] = textKinds let limit: UInt32 = 500 let conversations_filter_them = NostrFilter(kinds: conversation_kinds, pubkeys: [damus.pubkey], limit: limit, authors: [pubkey]) let conversations_filter_us = NostrFilter(kinds: conversation_kinds, pubkeys: [pubkey], limit: limit, authors: [damus.pubkey]) @@ -106,6 +117,15 @@ class ProfileModel: ObservableObject, Equatable { damus.nostrNetwork.pool.subscribe(sub_id: conversations_subid, filters: [conversations_filter_them, conversations_filter_us], handler: handle_event) } + private func subscribe_to_pinned_notes() { + guard let pinned_notes_list, pinned_notes_list.referenced_noterefs.first != nil else { + return + } + + let pinned_filter = NostrFilter(ids: Array(pinned_note_ids), kinds: [.text], authors: [pubkey]) + damus.nostrNetwork.pool.subscribe(sub_id: pinned_subid, filters: [pinned_filter], handler: handle_event) + } + func handle_profile_contact_event(_ ev: NostrEvent) { process_contact_event(state: damus, ev: ev) @@ -126,11 +146,24 @@ class ProfileModel: ObservableObject, Equatable { if self.events.insert(ev) { self.objectWillChange.send() } + if pinned_note_ids.contains(ev.id) && self.pinned_events.insert(ev) { + self.objectWillChange.send() + } } else if ev.known_kind == .contacts { handle_profile_contact_event(ev) - } - else if ev.known_kind == .relay_list { + } else if ev.known_kind == .relay_list { self.relay_list = try? NIP65.RelayList(event: ev) // Whether another user's list is malformatted is something beyond our control. Probably best to suppress errors + } else if ev.known_kind == .pinned_notes { + if let current_ev = self.pinned_notes_list { + guard ev.created_at > current_ev.created_at else { + return + } + pinned_events.incoming.removeAll() + pinned_events.events.removeAll() + } + + self.pinned_notes_list = ev + subscribe_to_pinned_notes() } seen_event.insert(ev.id) } @@ -148,6 +181,8 @@ class ProfileModel: ObservableObject, Equatable { default: return false } + } else if sub_id == self.pinned_subid { + return self.pubkey == ev.pubkey && pinned_note_ids.contains(ev.id) } return self.pubkey == ev.pubkey @@ -158,7 +193,7 @@ class ProfileModel: ObservableObject, Equatable { case .ws_event: return case .nostr_event(let resp): - guard resp.subid == self.sub_id || resp.subid == self.prof_subid || resp.subid == self.conversations_subid else { + guard [self.sub_id, self.prof_subid, self.conversations_subid, self.pinned_subid].contains(resp.subid) else { return } switch resp { @@ -179,12 +214,24 @@ class ProfileModel: ObservableObject, Equatable { if resp.subid == self.conversations_subid { conversation_events.insert(ev.id) } + + if resp.subid == self.pinned_subid, self.pinned_events.insert(ev) { + self.objectWillChange.send() + } } else if resp.subid == self.conversations_subid && !conversation_events.contains(ev.id) { guard relay_filtered_correctly(ev, subid: resp.subid) else { break } conversation_events.insert(ev.id) + } else if resp.subid == self.pinned_subid { + guard relay_filtered_correctly(ev, subid: resp.subid) else { + break + } + + if resp.subid == self.pinned_subid, self.pinned_events.insert(ev) { + self.objectWillChange.send() + } } case .notice: break diff --git a/damus/Nostr/NostrKind.swift b/damus/Nostr/NostrKind.swift index 7634aeff..c52283b6 100644 --- a/damus/Nostr/NostrKind.swift +++ b/damus/Nostr/NostrKind.swift @@ -19,6 +19,7 @@ enum NostrKind: UInt32, Codable { case like = 7 case chat = 42 case mute_list = 10000 + case pinned_notes = 10001 case relay_list = 10002 case interest_list = 10015 case list_deprecated = 30000 diff --git a/damus/Views/BookmarksView.swift b/damus/Views/BookmarksView.swift index 2b196b91..c3c20225 100644 --- a/damus/Views/BookmarksView.swift +++ b/damus/Views/BookmarksView.swift @@ -37,7 +37,7 @@ struct BookmarksView: View { } } else { ScrollView { - InnerTimelineView(events: EventHolder(events: bookmarks, incoming: []), damus: state, filter: noneFilter) + InnerTimelineView(events: EventHolder(events: bookmarks, incoming: []), pinned_events: EventHolder(), damus: state, filter: noneFilter) } .padding(.bottom, 10 + tabHeight + getSafeAreaBottom()) } diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift index cb4d8727..099a5d31 100644 --- a/damus/Views/EventView.swift +++ b/damus/Views/EventView.swift @@ -21,17 +21,21 @@ struct EventView: View { let options: EventViewOptions let damus: DamusState let pubkey: Pubkey + let pinned: Set - init(damus: DamusState, event: NostrEvent, pubkey: Pubkey? = nil, options: EventViewOptions = []) { + init(damus: DamusState, event: NostrEvent, pubkey: Pubkey? = nil, pinned: Set = [], options: EventViewOptions = []) { self.event = event self.options = options self.damus = damus self.pubkey = pubkey ?? event.pubkey + self.pinned = pinned } var body: some View { VStack { - if event.known_kind == .boost { + if pinned.contains(event.id) { + PinnedEventView(damus: damus, event: event, options: options) + } else if event.known_kind == .boost { if let inner_ev = event.get_inner_event(cache: damus.events) { RepostedEvent(damus: damus, event: event, inner_ev: inner_ev, options: options) } else { diff --git a/damus/Views/Events/FollowPack/FollowPackView.swift b/damus/Views/Events/FollowPack/FollowPackView.swift index 301dd30d..324887a9 100644 --- a/damus/Views/Events/FollowPack/FollowPackView.swift +++ b/damus/Views/Events/FollowPack/FollowPackView.swift @@ -98,7 +98,7 @@ struct FollowPackView: View { } if tab_selection == FollowPackTabSelection.posts { - InnerTimelineView(events: model.events, damus: state, filter: content_filter(event.publicKeys)) + InnerTimelineView(events: model.events, pinned_events: EventHolder(), damus: state, filter: content_filter(event.publicKeys)) } } .onAppear() { diff --git a/damus/Views/Events/Pinned/PinnedEventView.swift b/damus/Views/Events/Pinned/PinnedEventView.swift new file mode 100644 index 00000000..6c5c7faf --- /dev/null +++ b/damus/Views/Events/Pinned/PinnedEventView.swift @@ -0,0 +1,28 @@ +// +// PinnedEventView.swift +// damus +// +// Created by Terry Yiu on 7/21/25. +// + +import SwiftUI + +struct PinnedEventView: View { + let damus: DamusState + let event: NostrEvent + let options: EventViewOptions + + var body: some View { + VStack(alignment: .leading) { + PinnedHeaderView(damus: damus, pubkey: event.pubkey) + .padding(.horizontal) + .buttonStyle(PlainButtonStyle()) + + TextEvent(damus: damus, event: event, pubkey: event.pubkey, options: options) + } + } +} + +#Preview { + PinnedEventView(damus: test_damus_state, event: test_note, options: []) +} diff --git a/damus/Views/Events/Pinned/PinnedHeaderView.swift b/damus/Views/Events/Pinned/PinnedHeaderView.swift new file mode 100644 index 00000000..4b745b8a --- /dev/null +++ b/damus/Views/Events/Pinned/PinnedHeaderView.swift @@ -0,0 +1,33 @@ +// +// PinnedHeaderView.swift +// damus +// +// Created by Terry Yiu on 7/21/25. +// + +import SwiftUI + +struct PinnedHeaderView: View { + let damus: DamusState + let pubkey: Pubkey + + init(damus: DamusState, pubkey: Pubkey) { + self.damus = damus + self.pubkey = pubkey + } + + var body: some View { + HStack(alignment: .center) { + Image("pin") + .foregroundColor(Color.gray) + + Text("Pinned", comment: "FIXME") + .font(.subheadline) + .foregroundColor(.gray) + } + } +} + +#Preview { + PinnedHeaderView(damus: test_damus_state, pubkey: test_pubkey) +} diff --git a/damus/Views/LoadableNostrEventView.swift b/damus/Views/LoadableNostrEventView.swift index 6d4b4ed3..ccd3aea4 100644 --- a/damus/Views/LoadableNostrEventView.swift +++ b/damus/Views/LoadableNostrEventView.swift @@ -74,7 +74,7 @@ class LoadableNostrEventViewModel: ObservableObject { case .zap, .zap_request: guard let zap = await get_zap(from: ev, state: damus_state) else { return .not_found } return .loaded(route: Route.Zaps(target: zap.target)) - case .contacts, .metadata, .delete, .boost, .chat, .mute_list, .list_deprecated, .draft, .longform, .nwc_request, .nwc_response, .http_auth, .status, .relay_list, .follow_list, .interest_list: + case .contacts, .metadata, .delete, .boost, .chat, .mute_list, .list_deprecated, .draft, .longform, .nwc_request, .nwc_response, .http_auth, .status, .relay_list, .follow_list, .interest_list, .pinned_notes: return .unknown_or_unsupported_kind } case .naddr(let naddr): diff --git a/damus/Views/Profile/ProfileView.swift b/damus/Views/Profile/ProfileView.swift index 2c6f60d0..7fe74761 100644 --- a/damus/Views/Profile/ProfileView.swift +++ b/damus/Views/Profile/ProfileView.swift @@ -464,13 +464,13 @@ struct ProfileView: View { .background(colorScheme == .dark ? Color.black : Color.white) if filter_state == FilterState.posts { - InnerTimelineView(events: profile.events, damus: damus_state, filter: content_filter(FilterState.posts)) + InnerTimelineView(events: profile.events, pinned_events: profile.pinned_events, damus: damus_state, filter: content_filter(FilterState.posts)) } if filter_state == FilterState.posts_and_replies { - InnerTimelineView(events: profile.events, damus: damus_state, filter: content_filter(FilterState.posts_and_replies)) + InnerTimelineView(events: profile.events, pinned_events: profile.pinned_events, damus: damus_state, filter: content_filter(FilterState.posts_and_replies)) } if filter_state == FilterState.conversations && !profile.conversation_events.isEmpty { - InnerTimelineView(events: profile.events, damus: damus_state, filter: content_filter(FilterState.conversations)) + InnerTimelineView(events: profile.events, pinned_events: EventHolder(), damus: damus_state, filter: content_filter(FilterState.conversations)) } } .padding(.horizontal, Theme.safeAreaInsets?.left) diff --git a/damus/Views/Timeline/InnerTimelineView.swift b/damus/Views/Timeline/InnerTimelineView.swift index 0e572f8f..fb541c9d 100644 --- a/damus/Views/Timeline/InnerTimelineView.swift +++ b/damus/Views/Timeline/InnerTimelineView.swift @@ -10,11 +10,13 @@ import SwiftUI struct InnerTimelineView: View { @ObservedObject var events: EventHolder + @ObservedObject var pinned_events: EventHolder let state: DamusState let filter: (NostrEvent) -> Bool - init(events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true) { + init(events: EventHolder, pinned_events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true) { self.events = events + self.pinned_events = pinned_events self.state = damus self.filter = apply_mute_rules ? { filter($0) && !damus.mutelist_manager.is_event_muted($0) } : filter } @@ -29,7 +31,7 @@ struct InnerTimelineView: View { var body: some View { LazyVStack(spacing: 0) { - let events = self.events.events + let events = self.pinned_events.events + self.events.events if events.isEmpty { EmptyTimelineView() } else { @@ -38,7 +40,7 @@ struct InnerTimelineView: View { ForEach(indexed, id: \.0.id) { tup in let ev = tup.0 let ind = tup.1 - EventView(damus: state, event: ev, options: event_options) + EventView(damus: state, event: ev, pinned: Set(self.pinned_events.events.map { $0.id }), options: event_options) .onTapGesture { let event = ev.get_inner_event(cache: state.events) ?? ev let thread = ThreadModel(event: event, damus_state: state) @@ -69,7 +71,7 @@ struct InnerTimelineView: View { struct InnerTimelineView_Previews: PreviewProvider { static var previews: some View { - InnerTimelineView(events: test_event_holder, damus: test_damus_state, filter: { _ in true }) + InnerTimelineView(events: test_event_holder, pinned_events: EventHolder(), damus: test_damus_state, filter: { _ in true }) .frame(width: 300, height: 500) .border(Color.red) } diff --git a/damus/Views/TimelineView.swift b/damus/Views/TimelineView.swift index 8edff654..f1fcbdb7 100644 --- a/damus/Views/TimelineView.swift +++ b/damus/Views/TimelineView.swift @@ -61,7 +61,7 @@ struct TimelineView: View { .id("startblock") .frame(height: 0) - InnerTimelineView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules) + InnerTimelineView(events: events, pinned_events: EventHolder(), damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules) .redacted(reason: loading ? .placeholder : []) .shimmer(loading) .disabled(loading)