Browse Source

fix broken nagivation

Signed-off-by: William Casarin <jb55@jb55.com>
profiles-everywhere
William Casarin 3 years ago
parent
commit
4de2ce402e
  1. 4
      damus.xcodeproj/project.pbxproj
  2. 69
      damus/ContentView.swift
  3. 50
      damus/Models/ThreadModel.swift
  4. 2
      damus/Nostr/RelayConnection.swift
  5. 38
      damus/Notifications.swift
  6. 27
      damus/Views/ChatView.swift
  7. 17
      damus/Views/ChatroomView.swift
  8. 15
      damus/Views/EventActionBar.swift
  9. 62
      damus/Views/EventDetailView.swift
  10. 24
      damus/Views/EventView.swift
  11. 22
      damus/Views/PostView.swift
  12. 8
      damus/Views/ProfilePicView.swift
  13. 13
      damus/Views/ReplyQuoteView.swift
  14. 37
      damus/Views/ThreadView.swift
  15. 38
      damus/Views/TimelineView.swift

4
damus.xcodeproj/project.pbxproj

@ -26,6 +26,7 @@
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; }; 4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; }; 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; }; 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEE627F7A08100C66700 /* damusApp.swift */; }; 4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEE627F7A08100C66700 /* damusApp.swift */; };
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEE827F7A08100C66700 /* ContentView.swift */; }; 4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEE827F7A08100C66700 /* ContentView.swift */; };
4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CE6DEEA27F7A08200C66700 /* Assets.xcassets */; }; 4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CE6DEEA27F7A08200C66700 /* Assets.xcassets */; };
@ -83,6 +84,7 @@
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; }; 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; }; 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; }; 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
4CE6DEE327F7A08100C66700 /* damus.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = damus.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4CE6DEE327F7A08100C66700 /* damus.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = damus.app; sourceTree = BUILT_PRODUCTS_DIR; };
4CE6DEE627F7A08100C66700 /* damusApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = damusApp.swift; sourceTree = "<group>"; }; 4CE6DEE627F7A08100C66700 /* damusApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = damusApp.swift; sourceTree = "<group>"; };
4CE6DEE827F7A08100C66700 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; 4CE6DEE827F7A08100C66700 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@ -211,6 +213,7 @@
4CE6DEEA27F7A08200C66700 /* Assets.xcassets */, 4CE6DEEA27F7A08200C66700 /* Assets.xcassets */,
4CE6DEEC27F7A08200C66700 /* Preview Content */, 4CE6DEEC27F7A08200C66700 /* Preview Content */,
4CEE2AF4280B29E600AB5EEF /* TimeAgo.swift */, 4CEE2AF4280B29E600AB5EEF /* TimeAgo.swift */,
4CE4F8CC281352B30009DFBB /* Notifications.swift */,
); );
path = damus; path = damus;
sourceTree = "<group>"; sourceTree = "<group>";
@ -386,6 +389,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */,
4C75EFB728049D990006080F /* RelayPool.swift in Sources */, 4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */, 4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */, 4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,

69
damus/ContentView.swift

