Browse Source

Improved visual composition for threads

Changelog-Changed: Improve visual composition of threads
post-button-style
Thomas 2 years ago
committed by William Casarin
parent
commit
d1cbf74840
  1. 4
      damus.xcodeproj/project.pbxproj
  2. 4
      damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
  3. 3
      damus/ContentView.swift
  4. 14
      damus/Nostr/NostrEvent.swift
  5. 2
      damus/Views/ChatView.swift
  6. 2
      damus/Views/DMView.swift
  7. 3
      damus/Views/EventActionBar.swift
  8. 222
      damus/Views/EventView.swift
  9. 8
      damus/Views/NoteContentView.swift
  10. 12
      damus/Views/ProfileName.swift
  11. 2
      damus/Views/ReplyQuoteView.swift
  12. 13
      damus/Views/SearchResultsView.swift
  13. 314
      damus/Views/ThreadV2View.swift
  14. 11
      damus/Views/TimelineView.swift

4
damus.xcodeproj/project.pbxproj

@ -131,6 +131,7 @@
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -309,6 +310,7 @@
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = "<group>"; };
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -478,6 +480,7 @@
4C216F33286F5ACD00040376 /* DMView.swift */,
4C06670028FC7C5900038D2A /* RelayView.swift */,
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */,
);
path = Views;
sourceTree = "<group>";
@ -792,6 +795,7 @@
4C363A9A28283854006E126D /* Reply.swift in Sources */,
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,

4
damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

@ -40,8 +40,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/SparrowTek/Vault",
"state" : {
"revision" : "f5707fac23f4a17b3e5ed32dd444f502773615ae",
"version" : "1.0.2"
"revision" : "87db56c3c8b6421c65b0745f73e08b0dc56f79d4",
"version" : "1.0.3"
}
}
],

3
damus/ContentView.swift

@ -166,8 +166,7 @@ struct ContentView: View {
var MaybeThreadView: some View {
Group {
if let evid = self.active_event_id {
let thread_model = ThreadModel(evid: evid, damus_state: damus_state!)
ThreadView(thread: thread_model, damus: damus_state!, is_chatroom: false)
BuildThreadV2View(damus: damus_state!, event_id: evid)
} else {
EmptyView()
}

14
damus/Nostr/NostrEvent.swift

@ -45,11 +45,19 @@ struct EventId: Identifiable, CustomStringConvertible {
}
}
class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable {
class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Hashable, Comparable {
static func == (lhs: NostrEvent, rhs: NostrEvent) -> Bool {
return lhs.id == rhs.id
}
static func < (lhs: NostrEvent, rhs: NostrEvent) -> Bool {
return lhs.created_at < rhs.created_at
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
var id: String
var sig: String
var tags: [[String]]
@ -264,7 +272,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable {
return (self.flags & 1) != 0
}
init(content: String, pubkey: String, kind: Int = 1, tags: [[String]] = []) {
init(content: String, pubkey: String, kind: Int = 1, tags: [[String]] = [], createdAt: Int64 = Int64(Date().timeIntervalSince1970)) {
self.id = ""
self.sig = ""
@ -272,7 +280,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable {
self.pubkey = pubkey
self.kind = kind
self.tags = tags
self.created_at = Int64(Date().timeIntervalSince1970)
self.created_at = createdAt
}
/// Intiialization statement used to specificy ID

2
damus/Views/ChatView.swift

@ -106,7 +106,7 @@ struct ChatView: View {
}
}
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.content))
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.content), size: .normal)
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
let bar = make_actionbar_model(ev: event, damus: damus_state)

2
damus/Views/DMView.swift

@ -21,7 +21,7 @@ struct DMView: View {
Spacer()
}
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.get_content(damus_state.keypair.privkey)))
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), size: .normal)
.foregroundColor(is_ours ? Color.white : Color.primary)
.padding(10)
.background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15))

3
damus/Views/EventActionBar.swift

