commit 8568d4abc7cd95b432b511f5a8786341b44a713a
parent 020a1a4e6d4491b4d59fe77b48313357a7c93531
Author: William Casarin <jb55@jb55.com>
Date: Tue, 19 Apr 2022 09:26:29 -0700
fix up many things
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
6 files changed, 200 insertions(+), 52 deletions(-)
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -43,25 +43,54 @@ struct ContentView: View {
@State var selected_timeline: Timeline? = .home
@State var last_event_of_kind: [Int: NostrEvent] = [:]
@State var has_events: [String: ()] = [:]
+ @State var notifications_active: Bool = false
+ @State var new_notifications: Bool = false
@State var events: [NostrEvent] = []
@State var notifications: [NostrEvent] = []
+
+ // connect retry timer
+ let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect()
let sub_id = UUID().description
let pubkey = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
-
- func TimelineButton(timeline: Timeline, img: String) -> some View {
- NavigationLink(destination: Text("\(timeline.description)"), tag: timeline, selection: $selected_timeline){
- Label("", systemImage: img)
+
+ var NotificationTab: some View {
+ ZStack(alignment: .center) {
+ Button(action: {switch_timeline(.notifications)}) {
+ Label("", systemImage: selected_timeline == .notifications ? "bell.fill" : "bell")
+ .contentShape(Rectangle())
+ .frame(maxWidth: .infinity, minHeight: 30.0)
+ }
+ .foregroundColor(selected_timeline != .notifications ? .gray : .primary)
+
+ if new_notifications {
+ Circle()
+ .size(CGSize(width: 8, height: 8))
+ .frame(width: 10, height: 10, alignment: .topTrailing)
+ .alignmentGuide(VerticalAlignment.center) { a in a.height + 2.0 }
+ .alignmentGuide(HorizontalAlignment.center) { a in a.width - 12.0 }
+ .foregroundColor(.accentColor)
+ }
}
- .frame(maxWidth: .infinity)
- .foregroundColor(selected_timeline != timeline ? .gray : .primary)
}
-
- func TopBar(selected: Timeline) -> some View {
- HStack {
- TimelineButton(timeline: .home, img: selected == .home ? "house.fill" : "house")
- TimelineButton(timeline: .notifications, img: selected == .notifications ? "bell.fill" : "bell")
+
+ var HomeTab: some View {
+ Button(action: {switch_timeline(.home)}) {
+ Label("", systemImage: selected_timeline == .home ? "house.fill" : "house")
+ .contentShape(Rectangle())
+ .frame(maxWidth: .infinity, minHeight: 30.0)
+ }
+ .foregroundColor(selected_timeline != .home ? .gray : .primary)
+ }
+
+ var TabBar: some View {
+ VStack {
+ Divider()
+ HStack {
+ HomeTab
+ NotificationTab
+ }
}
}
@@ -77,28 +106,49 @@ struct ContentView: View {
}
}
}
-
+
+ var PostingTimelineView: some View {
+ ZStack {
+ if let pool = self.pool {
+ TimelineView(events: $events, pool: pool)
+ .environmentObject(profiles)
+ }
+ PostButtonContainer
+ }
+ }
+
var body: some View {
VStack {
- if self.loading {
- ProgressView()
- .progressViewStyle(.circular)
- .padding([.bottom], 4)
- }
-
- NavigationView {
- ZStack {
- if let pool = self.pool {
- TimelineView(events: $events, pool: pool)
+ if let pool = self.pool {
+ NavigationView {
+ VStack {
+ if self.loading {
+ ProgressView()
+ .progressViewStyle(.circular)
+ .padding([.bottom], 4)
+ }
+
+ PostingTimelineView
+ .onAppear() {
+ switch_timeline(.home)
+ }
+
+ let tlv = TimelineView(events: $notifications, pool: pool)
.environmentObject(profiles)
- .padding()
+ .navigationTitle("Notifications")
+ .navigationBarBackButtonHidden(true)
+
+ NavigationLink(destination: tlv, isActive: $notifications_active) {
+ EmptyView()
+ }
}
- PostButtonContainer
+ .navigationBarTitle("Damus", displayMode: .inline)
+
}
- .navigationBarTitle("Damus", displayMode: .inline)
+ .padding([.bottom], -8.0)
}
- TopBar(selected: selected_timeline ?? .home)
+ TabBar
}
.onAppear() {
self.connect()
@@ -116,6 +166,10 @@ struct ContentView: View {
let new_ev = post.to_event(privkey: privkey, pubkey: pubkey)
self.pool?.send(.event(new_ev))
}
+ .onReceive(timer) { n in
+ self.pool?.connect_to_disconnected()
+ self.loading = (self.pool?.num_connecting ?? 0) != 0
+ }
}
func is_friend(_ pubkey: String) -> Bool {
@@ -123,7 +177,15 @@ struct ContentView: View {
}
func switch_timeline(_ timeline: Timeline) {
- self.selected_timeline = timeline
+ if timeline == .notifications {
+ self.notifications_active = true
+ self.selected_timeline = .notifications
+ new_notifications = false
+ } else {
+ self.notifications_active = false
+ self.selected_timeline = .home
+ }
+ //self.selected_timeline = timeline
}
func add_relay(_ pool: RelayPool, _ relay: String) {
@@ -192,6 +254,11 @@ struct ContentView: View {
if let prof_since = get_metadata_since_time(last_metadata_event) {
profile_filter.since = prof_since
}
+
+ /*
+ var notification_filter = NostrFilter.filter_text
+ notification_filter.since = since
+ */
var contacts_filter = NostrFilter.filter_contacts
contacts_filter.authors = [self.pubkey]
@@ -199,8 +266,45 @@ struct ContentView: View {
let filters = [since_filter, profile_filter, contacts_filter]
print("connected to \(relay_id), refreshing from \(since)")
self.pool?.send(.subscribe(.init(filters: filters, sub_id: sub_id)))
+ //self.pool?.send(.subscribe(.init(filters: [notification_filter], sub_id: "notifications")))
}
-
+
+ func handle_notification(ev: NostrEvent) {
+ notifications.append(ev)
+ notifications = notifications.sorted { $0.created_at > $1.created_at }
+
+ let last_notified = get_last_notified()
+
+ if last_notified == nil || last_notified!.created_at < ev.created_at {
+ save_last_notified(ev)
+ new_notifications = true
+ }
+ }
+
+ func process_event(_ ev: NostrEvent) {
+ if has_events[ev.id] == nil {
+ has_events[ev.id] = ()
+ let last_k = last_event_of_kind[ev.kind]
+ if last_k == nil || ev.created_at > last_k!.created_at {
+ last_event_of_kind[ev.kind] = ev
+ }
+ if ev.kind == 1 {
+ if !should_hide_event(ev) {
+ self.events.append(ev)
+ self.events = self.events.sorted { $0.created_at > $1.created_at }
+
+ if is_notification(ev: ev, pubkey: pubkey) {
+ handle_notification(ev: ev)
+ }
+ }
+ } else if ev.kind == 0 {
+ handle_metadata_event(ev)
+ } else if ev.kind == 3 {
+ handle_contact_event(ev)
+ }
+ }
+ }
+
func handle_event(relay_id: String, conn_event: NostrConnectionEvent) {
switch conn_event {
case .ws_event(let ev):
@@ -211,10 +315,10 @@ struct ContentView: View {
self.events.insert(wsev, at: 0)
}
*/
+
switch ev {
case .connected:
- self.loading = ((self.pool?.num_connecting ?? 0) > 0)
send_filters(relay_id: relay_id)
case .error(let merr):
let desc = merr.debugDescription
@@ -231,6 +335,8 @@ struct ContentView: View {
default:
break
}
+
+ self.loading = (self.pool?.num_connecting ?? 0) != 0
print("ws_event \(ev)")
@@ -241,24 +347,8 @@ struct ContentView: View {
// TODO: other views like threads might have their own sub ids, so ignore those events... or should we?
return
}
-
- if has_events[ev.id] == nil {
- has_events[ev.id] = ()
- let last_k = last_event_of_kind[ev.kind]
- if last_k == nil || ev.created_at > last_k!.created_at {
- last_event_of_kind[ev.kind] = ev
- }
- if ev.kind == 1 {
- if !should_hide_event(ev) {
- self.events.append(ev)
- }
- self.events = self.events.sorted { $0.created_at > $1.created_at }
- } else if ev.kind == 0 {
- handle_metadata_event(ev)
- } else if ev.kind == 3 {
- handle_contact_event(ev)
- }
- }
+
+ self.process_event(ev)
case .notice(let msg):
self.events.insert(NostrEvent(content: "NOTICE from \(relay_id): \(msg)", pubkey: "system"), at: 0)
print(msg)
@@ -342,3 +432,44 @@ func ws_nostr_event(relay: String, ev: WebSocketEvent) -> NostrEvent? {
return NostrEvent(content: "reconnectSuggested \(b)", pubkey: relay)
}
}
+
+func is_notification(ev: NostrEvent, pubkey: String) -> Bool {
+ if ev.pubkey == pubkey {
+ return false
+ }
+ return ev.references(id: pubkey, key: "p")
+}
+
+
+extension UINavigationController: UIGestureRecognizerDelegate {
+ override open func viewDidLoad() {
+ super.viewDidLoad()
+ interactivePopGestureRecognizer?.delegate = self
+ }
+
+ public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
+ return viewControllers.count > 1
+ }
+}
+
+struct LastNotification {
+ let id: String
+ let created_at: Int64
+}
+
+func get_last_notified() -> LastNotification? {
+ let last = UserDefaults.standard.string(forKey: "last_notification")
+ let last_created = UserDefaults.standard.string(forKey: "last_notification_time")
+ .flatMap { Int64($0) }
+
+ return last.flatMap { id in
+ last_created.map { created in
+ return LastNotification(id: id, created_at: created)
+ }
+ }
+}
+
+func save_last_notified(_ ev: NostrEvent) {
+ UserDefaults.standard.set(ev.id, forKey: "last_notification")
+ UserDefaults.standard.set(String(ev.created_at), forKey: "last_notification_time")
+}
diff --git a/damus/Nostr/RelayConnection.swift b/damus/Nostr/RelayConnection.swift
@@ -17,6 +17,7 @@ class RelayConnection: WebSocketDelegate {
var isConnected: Bool = false
var isConnecting: Bool = false
var isReconnecting: Bool = false
+ var last_connection_attempt: Double = 0
var socket: WebSocket
var handleEvent: (NostrConnectionEvent) -> ()
let url: URL
@@ -25,9 +26,9 @@ class RelayConnection: WebSocketDelegate {
self.url = url
self.handleEvent = handleEvent
// just init, we don't actually use this one
- self.socket = WebSocket(request: URLRequest(url: self.url), compressionHandler: .none)
+ self.socket = make_websocket(url: url)
}
-
+
func reconnect() {
if self.isConnected {
self.isReconnecting = true
@@ -45,10 +46,11 @@ class RelayConnection: WebSocketDelegate {
var req = URLRequest(url: self.url)
req.timeoutInterval = 5
- socket = WebSocket(request: req, compressionHandler: .none)
+ socket = make_websocket(url: url)
socket.delegate = self
isConnecting = true
+ last_connection_attempt = Date().timeIntervalSince1970
socket.connect()
}
@@ -145,3 +147,7 @@ func make_nostr_subscription_req(_ filters: [NostrFilter], sub_id: String) -> St
return req
}
+func make_websocket(url: URL) -> WebSocket {
+ return WebSocket(request: URLRequest(url: url), compressionHandler: .none)
+}
+
diff --git a/damus/Nostr/RelayPool.swift b/damus/Nostr/RelayPool.swift
@@ -60,7 +60,16 @@ class RelayPool {
let relay = Relay(descriptor: descriptor, connection: conn)
self.relays.append(relay)
}
-
+
+ /// This is used to retry dead connections
+ func connect_to_disconnected() {
+ for relay in relays {
+ if !relay.connection.isConnected && !relay.connection.isConnecting {
+ relay.connection.connect()
+ }
+ }
+ }
+
func reconnect(to: [String]? = nil) {
let relays = to.map{ get_relays($0) } ?? self.relays
for relay in relays {
diff --git a/damus/Views/EventDetailView.swift b/damus/Views/EventDetailView.swift
@@ -169,7 +169,6 @@ struct EventDetailView: View {
}
}
}
- .padding()
.onDisappear() {
unsubscribe_to_thread()
}
diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift
@@ -79,6 +79,7 @@ struct EventView: View {
}
.padding([.leading], 2)
}
+ .contentShape(Rectangle())
.id(event.id)
.frame(minHeight: PFP_SIZE)
.padding([.bottom], 4)
diff --git a/damus/Views/TimelineView.swift b/damus/Views/TimelineView.swift
@@ -18,6 +18,7 @@ struct TimelineView: View {
ForEach(events, id: \.id) { (ev: NostrEvent) in
let evdet = EventDetailView(event: ev, pool: pool)
.navigationBarTitle("Thread")
+ .padding([.leading, .trailing], 6)
.environmentObject(profiles)
NavigationLink(destination: evdet) {
EventView(event: ev, highlight: .none, has_action_bar: true)
@@ -25,6 +26,7 @@ struct TimelineView: View {
.buttonStyle(PlainButtonStyle())
}
}
+ .padding([.leading, .trailing], 6)
.environmentObject(profiles)
}
}