@ -24,6 +24,11 @@ enum Sheets: Identifiable {
} }
} }
enum ThreadState {
case event_details
case chatroom
}
enum Timeline: String, CustomStringConvertible { enum Timeline: String, CustomStringConvertible {
case home case home
case notifications case notifications
@ -42,11 +47,13 @@ struct ContentView: View {
@State var loading: Bool = true @State var loading: Bool = true
@State var pool: RelayPool? = nil @State var pool: RelayPool? = nil
@State var selected_timeline: Timeline? = .home @State var selected_timeline: Timeline? = .home
@StateObject var thread: ThreadModel = ThreadModel()
@State var is_thread_open: Bool = false
@State var last_event_of_kind: [String: [Int: NostrEvent]] = [:] @State var last_event_of_kind: [String: [Int: NostrEvent]] = [:]
@State var has_events: [String: ()] = [:] @State var has_events: [String: ()] = [:]
@State var has_friend_event: [String: ()] = [:] @State var has_friend_event: [String: ()] = [:]
@State var new_notifications: Bool = false @State var new_notifications: Bool = false
@State var event: NostrEvent? = nil
@State var events: [NostrEvent] = [] @State var events: [NostrEvent] = []
@State var friend_events: [NostrEvent] = [] @State var friend_events: [NostrEvent] = []
@State var notifications: [NostrEvent] = [] @State var notifications: [NostrEvent] = []
@ -129,6 +136,7 @@ struct ContentView: View {
ZStack { ZStack {
if let pool = self.pool { if let pool = self.pool {
TimelineView(events: $friend_events, pool: pool) TimelineView(events: $friend_events, pool: pool)
.environmentObject(thread)
.environmentObject(profiles) .environmentObject(profiles)
} }
PostButtonContainer PostButtonContainer
@ -138,24 +146,35 @@ struct ContentView: View {
func MainContent(pool: RelayPool) -> some View { func MainContent(pool: RelayPool) -> some View {
NavigationView { NavigationView {
VStack { VStack {
PostingTimelineView switch selected_timeline {
.onAppear() { case .home:
switch_timeline(.home) PostingTimelineView
} .onAppear() {
switch_timeline(.home)
let notif = TimelineView(events: $notifications, pool: pool) }
.environmentObject(profiles)
.navigationTitle("Notifications") case .notifications:
.navigationBarBackButtonHidden(true) TimelineView(events: $notifications, pool: pool)
.environmentObject(profiles)
.navigationTitle("Notifications")
case .global:
TimelineView(events: $events, pool: pool)
.environmentObject(profiles)
.navigationTitle("Global")
case .none:
EmptyView()
}
let global = TimelineView(events: $events, pool: pool) let tv = ThreadView()
.environmentObject(thread)
.environmentObject(profiles) .environmentObject(profiles)
.navigationTitle("Global") .padding([.leading, .trailing], 6)
.navigationBarBackButtonHidden(true)
NavigationLink(destination: notif, tag: .notifications, selection: $selected_timeline) { EmptyView() } NavigationLink(destination: tv, isActive: $is_thread_open) {
EmptyView()
NavigationLink(destination: global, tag: .global, selection: $selected_timeline) { EmptyView() } }
} }
.navigationBarTitle("Damus", displayMode: .inline) .navigationBarTitle("Damus", displayMode: .inline)
} }
@ -183,6 +202,16 @@ struct ContentView: View {
PostView(references: []) PostView(references: [])
} }
} }
.onReceive(NotificationCenter.default.publisher(for: .open_thread)) { obj in
let ev = obj.object as! NostrEvent
thread.reset_events()
thread.set_active_event(ev)
is_thread_open = true
}
.onReceive(NotificationCenter.default.publisher(for: .broadcast_event)) { obj in
let ev = obj.object as! NostrEvent
self.pool?.send(.event(ev))
}
.onReceive(NotificationCenter.default.publisher(for: .post)) { obj in .onReceive(NotificationCenter.default.publisher(for: .post)) { obj in
let post_res = obj.object as! NostrPostResult let post_res = obj.object as! NostrPostResult
@ -253,11 +282,10 @@ struct ContentView: View {
add_relay(pool, "wss://nostr-relay.freeberty.net") add_relay(pool, "wss://nostr-relay.freeberty.net")
add_relay(pool, "wss://nostr-relay.untethr.me") add_relay(pool, "wss://nostr-relay.untethr.me")
pool.register_handler(sub_id: sub_id) { (relay_id, ev) in pool.register_handler(sub_id: sub_id, handler: handle_event)
self.handle_event(relay_id: relay_id, conn_event: ev)
}
self.pool = pool self.pool = pool
self.thread.pool = pool
pool.connect() pool.connect()
} }
@ -439,11 +467,13 @@ struct ContentView: View {
} }
} }
/*
struct ContentView_Previews: PreviewProvider { struct ContentView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
ContentView() ContentView()
} }
} }
*/
func get_metadata_since_time(_ metadata_event: NostrEvent?) -> Int64? { func get_metadata_since_time(_ metadata_event: NostrEvent?) -> Int64? {
@ -547,3 +577,4 @@ func save_last_notified(_ ev: NostrEvent) {
UserDefaults.standard.set(ev.id, forKey: "last_notification") UserDefaults.standard.set(ev.id, forKey: "last_notification")
UserDefaults.standard.set(String(ev.created_at), forKey: "last_notification_time") UserDefaults.standard.set(String(ev.created_at), forKey: "last_notification_time")
} }

50
damus/Models/ThreadModel.swift

@ -9,39 +9,53 @@ import Foundation
/// manages the lifetime of a thread /// manages the lifetime of a thread
class ThreadModel: ObservableObject { class ThreadModel: ObservableObject {
@Published var event: NostrEvent @Published var event: NostrEvent? = nil
@Published var events: [NostrEvent] = [] @Published var events: [NostrEvent] = []
@Published var event_map: [String: Int] = [:] @Published var event_map: [String: Int] = [:]
var replies: ReplyMap = ReplyMap() var replies: ReplyMap = ReplyMap()
let pool: RelayPool var pool: RelayPool? = nil
let sub_id = UUID().description var sub_id = UUID().description
init(event: NostrEvent, pool: RelayPool) { deinit {
self.event = event unsubscribe()
self.pool = pool
add_event(event)
} }
func unsubscribe() { func unsubscribe() {
guard let event = self.event else {
return
}
print("unsubscribing from thread \(event.id) with sub_id \(sub_id)") print("unsubscribing from thread \(event.id) with sub_id \(sub_id)")
self.pool.remove_handler(sub_id: sub_id) self.pool?.remove_handler(sub_id: sub_id)
self.pool.send(.unsubscribe(sub_id)) self.pool?.send(.unsubscribe(sub_id))
}
func reset_events() {
self.events.removeAll()
self.event_map.removeAll()
self.replies.replies.removeAll()
}
func set_active_event(_ ev: NostrEvent) {
unsubscribe()
self.event = ev
add_event(ev)
subscribe(ev)
} }
func subscribe() { private func subscribe(_ ev: NostrEvent) {
var ref_events = NostrFilter.filter_text var ref_events = NostrFilter.filter_text
var events = NostrFilter.filter_text var events_filter = NostrFilter.filter_text
// TODO: add referenced relays // TODO: add referenced relays
ref_events.referenced_ids = event.referenced_ids.map { $0.ref_id } ref_events.referenced_ids = ev.referenced_ids.map { $0.ref_id }
ref_events.referenced_ids!.append(event.id) ref_events.referenced_ids!.append(ev.id)
events.ids = ref_events.referenced_ids! events_filter.ids = ref_events.referenced_ids!
print("subscribing to thread \(event.id) with sub_id \(sub_id)") print("subscribing to thread \(ev.id) with sub_id \(sub_id)")
pool.register_handler(sub_id: sub_id, handler: handle_event) pool?.register_handler(sub_id: sub_id, handler: handle_event)
pool.send(.subscribe(.init(filters: [ref_events, events], sub_id: sub_id))) pool?.send(.subscribe(.init(filters: [ref_events, events_filter], sub_id: sub_id)))
} }
func lookup(_ event_id: String) -> NostrEvent? { func lookup(_ event_id: String) -> NostrEvent? {
@ -83,7 +97,7 @@ class ThreadModel: ObservableObject {
case .notice(let note): case .notice(let note):
if note.contains("Too many subscription filters") { if note.contains("Too many subscription filters") {
// TODO: resend filters? // TODO: resend filters?
pool.reconnect(to: [relay_id]) pool?.reconnect(to: [relay_id])
} }
break break
} }

2
damus/Nostr/RelayConnection.swift

@ -148,7 +148,7 @@ func make_nostr_subscription_req(_ filters: [NostrFilter], sub_id: String) -> St
} }
func make_websocket(url: URL) -> WebSocket { func make_websocket(url: URL) -> WebSocket {
var req = URLRequest(url: url) let req = URLRequest(url: url)
//req.setValue("chat,superchat", forHTTPHeaderField: "Sec-WebSocket-Protocol") //req.setValue("chat,superchat", forHTTPHeaderField: "Sec-WebSocket-Protocol")
return WebSocket(request: req, compressionHandler: .none) return WebSocket(request: req, compressionHandler: .none)
} }

38
damus/Notifications.swift

@ -0,0 +1,38 @@
//
// Notifications.swift
// damus
//
// Created by William Casarin on 2022-04-22.
//
import Foundation
extension Notification.Name {
static var thread_focus: Notification.Name {
return Notification.Name("thread focus")
}
}
extension Notification.Name {
static var select_event: Notification.Name {
return Notification.Name("select_event")
}
}
extension Notification.Name {
static var broadcast_event: Notification.Name {
return Notification.Name("broadcast event")
}
}
extension Notification.Name {
static var open_thread: Notification.Name {
return Notification.Name("open thread")
}
}
extension Notification.Name {
static var post: Notification.Name {
return Notification.Name("send post")
}
}

27
damus/Views/ChatView.swift

@ -20,7 +20,10 @@ struct ChatView: View {
} }
var is_active: Bool { var is_active: Bool {
thread.event.id == event.id guard let ev = thread.event else {
return false
}
return ev.id == event.id
} }
func prev_reply_is_same() -> String? { func prev_reply_is_same() -> String? {
@ -98,7 +101,7 @@ struct ChatView: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.textSelection(.enabled) .textSelection(.enabled)
if next_ev == nil || next_ev!.pubkey != event.pubkey { if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
EventActionBar(event: event) EventActionBar(event: event)
.environmentObject(profiles) .environmentObject(profiles)
} }
@ -112,30 +115,14 @@ struct ChatView: View {
.id(event.id) .id(event.id)
.frame(minHeight: just_started ? PFP_SIZE : 0) .frame(minHeight: just_started ? PFP_SIZE : 0)
.padding([.bottom], next_ev == nil ? 4 : 0) .padding([.bottom], next_ev == nil ? 4 : 0)
.onTapGesture {
if is_active {
convert_to_thread()
} else {
thread.event = event
}
}
//.border(Color.green) //.border(Color.green)
} }
@Environment(\.presentationMode) var presmode
func dismiss() {
presmode.wrappedValue.dismiss()
}
func convert_to_thread() {
NotificationCenter.default.post(name: .convert_to_thread, object: nil)
}
} }
extension Notification.Name { extension Notification.Name {
static var convert_to_thread: Notification.Name { static var toggle_thread_view: Notification.Name {
return Notification.Name("convert_to_thread") return Notification.Name("convert_to_thread")
} }
} }
@ -149,3 +136,5 @@ struct ChatView_Previews: PreviewProvider {
} }
*/ */

17
damus/Views/ChatroomView.swift

@ -9,26 +9,39 @@ import SwiftUI
struct ChatroomView: View { struct ChatroomView: View {
@EnvironmentObject var thread: ThreadModel @EnvironmentObject var thread: ThreadModel
@Environment(\.dismiss) var dismiss
var body: some View { var body: some View {
ScrollViewReader { scroller in ScrollViewReader { scroller in
ScrollView { ScrollView {
VStack { LazyVStack {
let count = thread.events.count let count = thread.events.count
ForEach(Array(zip(thread.events, thread.events.indices)), id: \.0.id) { (ev, ind) in ForEach(Array(zip(thread.events, thread.events.indices)), id: \.0.id) { (ev, ind) in
ChatView(event: thread.events[ind], ChatView(event: thread.events[ind],
prev_ev: ind > 0 ? thread.events[ind-1] : nil, prev_ev: ind > 0 ? thread.events[ind-1] : nil,
next_ev: ind == count-1 ? nil : thread.events[ind+1] next_ev: ind == count-1 ? nil : thread.events[ind+1]
) )
.onTapGesture {
if thread.event!.id == ev.id {
//dismiss()
toggle_thread_view()
} else {
thread.set_active_event(ev)
}
}
.environmentObject(thread) .environmentObject(thread)
} }
} }
} }
.onAppear() { .onAppear() {
scroll_to_event(scroller: scroller, id: thread.event.id, delay: 0.5, animate: true, anchor: .center) scroll_to_event(scroller: scroller, id: thread.event!.id, delay: 0.3, animate: true, anchor: .bottom)
} }
} }
} }
func toggle_thread_view() {
NotificationCenter.default.post(name: .toggle_thread_view, object: nil)
}
} }

15
damus/Views/EventActionBar.swift

@ -7,12 +7,6 @@
import SwiftUI import SwiftUI
extension Notification.Name {
static var thread_focus: Notification.Name {
return Notification.Name("thread focus")
}
}
enum ActionBarSheet: Identifiable { enum ActionBarSheet: Identifiable {
case reply case reply
@ -50,6 +44,15 @@ struct EventActionBar: View {
case .reply: case .reply:
ReplyView(replying_to: event) ReplyView(replying_to: event)
.environmentObject(profiles) .environmentObject(profiles)
.onReceive(NotificationCenter.default.publisher(for: .post)) { obj in
let res = obj.object as! NostrPostResult
switch res {
case .cancel:
self.sheet = nil
case .post:
self.sheet = nil
}
}
} }
} }
} }

