diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index af93ba92..daa93927 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -253,6 +253,7 @@ 4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276E2A2A5D110098A105 /* wasm.c */; }; 4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */; }; 4C9147002A2A891E00DDEA40 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C9146FF2A2A891E00DDEA40 /* error.c */; }; + 4C94D6432BA5AEFE00C26EFF /* QuoteRepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C94D6422BA5AEFE00C26EFF /* QuoteRepostsView.swift */; }; 4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; }; 4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */; }; 4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.swift */; }; @@ -1161,6 +1162,7 @@ 4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = ""; }; 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = ""; }; 4C9146FF2A2A891E00DDEA40 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = error.c; sourceTree = ""; }; + 4C94D6422BA5AEFE00C26EFF /* QuoteRepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteRepostsView.swift; sourceTree = ""; }; 4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = ""; }; 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusModel.swift; sourceTree = ""; }; 4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttrStringTestExtensions.swift; sourceTree = ""; }; @@ -1506,6 +1508,7 @@ children = ( 3AA24801297E3DC20090C62D /* RepostView.swift */, 4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */, + 4C94D6422BA5AEFE00C26EFF /* QuoteRepostsView.swift */, ); path = Reposts; sourceTree = ""; @@ -2735,14 +2738,6 @@ path = DamusNotificationService; sourceTree = ""; }; - E06336A72B7582D600A88E6B /* Assets */ = { - isa = PBXGroup; - children = ( - E06336A82B7582E000A88E6B /* img_with_location.jpeg */, - ); - path = Assets; - sourceTree = ""; - }; D7CBD1D22B8D21C100BFD889 /* Extensions */ = { isa = PBXGroup; children = ( @@ -2751,6 +2746,14 @@ path = Extensions; sourceTree = ""; }; + E06336A72B7582D600A88E6B /* Assets */ = { + isa = PBXGroup; + children = ( + E06336A82B7582E000A88E6B /* img_with_location.jpeg */, + ); + path = Assets; + sourceTree = ""; + }; F71694E82A66221E001F4053 /* Onboarding */ = { isa = PBXGroup; children = ( @@ -3248,6 +3251,7 @@ 4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */, 4C06670128FC7C5900038D2A /* RelayView.swift in Sources */, 4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */, + 4C94D6432BA5AEFE00C26EFF /* QuoteRepostsView.swift in Sources */, D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */, 4CA352AE2A76C1AC003BB08B /* FollowedNotify.swift in Sources */, 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */, diff --git a/damus/Models/EventsModel.swift b/damus/Models/EventsModel.swift index 74ee5b12..5e263c3f 100644 --- a/damus/Models/EventsModel.swift +++ b/damus/Models/EventsModel.swift @@ -7,7 +7,6 @@ import Foundation - class EventsModel: ObservableObject { let state: DamusState let target: NoteId diff --git a/damus/Models/ThreadModel.swift b/damus/Models/ThreadModel.swift index dbc86929..a02a299e 100644 --- a/damus/Models/ThreadModel.swift +++ b/damus/Models/ThreadModel.swift @@ -56,6 +56,7 @@ class ThreadModel: ObservableObject { func subscribe() { var meta_events = NostrFilter() + var quote_events = NostrFilter() var event_filter = NostrFilter() var ref_events = NostrFilter() @@ -74,11 +75,14 @@ class ThreadModel: ObservableObject { kinds.append(.like) } meta_events.kinds = kinds - meta_events.limit = 1000 - + + quote_events.kinds = [.text] + quote_events.quotes = [event.id] + quote_events.limit = 1000 + let base_filters = [event_filter, ref_events] - let meta_filters = [meta_events] + let meta_filters = [meta_events, quote_events] print("subscribing to thread \(event.id) with sub_id \(base_subid)") damus_state.pool.subscribe(sub_id: base_subid, filters: base_filters, handler: handle_event) @@ -90,7 +94,7 @@ class ThreadModel: ObservableObject { return } - let the_ev = damus_state.events.upsert(ev) + damus_state.events.upsert(ev) damus_state.replies.count_replies(ev, keypair: keypair) damus_state.events.add_replies(ev: ev, keypair: keypair) @@ -111,7 +115,13 @@ class ThreadModel: ObservableObject { } } else if ev.is_textlike { - self.add_event(ev, keypair: damus_state.keypair) + // handle thread quote reposts, we just count them instead of + // adding them to the thread + if let target = ev.is_quote_repost, target == self.event.id { + //let _ = self.damus_state.quote_reposts.add_event(ev, target: target) + } else { + self.add_event(ev, keypair: damus_state.keypair) + } } } diff --git a/damus/Nostr/Id.swift b/damus/Nostr/Id.swift index 101faddd..5d8864bd 100644 --- a/damus/Nostr/Id.swift +++ b/damus/Nostr/Id.swift @@ -41,7 +41,7 @@ struct QuoteId: IdType, TagKey, TagConvertible { self.id = data } - /// Refer to this QuoteId as a NoteId + /// The note id being quoted var note_id: NoteId { NoteId(self.id) } diff --git a/damus/Util/Router.swift b/damus/Util/Router.swift index b2899e93..3bf55eff 100644 --- a/damus/Util/Router.swift +++ b/damus/Util/Router.swift @@ -32,6 +32,7 @@ enum Route: Hashable { case DeveloperSettings(settings: UserSettingsStore) case Thread(thread: ThreadModel) case Reposts(reposts: EventsModel) + case QuoteReposts(quotes: EventsModel) case Reactions(reactions: EventsModel) case Zaps(target: ZapTarget) case Search(search: SearchModel) @@ -92,6 +93,8 @@ enum Route: Hashable { ThreadView(state: damusState, thread: thread) case .Reposts(let reposts): RepostsView(damus_state: damusState, model: reposts) + case .QuoteReposts(let quote_reposts): + QuoteRepostsView(damus_state: damusState, model: quote_reposts) case .Reactions(let reactions): ReactionsView(damus_state: damusState, model: reactions) case .Zaps(let target): @@ -178,6 +181,9 @@ enum Route: Hashable { case .Reposts(let reposts): hasher.combine("reposts") hasher.combine(reposts.target) + case .QuoteReposts(let evs_model): + hasher.combine("quote_reposts") + hasher.combine(evs_model.events.events.count) case .Zaps(let target): hasher.combine("zaps") hasher.combine(target.id) diff --git a/damus/Views/ActionBar/EventDetailBar.swift b/damus/Views/ActionBar/EventDetailBar.swift index af688c1c..8035b880 100644 --- a/damus/Views/ActionBar/EventDetailBar.swift +++ b/damus/Views/ActionBar/EventDetailBar.swift @@ -33,6 +33,15 @@ struct EventDetailBar: View { .buttonStyle(PlainButtonStyle()) } + if bar.quote_reposts > 0 { + NavigationLink(value: Route.QuoteReposts(quotes: .quotes(state: state, target: target))) { + let nounString = pluralizedString(key: "quoted_reposts_count", count: bar.quote_reposts) + let noun = Text(nounString).foregroundColor(.gray) + Text("\(Text(verbatim: bar.quote_reposts.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many quoted reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.") + } + .buttonStyle(PlainButtonStyle()) + } + if bar.likes > 0 && !state.settings.onlyzaps_mode { NavigationLink(value: Route.Reactions(reactions: .likes(state: state, target: target))) { let nounString = pluralizedString(key: "reactions_count", count: bar.likes) diff --git a/damus/Views/Reposts/QuoteRepostsView.swift b/damus/Views/Reposts/QuoteRepostsView.swift new file mode 100644 index 00000000..d9e53173 --- /dev/null +++ b/damus/Views/Reposts/QuoteRepostsView.swift @@ -0,0 +1,31 @@ +// +// QuoteRepostsView.swift +// damus +// +// Created by William Casarin on 2024-03-16. +// + +import SwiftUI + +struct QuoteRepostsView: View { + let damus_state: DamusState + @ObservedObject var model: EventsModel + + var body: some View { + TimelineView(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: ContentFilters.default_filters(damus_state: damus_state).filter(ev:)) + .navigationBarTitle(NSLocalizedString("Quotes", comment: "Navigation bar title for Quote Reposts view.")) + .onAppear { + model.subscribe() + } + .onDisappear { + model.unsubscribe() + } + } +} + +struct QuoteRepostsView_Previews: PreviewProvider { + static var previews: some View { + let state = test_damus_state + QuoteRepostsView(damus_state: state, model: .reposts(state: state, target: test_note.id)) + } +}