Files
damus/damus/Views/ThreadV2View.swift
2023-01-02 18:29:48 -08:00

326 lines
11 KiB
Swift

//
// 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 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!")
// Get the current event
current_events_uuid = subscribe(filters: [
NostrFilter(
ids: [self.event_id],
limit: 1
)
])
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
var body: some View {
ScrollViewReader { reader in
ScrollView {
VStack {
// MARK: - Parents events view
VStack {
ForEach(thread.parentEvents, id: \.id) { event in
NavigationLink(destination: BuildThreadV2View(
damus: damus,
event_id: event.id
)){
EventView(
event: event,
highlight: .none,
has_action_bar: true,
damus: damus,
show_friend_icon: true, // TODO: change it
size: .small
)
}
.buttonStyle(.plain)
.onAppear {
// TODO: find another solution to prevent layout shifting and layout blocking on large responses
reader.scrollTo("main", anchor: .bottom)
}
}
}.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
EventView(
event: thread.current,
highlight: .none,
has_action_bar: true,
damus: damus,
show_friend_icon: true, // TODO: change it
size: .selected
).id("main")
// MARK: - Responses of the actual event view
ForEach(thread.childEvents, id: \.id) { event in
NavigationLink(destination: BuildThreadV2View(
damus: damus,
event_id: event.id
)){
EventView(
event: event,
highlight: .none,
has_action_bar: true,
damus: damus,
show_friend_icon: true, // TODO: change it
size: .small
)
}.buttonStyle(.plain)
}
}.padding()
}.navigationBarTitle("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"),
]
)
)
}
}