@ -86,7 +86,6 @@ struct EventActionBar: View {
}
*/
}
.padding(.top, 1)
.alert("Boost", isPresented: $confirm_boost) {
Button("Cancel") {
confirm_boost = false
@ -139,7 +138,7 @@ struct EventActionBar: View {
func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View {
Button(action: action) {
Label("", systemImage: img)
Label("&nbsp;", systemImage: img)
.font(.footnote.weight(.medium))
.foregroundColor(col == nil ? Color.gray : col!)
}

222
damus/Views/EventView.swift

@ -36,6 +36,90 @@ enum Highlight {
}
}
enum EventViewKind {
case small
case normal
case big
case selected
}
func eventviewsize_to_font(_ size: EventViewKind) -> Font {
switch size {
case .small:
return .caption
case .normal:
return .body
case .big:
return .headline
case .selected:
return .headline
}
}
struct BuilderEventView: View {
let damus: DamusState
let event_id: String
@State var event: NostrEvent?
@State var subscription_uuid: String = UUID().description
func unsubscribe() {
damus.pool.unsubscribe(sub_id: subscription_uuid)
}
func subscribe(filters: [NostrFilter]) {
damus.pool.register_handler(sub_id: subscription_uuid, handler: handle_event)
damus.pool.send(.subscribe(.init(filters: filters, sub_id: subscription_uuid)))
}
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 == subscription_uuid {
if event != nil {
return
}
event = nostr_event
unsubscribe()
}
}
func load() {
subscribe(filters: [
NostrFilter(
ids: [self.event_id],
limit: 1
)
])
}
var body: some View {
VStack {
if event == nil {
ProgressView().padding()
} else {
NavigationLink(destination: BuildThreadV2View(damus: damus, event_id: event!.id)) {
EventView(damus: damus, event: event!, show_friend_icon: true, size: .small, embedded: true)
}.buttonStyle(.plain)
}
}
.frame(minWidth: 0, maxWidth: .infinity)
.border(Color.gray.opacity(0.2), width: 1)
.cornerRadius(2)
.onAppear {
self.load()
}
}
}
struct EventView: View {
let event: NostrEvent
let highlight: Highlight
@ -43,34 +127,42 @@ struct EventView: View {
let damus: DamusState
let pubkey: String
let show_friend_icon: Bool
let size: EventViewKind
let embedded: Bool
@EnvironmentObject var action_bar: ActionBarModel
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool) {
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
self.event = event
self.highlight = highlight
self.has_action_bar = has_action_bar
self.damus = damus
self.pubkey = event.pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool) {
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
self.event = event
self.highlight = .none
self.has_action_bar = false
self.damus = damus
self.pubkey = event.pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool) {
init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
self.event = event
self.highlight = .none
self.has_action_bar = false
self.damus = damus
self.pubkey = pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
var body: some View {
@ -108,25 +200,44 @@ struct EventView: View {
func TextEvent(_ event: NostrEvent, pubkey: String) -> some View {
let content = event.get_content(damus.keypair.privkey)
return HStack(alignment: .top) {
let profile = damus.profiles.lookup(id: pubkey)
VStack {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
if size != .selected {
VStack {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
if !embedded {
NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
}
}
Spacer()
}
Spacer()
}
VStack(alignment: .leading) {
HStack(alignment: .center) {
EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon)
Text("\(format_relative_time(event.created_at))")
.font(.body)
.foregroundColor(.gray)
if size == .selected {
VStack {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
}
}
}
EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon, size: size)
if size != .selected {
Text("\(format_relative_time(event.created_at))")
.font(eventviewsize_to_font(size))
.foregroundColor(.gray)
}
}
if event.is_reply(damus.keypair.privkey) {
@ -136,16 +247,55 @@ struct EventView: View {
.frame(maxWidth: .infinity, alignment: .leading)
}
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, show_images: should_show_images(contacts: damus.contacts, ev: event), artifacts: .just_content(content))
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, show_images: should_show_images(contacts: damus.contacts, ev: event), artifacts: .just_content(content), size: self.size)
.frame(maxWidth: .infinity, alignment: .leading)
if has_action_bar {
let bar = make_actionbar_model(ev: event, damus: damus)
EventActionBar(damus_state: damus, event: event, bar: bar)
.allowsHitTesting(!embedded)
if !embedded {
let blocks = event.blocks(damus.keypair.privkey).filter { block in
guard case .mention(let mention) = block else {
return false
}
guard case .event = mention.type else {
return false
}
if mention.ref.key != "e" {
return false
}
return true
}
/// MARK: - Preview
if let firstBlock = blocks.first, case .mention(let mention) = firstBlock, mention.ref.key == "e" {
BuilderEventView(damus: damus, event_id: mention.ref.id)
}
}
Divider()
.padding([.top], 4)
if !embedded {
if has_action_bar {
if size == .selected {
Text("\(format_date(event.created_at))")
.padding(.top, 10)
.font(.footnote)
.foregroundColor(.gray)
Divider()
.padding([.bottom], 4)
} else {
Rectangle().frame(height: 2).opacity(0)
}
let bar = make_actionbar_model(ev: event, damus: damus)
EventActionBar(damus_state: damus, event: event, bar: bar)
}
Divider()
.padding([.top], 4)
}
}
.padding([.leading], 2)
}
@ -231,6 +381,15 @@ func format_relative_time(_ created_at: Int64) -> String
return time_ago_since(Date(timeIntervalSince1970: Double(created_at)))
}
func format_date(_ created_at: Int64) -> String {
let date = Date(timeIntervalSince1970: TimeInterval(created_at))
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .short
dateFormatter.dateStyle = .short
return dateFormatter.string(from: date)
}
func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
let desc = make_reply_description(event.tags)
let pubkeys = desc.pubkeys
@ -285,6 +444,23 @@ func make_actionbar_model(ev: NostrEvent, damus: DamusState) -> ActionBarModel {
struct EventView_Previews: PreviewProvider {
static var previews: some View {
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true)
VStack {
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .small)
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .normal)
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .big)
EventView(
event: NostrEvent(
content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool",
pubkey: "pk",
createdAt: Int64(Date().timeIntervalSince1970 - 100)
),
highlight: .none,
has_action_bar: true,
damus: test_damus_state(),
show_friend_icon: true,
size: .selected
)
}
}
}

