damus

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

commit 6de7d7ae58e1bcb93d62a66e9b9a775a7765346d
parent e104de6431edc517660a48143f5e64e6d2b82643
Author: William Casarin <jb55@jb55.com>
Date:   Thu,  9 Jun 2022 13:47:25 -0700

edit relays

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

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 20++++++++++++++++++++
Adamus/Components/TextFieldAlert.swift | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/ContentView.swift | 66++++++++++++++++++++++++++++++++++--------------------------------
Mdamus/Models/Contacts.swift | 42++++++++++++++++++++++++++++++++++++++++--
Mdamus/Nostr/NostrEvent.swift | 13++++++++++++-
Adamus/Views/AddRelayView.swift | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adamus/Views/ConfigView.swift | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 328 insertions(+), 35 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -77,6 +77,9 @@ 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; }; 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; }; 4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; }; + 4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; }; + 4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */; }; + 4CE4F9E328528C5200C00DD9 /* AddRelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9E228528C5200C00DD9 /* AddRelayView.swift */; }; 4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEE627F7A08100C66700 /* damusApp.swift */; }; 4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEE827F7A08100C66700 /* ContentView.swift */; }; 4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CE6DEEA27F7A08200C66700 /* Assets.xcassets */; }; @@ -185,6 +188,9 @@ 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>"; }; 4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; }; + 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; }; + 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldAlert.swift; sourceTree = "<group>"; }; + 4CE4F9E228528C5200C00DD9 /* AddRelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRelayView.swift; sourceTree = "<group>"; }; 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>"; }; 4CE6DEE827F7A08100C66700 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; @@ -297,6 +303,8 @@ 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */, 4C90BD17283A9EE5008EE7EF /* LoginView.swift */, 4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */, + 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */, + 4CE4F9E228528C5200C00DD9 /* AddRelayView.swift */, ); path = Views; sourceTree = "<group>"; @@ -336,6 +344,14 @@ path = Util; sourceTree = "<group>"; }; + 4CE4F9DF285287A000C00DD9 /* Components */ = { + isa = PBXGroup; + children = ( + 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */, + ); + path = Components; + sourceTree = "<group>"; + }; 4CE6DEDA27F7A08100C66700 = { isa = PBXGroup; children = ( @@ -360,6 +376,7 @@ 4CE6DEE527F7A08100C66700 /* damus */ = { isa = PBXGroup; children = ( + 4CE4F9DF285287A000C00DD9 /* Components */, 4C7FF7D628233637009601DB /* Util */, 4C0A3F8D280F63FF000448DE /* Models */, 4C75EFAB28049CC80006080F /* Nostr */, @@ -551,6 +568,7 @@ 4C363A8A28236B57006E126D /* MentionView.swift in Sources */, 4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */, 4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */, + 4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */, 4C363AA828297703006E126D /* InsertSort.swift in Sources */, 4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */, 4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */, @@ -571,10 +589,12 @@ 4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */, 4C0A3F91280F6528000448DE /* ChatView.swift in Sources */, 4C75EFA627FF87A20006080F /* Nostr.swift in Sources */, + 4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */, 4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */, 4C75EFB328049D640006080F /* NostrEvent.swift in Sources */, 4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */, 4C363A8428233689006E126D /* Parser.swift in Sources */, + 4CE4F9E328528C5200C00DD9 /* AddRelayView.swift in Sources */, 4C363A9A28283854006E126D /* Reply.swift in Sources */, 4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */, 4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */, diff --git a/damus/Components/TextFieldAlert.swift b/damus/Components/TextFieldAlert.swift @@ -0,0 +1,61 @@ +// +// TextFieldAlert.swift +// damus +// +// Created by William Casarin on 2022-06-09. +// + +import SwiftUI + +struct TextFieldAlert<Presenting>: View where Presenting: View { + @Binding var isShowing: Bool + @Binding var text: String + let presenting: Presenting + let title: String + + var body: some View { + GeometryReader { (deviceSize: GeometryProxy) in + ZStack { + self.presenting + .disabled(isShowing) + VStack { + Text(self.title) + TextField("Relay", text: self.$text) + Divider() + HStack { + Button(action: { + withAnimation { + self.isShowing.toggle() + } + }) { + Text("Dismiss") + } + } + } + .padding() + .background(Color.white) + .frame( + width: deviceSize.size.width*0.7, + height: deviceSize.size.height*0.7 + ) + .shadow(radius: 1) + .opacity(self.isShowing ? 1 : 0) + } + } + } + +} + + +extension View { + + func textFieldAlert(isShowing: Binding<Bool>, + text: Binding<String>, + title: String) -> some View { + TextFieldAlert(isShowing: isShowing, + text: text, + presenting: self, + title: title) + } + +} diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -63,7 +63,7 @@ struct ContentView: View { var LoadingContainer: some View { VStack { - HStack { + HStack(alignment: .center) { Spacer() if home.signal.signal != home.signal.max_signal { @@ -71,6 +71,11 @@ struct ContentView: View { .font(.callout) .foregroundColor(.gray) } + + NavigationLink(destination: ConfigView(state: damus_state!)) { + Label("", systemImage: "gear") + } + .buttonStyle(PlainButtonStyle()) } Spacer() @@ -91,36 +96,32 @@ struct ContentView: View { } func MainContent(damus: DamusState) -> some View { - NavigationView { - VStack { - NavigationLink(destination: MaybeProfileView, isActive: $profile_open) { - EmptyView() - } - NavigationLink(destination: MaybeThreadView, isActive: $thread_open) { - EmptyView() - } - NavigationLink(destination: MaybeSearchView, isActive: $search_open) { - EmptyView() - } - switch selected_timeline { - case .search: - SearchHomeView(damus_state: damus_state!, model: SearchHomeModel(pool: damus_state!.pool) ) - - case .home: - PostingTimelineView - - case .notifications: - TimelineView(events: $home.notifications, damus: damus) - .navigationTitle("Notifications") + VStack { + NavigationLink(destination: MaybeProfileView, isActive: $profile_open) { + EmptyView() + } + NavigationLink(destination: MaybeThreadView, isActive: $thread_open) { + EmptyView() + } + NavigationLink(destination: MaybeSearchView, isActive: $search_open) { + EmptyView() + } + switch selected_timeline { + case .search: + SearchHomeView(damus_state: damus_state!, model: SearchHomeModel(pool: damus_state!.pool) ) - case .none: - EmptyView() - } + case .home: + PostingTimelineView + + case .notifications: + TimelineView(events: $home.notifications, damus: damus) + .navigationTitle("Notifications") + + case .none: + EmptyView() } - .navigationBarTitle("Damus", displayMode: .inline) - } - .navigationViewStyle(.stack) + .navigationBarTitle("Damus", displayMode: .inline) } var MaybeSearchView: some View { @@ -159,12 +160,13 @@ struct ContentView: View { var body: some View { VStack { if let damus = self.damus_state { - ZStack { + NavigationView { MainContent(damus: damus) - .padding([.bottom], -8.0) - - LoadingContainer + .toolbar { + LoadingContainer + } } + .navigationViewStyle(.stack) } TabBar(new_notifications: $home.new_notifications, selected: $selected_timeline, action: switch_timeline) diff --git a/damus/Models/Contacts.swift b/damus/Models/Contacts.swift @@ -138,14 +138,52 @@ func decode_json_relays(_ content: String) -> [String: RelayInfo]? { return decode_json(content) } -/* +func remove_relay(ev: NostrEvent, privkey: String, relay: String) -> NostrEvent? { + let damus_relay = RelayDescriptor(url: URL(string: "wss://relay.damus.io")!, info: .rw) + + var relays = ensure_relay_info(relays: [damus_relay], content: ev.content) + guard relays.index(forKey: relay) != nil else { + return nil + } + + relays.removeValue(forKey: relay) + + guard let content = encode_json(relays) else { + return nil + } + + let new_ev = NostrEvent(content: content, pubkey: ev.pubkey, kind: 3, tags: ev.tags) + new_ev.calculate_id() + new_ev.sign(privkey: privkey) + return new_ev +} + +func add_relay(ev: NostrEvent, privkey: String, relay: String, info: RelayInfo) -> NostrEvent? { + let damus_relay = RelayDescriptor(url: URL(string: "wss://relay.damus.io")!, info: .rw) + + var relays = ensure_relay_info(relays: [damus_relay], content: ev.content) + guard relays.index(forKey: relay) == nil else { + return nil + } + + relays[relay] = info + + guard let content = encode_json(relays) else { + return nil + } + + let new_ev = NostrEvent(content: content, pubkey: ev.pubkey, kind: 3, tags: ev.tags) + new_ev.calculate_id() + new_ev.sign(privkey: privkey) + return new_ev +} + func ensure_relay_info(relays: [RelayDescriptor], content: String) -> [String: RelayInfo] { guard let relay_info = decode_json_relays(content) else { return make_contact_relays(relays) } return relay_info } - */ func follow_with_existing_contacts(our_pubkey: String, our_contacts: NostrEvent, follow: ReferencedId) -> NostrEvent? { // don't update if we're already following diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift @@ -165,7 +165,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible { public var is_local: Bool { return (self.flags & 1) != 0 } - + init(content: String, pubkey: String, kind: Int = 1, tags: [[String]] = []) { self.id = "" self.sig = "" @@ -176,6 +176,17 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible { self.tags = tags self.created_at = Int64(Date().timeIntervalSince1970) } + + init(from: NostrEvent) { + self.id = from.id + self.sig = from.sig + + self.content = from.content + self.pubkey = from.pubkey + self.kind = from.kind + self.tags = from.tags + self.created_at = from.created_at + } func calculate_id() { self.id = calculate_event_id(ev: self) diff --git a/damus/Views/AddRelayView.swift b/damus/Views/AddRelayView.swift @@ -0,0 +1,55 @@ +// +// AddRelayView.swift +// damus +// +// Created by William Casarin on 2022-06-09. +// + +import SwiftUI + +struct AddRelayView: View { + @Binding var show_add_relay: Bool + @Binding var relay: String + + let action: (String?) -> Void + + var body: some View { + VStack(alignment: .leading) { + Form { + Section("Add Relay") { + TextField("wss://some.relay.com", text: $relay) + .textInputAutocapitalization(.never) + } + } + + VStack { + HStack { + Button("Cancel") { + show_add_relay = false + action(nil) + } + .contentShape(Rectangle()) + + Spacer() + + Button("Add") { + show_add_relay = false + action(relay) + } + .buttonStyle(.borderedProminent) + .contentShape(Rectangle()) + } + .padding() + } + } + } +} + +struct AddRelayView_Previews: PreviewProvider { + @State static var show: Bool = true + @State static var relay: String = "" + + static var previews: some View { + AddRelayView(show_add_relay: $show, relay: $relay, action: {_ in }) + } +} diff --git a/damus/Views/ConfigView.swift b/damus/Views/ConfigView.swift @@ -0,0 +1,106 @@ +// +// ConfigView.swift +// damus +// +// Created by William Casarin on 2022-06-09. +// + +import SwiftUI + +struct ConfigView: View { + let state: DamusState + @Environment(\.dismiss) var dismiss + @State var show_add_relay: Bool = false + @State var new_relay: String = "" + + func Relay(_ ev: NostrEvent, relay: String) -> some View { + return Text(relay) + .swipeActions { + if let privkey = state.keypair.privkey { + Button { + guard let new_ev = remove_relay( ev: ev, privkey: privkey, relay: relay) else { + return + } + + state.contacts.event = new_ev + state.pool.send(.event(new_ev)) + } label: { + Label("Delete", systemImage: "trash") + } + .tint(.red) + } + } + } + + var body: some View { + ZStack(alignment: .leading) { + Form { + if let ev = state.contacts.event { + Section("Relays") { + if let relays = decode_json_relays(ev.content) { + List(Array(relays.keys.sorted()), id: \.self) { relay in + Relay(ev, relay: relay) + } + } + + } + } + } + + VStack { + HStack { + Spacer() + + Button(action: { show_add_relay = true }) { + Label("", systemImage: "plus") + .foregroundColor(.accentColor) + .padding() + } + } + + Spacer() + } + } + .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.large) + .sheet(isPresented: $show_add_relay) { + AddRelayView(show_add_relay: $show_add_relay, relay: $new_relay) { _ in + guard let url = URL(string: new_relay) else { + return + } + + guard let ev = state.contacts.event else { + return + } + + guard let privkey = state.keypair.privkey else { + return + } + + let info = RelayInfo.rw + + guard (try? state.pool.add_relay(url, info: info)) != nil else { + return + } + + state.pool.connect(to: [new_relay]) + + guard let new_ev = add_relay(ev: ev, privkey: privkey, relay: new_relay, info: info) else { + return + } + + state.contacts.event = new_ev + state.pool.send(.event(new_ev)) + } + } + .onReceive(handle_notify(.switched_timeline)) { _ in + dismiss() + } + } +} + +struct ConfigView_Previews: PreviewProvider { + static var previews: some View { + ConfigView(state: test_damus_state()) + } +}