damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | LICENSE

commit ce989450f4fb5ab60b4f1aff56261a4b0eeed166
parent cb463c6da99bfca9cb4979081f7a2e97f32dea7e
Author: William Casarin <jb55@jb55.com>
Date:   Mon, 25 Apr 2022 08:28:07 -0700

many updates

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4++++
Mdamus/ContentView.swift | 44+++++++++++++++++++++++++++++++++++++++-----
Mdamus/Models/ThreadModel.swift | 5+++--
Mdamus/Nostr/NostrEvent.swift | 2--
Mdamus/Notifications.swift | 26++++++++++++++++++++++++++
Mdamus/Views/ChatView.swift | 2++
Mdamus/Views/EventActionBar.swift | 25++++++++-----------------
Mdamus/Views/EventDetailView.swift | 3++-
Mdamus/Views/EventView.swift | 17++++++++++++++---
Mdamus/Views/PostView.swift | 2+-
Mdamus/Views/ProfileName.swift | 1+
Mdamus/Views/ProfilePicView.swift | 93++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Adamus/Views/ProfileView.swift | 26++++++++++++++++++++++++++
Mdamus/Views/ReplyQuoteView.swift | 7++++---
14 files changed, 184 insertions(+), 73 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 4C75EFB728049D990006080F /* RelayPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB628049D990006080F /* RelayPool.swift */; }; 4C75EFB92804A2740006080F /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB82804A2740006080F /* EventView.swift */; }; 4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */; }; + 4C8682872814DE470026224F /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8682862814DE470026224F /* ProfileView.swift */; }; 4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; }; 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; }; 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; }; @@ -81,6 +82,7 @@ 4C75EFB628049D990006080F /* RelayPool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPool.swift; sourceTree = "<group>"; }; 4C75EFB82804A2740006080F /* EventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventView.swift; sourceTree = "<group>"; }; 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofOfWork.swift; sourceTree = "<group>"; }; + 4C8682862814DE470026224F /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.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>"; }; 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; }; @@ -159,6 +161,7 @@ 4C0A3F90280F6528000448DE /* ChatView.swift */, 4C0A3F94280F6C78000448DE /* ReplyQuoteView.swift */, 4C0A3F96280F8E02000448DE /* ThreadView.swift */, + 4C8682862814DE470026224F /* ProfileView.swift */, ); path = Views; sourceTree = "<group>"; @@ -407,6 +410,7 @@ 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */, 4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */, + 4C8682872814DE470026224F /* ProfileView.swift in Sources */, 4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */, 4CEE2AF3280B25C500AB5EEF /* ProfilePicView.swift in Sources */, 4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */, diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -15,11 +15,12 @@ struct TimestampedProfile { enum Sheets: Identifiable { case post + case reply(NostrEvent) var id: String { switch self { - case .post: - return "post" + case .post: return "post" + case .reply(let ev): return "reply-" + ev.id } } } @@ -43,12 +44,14 @@ struct ContentView: View { @State var status: String = "Not connected" @State var active_sheet: Sheets? = nil @State var profiles: Profiles = Profiles() + @State var active_profile: String? = nil @State var friends: [String: ()] = [:] @State var loading: Bool = true @State var pool: RelayPool? = nil @State var selected_timeline: Timeline? = .home @StateObject var thread: ThreadModel = ThreadModel() @State var is_thread_open: Bool = false + @State var is_profile_open: Bool = false @State var last_event_of_kind: [String: [Int: NostrEvent]] = [:] @State var has_events: [String: ()] = [:] @State var has_friend_event: [String: ()] = [:] @@ -172,9 +175,15 @@ struct ContentView: View { .environmentObject(profiles) .padding([.leading, .trailing], 6) + let pv = ProfileView() + NavigationLink(destination: tv, isActive: $is_thread_open) { EmptyView() } + + NavigationLink(destination: pv, isActive: $is_profile_open) { + EmptyView() + } } .navigationBarTitle("Damus", displayMode: .inline) } @@ -200,19 +209,36 @@ struct ContentView: View { switch item { case .post: PostView(references: []) + case .reply(let event): + ReplyView(replying_to: event) + .environmentObject(profiles) } } - .onReceive(NotificationCenter.default.publisher(for: .open_thread)) { obj in + .onReceive(handle_notify(.boost)) { notif in + let ev = notif.object as! NostrEvent + let boost = make_boost_event(ev, privkey: privkey, pubkey: pubkey) + self.pool?.send(.event(boost)) + } + .onReceive(handle_notify(.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 + .onReceive(handle_notify(.reply)) { notif in + let ev = notif.object as! NostrEvent + self.active_sheet = .reply(ev) + } + .onReceive(handle_notify(.broadcast_event)) { obj in let ev = obj.object as! NostrEvent self.pool?.send(.event(ev)) } - .onReceive(NotificationCenter.default.publisher(for: .post)) { obj in + .onReceive(handle_notify(.click_profile_pic)) { obj in + let pubkey = obj.object as! String + self.active_profile = pubkey + self.is_profile_open = true + } + .onReceive(handle_notify(.post)) { obj in let post_res = obj.object as! NostrPostResult switch post_res { case .post(let post): @@ -583,3 +609,11 @@ func save_last_notified(_ ev: NostrEvent) { UserDefaults.standard.set(String(ev.created_at), forKey: "last_notification_time") } + + +func make_boost_event(_ ev: NostrEvent, privkey: String, pubkey: String) -> NostrEvent { + let boost = NostrEvent(content: "", pubkey: pubkey, kind: 6, tags: [["e", ev.id]]) + boost.calculate_id() + boost.sign(privkey: privkey) + return boost +} diff --git a/damus/Models/ThreadModel.swift b/damus/Models/ThreadModel.swift @@ -68,8 +68,9 @@ class ThreadModel: ObservableObject { } private func subscribe(_ ev: NostrEvent) { - var ref_events = NostrFilter.filter_text - var events_filter = NostrFilter.filter_text + let kinds: [Int] = [1, 5, 6] + var ref_events = NostrFilter.filter_kinds(kinds) + var events_filter = NostrFilter.filter_kinds(kinds) // TODO: add referenced relays ref_events.referenced_ids = ev.referenced_ids.map { $0.ref_id } diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift @@ -217,8 +217,6 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible { self.kind = kind self.tags = tags self.created_at = Int64(Date().timeIntervalSince1970) - - self.calculate_id() } func calculate_id() { diff --git a/damus/Notifications.swift b/damus/Notifications.swift @@ -26,12 +26,24 @@ extension Notification.Name { } extension Notification.Name { + static var reply: Notification.Name { + return Notification.Name("reply") + } +} + +extension Notification.Name { static var switched_timeline: Notification.Name { return Notification.Name("switched_timeline") } } extension Notification.Name { + static var click_profile_pic: Notification.Name { + return Notification.Name("click_profile_pic") + } +} + +extension Notification.Name { static var scroll_to_top: Notification.Name { return Notification.Name("scroll_to_to") } @@ -54,3 +66,17 @@ extension Notification.Name { return Notification.Name("send post") } } + +extension Notification.Name { + static var boost: Notification.Name { + return Notification.Name("boost post") + } +} + +func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher { + return NotificationCenter.default.publisher(for: name) +} + +func notify(_ name: NSNotification.Name, _ object: Any?) { + NotificationCenter.default.post(name: name, object: object) +} diff --git a/damus/Views/ChatView.swift b/damus/Views/ChatView.swift @@ -113,6 +113,8 @@ struct ChatView: View { if just_started { HStack { ProfileName(pubkey: event.pubkey, profile: profile) + .foregroundColor(id_to_color(event.pubkey)) + //.shadow(color: Color.secondary, radius: 2, x: 2, y: 2) Text("\(format_relative_time(event.created_at))") .foregroundColor(.gray) } diff --git a/damus/Views/EventActionBar.swift b/damus/Views/EventActionBar.swift @@ -31,27 +31,17 @@ struct EventActionBar: View { } Spacer() + */ - EventActionButton(img: "bubble.left") { - self.sheet = .reply + notify(.reply, event) } - } - .sheet(item: $sheet) { sheet in - switch sheet { - case .reply: - ReplyView(replying_to: event) - .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 - } - } + .padding([.trailing], 40) + + EventActionButton(img: "arrow.2.squarepath") { + notify(.boost, event) } + } } } @@ -64,3 +54,4 @@ func EventActionButton(img: String, action: @escaping () -> ()) -> some View { .foregroundColor(.gray) } } + diff --git a/damus/Views/EventDetailView.swift b/damus/Views/EventDetailView.swift @@ -110,7 +110,7 @@ struct EventDetailView: View { } } .onAppear() { - if highlight == .main { + if highlight.is_main { scroll_to_event(scroller: scroller, id: ev.id, delay: 0.5, animate: true) } } @@ -270,6 +270,7 @@ func calculated_collapsed_events(collapsed: Bool, active: NostrEvent?, events: [ } count += 1 case .main: fallthrough + case .custom: fallthrough case .reply: if count != 0 { let c = CollapsedEvents(count: count, start: start, end: i) diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift @@ -12,12 +12,20 @@ enum Highlight { case none case main case reply + case custom(Color, Float) + var is_main: Bool { + if case .main = self { + return true + } + return false + } + var is_none: Bool { - switch self { - case .none: return true - default: return false + if case .none = self { + return true } + return false } var is_replied_to: Bool { @@ -40,6 +48,9 @@ struct EventView: View { HStack { VStack { ProfilePicView(picture: profile?.picture, size: PFP_SIZE!, highlight: highlight) + .onTapGesture { + NotificationCenter.default.post(name: .click_profile_pic, object: event.pubkey) + } Spacer() } diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift @@ -63,7 +63,7 @@ struct PostView: View { func send_post() { let new_post = NostrPost(content: self.post, references: references) NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post)) - //dismiss() + dismiss() } var body: some View { diff --git a/damus/Views/ProfileName.swift b/damus/Views/ProfileName.swift @@ -9,6 +9,7 @@ import SwiftUI func ProfileName(pubkey: String, profile: Profile?) -> some View { Text(String(Profile.displayName(profile: profile, pubkey: pubkey))) + .foregroundColor(hex_to_rgb(pubkey)) .bold() } diff --git a/damus/Views/ProfilePicView.swift b/damus/Views/ProfilePicView.swift @@ -11,23 +11,24 @@ let PFP_SIZE: CGFloat? = 52.0 let CORNER_RADIUS: CGFloat = 32 func id_to_color(_ id: String) -> Color { - return .init(hex: String(id.reversed().prefix(6))) + return hex_to_rgb(id) } func highlight_color(_ h: Highlight) -> Color { switch h { - case .reply: fallthrough - case .none: return Color.black case .main: return Color.red + case .reply: return Color.black + case .none: return Color.black + case .custom(let c, _): return c } } func pfp_line_width(_ h: Highlight) -> CGFloat { switch h { - case .none: fallthrough - case .reply: - return 0 + case .reply: return 0 + case .none: return 0 case .main: return 2 + case .custom(_, let lw): return CGFloat(lw) } } @@ -40,23 +41,29 @@ struct ProfilePicView: View { Color.purple.opacity(0.2) } - var body: some View { - if let pic = picture.flatMap({ URL(string: $0) }) { - AsyncImage(url: pic) { img in - img.resizable() - } placeholder: { Placeholder } - .frame(width: size, height: size) - .clipShape(Circle()) - .overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight))) - .padding(2) - } else { - Placeholder + var MainContent: some View { + Group { + if let pic = picture.flatMap({ URL(string: $0) }) { + AsyncImage(url: pic) { img in + img.resizable() + } placeholder: { Placeholder } .frame(width: size, height: size) - .cornerRadius(CORNER_RADIUS) + .clipShape(Circle()) .overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight))) .padding(2) + } else { + Placeholder + .frame(width: size, height: size) + .cornerRadius(CORNER_RADIUS) + .overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight))) + .padding(2) + } } } + + var body: some View { + MainContent + } } struct ProfilePicView_Previews: PreviewProvider { @@ -66,28 +73,36 @@ struct ProfilePicView_Previews: PreviewProvider { } -extension Color { - init(hex: String) { - var int: UInt64 = 0 - Scanner(string: hex).scanHexInt64(&int) - let a, r, g, b: UInt64 - switch hex.count { - case 3: // RGB (12-bit) - (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) - case 6: // RGB (24-bit) - (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) - case 8: // ARGB (32-bit) - (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) - default: - (a, r, g, b) = (1, 1, 1, 0) +func hex_to_rgb(_ hex: String) -> Color { + guard hex.count >= 6 else { + return Color.black + } + + let arr = Array(hex.utf8) + var rgb: [UInt8] = [] + var i: Int = 0 + + while i < 6 { + let cs1 = arr[i] + let cs2 = arr[i+1] + + guard let c1 = char_to_hex(cs1) else { + return Color.black } - self.init( - .sRGB, - red: Double(r) / 255, - green: Double(g) / 255, - blue: Double(b) / 255, - opacity: Double(a) / 255 - ) + guard let c2 = char_to_hex(cs2) else { + return Color.black + } + + rgb.append((c1 << 4) | c2) + i += 2 } + + return Color.init( + .sRGB, + red: Double(rgb[0]) / 255, + green: Double(rgb[1]) / 255, + blue: Double(rgb[2]) / 255, + opacity: 1 + ) } diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift @@ -0,0 +1,26 @@ +// +// ProfileView.swift +// damus +// +// Created by William Casarin on 2022-04-23. +// + +import SwiftUI + +struct ProfileView: View { + let profile: Profile? = nil + + var body: some View { + VStack { + ProfilePicView(picture: profile?.picture, size: 64, highlight: .custom(Color.black, 4)) + //ProfileName(pubkey: <#T##String#>, profile: <#T##Profile?#>) + } + .navigationBarTitle("Profile") + } +} + +struct ProfileView_Previews: PreviewProvider { + static var previews: some View { + ProfileView() + } +} diff --git a/damus/Views/ReplyQuoteView.swift b/damus/Views/ReplyQuoteView.swift @@ -18,12 +18,13 @@ struct ReplyQuoteView: View { HStack(alignment: .top) { Rectangle().frame(width: 2) .padding([.leading], 4) - + .foregroundColor(.accentColor) VStack(alignment: .leading) { HStack(alignment: .top) { - ProfilePicView(picture: profiles.lookup(id: event.pubkey)?.picture, size: 16, highlight: .none) - ProfileName(pubkey: event.pubkey, profile: profiles.lookup(id: event.pubkey)) + ProfilePicView(picture: profiles.lookup(id: event.pubkey)?.picture, size: 16, highlight: .reply) + Text(Profile.displayName(profile: profiles.lookup(id: event.pubkey), pubkey: event.pubkey)) + .foregroundColor(.accentColor) Text("\(format_relative_time(event.created_at))") .foregroundColor(.gray) }