ui: add quoted reposts view to threads
This adds quote reposts as an additional detail view on threads. It will list quoted reposts that have the `q` tag. Not all clients have updated to this yet (like primal), but hopefully they will soon. Changelog-Added: Show list of quoted reposts in threads Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
@@ -253,6 +253,7 @@
|
|||||||
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276E2A2A5D110098A105 /* wasm.c */; };
|
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276E2A2A5D110098A105 /* wasm.c */; };
|
||||||
4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */; };
|
4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */; };
|
||||||
4C9147002A2A891E00DDEA40 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C9146FF2A2A891E00DDEA40 /* error.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 */; };
|
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
|
||||||
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */; };
|
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */; };
|
||||||
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.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 = "<group>"; };
|
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
|
||||||
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
|
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
|
||||||
4C9146FF2A2A891E00DDEA40 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = error.c; sourceTree = "<group>"; };
|
4C9146FF2A2A891E00DDEA40 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = error.c; sourceTree = "<group>"; };
|
||||||
|
4C94D6422BA5AEFE00C26EFF /* QuoteRepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteRepostsView.swift; sourceTree = "<group>"; };
|
||||||
4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = "<group>"; };
|
4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = "<group>"; };
|
||||||
4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusModel.swift; sourceTree = "<group>"; };
|
4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusModel.swift; sourceTree = "<group>"; };
|
||||||
4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttrStringTestExtensions.swift; sourceTree = "<group>"; };
|
4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttrStringTestExtensions.swift; sourceTree = "<group>"; };
|
||||||
@@ -1506,6 +1508,7 @@
|
|||||||
children = (
|
children = (
|
||||||
3AA24801297E3DC20090C62D /* RepostView.swift */,
|
3AA24801297E3DC20090C62D /* RepostView.swift */,
|
||||||
4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */,
|
4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */,
|
||||||
|
4C94D6422BA5AEFE00C26EFF /* QuoteRepostsView.swift */,
|
||||||
);
|
);
|
||||||
path = Reposts;
|
path = Reposts;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2735,14 +2738,6 @@
|
|||||||
path = DamusNotificationService;
|
path = DamusNotificationService;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E06336A72B7582D600A88E6B /* Assets */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E06336A82B7582E000A88E6B /* img_with_location.jpeg */,
|
|
||||||
);
|
|
||||||
path = Assets;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D7CBD1D22B8D21C100BFD889 /* Extensions */ = {
|
D7CBD1D22B8D21C100BFD889 /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -2751,6 +2746,14 @@
|
|||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E06336A72B7582D600A88E6B /* Assets */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E06336A82B7582E000A88E6B /* img_with_location.jpeg */,
|
||||||
|
);
|
||||||
|
path = Assets;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
F71694E82A66221E001F4053 /* Onboarding */ = {
|
F71694E82A66221E001F4053 /* Onboarding */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -3248,6 +3251,7 @@
|
|||||||
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
|
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
|
||||||
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */,
|
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */,
|
||||||
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
|
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
|
||||||
|
4C94D6432BA5AEFE00C26EFF /* QuoteRepostsView.swift in Sources */,
|
||||||
D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */,
|
D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */,
|
||||||
4CA352AE2A76C1AC003BB08B /* FollowedNotify.swift in Sources */,
|
4CA352AE2A76C1AC003BB08B /* FollowedNotify.swift in Sources */,
|
||||||
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
|
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
class EventsModel: ObservableObject {
|
class EventsModel: ObservableObject {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
let target: NoteId
|
let target: NoteId
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class ThreadModel: ObservableObject {
|
|||||||
|
|
||||||
func subscribe() {
|
func subscribe() {
|
||||||
var meta_events = NostrFilter()
|
var meta_events = NostrFilter()
|
||||||
|
var quote_events = NostrFilter()
|
||||||
var event_filter = NostrFilter()
|
var event_filter = NostrFilter()
|
||||||
var ref_events = NostrFilter()
|
var ref_events = NostrFilter()
|
||||||
|
|
||||||
@@ -74,11 +75,14 @@ class ThreadModel: ObservableObject {
|
|||||||
kinds.append(.like)
|
kinds.append(.like)
|
||||||
}
|
}
|
||||||
meta_events.kinds = kinds
|
meta_events.kinds = kinds
|
||||||
|
|
||||||
meta_events.limit = 1000
|
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 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)")
|
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)
|
damus_state.pool.subscribe(sub_id: base_subid, filters: base_filters, handler: handle_event)
|
||||||
@@ -90,7 +94,7 @@ class ThreadModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let the_ev = damus_state.events.upsert(ev)
|
damus_state.events.upsert(ev)
|
||||||
damus_state.replies.count_replies(ev, keypair: keypair)
|
damus_state.replies.count_replies(ev, keypair: keypair)
|
||||||
damus_state.events.add_replies(ev: ev, keypair: keypair)
|
damus_state.events.add_replies(ev: ev, keypair: keypair)
|
||||||
|
|
||||||
@@ -111,7 +115,13 @@ class ThreadModel: ObservableObject {
|
|||||||
|
|
||||||
}
|
}
|
||||||
} else if ev.is_textlike {
|
} 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ struct QuoteId: IdType, TagKey, TagConvertible {
|
|||||||
self.id = data
|
self.id = data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Refer to this QuoteId as a NoteId
|
/// The note id being quoted
|
||||||
var note_id: NoteId {
|
var note_id: NoteId {
|
||||||
NoteId(self.id)
|
NoteId(self.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ enum Route: Hashable {
|
|||||||
case DeveloperSettings(settings: UserSettingsStore)
|
case DeveloperSettings(settings: UserSettingsStore)
|
||||||
case Thread(thread: ThreadModel)
|
case Thread(thread: ThreadModel)
|
||||||
case Reposts(reposts: EventsModel)
|
case Reposts(reposts: EventsModel)
|
||||||
|
case QuoteReposts(quotes: EventsModel)
|
||||||
case Reactions(reactions: EventsModel)
|
case Reactions(reactions: EventsModel)
|
||||||
case Zaps(target: ZapTarget)
|
case Zaps(target: ZapTarget)
|
||||||
case Search(search: SearchModel)
|
case Search(search: SearchModel)
|
||||||
@@ -92,6 +93,8 @@ enum Route: Hashable {
|
|||||||
ThreadView(state: damusState, thread: thread)
|
ThreadView(state: damusState, thread: thread)
|
||||||
case .Reposts(let reposts):
|
case .Reposts(let reposts):
|
||||||
RepostsView(damus_state: damusState, model: reposts)
|
RepostsView(damus_state: damusState, model: reposts)
|
||||||
|
case .QuoteReposts(let quote_reposts):
|
||||||
|
QuoteRepostsView(damus_state: damusState, model: quote_reposts)
|
||||||
case .Reactions(let reactions):
|
case .Reactions(let reactions):
|
||||||
ReactionsView(damus_state: damusState, model: reactions)
|
ReactionsView(damus_state: damusState, model: reactions)
|
||||||
case .Zaps(let target):
|
case .Zaps(let target):
|
||||||
@@ -178,6 +181,9 @@ enum Route: Hashable {
|
|||||||
case .Reposts(let reposts):
|
case .Reposts(let reposts):
|
||||||
hasher.combine("reposts")
|
hasher.combine("reposts")
|
||||||
hasher.combine(reposts.target)
|
hasher.combine(reposts.target)
|
||||||
|
case .QuoteReposts(let evs_model):
|
||||||
|
hasher.combine("quote_reposts")
|
||||||
|
hasher.combine(evs_model.events.events.count)
|
||||||
case .Zaps(let target):
|
case .Zaps(let target):
|
||||||
hasher.combine("zaps")
|
hasher.combine("zaps")
|
||||||
hasher.combine(target.id)
|
hasher.combine(target.id)
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ struct EventDetailBar: View {
|
|||||||
.buttonStyle(PlainButtonStyle())
|
.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 {
|
if bar.likes > 0 && !state.settings.onlyzaps_mode {
|
||||||
NavigationLink(value: Route.Reactions(reactions: .likes(state: state, target: target))) {
|
NavigationLink(value: Route.Reactions(reactions: .likes(state: state, target: target))) {
|
||||||
let nounString = pluralizedString(key: "reactions_count", count: bar.likes)
|
let nounString = pluralizedString(key: "reactions_count", count: bar.likes)
|
||||||
|
|||||||
31
damus/Views/Reposts/QuoteRepostsView.swift
Normal file
31
damus/Views/Reposts/QuoteRepostsView.swift
Normal file
@@ -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<AnyView>(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))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user