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>
This commit is contained in:
ericholguin 2024-03-07 15:28:08 +00:00 committed by William Casarin
parent 122775e586
commit 0719e94fbc
9 changed files with 295 additions and 381 deletions

View File

@ -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 */,

View File

@ -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):

View File

@ -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)
}
}

View File

@ -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)
}
}
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)
}
}
.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))
NavigationView {
ZStack(alignment: .bottom){
TabView(selection: $selectedTab) {
RelayList(title: "My Relays", relayList: relays, recommended: false)
.tag(0)
Spacer()
Button(action: {
show_add_relay.toggle()
}) {
HStack {
Text(verbatim: "Add relay")
.padding(10)
RelayList(title: "Recommended", relayList: recommended, recommended: true)
.tag(1)
}
ZStack{
HStack{
ForEach((RelayTab.allCases), id: \.self){ item in
Button{
selectedTab = item.rawValue
} label: {
CustomTabItem(title: item.title, isActive: (selectedTab == item.rawValue))
}
}
}
}
.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)
}
}

View File

@ -66,97 +66,101 @@ 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)
}
}
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 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.")
})
}
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)
Section(NSLocalizedString("Description", comment: "Label to display relay description.")) {
FieldText(nip11.description)
}
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))
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 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 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)
}
}
}
}
.onReceive(handle_notify(.switched_timeline)) { notif in
@ -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 {

View File

@ -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))
}
}

View File

@ -51,7 +51,6 @@ struct RelayStatusView: View {
)
}
}
.padding(.trailing, 20)
}
}

View File

@ -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)
}
}

View File

@ -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."))
}
}