damus

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

commit e3283fc8f84b25ef2a7889678c501106eb3c9592
parent 54fdcd1c84b3c79cc01539eb3bd688f776a0db95
Author: William Casarin <jb55@jb55.com>
Date:   Mon, 27 Mar 2023 11:47:23 -0400

Merge remote-tracking branch 'eric/relay-config-changes'

Diffstat:
Adamus/Assets.xcassets/bitcoin-logo.imageset/Contents.json | 21+++++++++++++++++++++
Adamus/Assets.xcassets/bitcoin-logo.imageset/bitcoin-logo.svg | 7+++++++
Mdamus/Views/AddRelayView.swift | 71++++++++++++++++++++---------------------------------------------------
Mdamus/Views/Relays/RecommendedRelayView.swift | 75++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mdamus/Views/Relays/RelayConfigView.swift | 156++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mdamus/Views/Relays/RelayDetailView.swift | 46++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/Relays/RelayStatus.swift | 34++++++++++++++++++++++++----------
Mdamus/Views/Relays/RelayType.swift | 9++++++---
Mdamus/Views/Relays/RelayView.swift | 56+++++++++++++++++++++++++++++++++++++++++---------------
Mdamus/Views/UserRelaysView.swift | 16+++++++++++++++-
10 files changed, 343 insertions(+), 148 deletions(-)