62
damus/Views/EventDetailView.swift

@ -48,7 +48,8 @@ struct EventDetailView: View {
} }
} }
func OurEventView(proxy: ScrollViewProxy, ev: NostrEvent, highlight: Highlight, collapsed_events: [CollapsedEvent]) -> some View { /*
func OldEventView(proxy: ScrollViewProxy, ev: NostrEvent, highlight: Highlight, collapsed_events: [CollapsedEvent]) -> some View {
Group { Group {
if ev.id == thread.event.id { if ev.id == thread.event.id {
EventView(event: ev, highlight: .main, has_action_bar: true) EventView(event: ev, highlight: .main, has_action_bar: true)
@ -76,6 +77,7 @@ struct EventDetailView: View {
} }
} }
} }
*/
func uncollapse_section(scroller: ScrollViewProxy, c: CollapsedEvents) func uncollapse_section(scroller: ScrollViewProxy, c: CollapsedEvents)
{ {
@ -86,27 +88,51 @@ struct EventDetailView: View {
toggle_collapse_thread(scroller: scroller, id: start_id, animate: true, anchor: .top) toggle_collapse_thread(scroller: scroller, id: start_id, animate: true, anchor: .top)
} }
func CollapsedEventView(_ cev: CollapsedEvent, scroller: ScrollViewProxy) -> some View {
Group {
switch cev {
case .collapsed(let c):
Text("··· \(c.count) other replies ···")
.font(.footnote)
.foregroundColor(.gray)
.onTapGesture {
//self.uncollapse_section(scroller: proxy, c: c)
//self.toggle_collapse_thread(scroller: proxy, id: nil)
toggle_thread_view()
}
case .event(let ev, let highlight):
EventView(event: ev, highlight: highlight, has_action_bar: true)
.onTapGesture {
if thread.event!.id == ev.id {
toggle_thread_view()
} else {
thread.set_active_event(ev)
}
}
.onAppear() {
if highlight == .main {
scroll_to_event(scroller: scroller, id: ev.id, delay: 0.5, animate: true)
}
}
}
}
}
var body: some View { var body: some View {
ScrollViewReader { proxy in ScrollViewReader { proxy in
ScrollView { ScrollView {
let collapsed_events = calculated_collapsed_events(collapsed: self.collapsed, active: thread.event, events: thread.events) let collapsed_events = calculated_collapsed_events(collapsed: self.collapsed, active: thread.event, events: thread.events)
ForEach(collapsed_events, id: \.id) { cev in ForEach(collapsed_events, id: \.id) { cev in
switch cev { CollapsedEventView(cev, scroller: proxy)
case .collapsed(let c): }
Text("··· \(c.count) other replies ···")
.font(.footnote)
.foregroundColor(.gray)
.onTapGesture {
self.uncollapse_section(scroller: proxy, c: c)
//self.toggle_collapse_thread(scroller: proxy, id: nil)
}
case .event(let ev, let highlight):
OurEventView(proxy: proxy, ev: ev, highlight: highlight, collapsed_events: collapsed_events)
}
}
} }
} }
.navigationBarTitle("Thread")
}
func toggle_thread_view() {
NotificationCenter.default.post(name: .toggle_thread_view, object: nil)
} }
} }
@ -214,9 +240,13 @@ func determine_highlight(reply_map: [String: ()], current: NostrEvent, active: N
*/ */
} }
func calculated_collapsed_events(collapsed: Bool, active: NostrEvent, events: [NostrEvent]) -> [CollapsedEvent] { func calculated_collapsed_events(collapsed: Bool, active: NostrEvent?, events: [NostrEvent]) -> [CollapsedEvent] {
var count: Int = 0 var count: Int = 0
guard let active = active else {
return []
}
let reply_map = make_reply_map(active: active, events: events) let reply_map = make_reply_map(active: active, events: events)
if !collapsed { if !collapsed {

24
damus/Views/EventView.swift

@ -50,9 +50,6 @@ struct EventView: View {
Text("\(format_relative_time(event.created_at))") Text("\(format_relative_time(event.created_at))")
.foregroundColor(.gray) .foregroundColor(.gray)
Spacer() Spacer()
if (event.pow ?? 0) >= 10 {
PowView(event.pow)
}
} }
if event.is_reply { if event.is_reply {
@ -82,6 +79,25 @@ struct EventView: View {
.id(event.id) .id(event.id)
.frame(minHeight: PFP_SIZE) .frame(minHeight: PFP_SIZE)
.padding([.bottom], 4) .padding([.bottom], 4)
.contextMenu {
Button {
UIPasteboard.general.string = event.content
} label: {
Label("Copy", systemImage: "doc.on.doc")
}
Button {
UIPasteboard.general.string = event.id
} label: {
Label("Copy ID", systemImage: "tag")
}
Button {
NotificationCenter.default.post(name: .broadcast_event, object: event)
} label: {
Label("Broadcast", systemImage: "globe")
}
}
} }
} }
@ -119,3 +135,5 @@ func reply_others_desc(n: Int, n_pubkeys: Int) -> String {
let plural = other == 1 ? "" : "s" let plural = other == 1 ? "" : "s"
return n > 1 ? " & \(other) other\(plural)" : "" return n > 1 ? " & \(other) other\(plural)" : ""
} }

22
damus/Views/PostView.swift

@ -7,10 +7,9 @@
import SwiftUI import SwiftUI
extension Notification.Name { enum NostrPostResult {
static var post: Notification.Name { case post(NostrPost)
return Notification.Name("send post") case cancel
}
} }
struct NostrPost { struct NostrPost {
@ -46,27 +45,32 @@ struct PostView: View {
@FocusState var focus: Bool @FocusState var focus: Bool
let references: [ReferencedId] let references: [ReferencedId]
@Environment(\.presentationMode) var presmode @Environment(\.presentationMode) var presentationMode
enum FocusField: Hashable { enum FocusField: Hashable {
case post case post
} }
func cancel() {
NotificationCenter.default.post(name: .post, object: NostrPostResult.cancel)
dismiss()
}
func dismiss() { func dismiss() {
presmode.wrappedValue.dismiss() self.presentationMode.wrappedValue.dismiss()
} }
func send_post() { func send_post() {
let new_post = NostrPost(content: self.post, references: references) let new_post = NostrPost(content: self.post, references: references)
NotificationCenter.default.post(name: .post, object: new_post) NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
dismiss() //dismiss()
} }
var body: some View { var body: some View {
VStack { VStack {
HStack { HStack {
Button("Cancel") { Button("Cancel") {
self.dismiss() self.cancel()
} }
.foregroundColor(.primary) .foregroundColor(.primary)

8
damus/Views/ProfilePicView.swift

@ -16,17 +16,19 @@ func id_to_color(_ id: String) -> Color {
func highlight_color(_ h: Highlight) -> Color { func highlight_color(_ h: Highlight) -> Color {
switch h { switch h {
case .reply: fallthrough
case .none: return Color.black case .none: return Color.black
case .main: return Color.red case .main: return Color.red
case .reply: return Color.blue
} }
} }
func pfp_line_width(_ h: Highlight) -> CGFloat { func pfp_line_width(_ h: Highlight) -> CGFloat {
if h.is_none { switch h {
case .none: fallthrough
case .reply:
return 0 return 0
case .main: return 4
} }
return 4
} }
struct ProfilePicView: View { struct ProfilePicView: View {

13
damus/Views/ReplyQuoteView.swift

@ -41,12 +41,15 @@ struct ReplyQuoteView: View {
var body: some View { var body: some View {
Group { Group {
if let event = thread.lookup(event_id) { if let event = thread.lookup(event_id) {
Group { MainContent(event: event)
MainContent(event: event) .padding(4)
.padding(4) .frame(maxHeight: 100)
} .background(event.id == thread.event!.id ? Color.red.opacity(0.2) : Color.secondary.opacity(0.2))
.background(Color.secondary.opacity(0.2))
.cornerRadius(8.0) .cornerRadius(8.0)
.contentShape(Rectangle())
.onTapGesture {
thread.set_active_event(event)
}
} else { } else {
ProgressView() ProgressView()
.progressViewStyle(.circular) .progressViewStyle(.circular)

37
damus/Views/ThreadView.swift

@ -7,30 +7,37 @@
import SwiftUI import SwiftUI
struct ThreadView: View { struct ThreadView: View {
@StateObject var thread: ThreadModel @State var is_chatroom: Bool = false
@State var is_thread: Bool = false
@EnvironmentObject var profiles: Profiles @EnvironmentObject var profiles: Profiles
@EnvironmentObject var thread: ThreadModel
var body: some View { var body: some View {
Group { Group {
ChatroomView() if is_chatroom {
.environmentObject(thread) ChatroomView()
.onReceive(NotificationCenter.default.publisher(for: .convert_to_thread)) { _ in .navigationBarTitle("Chat")
is_thread = true .environmentObject(profiles)
} .environmentObject(thread)
} else {
let edv = EventDetailView(thread: thread).environmentObject(profiles) EventDetailView(thread: thread)
NavigationLink(destination: edv, isActive: $is_thread) { .navigationBarTitle("Thread")
.environmentObject(profiles)
.environmentObject(thread)
}
/*
NavigationLink(destination: edv, isActive: $is_chatroom) {
EmptyView() EmptyView()
} }
*/
} }
.onDisappear() { .onReceive(NotificationCenter.default.publisher(for: .toggle_thread_view)) { _ in
thread.unsubscribe() is_chatroom = !is_chatroom
} //print("is_chatroom: \(is_chatroom)")
.onAppear() {
thread.subscribe()
} }
} }
} }

38
damus/Views/TimelineView.swift

@ -7,13 +7,25 @@
import SwiftUI import SwiftUI
enum TimelineAction {
case chillin
case navigating
}
struct TimelineView: View { struct TimelineView: View {
@Binding var events: [NostrEvent] @Binding var events: [NostrEvent]
@EnvironmentObject var profiles: Profiles @EnvironmentObject var profiles: Profiles
let pool: RelayPool let pool: RelayPool
var body: some View { var body: some View {
MainContent
.padding([.leading, .trailing], 6)
.environmentObject(profiles)
}
var MainContent: some View {
ScrollView { ScrollView {
LazyVStack { LazyVStack {
ForEach(events, id: \.id) { (ev: NostrEvent) in ForEach(events, id: \.id) { (ev: NostrEvent) in
@ -24,20 +36,13 @@ struct TimelineView: View {
.environmentObject(profiles) .environmentObject(profiles)
*/ */
let evdet = ThreadView(thread: ThreadModel(event: ev, pool: pool)) EventView(event: ev, highlight: .none, has_action_bar: true)
.navigationBarTitle("Chat") .onTapGesture {
.padding([.leading, .trailing], 6) NotificationCenter.default.post(name: .open_thread, object: ev)
.environmentObject(profiles) }
NavigationLink(destination: evdet) {
EventView(event: ev, highlight: .none, has_action_bar: true)
}
.buttonStyle(PlainButtonStyle())
} }
} }
} }
.padding([.leading, .trailing], 6)
.environmentObject(profiles)
} }
} }
@ -48,3 +53,14 @@ struct TimelineView_Previews: PreviewProvider {
} }
} }
*/ */
struct NavigationLazyView<Content: View>: View {
let build: () -> Content
init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}
var body: Content {
build()
}
}

Loading…
Cancel
Save