damus

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

commit 0719e94fbcb49b6b313d41c32d6c3592675f8068
parent 122775e586d09fc1fb2136e073fba9dfcecb159a
Author: ericholguin <ericholguin@apache.org>
Date:   Thu,  7 Mar 2024 15:28:08 +0000

ux: Relay View Improvements

This patch removes the Recommended Relay View and the old representation of recommended relays.
Adds a tab view style to the Relay Config View allowing the user to switch between their connected relays
and recommended relays. They can add and remove from the recommended view as well. For users logged in with
a pubkey the add button will no longer be displayed.

Testing
——
iPhone 15 Pro Max (17.0) Light Mode:
https://v.nostr.build/QGMZ.mp4

iPhone SE (3rd generation) (16.4) Dark Mode:
https://v.nostr.build/Wlw3.mp4
——

Changelog-Changed: Relay config view user interface

Signed-off-by: ericholguin <ericholguin@apache.org>
Link: 20240307152808.47929-1-ericholguin@apache.org
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4----
Mdamus/Util/Router.swift | 2+-
Ddamus/Views/Relays/RecommendedRelayView.swift | 131-------------------------------------------------------------------------------
Mdamus/Views/Relays/RelayConfigView.swift | 216+++++++++++++++++++++++++++++++++++++------------------------------------------
Mdamus/Views/Relays/RelayDetailView.swift | 171+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mdamus/Views/Relays/RelayPicView.swift | 2+-
Mdamus/Views/Relays/RelayStatusView.swift | 1-
Mdamus/Views/Relays/RelayView.swift | 140++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mdamus/Views/UserRelaysView.swift | 5+----
9 files changed, 293 insertions(+), 379 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -288,7 +288,6 @@ 4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; }; 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; }; 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; }; - 4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */; }; 4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */; }; 4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838529656C8B00DC99E7 /* NIP05.swift */; }; 4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */; }; @@ -1205,7 +1204,6 @@ 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.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>"; }; - 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedRelayView.swift; sourceTree = "<group>"; }; 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRelaysView.swift; sourceTree = "<group>"; }; 4CB8838529656C8B00DC99E7 /* NIP05.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05.swift; sourceTree = "<group>"; }; 4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailBar.swift; sourceTree = "<group>"; }; @@ -2294,7 +2292,6 @@ isa = PBXGroup; children = ( 4CE879532996BA0000F758CC /* Detail */, - 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */, 4C06670028FC7C5900038D2A /* RelayView.swift */, 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */, F7908E91298B0F0700AB113A /* RelayDetailView.swift */, @@ -3417,7 +3414,6 @@ 7527271E2A93FF0100214108 /* Block.swift in Sources */, 4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */, 4C12536C2A76D4B00004F4B8 /* RepostedNotify.swift in Sources */, - 4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */, 4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */, 4C32B9592A9AD44700DC3548 /* Table.swift in Sources */, 4C5D5C9D2A6B2CB40024563C /* AsciiCharacter.swift in Sources */, diff --git a/damus/Util/Router.swift b/damus/Util/Router.swift @@ -53,7 +53,7 @@ enum Route: Hashable { case .Followers(let followers): FollowersView(damus_state: damusState, followers: followers) case .Relay(let relay, let showActionButtons): - RelayView(state: damusState, relay: relay, showActionButtons: showActionButtons) + RelayView(state: damusState, relay: relay, showActionButtons: showActionButtons, recommended: false) case .RelayDetail(let relay, let metadata): RelayDetailView(state: damusState, relay: relay, nip11: metadata) case .Following(let following): diff --git a/damus/Views/Relays/RecommendedRelayView.swift b/damus/Views/Relays/RecommendedRelayView.swift @@ -1,131 +0,0 @@ -// -// RecommendedRelayView.swift -// damus -// -// Created by William Casarin on 2022-12-29. -// - -import SwiftUI - -struct RecommendedRelayView: View { - let damus: DamusState - let relay: String - let add_button: Bool - let user_recommended: Bool - - @ObservedObject private var model_cache: RelayModelCache - - init(damus: DamusState, relay: String, add_button: Bool = true, user_recommended: Bool = false) { - self.damus = damus - self.relay = relay - self.add_button = add_button - self.user_recommended = user_recommended - self.model_cache = damus.relay_model_cache - } - - var body: some View { - let meta = model_cache.model(with_relay_id: relay)?.metadata - - if user_recommended { - HStack { - RelayPicView(relay: relay, icon: meta?.icon, size: 50, highlight: .none, disable_animation: false) - .padding(.horizontal, 5) - - VStack(alignment: .leading) { - HStack { - Text(meta?.name ?? relay) - .font(.headline) - .padding(.bottom, 2) - - RelayType(is_paid: damus.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false) - } - - Text(relay) - .font(.subheadline) - .foregroundColor(.gray) - } - - Spacer() - - if let keypair = damus.keypair.to_full() { - VStack(alignment: .center) { - if damus.pool.get_relay(relay) == nil { - AddButton(keypair: keypair) - } else { - Image(systemName: "checkmark.circle") - .resizable() - .frame(width: 30, height: 30) - .foregroundColor(DamusColors.success) - .padding(.trailing, 10) - } - } - .padding(.horizontal, 5) - } - } - } else { - VStack { - RelayPicView(relay: relay, icon: meta?.icon, size: 70, highlight: .none, disable_animation: false) - if let meta = damus.relay_model_cache.model(with_relay_id: relay)?.metadata { - NavigationLink(value: Route.RelayDetail(relay: relay, metadata: meta)){ - EmptyView() - } - .opacity(0.0) - } - - HStack { - Text(meta?.name ?? relay) - .lineLimit(1) - .frame(maxWidth: 150) - .padding(.vertical, 5) - } - .contextMenu { - CopyAction(relay: relay) - } - - if let keypair = damus.keypair.to_full() { - AddButton(keypair: keypair) - } - } - } - } - - func CopyAction(relay: String) -> some View { - Button { - UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text") - } label: { - Label(NSLocalizedString("Copy", comment: "Button to copy a relay server address."), image: "copy") - } - } - - func AddButton(keypair: FullKeypair) -> some View { - Button(action: { - add_action(keypair: keypair) - }) { - Text(NSLocalizedString("Add", comment: "Button to add relay server to list.")) - .padding(10) - } - .buttonStyle(NeutralButtonStyle()) - } - - func add_action(keypair: FullKeypair) { - guard let ev_before_add = damus.contacts.event else { - return - } - guard let relay_url = RelayURL(relay), - let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: damus.pool.our_descriptors, relay: relay_url, info: .rw) else { - return - } - process_contact_event(state: damus, ev: ev_after_add) - damus.postbox.send(ev_after_add) - - if let relay_metadata = make_relay_metadata(relays: damus.pool.our_descriptors, keypair: keypair) { - damus.postbox.send(relay_metadata) - } - } -} - -struct RecommendedRelayView_Previews: PreviewProvider { - static var previews: some View { - RecommendedRelayView(damus: test_damus_state, relay: "wss://relay.damus.io", user_recommended: true) - } -} diff --git a/damus/Views/Relays/RelayConfigView.swift b/damus/Views/Relays/RelayConfigView.swift @@ -7,156 +7,83 @@ import SwiftUI +enum RelayTab: Int, CaseIterable{ + case myRelays = 0 + case recommended + + var title: String{ + switch self { + case .myRelays: + return "My relays" + case .recommended: + return "Recommended" + } + } +} + struct RelayConfigView: View { let state: DamusState @State var relays: [RelayDescriptor] @State private var showActionButtons = false @State var show_add_relay: Bool = false - @SceneStorage("RelayConfigView.show_recommended") var show_recommended : Bool = true + @State var selectedTab = 0 @Environment(\.dismiss) var dismiss init(state: DamusState) { self.state = state _relays = State(initialValue: state.pool.our_descriptors) + UITabBar.appearance().isHidden = true } var recommended: [RelayDescriptor] { let rs: [RelayDescriptor] = [] let recommended_relay_addresses = get_default_bootstrap_relays() return recommended_relay_addresses.reduce(into: rs) { xs, x in - if state.pool.get_relay(x) == nil, let url = RelayURL(x) { + if let url = RelayURL(x) { xs.append(RelayDescriptor(url: url, info: .rw)) } } } var body: some View { - MainContent - .onReceive(handle_notify(.relays_changed)) { _ in - self.relays = state.pool.our_descriptors - } - .onReceive(handle_notify(.switched_timeline)) { _ in - dismiss() - } - } - - var MainContent: some View { - VStack { - Divider() - - if showActionButtons && !show_recommended { - VStack { - Button(action: { - withAnimation(.easeOut(duration: 0.2)) { - show_recommended.toggle() - } - }) { - Text("Show recommended relays", comment: "Button to show recommended relays.") - .foregroundStyle(DamusLightGradient.gradient) - .padding(10) - .background { - RoundedRectangle(cornerRadius: 15) - .stroke(DamusLightGradient.gradient) - } - } - .padding(.top, 10) + NavigationView { + ZStack(alignment: .bottom){ + TabView(selection: $selectedTab) { + RelayList(title: "My Relays", relayList: relays, recommended: false) + .tag(0) + + RelayList(title: "Recommended", relayList: recommended, recommended: true) + .tag(1) } - } - - if recommended.count > 0 && show_recommended { - VStack { - HStack(alignment: .top) { - Spacer() - Button(action: { - withAnimation(.easeOut(duration: 0.2)) { - show_recommended.toggle() - } - }) { - Image(systemName: "xmark.circle") - .font(.system(size: 18)) - .foregroundStyle(DamusLightGradient.gradient) - } - .padding([.top, .trailing], 8) - } - - Text("Recommended relays", comment: "Title for view of recommended relays.") - .foregroundStyle(DamusLightGradient.gradient) - .padding(10) - .background { - RoundedRectangle(cornerRadius: 15) - .stroke(DamusLightGradient.gradient) - } - - ScrollView(.horizontal) { - HStack(spacing: 20) { - ForEach(recommended, id: \.url) { r in - RecommendedRelayView(damus: state, relay: r.url.id) + ZStack{ + HStack{ + ForEach((RelayTab.allCases), id: \.self){ item in + Button{ + selectedTab = item.rawValue + } label: { + CustomTabItem(title: item.title, isActive: (selectedTab == item.rawValue)) } } - .padding(.horizontal, 30) - .padding(.vertical, 5) - } - .scrollIndicators(.hidden) - .mask( - HStack(spacing: 0) { - LinearGradient(gradient: Gradient(colors: [Color.clear, Color.white]), startPoint: .leading, endPoint: .trailing) - .frame(width: 30) - - Rectangle() - .fill(Color.white) - .frame(maxWidth: .infinity) - - LinearGradient(gradient: Gradient(colors: [Color.white, Color.clear]), startPoint: .leading, endPoint: .trailing) - .frame(width: 30) - } - ) - .padding() - } - .frame(minWidth: 250, maxWidth: .infinity, minHeight: 250, alignment: .center) - .background { - RoundedRectangle(cornerRadius: 12) - .fill(DamusLightGradient.gradient.opacity(0.15), strokeBorder: DamusLightGradient.gradient, lineWidth: 1) - } - .padding(.horizontal) - } - - HStack { - Text(NSLocalizedString("My Relays", comment: "Section title for relay servers that the user is connected to.")) - .font(.system(size: 32, weight: .bold)) - - Spacer() - - Button(action: { - show_add_relay.toggle() - }) { - HStack { - Text(verbatim: "Add relay") - .padding(10) } } - .buttonStyle(NeutralButtonStyle()) + .frame(width: 235, height: 35) + .background(.damusNeutral3) + .cornerRadius(30) + .padding(.horizontal, 26) } - .padding(25) - - List(Array(relays), id: \.url) { relay in - RelayView(state: state, relay: relay.url.id, showActionButtons: $showActionButtons) - } - .listStyle(PlainListStyle()) } .navigationTitle(NSLocalizedString("Relays", comment: "Title of relays view")) .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) + .navigationBarItems(leading: BackNav()) .sheet(isPresented: $show_add_relay, onDismiss: { self.show_add_relay = false }) { - if #available(iOS 16.0, *) { - AddRelayView(state: state) - .presentationDetents([.height(300)]) - .presentationDragIndicator(.visible) - } else { - AddRelayView(state: state) - } + AddRelayView(state: state) + .presentationDetents([.height(300)]) + .presentationDragIndicator(.visible) } .toolbar { - if state.keypair.privkey != nil { + if state.keypair.privkey != nil && selectedTab == 0 { if showActionButtons { Button("Done") { withAnimation { @@ -172,6 +99,65 @@ struct RelayConfigView: View { } } } + .onReceive(handle_notify(.relays_changed)) { _ in + self.relays = state.pool.our_descriptors + } + .onAppear { + notify(.display_tabbar(false)) + } + .onDisappear { + notify(.display_tabbar(true)) + } + .ignoresSafeArea(.all) + } + + func RelayList(title: String, relayList: [RelayDescriptor], recommended: Bool) -> some View { + ScrollView(showsIndicators: false) { + HStack { + Text(NSLocalizedString(title, comment: "Section title for type of relay server list")) + .font(.system(size: 32, weight: .bold)) + + + Spacer() + + if state.keypair.privkey != nil { + Button(action: { + show_add_relay.toggle() + }) { + HStack { + Text(verbatim: "Add relay") + .padding(10) + } + } + .buttonStyle(NeutralButtonStyle()) + } + } + .padding(.top, 5) + + ForEach(relayList, id: \.url) { relay in + Group { + RelayView(state: state, relay: relay.url.id, showActionButtons: $showActionButtons, recommended: recommended) + Divider() + } + } + + Spacer() + .padding(25) + } + .padding(.horizontal) + } +} + +extension RelayConfigView{ + func CustomTabItem(title: String, isActive: Bool) -> some View { + HStack { + Text(title) + .font(.system(size: 12, weight: isActive ? .bold : .regular)) + .foregroundColor(isActive ? .damusAdaptableBlack : .damusAdaptableBlack.opacity(0.7)) + } + .frame(width: 110, height: 30) + .background(isActive ? .damusAdaptableWhite.opacity(0.9) : .clear) + .cornerRadius(30) } } diff --git a/damus/Views/Relays/RelayDetailView.swift b/damus/Views/Relays/RelayDetailView.swift @@ -66,95 +66,99 @@ struct RelayDetailView: View { } var body: some View { - Group { - Form { - if let keypair = state.keypair.to_full() { - if check_connection() { - RemoveRelayButton(keypair) - } else { - Button(action: { - guard let ev_before_add = state.contacts.event else { - return + NavigationView { + ZStack { + Group { + Form { + if let keypair = state.keypair.to_full() { + if check_connection() { + RemoveRelayButton(keypair) + } else { + Button(action: { + guard let ev_before_add = state.contacts.event else { + return + } + guard let relay_url = RelayURL(relay), + let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else { + return + } + process_contact_event(state: state, ev: ev_after_add) + state.postbox.send(ev_after_add) + + if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) { + state.postbox.send(relay_metadata) + } + dismiss() + }) { + Text("Connect To Relay", comment: "Button to connect to the relay.") + } } - guard let relay_url = RelayURL(relay), - let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else { - return + } + + if let authentication_state: RelayAuthenticationState = relay_object?.authentication_state, + authentication_state != .none { + Section(NSLocalizedString("Authentication", comment: "Header label to display authentication details for a given relay.")) { + RelayAuthenticationDetail(state: authentication_state) } - process_contact_event(state: state, ev: ev_after_add) - state.postbox.send(ev_after_add) - - if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) { - state.postbox.send(relay_metadata) + } + + if let pubkey = nip11?.pubkey { + Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) { + UserViewRow(damus_state: state, pubkey: pubkey) + .onTapGesture { + state.nav.push(route: Route.ProfileByKey(pubkey: pubkey)) + } } - dismiss() - }) { - Text("Connect To Relay", comment: "Button to connect to the relay.") } - } - } - - if let authentication_state: RelayAuthenticationState = relay_object?.authentication_state, - authentication_state != .none { - Section(NSLocalizedString("Authentication", comment: "Header label to display authentication details for a given relay.")) { - RelayAuthenticationDetail(state: authentication_state) - } - } - - if let pubkey = nip11?.pubkey { - Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) { - UserViewRow(damus_state: state, pubkey: pubkey) - .onTapGesture { - state.nav.push(route: Route.ProfileByKey(pubkey: pubkey)) + + if let relay_connection { + Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) { + HStack { + Text(relay) + Spacer() + RelayStatusView(connection: relay_connection) + } } - } - } - - if let relay_connection { - Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) { - HStack { - Text(relay) - Spacer() - RelayStatusView(connection: relay_connection) } - } - } - - if let nip11 { - if nip11.is_paid { - Section(content: { - RelayPaidDetail(payments_url: nip11.payments_url) - }, header: { - Text("Paid Relay", comment: "Section header that indicates the relay server requires payment.") - }, footer: { - Text("This is a paid relay, you must pay for notes to be accepted.", comment: "Footer description that explains that the relay server requires payment to post.") - }) - } - - Section(NSLocalizedString("Description", comment: "Label to display relay description.")) { - FieldText(nip11.description) - } - Section(NSLocalizedString("Contact", comment: "Label to display relay contact information.")) { - FieldText(nip11.contact) - } - Section(NSLocalizedString("Software", comment: "Label to display relay software.")) { - FieldText(nip11.software) - } - Section(NSLocalizedString("Version", comment: "Label to display relay software version.")) { - FieldText(nip11.version) - } - if let nips = nip11.supported_nips, nips.count > 0 { - Section(NSLocalizedString("Supported NIPs", comment: "Label to display relay's supported NIPs.")) { - Text(nipsList(nips: nips)) + + if let nip11 { + if nip11.is_paid { + Section(content: { + RelayPaidDetail(payments_url: nip11.payments_url) + }, header: { + Text("Paid Relay", comment: "Section header that indicates the relay server requires payment.") + }, footer: { + Text("This is a paid relay, you must pay for notes to be accepted.", comment: "Footer description that explains that the relay server requires payment to post.") + }) + } + + Section(NSLocalizedString("Description", comment: "Label to display relay description.")) { + FieldText(nip11.description) + } + Section(NSLocalizedString("Contact", comment: "Label to display relay contact information.")) { + FieldText(nip11.contact) + } + Section(NSLocalizedString("Software", comment: "Label to display relay software.")) { + FieldText(nip11.software) + } + Section(NSLocalizedString("Version", comment: "Label to display relay software version.")) { + FieldText(nip11.version) + } + if let nips = nip11.supported_nips, nips.count > 0 { + Section(NSLocalizedString("Supported NIPs", comment: "Label to display relay's supported NIPs.")) { + Text(nipsList(nips: nips)) + } + } + } + + if state.settings.developer_mode { + Section(NSLocalizedString("Log", comment: "Label to display developer mode logs.")) { + Text(log.contents ?? NSLocalizedString("No logs to display", comment: "Label to indicate that there are no developer mode logs available to be displayed on the screen")) + .font(.system(size: 13)) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + } } - } - } - - if state.settings.developer_mode { - Section(NSLocalizedString("Log", comment: "Label to display developer mode logs.")) { - Text(log.contents ?? NSLocalizedString("No logs to display", comment: "Label to indicate that there are no developer mode logs available to be displayed on the screen")) - .font(.system(size: 13)) - .lineLimit(nil) - .fixedSize(horizontal: false, vertical: true) } } } @@ -164,6 +168,9 @@ struct RelayDetailView: View { } .navigationTitle(nip11?.name ?? relay) .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) + .navigationBarItems(leading: BackNav()) + .ignoresSafeArea(.all) } private func nipsList(nips: [Int]) -> AttributedString { diff --git a/damus/Views/Relays/RelayPicView.swift b/damus/Views/Relays/RelayPicView.swift @@ -61,7 +61,7 @@ struct InnerRelayPicView: View { } .frame(width: size, height: size) .clipShape(RoundedRectangle(cornerRadius: 15)) - .overlay(RoundedRectangle(cornerRadius: 15).stroke(failedImage ? .gray : highlight_color(highlight), lineWidth: failedImage ? 1 : pfp_line_width(highlight))) + .overlay(RoundedRectangle(cornerRadius: 15).stroke(.gray.opacity(0.5), lineWidth: 0.5)) } } diff --git a/damus/Views/Relays/RelayStatusView.swift b/damus/Views/Relays/RelayStatusView.swift @@ -51,7 +51,6 @@ struct RelayStatusView: View { ) } } - .padding(.trailing, 20) } } diff --git a/damus/Views/Relays/RelayView.swift b/damus/Views/Relays/RelayView.swift @@ -10,22 +10,31 @@ import SwiftUI struct RelayView: View { let state: DamusState let relay: String + let recommended: Bool @ObservedObject private var model_cache: RelayModelCache + @State var relay_state: Bool @Binding var showActionButtons: Bool - init(state: DamusState, relay: String, showActionButtons: Binding<Bool>) { + init(state: DamusState, relay: String, showActionButtons: Binding<Bool>, recommended: Bool) { self.state = state self.relay = relay + self.recommended = recommended self.model_cache = state.relay_model_cache _showActionButtons = showActionButtons + let relay_state = RelayView.get_relay_state(pool: state.pool, relay: relay) + self._relay_state = State(initialValue: relay_state) + } + + static func get_relay_state(pool: RelayPool, relay: String) -> Bool { + return pool.get_relay(relay) == nil } var body: some View { Group { HStack { if let privkey = state.keypair.privkey { - if showActionButtons { + if showActionButtons && !recommended { RemoveButton(privkey: privkey, showText: false) } } @@ -39,39 +48,60 @@ struct RelayView: View { Text(meta?.name ?? relay) .font(.headline) .padding(.bottom, 2) + .lineLimit(1) RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false) } Text(relay) .font(.subheadline) .foregroundColor(.gray) + .lineLimit(1) + .contextMenu { + CopyAction(relay: relay) + + if let privkey = state.keypair.privkey { + RemoveButton(privkey: privkey, showText: true) + } + } } Spacer() + + if recommended { + if let keypair = state.keypair.to_full() { + VStack(alignment: .center) { + if relay_state { + AddButton(keypair: keypair) + } else { + Button(action: { + remove_action(privkey: keypair.privkey) + }) { + Text(NSLocalizedString("Added", comment: "Button to show relay server is already added to list.")) + .font(.caption) + } + .buttonStyle(NeutralButtonShape.capsule.style) + .opacity(0.5) + } + } + .padding(.horizontal, 5) + } + } else { + if let relay_connection { + RelayStatusView(connection: relay_connection) + } - if let relay_connection { - RelayStatusView(connection: relay_connection) - .background( - NavigationLink(value: Route.RelayDetail(relay: relay, metadata: meta), label: { - EmptyView() - }) - .buttonStyle(.plain) - .disabled(showActionButtons) - ) + Image("chevron-large-right") + .resizable() + .frame(width: 15, height: 15) + .foregroundColor(.gray) } } + .contentShape(Rectangle()) } - .swipeActions { - if let privkey = state.keypair.privkey { - RemoveButton(privkey: privkey, showText: false) - .tint(.red) - } + .onReceive(handle_notify(.relays_changed)) { _ in + self.relay_state = RelayView.get_relay_state(pool: state.pool, relay: self.relay) } - .contextMenu { - CopyAction(relay: relay) - - if let privkey = state.keypair.privkey { - RemoveButton(privkey: privkey, showText: true) - } + .onTapGesture { + state.nav.push(route: Route.RelayDetail(relay: relay, metadata: model_cache.model(with_relay_id: relay)?.metadata)) } } @@ -79,6 +109,52 @@ struct RelayView: View { state.pool.get_relay(relay)?.connection } + func add_action(keypair: FullKeypair) { + guard let ev_before_add = state.contacts.event else { + return + } + guard let relay_url = RelayURL(relay), + let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else { + return + } + process_contact_event(state: state, ev: ev_after_add) + state.postbox.send(ev_after_add) + + if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) { + state.postbox.send(relay_metadata) + } + } + + func remove_action(privkey: Privkey) { + guard let ev = state.contacts.event else { + return + } + + let descriptors = state.pool.our_descriptors + guard let keypair = state.keypair.to_full(), + let relay_url = RelayURL(relay), + let new_ev = remove_relay(ev: ev, current_relays: descriptors, keypair: keypair, relay: relay_url) else { + return + } + + process_contact_event(state: state, ev: new_ev) + state.postbox.send(new_ev) + + if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) { + state.postbox.send(relay_metadata) + } + } + + func AddButton(keypair: FullKeypair) -> some View { + Button(action: { + add_action(keypair: keypair) + }) { + Text(NSLocalizedString("Add", comment: "Button to add relay server to list.")) + .font(.caption) + } + .buttonStyle(NeutralButtonShape.capsule.style) + } + func CopyAction(relay: String) -> some View { Button { UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text") @@ -89,23 +165,7 @@ struct RelayView: View { func RemoveButton(privkey: Privkey, showText: Bool) -> some View { Button(action: { - guard let ev = state.contacts.event else { - return - } - - let descriptors = state.pool.our_descriptors - guard let keypair = state.keypair.to_full(), - let relay_url = RelayURL(relay), - let new_ev = remove_relay(ev: ev, current_relays: descriptors, keypair: keypair, relay: relay_url) else { - return - } - - process_contact_event(state: state, ev: new_ev) - state.postbox.send(new_ev) - - if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) { - state.postbox.send(relay_metadata) - } + remove_action(privkey: privkey) }) { if showText { Text(NSLocalizedString("Disconnect", comment: "Button to disconnect from a relay server.")) @@ -122,6 +182,6 @@ struct RelayView: View { struct RelayView_Previews: PreviewProvider { static var previews: some View { - RelayView(state: test_damus_state, relay: "wss://relay.damus.io", showActionButtons: .constant(false)) + RelayView(state: test_damus_state, relay: "wss://relay.damus.io", showActionButtons: .constant(false), recommended: false) } } diff --git a/damus/Views/UserRelaysView.swift b/damus/Views/UserRelaysView.swift @@ -28,12 +28,9 @@ struct UserRelaysView: View { var body: some View { List(relay_state, id: \.0) { (r, add) in - RecommendedRelayView(damus: state, relay: r, add_button: add, user_recommended: true) + RelayView(state: state, relay: r, showActionButtons: .constant(true), recommended: true) } .listStyle(PlainListStyle()) - .onReceive(handle_notify(.relays_changed)) { _ in - self.relay_state = UserRelaysView.make_relay_state(pool: state.pool, relays: self.relays) - } .navigationBarTitle(NSLocalizedString("Relays", comment: "Navigation bar title that shows the list of relays for a user.")) } }