Thread Caching
Changelog-Added: Threads now load instantly and are cached
This commit is contained in:
@@ -170,6 +170,8 @@
|
|||||||
4CC7AAF6297F1A6A00430951 /* EventBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF5297F1A6A00430951 /* EventBody.swift */; };
|
4CC7AAF6297F1A6A00430951 /* EventBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF5297F1A6A00430951 /* EventBody.swift */; };
|
||||||
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; };
|
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; };
|
||||||
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; };
|
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; };
|
||||||
|
4CCEB7A929B29DD50078AA28 /* SearchResultsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7A829B29DD50078AA28 /* SearchResultsModel.swift */; };
|
||||||
|
4CCEB7AB29B2A1320078AA28 /* SearchedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AA29B2A1320078AA28 /* SearchedEventView.swift */; };
|
||||||
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
||||||
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; };
|
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; };
|
||||||
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
|
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
|
||||||
@@ -235,7 +237,7 @@
|
|||||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||||
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
|
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
|
||||||
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
||||||
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; };
|
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
|
||||||
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12C29A1855400E10810 /* BookmarksManager.swift */; };
|
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12C29A1855400E10810 /* BookmarksManager.swift */; };
|
||||||
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12E29A18EF500E10810 /* BookmarksView.swift */; };
|
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12E29A18EF500E10810 /* BookmarksView.swift */; };
|
||||||
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E91298B0F0700AB113A /* RelayDetailView.swift */; };
|
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E91298B0F0700AB113A /* RelayDetailView.swift */; };
|
||||||
@@ -510,6 +512,8 @@
|
|||||||
4CC7AAF5297F1A6A00430951 /* EventBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBody.swift; sourceTree = "<group>"; };
|
4CC7AAF5297F1A6A00430951 /* EventBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBody.swift; sourceTree = "<group>"; };
|
||||||
4CC7AAF7297F1CEE00430951 /* EventProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfile.swift; sourceTree = "<group>"; };
|
4CC7AAF7297F1CEE00430951 /* EventProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfile.swift; sourceTree = "<group>"; };
|
||||||
4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; };
|
4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; };
|
||||||
|
4CCEB7A829B29DD50078AA28 /* SearchResultsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsModel.swift; sourceTree = "<group>"; };
|
||||||
|
4CCEB7AA29B2A1320078AA28 /* SearchedEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SearchedEventView.swift; path = ../../Models/SearchedEventView.swift; sourceTree = "<group>"; };
|
||||||
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
|
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
|
||||||
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHolder.swift; sourceTree = "<group>"; };
|
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHolder.swift; sourceTree = "<group>"; };
|
||||||
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; };
|
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; };
|
||||||
@@ -577,7 +581,7 @@
|
|||||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
||||||
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
|
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
|
||||||
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
|
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
|
||||||
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadV2View.swift; sourceTree = "<group>"; };
|
E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; };
|
||||||
F75BA12C29A1855400E10810 /* BookmarksManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksManager.swift; sourceTree = "<group>"; };
|
F75BA12C29A1855400E10810 /* BookmarksManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksManager.swift; sourceTree = "<group>"; };
|
||||||
F75BA12E29A18EF500E10810 /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = "<group>"; };
|
F75BA12E29A18EF500E10810 /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = "<group>"; };
|
||||||
F7908E91298B0F0700AB113A /* RelayDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayDetailView.swift; sourceTree = "<group>"; };
|
F7908E91298B0F0700AB113A /* RelayDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayDetailView.swift; sourceTree = "<group>"; };
|
||||||
@@ -688,6 +692,7 @@
|
|||||||
4C0A3F8D280F63FF000448DE /* Models */ = {
|
4C0A3F8D280F63FF000448DE /* Models */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4CCEB7A729B29DC90078AA28 /* Search */,
|
||||||
4C54AA0829A55416003E4487 /* Notifications */,
|
4C54AA0829A55416003E4487 /* Notifications */,
|
||||||
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */,
|
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */,
|
||||||
4C0A3F8E280F640A000448DE /* ThreadModel.swift */,
|
4C0A3F8E280F640A000448DE /* ThreadModel.swift */,
|
||||||
@@ -805,7 +810,7 @@
|
|||||||
4C363AA128296A7E006E126D /* SearchView.swift */,
|
4C363AA128296A7E006E126D /* SearchView.swift */,
|
||||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */,
|
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */,
|
||||||
4C3AC7A02835A81400E1F516 /* SetupView.swift */,
|
4C3AC7A02835A81400E1F516 /* SetupView.swift */,
|
||||||
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */,
|
E9E4ED0A295867B900DD7078 /* ThreadView.swift */,
|
||||||
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */,
|
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */,
|
||||||
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */,
|
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */,
|
||||||
647D9A8C2968520300A295DE /* SideMenuView.swift */,
|
647D9A8C2968520300A295DE /* SideMenuView.swift */,
|
||||||
@@ -925,6 +930,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
||||||
|
4CCEB7AA29B2A1320078AA28 /* SearchedEventView.swift */,
|
||||||
4CC7AAF1297F129C00430951 /* EmbeddedEventView.swift */,
|
4CC7AAF1297F129C00430951 /* EmbeddedEventView.swift */,
|
||||||
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */,
|
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */,
|
||||||
4CC7AAF5297F1A6A00430951 /* EventBody.swift */,
|
4CC7AAF5297F1A6A00430951 /* EventBody.swift */,
|
||||||
@@ -938,6 +944,14 @@
|
|||||||
path = Events;
|
path = Events;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4CCEB7A729B29DC90078AA28 /* Search */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4CCEB7A829B29DD50078AA28 /* SearchResultsModel.swift */,
|
||||||
|
);
|
||||||
|
path = Search;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4CE0E2B029A3DF4700DB4CA2 /* Timeline */ = {
|
4CE0E2B029A3DF4700DB4CA2 /* Timeline */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1306,6 +1320,7 @@
|
|||||||
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
||||||
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
|
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
|
||||||
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
|
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
|
||||||
|
4CCEB7A929B29DD50078AA28 /* SearchResultsModel.swift in Sources */,
|
||||||
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
|
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
|
||||||
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
|
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
|
||||||
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,
|
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,
|
||||||
@@ -1355,7 +1370,7 @@
|
|||||||
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
|
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
|
||||||
4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */,
|
4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */,
|
||||||
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
|
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
|
||||||
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
|
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */,
|
||||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
||||||
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
|
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
|
||||||
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
|
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
|
||||||
@@ -1393,6 +1408,7 @@
|
|||||||
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */,
|
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */,
|
||||||
4C3EA67528FF7A5A00C48A62 /* take.c in Sources */,
|
4C3EA67528FF7A5A00C48A62 /* take.c in Sources */,
|
||||||
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
|
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
|
||||||
|
4CCEB7AB29B2A1320078AA28 /* SearchedEventView.swift in Sources */,
|
||||||
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */,
|
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */,
|
||||||
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
|
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
|
||||||
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
|
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ struct ContentView: View {
|
|||||||
@State var event: NostrEvent? = nil
|
@State var event: NostrEvent? = nil
|
||||||
@State var active_profile: String? = nil
|
@State var active_profile: String? = nil
|
||||||
@State var active_search: NostrFilter? = nil
|
@State var active_search: NostrFilter? = nil
|
||||||
@State var active_event_id: String? = nil
|
@State var active_event: NostrEvent = test_event
|
||||||
@State var profile_open: Bool = false
|
@State var profile_open: Bool = false
|
||||||
@State var thread_open: Bool = false
|
@State var thread_open: Bool = false
|
||||||
@State var search_open: Bool = false
|
@State var search_open: Bool = false
|
||||||
@@ -176,7 +176,8 @@ struct ContentView: View {
|
|||||||
NavigationLink(destination: MaybeProfileView, isActive: $profile_open) {
|
NavigationLink(destination: MaybeProfileView, isActive: $profile_open) {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
NavigationLink(destination: MaybeThreadView, isActive: $thread_open) {
|
let thread = ThreadModel(event: active_event, damus_state: damus_state!)
|
||||||
|
NavigationLink(destination: ThreadView(state: damus_state!, thread: thread), isActive: $thread_open) {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
NavigationLink(destination: MaybeSearchView, isActive: $search_open) {
|
NavigationLink(destination: MaybeSearchView, isActive: $search_open) {
|
||||||
@@ -223,16 +224,6 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var MaybeThreadView: some View {
|
|
||||||
Group {
|
|
||||||
if let evid = self.active_event_id {
|
|
||||||
BuildThreadV2View(damus: damus_state!, event_id: evid)
|
|
||||||
} else {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var MaybeProfileView: some View {
|
var MaybeProfileView: some View {
|
||||||
Group {
|
Group {
|
||||||
if let pk = self.active_profile {
|
if let pk = self.active_profile {
|
||||||
@@ -352,7 +343,11 @@ struct ContentView: View {
|
|||||||
active_profile = ref.ref_id
|
active_profile = ref.ref_id
|
||||||
profile_open = true
|
profile_open = true
|
||||||
} else if ref.key == "e" {
|
} else if ref.key == "e" {
|
||||||
active_event_id = ref.ref_id
|
find_event(state: damus_state!, evid: ref.ref_id, find_from: nil) { ev in
|
||||||
|
if let ev {
|
||||||
|
active_event = ev
|
||||||
|
}
|
||||||
|
}
|
||||||
thread_open = true
|
thread_open = true
|
||||||
}
|
}
|
||||||
case .filter(let filt):
|
case .filter(let filt):
|
||||||
@@ -766,3 +761,45 @@ func setup_notifications() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func find_event(state: DamusState, evid: String, find_from: [String]?, callback: @escaping (NostrEvent?) -> ()) {
|
||||||
|
if let ev = state.events.lookup(evid) {
|
||||||
|
callback(ev)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let subid = UUID().description
|
||||||
|
|
||||||
|
var has_event = false
|
||||||
|
var filter = NostrFilter.filter_ids([ evid ])
|
||||||
|
filter.limit = 1
|
||||||
|
var attempts = 0
|
||||||
|
|
||||||
|
state.pool.subscribe_to(sub_id: subid, filters: [filter], to: find_from) { relay_id, res in
|
||||||
|
guard case .nostr_event(let ev) = res else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard ev.subid == subid else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ev {
|
||||||
|
case .event(_, let ev):
|
||||||
|
has_event = true
|
||||||
|
callback(ev)
|
||||||
|
state.pool.unsubscribe(sub_id: subid)
|
||||||
|
case .eose:
|
||||||
|
if !has_event {
|
||||||
|
attempts += 1
|
||||||
|
if attempts == state.pool.descriptors.count / 2 {
|
||||||
|
callback(nil)
|
||||||
|
}
|
||||||
|
state.pool.unsubscribe(sub_id: subid, to: [relay_id])
|
||||||
|
}
|
||||||
|
case .notice(_):
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,12 +8,25 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class ReplyMap {
|
class ReplyMap {
|
||||||
var replies: [String: String] = [:]
|
var replies: [String: Set<String>] = [:]
|
||||||
|
|
||||||
func lookup(_ id: String) -> String? {
|
func lookup(_ id: String) -> Set<String>? {
|
||||||
return replies[id]
|
return replies[id]
|
||||||
}
|
}
|
||||||
func add(id: String, reply_id: String) {
|
|
||||||
replies[id] = reply_id
|
private func ensure_set(id: String) {
|
||||||
|
if replies[id] == nil {
|
||||||
|
replies[id] = Set()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func add(id: String, reply_id: String) -> Bool {
|
||||||
|
ensure_set(id: id)
|
||||||
|
if (replies[id]!).contains(reply_id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
replies[id]!.insert(reply_id)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
damus/Models/Search/SearchResultsModel.swift
Normal file
12
damus/Models/Search/SearchResultsModel.swift
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// SearchResultsModel.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-03-03.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
class SearchResultsModel: ObservableObject {
|
||||||
|
}
|
||||||
53
damus/Models/SearchedEventView.swift
Normal file
53
damus/Models/SearchedEventView.swift
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// SearchedEventView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-03-03.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
enum EventSearchState {
|
||||||
|
case searching
|
||||||
|
case not_found
|
||||||
|
case found(NostrEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchedEventView: View {
|
||||||
|
let state: DamusState
|
||||||
|
let event_id: String
|
||||||
|
@State var search_state: EventSearchState = .searching
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
switch search_state {
|
||||||
|
case .not_found:
|
||||||
|
Text("Event could not be found")
|
||||||
|
case .searching:
|
||||||
|
Text("Searching...")
|
||||||
|
case .found(let ev):
|
||||||
|
let thread = ThreadModel(event: ev, damus_state: state)
|
||||||
|
let dest = ThreadView(state: state, thread: thread)
|
||||||
|
NavigationLink(destination: dest) {
|
||||||
|
EventView(damus: state, event: ev)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
find_event(state: state, evid: event_id, find_from: nil) { ev in
|
||||||
|
if let ev {
|
||||||
|
self.search_state = .found(ev)
|
||||||
|
} else {
|
||||||
|
self.search_state = .not_found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchedEventView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
SearchedEventView(state: test_damus_state(), event_id: "event_id")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,162 +30,100 @@ enum InitialEvent {
|
|||||||
|
|
||||||
/// manages the lifetime of a thread
|
/// manages the lifetime of a thread
|
||||||
class ThreadModel: ObservableObject {
|
class ThreadModel: ObservableObject {
|
||||||
@Published var initial_event: InitialEvent
|
@Published var event: NostrEvent
|
||||||
@Published var events: [NostrEvent] = []
|
var event_map: Set<NostrEvent>
|
||||||
@Published var event_map: [String: Int] = [:]
|
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
|
|
||||||
var replies: ReplyMap = ReplyMap()
|
var replies: ReplyMap = ReplyMap()
|
||||||
|
|
||||||
var event: NostrEvent? {
|
init(event: NostrEvent, damus_state: DamusState) {
|
||||||
switch initial_event {
|
self.damus_state = damus_state
|
||||||
case .event(let ev):
|
self.event_map = Set()
|
||||||
return ev
|
self.event = event
|
||||||
case .event_id(let evid):
|
add_event(event, privkey: nil)
|
||||||
for event in events {
|
|
||||||
if event.id == evid {
|
|
||||||
return event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
|
||||||
let profiles_subid = UUID().description
|
let profiles_subid = UUID().description
|
||||||
var base_subid = UUID().description
|
let base_subid = UUID().description
|
||||||
|
let meta_subid = UUID().description
|
||||||
init(evid: String, damus_state: DamusState) {
|
|
||||||
self.damus_state = damus_state
|
|
||||||
self.initial_event = .event_id(evid)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(event: NostrEvent, damus_state: DamusState) {
|
var subids: [String] {
|
||||||
self.damus_state = damus_state
|
return [profiles_subid, base_subid, meta_subid]
|
||||||
self.initial_event = .event(event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsubscribe() {
|
func unsubscribe() {
|
||||||
|
self.damus_state.pool.remove_handler(sub_id: base_subid)
|
||||||
|
self.damus_state.pool.remove_handler(sub_id: meta_subid)
|
||||||
|
self.damus_state.pool.remove_handler(sub_id: profiles_subid)
|
||||||
self.damus_state.pool.unsubscribe(sub_id: base_subid)
|
self.damus_state.pool.unsubscribe(sub_id: base_subid)
|
||||||
print("unsubscribing from thread \(initial_event.id) with sub_id \(base_subid)")
|
self.damus_state.pool.unsubscribe(sub_id: meta_subid)
|
||||||
|
self.damus_state.pool.unsubscribe(sub_id: profiles_subid)
|
||||||
|
print("unsubscribing from thread \(event.id) with sub_id \(base_subid)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset_events() {
|
@discardableResult
|
||||||
self.events.removeAll()
|
func set_active_event(_ ev: NostrEvent, privkey: String?) -> Bool {
|
||||||
self.event_map.removeAll()
|
self.event = ev
|
||||||
self.replies.replies.removeAll()
|
add_event(ev, privkey: privkey)
|
||||||
}
|
|
||||||
|
|
||||||
func should_resubscribe(_ ev_b: NostrEvent) -> Bool {
|
|
||||||
if self.events.count == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if ev_b.is_root_event() {
|
//self.objectWillChange.send()
|
||||||
return false
|
return false
|
||||||
}
|
|
||||||
|
|
||||||
// rough heuristic to save us from resubscribing all the time
|
|
||||||
//return ev_b.count_ids() != self.event.count_ids()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func set_active_event(_ ev: NostrEvent, privkey: String?) {
|
|
||||||
if should_resubscribe(ev) {
|
|
||||||
unsubscribe()
|
|
||||||
self.initial_event = .event(ev)
|
|
||||||
subscribe()
|
|
||||||
} else {
|
|
||||||
self.initial_event = .event(ev)
|
|
||||||
if events.count == 0 {
|
|
||||||
add_event(ev, privkey: privkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribe() {
|
func subscribe() {
|
||||||
|
var meta_events = NostrFilter()
|
||||||
|
var event_filter = NostrFilter()
|
||||||
var ref_events = NostrFilter()
|
var ref_events = NostrFilter()
|
||||||
var events_filter = NostrFilter()
|
|
||||||
//var likes_filter = NostrFilter.filter_kinds(7])
|
//var likes_filter = NostrFilter.filter_kinds(7])
|
||||||
|
|
||||||
// TODO: add referenced relays
|
let thread_id = event.thread_id(privkey: nil)
|
||||||
switch self.initial_event {
|
|
||||||
case .event(let ev):
|
ref_events.referenced_ids = [thread_id, event.id]
|
||||||
ref_events.referenced_ids = ev.referenced_ids.map { $0.ref_id }
|
ref_events.kinds = [1]
|
||||||
ref_events.referenced_ids?.append(ev.id)
|
ref_events.limit = 1000
|
||||||
ref_events.limit = 50
|
|
||||||
events_filter.ids = ref_events.referenced_ids ?? []
|
event_filter.ids = [thread_id, event.id]
|
||||||
events_filter.limit = 100
|
|
||||||
events_filter.ids?.append(ev.id)
|
meta_events.referenced_ids = [event.id]
|
||||||
case .event_id(let evid):
|
meta_events.kinds = [9735, 1, 6, 7]
|
||||||
ref_events.referenced_ids = [evid]
|
meta_events.limit = 1000
|
||||||
ref_events.limit = 50
|
|
||||||
events_filter.ids = [evid]
|
/*
|
||||||
events_filter.limit = 100
|
if let last_ev = self.events.last {
|
||||||
|
if last_ev.created_at <= Int64(Date().timeIntervalSince1970) {
|
||||||
|
ref_events.since = last_ev.created_at
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
let base_filters = [event_filter, ref_events]
|
||||||
|
let meta_filters = [meta_events]
|
||||||
|
|
||||||
//likes_filter.ids = ref_events.referenced_ids!
|
print("subscribing to thread \(event.id) with sub_id \(base_subid)")
|
||||||
|
|
||||||
print("subscribing to thread \(initial_event.id) with sub_id \(base_subid)")
|
|
||||||
damus_state.pool.register_handler(sub_id: base_subid, handler: handle_event)
|
|
||||||
loading = true
|
loading = true
|
||||||
damus_state.pool.send(.subscribe(.init(filters: [ref_events, events_filter], sub_id: base_subid)))
|
damus_state.pool.subscribe(sub_id: base_subid, filters: base_filters, handler: handle_event)
|
||||||
}
|
damus_state.pool.subscribe(sub_id: meta_subid, filters: meta_filters, handler: handle_event)
|
||||||
|
|
||||||
func lookup(_ event_id: String) -> NostrEvent? {
|
|
||||||
if let i = event_map[event_id] {
|
|
||||||
return events[i]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func add_event(_ ev: NostrEvent, privkey: String?) {
|
func add_event(_ ev: NostrEvent, privkey: String?) {
|
||||||
guard ev.should_show_event else {
|
if event_map.contains(ev) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if event_map[ev.id] != nil {
|
let the_ev = damus_state.events.upsert(ev)
|
||||||
return
|
damus_state.events.add_replies(ev: the_ev)
|
||||||
}
|
|
||||||
|
|
||||||
for reply in ev.direct_replies(privkey) {
|
event_map.insert(ev)
|
||||||
self.replies.add(id: ev.id, reply_id: reply.ref_id)
|
objectWillChange.send()
|
||||||
}
|
|
||||||
|
|
||||||
if insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at < $1.created_at }) {
|
|
||||||
objectWillChange.send()
|
|
||||||
}
|
|
||||||
//self.events.append(ev)
|
|
||||||
//self.events = self.events.sorted { $0.created_at < $1.created_at }
|
|
||||||
|
|
||||||
var i: Int = 0
|
|
||||||
for ev in events {
|
|
||||||
self.event_map[ev.id] = i
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if let evid = self.initial_event.is_event_id {
|
|
||||||
if ev.id == evid {
|
|
||||||
// this should trigger a resubscribe...
|
|
||||||
set_active_event(ev, privkey: privkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_channel_meta(_ ev: NostrEvent) {
|
|
||||||
guard let meta: ChatroomMetadata = decode_json(ev.content) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
notify(.chatroom_meta, meta)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
||||||
|
|
||||||
let (sub_id, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: ev) { sid, ev in
|
let (sub_id, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: ev) { sid, ev in
|
||||||
guard sid == base_subid || sid == profiles_subid else {
|
guard subids.contains(sid) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,21 +131,19 @@ class ThreadModel: ObservableObject {
|
|||||||
process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||||
} else if ev.is_textlike {
|
} else if ev.is_textlike {
|
||||||
self.add_event(ev, privkey: self.damus_state.keypair.privkey)
|
self.add_event(ev, privkey: self.damus_state.keypair.privkey)
|
||||||
} else if ev.known_kind == .channel_meta || ev.known_kind == .channel_create {
|
|
||||||
handle_channel_meta(ev)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard done && (sub_id == base_subid || sub_id == profiles_subid) else {
|
guard done, let sub_id, subids.contains(sub_id) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events.contains { ev in ev.id == initial_event.id }) {
|
if event_map.contains(event) {
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if sub_id == self.base_subid {
|
if sub_id == self.base_subid {
|
||||||
load_profiles(profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: damus_state)
|
load_profiles(profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(Array(event_map)), damus_state: damus_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -215,6 +215,16 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func thread_id(privkey: String?) -> String {
|
||||||
|
for ref in event_refs(privkey) {
|
||||||
|
if let thread_id = ref.is_thread_id {
|
||||||
|
return thread_id.ref_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.id
|
||||||
|
}
|
||||||
|
|
||||||
public func last_refid() -> ReferencedId? {
|
public func last_refid() -> ReferencedId? {
|
||||||
var mlast: Int? = nil
|
var mlast: Int? = nil
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ struct NostrFilter: Codable, Equatable {
|
|||||||
public static var filter_text: NostrFilter {
|
public static var filter_text: NostrFilter {
|
||||||
return filter_kinds([1])
|
return filter_kinds([1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func filter_ids(_ ids: [String]) -> NostrFilter {
|
||||||
|
return NostrFilter(ids: ids, kinds: nil, referenced_ids: nil, pubkeys: nil, since: nil, until: nil, authors: nil, hashtag: nil)
|
||||||
|
}
|
||||||
|
|
||||||
public static var filter_profiles: NostrFilter {
|
public static var filter_profiles: NostrFilter {
|
||||||
return filter_kinds([0])
|
return filter_kinds([0])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,64 @@ import Foundation
|
|||||||
|
|
||||||
class EventCache {
|
class EventCache {
|
||||||
private var events: [String: NostrEvent]
|
private var events: [String: NostrEvent]
|
||||||
|
private var replies: ReplyMap = ReplyMap()
|
||||||
|
|
||||||
|
//private var thread_latest: [String: Int64]
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.events = [:]
|
||||||
|
self.replies = ReplyMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
func parent_events(event: NostrEvent) -> [NostrEvent] {
|
||||||
|
var parents: [NostrEvent] = []
|
||||||
|
|
||||||
|
var ev = event
|
||||||
|
|
||||||
|
while true {
|
||||||
|
guard let direct_reply = ev.direct_replies(nil).first else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let next_ev = lookup(direct_reply.ref_id), next_ev != ev else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
parents.append(next_ev)
|
||||||
|
ev = next_ev
|
||||||
|
}
|
||||||
|
|
||||||
|
return parents.reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
func add_replies(ev: NostrEvent) {
|
||||||
|
for reply in ev.direct_replies(nil) {
|
||||||
|
replies.add(id: reply.ref_id, reply_id: ev.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func child_events(event: NostrEvent) -> [NostrEvent] {
|
||||||
|
guard let xs = replies.lookup(event.id) else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let evs: [NostrEvent] = xs.reduce(into: [], { evs, evid in
|
||||||
|
guard let ev = self.lookup(evid) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
evs.append(ev)
|
||||||
|
}).sorted(by: { $0.created_at < $1.created_at })
|
||||||
|
return evs
|
||||||
|
}
|
||||||
|
|
||||||
|
func upsert(_ ev: NostrEvent) -> NostrEvent {
|
||||||
|
if let found = lookup(ev.id) {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
insert(ev)
|
||||||
|
return ev
|
||||||
|
}
|
||||||
|
|
||||||
func lookup(_ evid: String) -> NostrEvent? {
|
func lookup(_ evid: String) -> NostrEvent? {
|
||||||
return events[evid]
|
return events[evid]
|
||||||
@@ -21,7 +79,4 @@ class EventCache {
|
|||||||
events[ev.id] = ev
|
events[ev.id] = ev
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
|
||||||
self.events = [:]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,14 @@ struct ChatView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let rep = thread.replies.lookup(event.id) {
|
if let rep = thread.replies.lookup(event.id) {
|
||||||
return rep == prev.id
|
return rep.contains(prev.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_active: Bool {
|
var is_active: Bool {
|
||||||
return thread.initial_event.id == event.id
|
return thread.event.id == event.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func prev_reply_is_same() -> String? {
|
func prev_reply_is_same() -> String? {
|
||||||
@@ -161,7 +161,7 @@ func prev_reply_is_same(event: NostrEvent, prev_ev: NostrEvent?, replies: ReplyM
|
|||||||
if let prev_reply_id = replies.lookup(prev.id) {
|
if let prev_reply_id = replies.lookup(prev.id) {
|
||||||
if let cur_reply_id = replies.lookup(event.id) {
|
if let cur_reply_id = replies.lookup(event.id) {
|
||||||
if prev_reply_id != cur_reply_id {
|
if prev_reply_id != cur_reply_id {
|
||||||
return cur_reply_id
|
return cur_reply_id.first
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
/*
|
||||||
struct ChatroomView: View {
|
struct ChatroomView: View {
|
||||||
@EnvironmentObject var thread: ThreadModel
|
@EnvironmentObject var thread: ThreadModel
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
@@ -26,7 +27,7 @@ struct ChatroomView: View {
|
|||||||
)
|
)
|
||||||
.event_context_menu(ev, keypair: damus.keypair, target_pubkey: ev.pubkey, bookmarks: damus.bookmarks)
|
.event_context_menu(ev, keypair: damus.keypair, target_pubkey: ev.pubkey, bookmarks: damus.bookmarks)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if thread.initial_event.id == ev.id {
|
if thread.event.id == ev.id {
|
||||||
//dismiss()
|
//dismiss()
|
||||||
toggle_thread_view()
|
toggle_thread_view()
|
||||||
} else {
|
} else {
|
||||||
@@ -44,7 +45,7 @@ struct ChatroomView: View {
|
|||||||
}
|
}
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .select_quote)) { notif in
|
.onReceive(NotificationCenter.default.publisher(for: .select_quote)) { notif in
|
||||||
let ev = notif.object as! NostrEvent
|
let ev = notif.object as! NostrEvent
|
||||||
if ev.id != thread.initial_event.id {
|
if ev.id != thread.event.id {
|
||||||
thread.set_active_event(ev, privkey: damus.keypair.privkey)
|
thread.set_active_event(ev, privkey: damus.keypair.privkey)
|
||||||
}
|
}
|
||||||
scroll_to_event(scroller: scroller, id: ev.id, delay: 0, animate: true)
|
scroll_to_event(scroller: scroller, id: ev.id, delay: 0, animate: true)
|
||||||
@@ -57,7 +58,7 @@ struct ChatroomView: View {
|
|||||||
once = true
|
once = true
|
||||||
}
|
}
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
scroll_to_event(scroller: scroller, id: thread.initial_event.id, delay: 0.1, animate: false)
|
scroll_to_event(scroller: scroller, id: thread.event.id, delay: 0.1, animate: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +77,9 @@ struct ChatroomView_Previews: PreviewProvider {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let state = test_damus_state()
|
let state = test_damus_state()
|
||||||
ChatroomView(damus: state)
|
ChatroomView(damus: state)
|
||||||
.environmentObject(ThreadModel(evid: "&849ab9bb263ed2819db06e05f1a1a3b72878464e8c7146718a2fc1bf1912f893", damus_state: state))
|
.environmentObject(ThreadModel(event: test_event, damus_state: state))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct EventDetailView: View {
|
|||||||
|
|
||||||
func scroll_after_load(thread: ThreadModel, proxy: ScrollViewProxy) {
|
func scroll_after_load(thread: ThreadModel, proxy: ScrollViewProxy) {
|
||||||
if !thread.loading {
|
if !thread.loading {
|
||||||
let id = thread.initial_event.id
|
let id = thread.event.id
|
||||||
scroll_to_event(scroller: proxy, id: id, delay: 0.1, animate: false)
|
scroll_to_event(scroller: proxy, id: id, delay: 0.1, animate: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ struct BuilderEventView: View {
|
|||||||
VStack {
|
VStack {
|
||||||
if let event = event {
|
if let event = event {
|
||||||
let ev = event.inner_event ?? event
|
let ev = event.inner_event ?? event
|
||||||
NavigationLink(destination: BuildThreadV2View(damus: damus, event_id: ev.id)) {
|
let thread = ThreadModel(event: ev, damus_state: damus)
|
||||||
|
let dest = ThreadView(state: damus, thread: thread)
|
||||||
|
NavigationLink(destination: dest) {
|
||||||
EmbeddedEventView(damus_state: damus, event: event)
|
EmbeddedEventView(damus_state: damus, event: event)
|
||||||
.padding(8)
|
.padding(8)
|
||||||
}.buttonStyle(.plain)
|
}.buttonStyle(.plain)
|
||||||
|
|||||||
@@ -13,18 +13,14 @@ struct MutedEventView: View {
|
|||||||
let scroller: ScrollViewProxy?
|
let scroller: ScrollViewProxy?
|
||||||
|
|
||||||
let selected: Bool
|
let selected: Bool
|
||||||
@Binding var nav_target: String?
|
|
||||||
@Binding var navigating: Bool
|
|
||||||
@State var shown: Bool
|
@State var shown: Bool
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
init(damus_state: DamusState, event: NostrEvent, scroller: ScrollViewProxy?, nav_target: Binding<String?>, navigating: Binding<Bool>, selected: Bool) {
|
init(damus_state: DamusState, event: NostrEvent, scroller: ScrollViewProxy?, selected: Bool) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
self.event = event
|
self.event = event
|
||||||
self.scroller = scroller
|
self.scroller = scroller
|
||||||
self.selected = selected
|
self.selected = selected
|
||||||
self._nav_target = nav_target
|
|
||||||
self._navigating = navigating
|
|
||||||
self._shown = State(initialValue: should_show_event(contacts: damus_state.contacts, ev: event))
|
self._shown = State(initialValue: should_show_event(contacts: damus_state.contacts, ev: event))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,14 +54,6 @@ struct MutedEventView: View {
|
|||||||
SelectedEventView(damus: damus_state, event: event)
|
SelectedEventView(damus: damus_state, event: event)
|
||||||
} else {
|
} else {
|
||||||
EventView(damus: damus_state, event: event)
|
EventView(damus: damus_state, event: event)
|
||||||
.onTapGesture {
|
|
||||||
nav_target = event.id
|
|
||||||
navigating = true
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
// TODO: find another solution to prevent layout shifting and layout blocking on large responses
|
|
||||||
scroller?.scrollTo("main", anchor: .bottom)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,12 +89,12 @@ struct MutedEventView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct MutedEventView_Previews: PreviewProvider {
|
struct MutedEventView_Previews: PreviewProvider {
|
||||||
@State static var nav_target: String? = nil
|
@State static var nav_target: NostrEvent = test_event
|
||||||
@State static var navigating: Bool = false
|
@State static var navigating: Bool = false
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
|
|
||||||
MutedEventView(damus_state: test_damus_state(), event: test_event, scroller: nil, nav_target: $nav_target, navigating: $navigating, selected: false)
|
MutedEventView(damus_state: test_damus_state(), event: test_event, scroller: nil, selected: false)
|
||||||
.frame(width: .infinity, height: 50)
|
.frame(width: .infinity, height: 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,7 +207,9 @@ struct EventGroupView: View {
|
|||||||
GroupDescription
|
GroupDescription
|
||||||
|
|
||||||
if let event {
|
if let event {
|
||||||
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: event.id)) {
|
let thread = ThreadModel(event: event, damus_state: state)
|
||||||
|
let dest = ThreadView(state: state, thread: thread)
|
||||||
|
NavigationLink(destination: dest) {
|
||||||
Text(event.content)
|
Text(event.content)
|
||||||
.padding([.top], 1)
|
.padding([.top], 1)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ struct NotificationItemView: View {
|
|||||||
EventGroupView(state: state, event: ev, group: .reaction(evgrp))
|
EventGroupView(state: state, event: ev, group: .reaction(evgrp))
|
||||||
|
|
||||||
case .reply(let ev):
|
case .reply(let ev):
|
||||||
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: ev.id)) {
|
NavigationLink(destination: ThreadView(state: state, thread: ThreadModel(event: ev, damus_state: state))) {
|
||||||
EventView(damus: state, event: ev)
|
EventView(damus: state, event: ev)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
|||||||
@@ -43,39 +43,36 @@ struct SearchResultsView: View {
|
|||||||
case .profile(let prof):
|
case .profile(let prof):
|
||||||
let decoded = try? bech32_decode(prof)
|
let decoded = try? bech32_decode(prof)
|
||||||
let hex = hex_encode(decoded!.data)
|
let hex = hex_encode(decoded!.data)
|
||||||
let prof_model = ProfileModel(pubkey: hex, damus: damus_state)
|
let prof_view = ProfileView(damus_state: damus_state, pubkey: hex)
|
||||||
let f = FollowersModel(damus_state: damus_state, target: prof)
|
NavigationLink(destination: prof_view) {
|
||||||
let dst = ProfileView(damus_state: damus_state, profile: prof_model, followers: f)
|
|
||||||
NavigationLink(destination: dst) {
|
|
||||||
Text("Goto profile \(prof)", comment: "Navigation link to go to profile.")
|
Text("Goto profile \(prof)", comment: "Navigation link to go to profile.")
|
||||||
}
|
}
|
||||||
case .hex(let h):
|
case .hex(let h):
|
||||||
let prof_model = ProfileModel(pubkey: h, damus: damus_state)
|
let prof_view = ProfileView(damus_state: damus_state, pubkey: h)
|
||||||
let f = FollowersModel(damus_state: damus_state, target: h)
|
//let ev_view = ThreadView(damus: damus_state, event_id: h)
|
||||||
let prof_view = ProfileView(damus_state: damus_state, profile: prof_model, followers: f)
|
|
||||||
let ev_view = BuildThreadV2View(
|
|
||||||
damus: damus_state,
|
|
||||||
event_id: h
|
|
||||||
)
|
|
||||||
|
|
||||||
|
NavigationLink(destination: prof_view) {
|
||||||
|
Text("Goto profile \(h)", comment: "Navigation link to go to profile referenced by hex code.")
|
||||||
|
}
|
||||||
|
/*
|
||||||
VStack(spacing: 50) {
|
VStack(spacing: 50) {
|
||||||
NavigationLink(destination: prof_view) {
|
|
||||||
Text("Goto profile \(h)", comment: "Navigation link to go to profile referenced by hex code.")
|
|
||||||
}
|
|
||||||
NavigationLink(destination: ev_view) {
|
NavigationLink(destination: ev_view) {
|
||||||
Text("Goto post \(h)", comment: "Navigation link to go to post referenced by hex code.")
|
Text("Goto post \(h)", comment: "Navigation link to go to post referenced by hex code.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
case .note(let nid):
|
case .note(let nid):
|
||||||
|
/*
|
||||||
let decoded = try? bech32_decode(nid)
|
let decoded = try? bech32_decode(nid)
|
||||||
let hex = hex_encode(decoded!.data)
|
let hex = hex_encode(decoded!.data)
|
||||||
let ev_view = BuildThreadV2View(
|
let ev_view = ThreadView(state: state, ev: ev)
|
||||||
damus: damus_state,
|
*/
|
||||||
event_id: hex
|
Text("Todo: fix this")
|
||||||
)
|
/*
|
||||||
NavigationLink(destination: ev_view) {
|
NavigationLink(destination: ev_view) {
|
||||||
Text("Goto post \(nid)", comment: "Navigation link to go to post referenced by note ID.")
|
Text("Goto post \(nid)", comment: "Navigation link to go to post referenced by note ID.")
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
case .none:
|
case .none:
|
||||||
Text("none", comment: "No search results.")
|
Text("none", comment: "No search results.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,336 +0,0 @@
|
|||||||
//
|
|
||||||
// ThreadV2View.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Thomas Tastet on 25/12/2022.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ThreadV2 {
|
|
||||||
var parentEvents: [NostrEvent]
|
|
||||||
var current: NostrEvent
|
|
||||||
var childEvents: [NostrEvent]
|
|
||||||
|
|
||||||
mutating func clean() {
|
|
||||||
// remove duplicates
|
|
||||||
self.parentEvents = Array(Set(self.parentEvents))
|
|
||||||
self.childEvents = Array(Set(self.childEvents))
|
|
||||||
|
|
||||||
// remove empty contents
|
|
||||||
self.parentEvents = self.parentEvents.filter { event in
|
|
||||||
return !event.content.isEmpty
|
|
||||||
}
|
|
||||||
self.childEvents = self.childEvents.filter { event in
|
|
||||||
return !event.content.isEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort events by publication date
|
|
||||||
self.parentEvents = self.parentEvents.sorted { event1, event2 in
|
|
||||||
return event1 < event2
|
|
||||||
}
|
|
||||||
self.childEvents = self.childEvents.sorted { event1, event2 in
|
|
||||||
return event1 < event2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct BuildThreadV2View: View {
|
|
||||||
let damus: DamusState
|
|
||||||
|
|
||||||
@State var parents_ids: [String] = []
|
|
||||||
let event_id: String
|
|
||||||
|
|
||||||
@State var current_event: NostrEvent? = nil
|
|
||||||
|
|
||||||
@State var thread: ThreadV2? = nil
|
|
||||||
|
|
||||||
@State var current_events_uuid: String = ""
|
|
||||||
@State var extra_events_uuid: String = ""
|
|
||||||
@State var childs_events_uuid: String = ""
|
|
||||||
@State var parents_events_uuids: [String] = []
|
|
||||||
|
|
||||||
@State var subscriptions_uuids: [String] = []
|
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
|
||||||
|
|
||||||
init(damus: DamusState, event_id: String) {
|
|
||||||
self.damus = damus
|
|
||||||
self.event_id = event_id
|
|
||||||
}
|
|
||||||
|
|
||||||
func unsubscribe_all() {
|
|
||||||
print("ThreadV2View: Unsubscribe all..")
|
|
||||||
|
|
||||||
for subscriptions in subscriptions_uuids {
|
|
||||||
unsubscribe(subscriptions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func unsubscribe(_ sub_id: String) {
|
|
||||||
if subscriptions_uuids.contains(sub_id) {
|
|
||||||
damus.pool.unsubscribe(sub_id: sub_id)
|
|
||||||
|
|
||||||
subscriptions_uuids.remove(at: subscriptions_uuids.firstIndex(of: sub_id)!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func subscribe(filters: [NostrFilter], sub_id: String = UUID().description) -> String {
|
|
||||||
damus.pool.register_handler(sub_id: sub_id, handler: handle_event)
|
|
||||||
damus.pool.send(.subscribe(.init(filters: filters, sub_id: sub_id)))
|
|
||||||
|
|
||||||
subscriptions_uuids.append(sub_id)
|
|
||||||
|
|
||||||
return sub_id
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_current_events(ev: NostrEvent) {
|
|
||||||
if current_event != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
current_event = ev
|
|
||||||
|
|
||||||
thread = ThreadV2(
|
|
||||||
parentEvents: [],
|
|
||||||
current: current_event!,
|
|
||||||
childEvents: []
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get parents
|
|
||||||
parents_ids = current_event!.tags.enumerated().filter { (index, tag) in
|
|
||||||
return tag.count >= 2 && tag[0] == "e" && !current_event!.content.contains("#[\(index)]")
|
|
||||||
}.map { tag in
|
|
||||||
return tag.1[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
print("ThreadV2View: Parents list: (\(parents_ids)")
|
|
||||||
|
|
||||||
if parents_ids.count > 0 {
|
|
||||||
// Ask for parents
|
|
||||||
let parents_events = NostrFilter(
|
|
||||||
ids: parents_ids,
|
|
||||||
limit: UInt32(parents_ids.count)
|
|
||||||
)
|
|
||||||
|
|
||||||
let uuid = subscribe(filters: [parents_events])
|
|
||||||
parents_events_uuids.append(uuid)
|
|
||||||
print("ThreadV2View: Ask for parents (\(uuid)) (\(parents_events))")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask for children
|
|
||||||
let childs_events = NostrFilter(
|
|
||||||
kinds: [1],
|
|
||||||
referenced_ids: [self.event_id],
|
|
||||||
limit: 50
|
|
||||||
)
|
|
||||||
childs_events_uuid = subscribe(filters: [childs_events])
|
|
||||||
print("ThreadV2View: Ask for children (\(childs_events) (\(childs_events_uuid))")
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_parent_events(sub_id: String, nostr_event: NostrEvent) {
|
|
||||||
|
|
||||||
// We are filtering this later
|
|
||||||
thread!.parentEvents.append(nostr_event)
|
|
||||||
|
|
||||||
// Get parents of parents
|
|
||||||
let local_parents_ids = nostr_event.tags.enumerated().filter { (index, tag) in
|
|
||||||
return tag.count >= 2 && tag[0] == "e" && !nostr_event.content.contains("#[\(index)]")
|
|
||||||
}.map { tag in
|
|
||||||
return tag.1[1]
|
|
||||||
}.filter { tag_id in
|
|
||||||
return !parents_ids.contains(tag_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
print("ThreadV2View: Sub Parents list: (\(local_parents_ids))")
|
|
||||||
|
|
||||||
// Expand new parents id
|
|
||||||
parents_ids.append(contentsOf: local_parents_ids)
|
|
||||||
|
|
||||||
if local_parents_ids.count > 0 {
|
|
||||||
// Ask for parents
|
|
||||||
let parents_events = NostrFilter(
|
|
||||||
ids: local_parents_ids,
|
|
||||||
limit: UInt32(local_parents_ids.count)
|
|
||||||
)
|
|
||||||
let uuid = subscribe(filters: [parents_events])
|
|
||||||
parents_events_uuids.append(uuid)
|
|
||||||
print("ThreadV2View: Ask for sub_parents (\(local_parents_ids)) \(uuid)")
|
|
||||||
}
|
|
||||||
|
|
||||||
thread!.clean()
|
|
||||||
unsubscribe(sub_id)
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
|
|
||||||
guard case .nostr_event(let nostr_response) = ev else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard case .event(let id, let nostr_event) = nostr_response else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is current event
|
|
||||||
if id == current_events_uuid {
|
|
||||||
handle_current_events(ev: nostr_event)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if parents_events_uuids.contains(id) {
|
|
||||||
handle_parent_events(sub_id: id, nostr_event: nostr_event)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if id == childs_events_uuid {
|
|
||||||
// We are filtering this later
|
|
||||||
thread!.childEvents.append(nostr_event)
|
|
||||||
|
|
||||||
thread!.clean()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func reload() {
|
|
||||||
self.unsubscribe_all()
|
|
||||||
print("ThreadV2View: Reload!")
|
|
||||||
|
|
||||||
var extra = NostrFilter.filter_kinds([9735, 6, 7])
|
|
||||||
extra.referenced_ids = [ self.event_id ]
|
|
||||||
|
|
||||||
// Get the current event
|
|
||||||
current_events_uuid = subscribe(filters: [
|
|
||||||
NostrFilter(ids: [self.event_id], limit: 1)
|
|
||||||
])
|
|
||||||
|
|
||||||
extra_events_uuid = subscribe(filters: [extra])
|
|
||||||
print("subscribing to threadV2 \(event_id) with sub_id \(current_events_uuid)")
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
if thread == nil {
|
|
||||||
ProgressView()
|
|
||||||
} else {
|
|
||||||
ThreadV2View(damus: damus, thread: thread!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
if self.thread == nil {
|
|
||||||
self.reload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onDisappear {
|
|
||||||
self.unsubscribe_all()
|
|
||||||
}
|
|
||||||
.onReceive(handle_notify(.switched_timeline)) { n in
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ThreadV2View: View {
|
|
||||||
let damus: DamusState
|
|
||||||
let thread: ThreadV2
|
|
||||||
@State var nav_target: String? = nil
|
|
||||||
@State var navigating: Bool = false
|
|
||||||
|
|
||||||
var MaybeBuildThreadView: some View {
|
|
||||||
Group {
|
|
||||||
if let evid = nav_target {
|
|
||||||
BuildThreadV2View(damus: damus, event_id: evid)
|
|
||||||
} else {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationLink(destination: MaybeBuildThreadView, isActive: $navigating) {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
ScrollViewReader { reader in
|
|
||||||
ScrollView {
|
|
||||||
VStack {
|
|
||||||
// MARK: - Parents events view
|
|
||||||
VStack {
|
|
||||||
ForEach(thread.parentEvents, id: \.id) { event in
|
|
||||||
MutedEventView(damus_state: damus,
|
|
||||||
event: event,
|
|
||||||
scroller: reader,
|
|
||||||
nav_target: $nav_target,
|
|
||||||
navigating: $navigating,
|
|
||||||
selected: false
|
|
||||||
)
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
.padding(.top, 4)
|
|
||||||
.padding(.leading, 25 * 2)
|
|
||||||
}
|
|
||||||
}.background(GeometryReader { geometry in
|
|
||||||
// get the height and width of the EventView view
|
|
||||||
let eventHeight = geometry.frame(in: .global).height
|
|
||||||
// let eventWidth = geometry.frame(in: .global).width
|
|
||||||
|
|
||||||
// vertical gray line in the background
|
|
||||||
Rectangle()
|
|
||||||
.fill(Color.gray.opacity(0.25))
|
|
||||||
.frame(width: 2, height: eventHeight)
|
|
||||||
.offset(x: 25, y: 40)
|
|
||||||
})
|
|
||||||
|
|
||||||
// MARK: - Actual event view
|
|
||||||
MutedEventView(
|
|
||||||
damus_state: damus,
|
|
||||||
event: thread.current,
|
|
||||||
scroller: reader,
|
|
||||||
nav_target: $nav_target,
|
|
||||||
navigating: $navigating,
|
|
||||||
selected: true
|
|
||||||
).id("main")
|
|
||||||
|
|
||||||
// MARK: - Responses of the actual event view
|
|
||||||
LazyVStack {
|
|
||||||
ForEach(thread.childEvents, id: \.id) { event in
|
|
||||||
MutedEventView(
|
|
||||||
damus_state: damus,
|
|
||||||
event: event,
|
|
||||||
scroller: nil,
|
|
||||||
nav_target: $nav_target,
|
|
||||||
navigating: $navigating,
|
|
||||||
selected: false
|
|
||||||
)
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
.padding([.top], 4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.padding()
|
|
||||||
}.navigationBarTitle(NSLocalizedString("Thread", comment: "Navigation bar title for note thread."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ThreadV2View_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
BuildThreadV2View(damus: test_damus_state(), event_id: "ac9fd97b53b0c1d22b3aea2a3d62e11ae393960f5f91ee1791987d60151339a7")
|
|
||||||
ThreadV2View(
|
|
||||||
damus: test_damus_state(),
|
|
||||||
thread: ThreadV2(
|
|
||||||
parentEvents: [
|
|
||||||
NostrEvent(id: "1", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
|
||||||
NostrEvent(id: "2", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
|
||||||
NostrEvent(id: "3", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
|
||||||
],
|
|
||||||
current: NostrEvent(id: "4", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
|
||||||
childEvents: [
|
|
||||||
NostrEvent(id: "5", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
|
||||||
NostrEvent(id: "6", content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool 4", pubkey: "916b7aca250f43b9f842faccc831db4d155088632a8c27c0d140f2043331ba57"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
101
damus/Views/ThreadView.swift
Normal file
101
damus/Views/ThreadView.swift
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
//
|
||||||
|
// ThreadV2View.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Thomas Tastet on 25/12/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ThreadView: View {
|
||||||
|
let state: DamusState
|
||||||
|
|
||||||
|
@StateObject var thread: ThreadModel
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
|
var parent_events: [NostrEvent] {
|
||||||
|
state.events.parent_events(event: thread.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
var child_events: [NostrEvent] {
|
||||||
|
state.events.child_events(event: thread.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollViewReader { reader in
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack {
|
||||||
|
// MARK: - Parents events view
|
||||||
|
ForEach(parent_events, id: \.id) { parent_event in
|
||||||
|
MutedEventView(damus_state: state,
|
||||||
|
event: parent_event,
|
||||||
|
scroller: reader,
|
||||||
|
selected: false
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
thread.set_active_event(parent_event, privkey: state.keypair.privkey)
|
||||||
|
scroll_to_event(scroller: reader, id: parent_event.id, delay: 0.1, animate: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.padding(.top, 4)
|
||||||
|
.padding(.leading, 25 * 2)
|
||||||
|
}.background(GeometryReader { geometry in
|
||||||
|
// get the height and width of the EventView view
|
||||||
|
let eventHeight = geometry.frame(in: .global).height
|
||||||
|
// let eventWidth = geometry.frame(in: .global).width
|
||||||
|
|
||||||
|
// vertical gray line in the background
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.gray.opacity(0.25))
|
||||||
|
.frame(width: 2, height: eventHeight)
|
||||||
|
.offset(x: 25, y: 40)
|
||||||
|
})
|
||||||
|
|
||||||
|
// MARK: - Actual event view
|
||||||
|
MutedEventView(
|
||||||
|
damus_state: state,
|
||||||
|
event: self.thread.event,
|
||||||
|
scroller: reader,
|
||||||
|
selected: true
|
||||||
|
)
|
||||||
|
.id(self.thread.event.id)
|
||||||
|
|
||||||
|
ForEach(child_events, id: \.id) { child_event in
|
||||||
|
MutedEventView(
|
||||||
|
damus_state: state,
|
||||||
|
event: child_event,
|
||||||
|
scroller: nil,
|
||||||
|
selected: false
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
thread.set_active_event(child_event, privkey: state.keypair.privkey)
|
||||||
|
scroll_to_event(scroller: reader, id: child_event.id, delay: 0.1, animate: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.padding([.top], 4)
|
||||||
|
}
|
||||||
|
}.padding()
|
||||||
|
}.navigationBarTitle(NSLocalizedString("Thread", comment: "Navigation bar title for note thread."))
|
||||||
|
.onAppear {
|
||||||
|
thread.subscribe()
|
||||||
|
scroll_to_event(scroller: reader, id: self.thread.event.id, delay: 0.0, animate: false)
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
thread.unsubscribe()
|
||||||
|
}
|
||||||
|
.onReceive(handle_notify(.switched_timeline)) { notif in
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
let state = test_damus_state()
|
||||||
|
let thread = ThreadModel(event: test_event, damus_state: state)
|
||||||
|
ThreadView(state: state, thread: thread)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,21 +13,22 @@ struct InnerTimelineView: View {
|
|||||||
let damus: DamusState
|
let damus: DamusState
|
||||||
let show_friend_icon: Bool
|
let show_friend_icon: Bool
|
||||||
let filter: (NostrEvent) -> Bool
|
let filter: (NostrEvent) -> Bool
|
||||||
@State var nav_target: NostrEvent? = nil
|
@State var nav_target: NostrEvent
|
||||||
@State var navigating: Bool = false
|
@State var navigating: Bool = false
|
||||||
|
|
||||||
var MaybeBuildThreadView: some View {
|
init(events: EventHolder, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool) {
|
||||||
Group {
|
self.events = events
|
||||||
if let ev = nav_target {
|
self.damus = damus
|
||||||
BuildThreadV2View(damus: damus, event_id: (ev.inner_event ?? ev).id)
|
self.show_friend_icon = show_friend_icon
|
||||||
} else {
|
self.filter = filter
|
||||||
EmptyView()
|
// dummy event to avoid MaybeThreadView
|
||||||
}
|
self._nav_target = State(initialValue: test_event)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationLink(destination: MaybeBuildThreadView, isActive: $navigating) {
|
let thread = ThreadModel(event: nav_target, damus_state: damus)
|
||||||
|
let dest = ThreadView(state: damus, thread: thread)
|
||||||
|
NavigationLink(destination: dest, isActive: $navigating) {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
LazyVStack(spacing: 0) {
|
LazyVStack(spacing: 0) {
|
||||||
@@ -55,7 +56,7 @@ struct InnerTimelineView: View {
|
|||||||
|
|
||||||
struct InnerTimelineView_Previews: PreviewProvider {
|
struct InnerTimelineView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
InnerTimelineView(events: test_event_holder, damus: test_damus_state(), show_friend_icon: true, filter: { _ in true }, nav_target: nil, navigating: false)
|
InnerTimelineView(events: test_event_holder, damus: test_damus_state(), show_friend_icon: true, filter: { _ in true })
|
||||||
.frame(width: 300, height: 500)
|
.frame(width: 300, height: 500)
|
||||||
.border(Color.red)
|
.border(Color.red)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user