diff --git a/damus/Assets.xcassets/bitcoin-logo.imageset/Contents.json b/damus/Assets.xcassets/bitcoin-logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bitcoin-logo.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/damus/Assets.xcassets/bitcoin-logo.imageset/bitcoin-logo.svg b/damus/Assets.xcassets/bitcoin-logo.imageset/bitcoin-logo.svg @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20px" height="20px" viewBox="0 0 20 20" version="1.1"> +<g id="surface1"> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(96.862745%,57.647059%,10.196078%);fill-opacity:1;" d="M 19.699219 12.417969 C 18.363281 17.777344 12.9375 21.035156 7.582031 19.699219 C 2.226562 18.363281 -1.035156 12.9375 0.300781 7.582031 C 1.636719 2.222656 7.0625 -1.035156 12.417969 0.300781 C 17.773438 1.632812 21.035156 7.0625 19.699219 12.417969 Z M 19.699219 12.417969 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 14.410156 8.574219 C 14.609375 7.246094 13.59375 6.53125 12.210938 6.050781 L 12.660156 4.25 L 11.5625 3.976562 L 11.125 5.730469 C 10.835938 5.660156 10.539062 5.589844 10.246094 5.523438 L 10.6875 3.757812 L 9.589844 3.484375 L 9.140625 5.285156 C 8.902344 5.230469 8.667969 5.179688 8.4375 5.121094 L 8.441406 5.117188 L 6.925781 4.738281 L 6.636719 5.910156 C 6.636719 5.910156 7.449219 6.097656 7.433594 6.109375 C 7.875 6.21875 7.957031 6.511719 7.941406 6.746094 L 7.429688 8.800781 C 7.460938 8.808594 7.5 8.820312 7.546875 8.835938 L 7.429688 8.808594 L 6.710938 11.683594 C 6.65625 11.820312 6.519531 12.023438 6.210938 11.945312 C 6.21875 11.960938 5.410156 11.746094 5.410156 11.746094 L 4.867188 13 L 6.296875 13.359375 C 6.5625 13.425781 6.820312 13.492188 7.078125 13.558594 L 6.621094 15.382812 L 7.71875 15.65625 L 8.167969 13.851562 C 8.46875 13.933594 8.757812 14.007812 9.042969 14.078125 L 8.59375 15.875 L 9.691406 16.148438 L 10.144531 14.328125 C 12.015625 14.683594 13.425781 14.539062 14.015625 12.847656 C 14.492188 11.484375 13.992188 10.699219 13.007812 10.1875 C 13.726562 10.019531 14.265625 9.550781 14.410156 8.574219 Z M 11.902344 12.089844 C 11.5625 13.453125 9.269531 12.71875 8.523438 12.53125 L 9.128906 10.117188 C 9.871094 10.300781 12.253906 10.667969 11.902344 12.089844 Z M 12.242188 8.554688 C 11.933594 9.796875 10.023438 9.164062 9.402344 9.011719 L 9.949219 6.820312 C 10.570312 6.976562 12.5625 7.261719 12.242188 8.554688 Z M 12.242188 8.554688 "/> +</g> +</svg> diff --git a/damus/Views/AddRelayView.swift b/damus/Views/AddRelayView.swift @@ -8,72 +8,41 @@ 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(NSLocalizedString("Add Relay", comment: "Label for section for adding a relay server.")) { - ZStack(alignment: .leading) { - HStack{ - TextField(NSLocalizedString("wss://some.relay.com", comment: "Placeholder example for relay server address."), text: $relay) - .padding(2) - .padding(.leading, 25) - .autocorrectionDisabled(true) - .textInputAutocapitalization(.never) - - Label("", systemImage: "xmark.circle.fill") - .foregroundColor(.blue) - .padding(.trailing, -25.0) - .opacity((relay == "") ? 0.0 : 1.0) - .onTapGesture { - self.relay = "" - } - } - - Label("", systemImage: "doc.on.clipboard") - .padding(.leading, -10) - .onTapGesture { - if let pastedrelay = UIPasteboard.general.string { - self.relay = pastedrelay - } - } + ZStack(alignment: .leading) { + HStack{ + TextField(NSLocalizedString("wss://some.relay.com", comment: "Placeholder example for relay server address."), text: $relay) + .padding(2) + .padding(.leading, 25) + .autocorrectionDisabled(true) + .textInputAutocapitalization(.never) + + Label("", systemImage: "xmark.circle.fill") + .foregroundColor(.accentColor) + .padding(.trailing, -25.0) + .opacity((relay == "") ? 0.0 : 1.0) + .onTapGesture { + self.relay = "" } - } } - VStack { - HStack { - Button(NSLocalizedString("Cancel", comment: "Button to cancel out of view adding user inputted relay.")) { - show_add_relay = false - action(nil) - } - .contentShape(Rectangle()) - - Spacer() - - Button(NSLocalizedString("Add", comment: "Button to confirm adding user inputted relay.")) { - show_add_relay = false - action(relay) - relay = "" - } - .buttonStyle(.borderedProminent) - .contentShape(Rectangle()) + Label("", systemImage: "doc.on.clipboard") + .padding(.leading, -10) + .onTapGesture { + if let pastedrelay = UIPasteboard.general.string { + self.relay = pastedrelay } - .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 }) + AddRelayView(relay: $relay) } } diff --git a/damus/Views/Relays/RecommendedRelayView.swift b/damus/Views/Relays/RecommendedRelayView.swift @@ -12,22 +12,33 @@ struct RecommendedRelayView: View { let relay: String let add_button: Bool - init(damus: DamusState, relay: String) { + @Binding var showActionButtons: Bool + + init(damus: DamusState, relay: String, showActionButtons: Binding<Bool>) { self.damus = damus self.relay = relay self.add_button = true + self._showActionButtons = showActionButtons } - init(damus: DamusState, relay: String, add_button: Bool) { + init(damus: DamusState, relay: String, add_button: Bool, showActionButtons: Binding<Bool>) { self.damus = damus self.relay = relay self.add_button = add_button + self._showActionButtons = showActionButtons } var body: some View { ZStack { HStack { + if let privkey = damus.keypair.privkey { + if showActionButtons && add_button { + AddButton(privkey: privkey, showText: false) + } + } + RelayType(is_paid: damus.relay_metadata.lookup(relay_id: relay)?.is_paid ?? false) + Text(relay).layoutPriority(1) if let meta = damus.relay_metadata.lookup(relay_id: relay) { @@ -37,41 +48,75 @@ struct RecommendedRelayView: View { EmptyView() } .opacity(0.0) + .disabled(showActionButtons) Spacer() + Image(systemName: "info.circle") + .font(.system(size: 20, weight: .regular)) .foregroundColor(Color.accentColor) + } else { + Spacer() + + Image(systemName: "questionmark.circle") + .font(.system(size: 20, weight: .regular)) + .foregroundColor(.gray) } } } .swipeActions { if add_button { if let privkey = damus.keypair.privkey { - AddAction(privkey: privkey) + AddButton(privkey: privkey, showText: false) + .tint(.accentColor) } } } + .contextMenu { + CopyAction(relay: relay) + + if let privkey = damus.keypair.privkey { + AddButton(privkey: privkey, showText: true) + } + } } - func AddAction(privkey: String) -> some View { + func CopyAction(relay: String) -> some View { Button { - guard let ev_before_add = damus.contacts.event else { - return - } - guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else { - return - } - process_contact_event(state: damus, ev: ev_after_add) - damus.pool.send(.event(ev_after_add)) + UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text") } label: { - Label(NSLocalizedString("Add Relay", comment: "Button to add recommended relay server."), systemImage: "plus.circle") + Label(NSLocalizedString("Copy", comment: "Button to copy a relay server address."), systemImage: "doc.on.doc") + } + } + + func AddButton(privkey: String, showText: Bool) -> some View { + Button(action: { + add_action(privkey: privkey) + }) { + if showText { + Text(NSLocalizedString("Connect", comment: "Button to connect to recommended relay server.")) + } + Image(systemName: "plus.circle.fill") + .font(.system(size: 20, weight: .medium)) + .foregroundColor(.accentColor) + .padding(.leading, 5) + } + } + + func add_action(privkey: String) { + guard let ev_before_add = damus.contacts.event else { + return + } + guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else { + return } - .tint(.accentColor) + process_contact_event(state: damus, ev: ev_after_add) + damus.pool.send(.event(ev_after_add)) } } struct RecommendedRelayView_Previews: PreviewProvider { static var previews: some View { - RecommendedRelayView(damus: test_damus_state(), relay: "wss://relay.damus.io") + RecommendedRelayView(damus: test_damus_state(), relay: "wss://relay.damus.io", showActionButtons: .constant(false)) } } diff --git a/damus/Views/Relays/RelayConfigView.swift b/damus/Views/Relays/RelayConfigView.swift @@ -10,8 +10,8 @@ import SwiftUI struct RelayConfigView: View { let state: DamusState @State var new_relay: String = "" - @State var show_add_relay: Bool = false @State var relays: [RelayDescriptor] + @State private var showActionButtons = false @Environment(\.dismiss) var dismiss @@ -37,72 +37,122 @@ struct RelayConfigView: View { .onReceive(handle_notify(.switched_timeline)) { _ in dismiss() } - .sheet(isPresented: $show_add_relay) { - AddRelayView(show_add_relay: $show_add_relay, relay: $new_relay) { m_relay in - guard var relay = m_relay else { - return - } - - if relay.starts(with: "wss://") == false && relay.starts(with: "ws://") == false { - relay = "wss://" + relay - } - - if relay.hasSuffix("/") { - relay.removeLast(); - } - - guard let url = URL(string: 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: [relay]) - - guard let new_ev = add_relay(ev: ev, privkey: privkey, current_relays: state.pool.descriptors, relay: relay, info: info) else { - return - } - - process_contact_event(state: state, ev: ev) - - state.pool.send(.event(new_ev)) - } - } } var MainContent: some View { Form { Section { + AddRelayView(relay: $new_relay) + } header: { + HStack { + Text(NSLocalizedString("Connect To Relay", comment: "Label for section for adding a relay server.")) + .font(.system(size: 18, weight: .heavy)) + .padding(.bottom, 5) + } + } footer: { + VStack { + HStack { + Spacer() + if(!new_relay.isEmpty) { + Button(NSLocalizedString("Cancel", comment: "Button to cancel out of view adding user inputted relay.")) { + new_relay = "" + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } + .font(.system(size: 14, weight: .bold)) + .frame(width: 80, height: 30) + .foregroundColor(.white) + .background(LINEAR_GRADIENT) + .clipShape(Capsule()) + .padding(EdgeInsets(top: 15, leading: 0, bottom: 0, trailing: 0)) + + Button(NSLocalizedString("Add", comment: "Button to confirm adding user inputted relay.")) { + + if new_relay.starts(with: "wss://") == false && new_relay.starts(with: "ws://") == false { + new_relay = "wss://" + new_relay + } + + if new_relay.hasSuffix("/") { + new_relay.removeLast(); + } + + 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, current_relays: state.pool.descriptors, relay: new_relay, info: info) else { + return + } + + process_contact_event(state: state, ev: ev) + + state.pool.send(.event(new_ev)) + + new_relay = "" + + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } + .font(.system(size: 14, weight: .bold)) + .frame(width: 80, height: 30) + .foregroundColor(.white) + .background(LINEAR_GRADIENT) + .clipShape(Capsule()) + .padding(EdgeInsets(top: 15, leading: 0, bottom: 0, trailing: 0)) + } + } + } + } + + Section { List(Array(relays), id: \.url) { relay in - RelayView(state: state, relay: relay.url.absoluteString) + RelayView(state: state, relay: relay.url.absoluteString, showActionButtons: $showActionButtons) } } header: { HStack { - Text("Relays", comment: "Header text for relay server list for configuration.") - Spacer() - Button(action: { show_add_relay = true }) { - Image(systemName: "plus") - .foregroundColor(.accentColor) - } + Text(NSLocalizedString("Connected Relays", comment: "Section title for relay servers that are connected.")) + .font(.system(size: 18, weight: .heavy)) + .padding(.bottom, 5) } } if recommended.count > 0 { - Section(NSLocalizedString("Recommended Relays", comment: "Section title for recommend relay servers that could be added as part of configuration")) { + Section { List(recommended, id: \.url) { r in - RecommendedRelayView(damus: state, relay: r.url.absoluteString) + RecommendedRelayView(damus: state, relay: r.url.absoluteString, showActionButtons: $showActionButtons) + } + } header: { + Text(NSLocalizedString("Recommended Relays", comment: "Section title for recommend relay servers that could be added as part of configuration")) + .font(.system(size: 18, weight: .heavy)) + .padding(.bottom, 5) + } + } + } + .navigationTitle(NSLocalizedString("Relays", comment: "Title of relays view")) + .navigationBarTitleDisplayMode(.large) + .toolbar { + if state.keypair.privkey != nil { + if showActionButtons { + Button("Done") { + showActionButtons.toggle() + } + } else { + Button("Edit") { + showActionButtons.toggle() } } } diff --git a/damus/Views/Relays/RelayDetailView.swift b/damus/Views/Relays/RelayDetailView.swift @@ -16,6 +16,15 @@ struct RelayDetailView: View { @Environment(\.dismiss) var dismiss + func check_connection() -> Bool { + for relay in state.pool.relays { + if relay.id == self.relay { + return true + } + } + return false + } + func FieldText(_ str: String?) -> some View { Text(str ?? "No data available") } @@ -23,6 +32,42 @@ struct RelayDetailView: View { var body: some View { Group { Form { + + if let privkey = state.keypair.privkey { + if check_connection() { + Button(action: { + guard let ev = state.contacts.event else { + return + } + + let descriptors = state.pool.descriptors + guard let new_ev = remove_relay( ev: ev, current_relays: descriptors, privkey: privkey, relay: relay) else { + return + } + + process_contact_event(state: state, ev: new_ev) + state.pool.send(.event(new_ev)) + dismiss() + }) { + Text("Disconnect From Relay", comment: "Button to disconnect from the relay.") + } + } else { + Button(action: { + guard let ev_before_add = state.contacts.event else { + return + } + guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: state.pool.descriptors, relay: relay, info: .rw) else { + return + } + process_contact_event(state: state, ev: ev_after_add) + state.pool.send(.event(ev_after_add)) + dismiss() + }) { + Text("Connect To Relay", comment: "Button to connect to the relay.") + } + } + } + if let pubkey = nip11.pubkey { Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) { UserView(damus_state: state, pubkey: pubkey) @@ -68,6 +113,7 @@ struct RelayDetailView: View { dismiss() } .navigationTitle(nip11.name ?? "") + .navigationBarTitleDisplayMode(.inline) } private func nipsList(nips: [Int]) -> AttributedString { diff --git a/damus/Views/Relays/RelayStatus.swift b/damus/Views/Relays/RelayStatus.swift @@ -14,16 +14,20 @@ struct RelayStatus: View { let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect() @State var conn_color: Color = .gray + @State var conn_image: String = "network" + @State var connecting: Bool = false - func update_connection_color() { + func update_connection() { for relay in pool.relays { if relay.id == self.relay { let c = relay.connection if c.isConnected { + conn_image = "network" conn_color = .green } else if c.isConnecting || c.isReconnecting { - conn_color = .yellow + connecting = true } else { + conn_image = "exclamationmark.circle.fill" conn_color = .red } } @@ -31,15 +35,25 @@ struct RelayStatus: View { } var body: some View { - Circle() - .frame(width: 8.0, height: 8.0) - .foregroundColor(conn_color) - .onReceive(timer) { _ in - update_connection_color() - } - .onAppear() { - update_connection_color() + HStack { + if connecting { + ProgressView() + .padding(.trailing, 4) + } else { + Image(systemName: conn_image) + .frame(width: 8.0, height: 8.0) + .foregroundColor(conn_color) + .padding(.leading, 5) + .padding(.trailing, 10) } + } + .onReceive(timer) { _ in + update_connection() + } + .onAppear() { + update_connection() + } + } } diff --git a/damus/Views/Relays/RelayType.swift b/damus/Views/Relays/RelayType.swift @@ -11,9 +11,12 @@ struct RelayType: View { let is_paid: Bool var body: some View { - - Image(systemName: is_paid ? "dollarsign.circle.fill" : "globe.americas.fill") - .foregroundColor(is_paid ? Color("DamusGreen") : .gray) + if is_paid { + Image("bitcoin-logo") + } else { + Image(systemName: "globe.americas.fill") + .foregroundColor(.gray) + } } } diff --git a/damus/Views/Relays/RelayView.swift b/damus/Views/Relays/RelayView.swift @@ -11,32 +11,53 @@ struct RelayView: View { let state: DamusState let relay: String + @Binding var showActionButtons: Bool + var body: some View { Group { HStack { - RelayStatus(pool: state.pool, relay: relay) + if let privkey = state.keypair.privkey { + if showActionButtons { + RemoveButton(privkey: privkey, showText: false) + } + else { + RelayStatus(pool: state.pool, relay: relay) + } + } + RelayType(is_paid: state.relay_metadata.lookup(relay_id: relay)?.is_paid ?? false) + if let meta = state.relay_metadata.lookup(relay_id: relay) { - NavigationLink { - RelayDetailView(state: state, relay: relay, nip11: meta) - } label: { - Text(relay) - } + Text(relay) + .background( + NavigationLink("", destination: RelayDetailView(state: state, relay: relay, nip11: meta)).opacity(0.0) + .disabled(showActionButtons) + ) + Spacer() + + Image(systemName: "info.circle") + .font(.system(size: 20, weight: .regular)) + .foregroundColor(Color.accentColor) } else { Text(relay) + Spacer() + Image(systemName: "questionmark.circle") + .font(.system(size: 20, weight: .regular)) + .foregroundColor(.gray) } } } .swipeActions { if let privkey = state.keypair.privkey { - RemoveAction(privkey: privkey) + RemoveButton(privkey: privkey, showText: false) + .tint(.red) } } .contextMenu { CopyAction(relay: relay) if let privkey = state.keypair.privkey { - RemoveAction(privkey: privkey) + RemoveButton(privkey: privkey, showText: true) } } } @@ -48,9 +69,9 @@ struct RelayView: View { Label(NSLocalizedString("Copy", comment: "Button to copy a relay server address."), systemImage: "doc.on.doc") } } - - func RemoveAction(privkey: String) -> some View { - Button { + + func RemoveButton(privkey: String, showText: Bool) -> some View { + Button(action: { guard let ev = state.contacts.event else { return } @@ -62,15 +83,20 @@ struct RelayView: View { process_contact_event(state: state, ev: new_ev) state.pool.send(.event(new_ev)) - } label: { - Label(NSLocalizedString("Delete", comment: "Button to delete a relay server that the user connects to."), systemImage: "trash") + }) { + if showText { + Text(NSLocalizedString("Disconnect", comment: "Button to disconnect from a relay server.")) + } + Image(systemName: "minus.circle.fill") + .font(.system(size: 20, weight: .medium)) + .foregroundColor(.red) + .padding(.leading, 5) } - .tint(.red) } } struct RelayView_Previews: PreviewProvider { static var previews: some View { - RelayView(state: test_damus_state(), relay: "wss://relay.damus.io") + RelayView(state: test_damus_state(), relay: "wss://relay.damus.io", showActionButtons: .constant(false)) } } diff --git a/damus/Views/UserRelaysView.swift b/damus/Views/UserRelaysView.swift @@ -13,6 +13,7 @@ struct UserRelaysView: View { let relays: [String] @State var relay_state: [(String, Bool)] + @State private var showAddButton = false init (state: DamusState, pubkey: String, relays: [String]) { self.state = state @@ -30,12 +31,25 @@ struct UserRelaysView: View { var body: some View { List(relay_state, id: \.0) { (r, add) in - RecommendedRelayView(damus: state, relay: r, add_button: add) + RecommendedRelayView(damus: state, relay: r, add_button: add, showActionButtons: $showAddButton) } .onReceive(handle_notify(.relays_changed)) { _ in self.relay_state = UserRelaysView.make_relay_state(pool: state.pool, relays: self.relays) } .navigationBarTitle("Relays") + .toolbar{ + if state.keypair.privkey != nil { + if showAddButton { + Button("Done") { + showAddButton.toggle() + } + } else { + Button("Show Add") { + showAddButton.toggle() + } + } + } + } } }