8
damus/Views/NoteContentView.swift

@ -57,6 +57,8 @@ struct NoteContentView: View {
@State var artifacts: NoteArtifacts
let size: EventViewKind
func MainContent() -> some View {
let md_opts: AttributedString.MarkdownParsingOptions =
.init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
@ -64,10 +66,10 @@ struct NoteContentView: View {
return VStack(alignment: .leading) {
if let txt = try? AttributedString(markdown: artifacts.content, options: md_opts) {
Text(txt)
.font(.body)
.font(eventviewsize_to_font(size))
} else {
Text(artifacts.content)
.font(.body)
.font(eventviewsize_to_font(size))
}
if show_images && artifacts.images.count > 0 {
ImageCarousel(urls: artifacts.images)
@ -126,6 +128,6 @@ struct NoteContentView_Previews: PreviewProvider {
let state = test_damus_state()
let content = "hi there https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg"
let artifacts = NoteArtifacts(content: content, images: [], invoices: [])
NoteContentView(privkey: "", event: NostrEvent(content: content, pubkey: "pk"), profiles: state.profiles, show_images: true, artifacts: artifacts)
NoteContentView(privkey: "", event: NostrEvent(content: content, pubkey: "pk"), profiles: state.profiles, show_images: true, artifacts: artifacts, size: .normal)
}
}

12
damus/Views/ProfileName.swift

@ -103,20 +103,24 @@ struct EventProfileName: View {
@State var display_name: String?
init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool) {
let size: EventViewKind
init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool, size: EventViewKind = .normal) {
self.pubkey = pubkey
self.profile = profile
self.prefix = ""
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed
self.size = size
}
init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool) {
init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool, size: EventViewKind = .normal) {
self.pubkey = pubkey
self.profile = profile
self.prefix = prefix
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed
self.size = size
}
var friend_icon: String? {
@ -143,10 +147,10 @@ struct EventProfileName: View {
Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
.foregroundColor(.gray)
.font(.body)
.font(eventviewsize_to_font(size))
} else {
Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
.font(.body)
.font(eventviewsize_to_font(size))
.fontWeight(.bold)
}

2
damus/Views/ReplyQuoteView.swift

@ -31,7 +31,7 @@ struct ReplyQuoteView: View {
.foregroundColor(.gray)
}
NoteContentView(privkey: privkey, event: event, profiles: profiles, show_images: false, artifacts: .just_content(event.content))
NoteContentView(privkey: privkey, event: event, profiles: profiles, show_images: false, artifacts: .just_content(event.content), size: .normal)
.font(.callout)
.foregroundColor(.accentColor)

13
damus/Views/SearchResultsView.swift

@ -53,8 +53,11 @@ struct SearchResultsView: View {
let prof_model = ProfileModel(pubkey: h, damus: damus_state)
let f = FollowersModel(damus_state: damus_state, target: h)
let prof_view = ProfileView(damus_state: damus_state, profile: prof_model, followers: f)
let thread_model = ThreadModel(evid: h, damus_state: damus_state)
let ev_view = ThreadView(thread: thread_model, damus: damus_state, is_chatroom: false)
let ev_view = BuildThreadV2View(
damus: damus_state,
event_id: h
)
VStack(spacing: 50) {
NavigationLink(destination: prof_view) {
Text("Goto profile \(h)")
@ -66,8 +69,10 @@ struct SearchResultsView: View {
case .note(let nid):
let decoded = try? bech32_decode(nid)
let hex = hex_encode(decoded!.data)
let thread_model = ThreadModel(evid: hex, damus_state: damus_state)
let ev_view = ThreadView(thread: thread_model, damus: damus_state, is_chatroom: false)
let ev_view = BuildThreadV2View(
damus: damus_state,
event_id: hex
)
NavigationLink(destination: ev_view) {
Text("Goto post \(nid)")
}

314
damus/Views/ThreadV2View.swift

@ -0,0 +1,314 @@
//
// 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_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 {
if current_event != nil {
return
}
current_event = nostr_event
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(
referenced_ids: [self.event_id],
limit: 50
)
childs_events_uuid = subscribe(filters: [childs_events])
print("ThreadV2View: Ask for children (\(childs_events) (\(childs_events_uuid))")
return
}
if parents_events_uuids.contains(id) {
// 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(id)
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: .center)
}
}
}.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.5))
.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"),
]
)
)
}
}

11
damus/Views/TimelineView.swift

@ -24,11 +24,14 @@ struct InnerTimelineView: View {
EmptyTimelineView()
} else {
ForEach(events.filter(filter), id: \.id) { (ev: NostrEvent) in
let tm = ThreadModel(event: inner_event_or_self(ev: ev), damus_state: damus)
let is_chatroom = should_show_chatroom(ev)
let tv = ThreadView(thread: tm, damus: damus, is_chatroom: is_chatroom)
//let tm = ThreadModel(event: inner_event_or_self(ev: ev), damus_state: damus)
//let is_chatroom = should_show_chatroom(ev)
//let tv = ThreadView(thread: tm, damus: damus, is_chatroom: is_chatroom)
NavigationLink(destination: tv) {
NavigationLink(destination: BuildThreadV2View(
damus: damus,
event_id: ev.id
)) {
EventView(event: ev, highlight: .none, has_action_bar: true, damus: damus, show_friend_icon: show_friend_icon)
}
.isDetailLink(true)

Loading…
Cancel
Save