1
0
mirror of git://jb55.com/damus synced 2024-09-30 00:40:45 +00:00

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()
}
}
NavigationView {
ZStack(alignment: .bottom){
TabView(selection: $selectedTab) {
RelayList(title: "My Relays", relayList: relays, recommended: false)
.tag(0)
var MainContent: some View {
VStack {
Divider()
if showActionButtons && !show_recommended {
VStack {
Button(action: {
withAnimation(.easeOut(duration: 0.2)) {
show_recommended.toggle()
RelayList(title: "Recommended", relayList: recommended, recommended: true)
.tag(1)
}
}) {
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)
}
ZStack{
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)
ForEach((RelayTab.allCases), id: \.self){ item in
Button{
selectedTab = item.rawValue
} label: {
CustomTabItem(title: item.title, isActive: (selectedTab == item.rawValue))
}
}
.buttonStyle(NeutralButtonStyle())
}
.padding(25)
List(Array(relays), id: \.url) { relay in
RelayView(state: state, relay: relay.url.id, showActionButtons: $showActionButtons)
}
.listStyle(PlainListStyle())
.frame(width: 235, height: 35)
.background(.damusNeutral3)
.cornerRadius(30)
.padding(.horizontal, 26)
}
}
.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)
}
}
.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,6 +66,8 @@ struct RelayDetailView: View {
}
var body: some View {
NavigationView {
ZStack {
Group {
Form {
if let keypair = state.keypair.to_full() {
@ -159,11 +161,16 @@ struct RelayDetailView: View {
}
}
}
}
}
.onReceive(handle_notify(.switched_timeline)) { notif in
dismiss()
}
.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,33 +48,13 @@ 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)
}
Spacer()
if let relay_connection {
RelayStatusView(connection: relay_connection)
.background(
NavigationLink(value: Route.RelayDetail(relay: relay, metadata: meta), label: {
EmptyView()
})
.buttonStyle(.plain)
.disabled(showActionButtons)
)
}
}
}
.swipeActions {
if let privkey = state.keypair.privkey {
RemoveButton(privkey: privkey, showText: false)
.tint(.red)
}
}
.lineLimit(1)
.contextMenu {
CopyAction(relay: relay)
@ -75,20 +64,68 @@ struct RelayView: View {
}
}
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)
}
Image("chevron-large-right")
.resizable()
.frame(width: 15, height: 15)
.foregroundColor(.gray)
}
}
.contentShape(Rectangle())
}
.onReceive(handle_notify(.relays_changed)) { _ in
self.relay_state = RelayView.get_relay_state(pool: state.pool, relay: self.relay)
}
.onTapGesture {
state.nav.push(route: Route.RelayDetail(relay: relay, metadata: model_cache.model(with_relay_id: relay)?.metadata))
}
}
private var relay_connection: RelayConnection? {
state.pool.get_relay(relay)?.connection
}
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: "copy2")
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 RemoveButton(privkey: Privkey, showText: Bool) -> some View {
Button(action: {
func remove_action(privkey: Privkey) {
guard let ev = state.contacts.event else {
return
}
@ -106,6 +143,29 @@ struct RelayView: View {
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")
} label: {
Label(NSLocalizedString("Copy", comment: "Button to copy a relay server address."), image: "copy2")
}
}
func RemoveButton(privkey: Privkey, showText: Bool) -> some View {
Button(action: {
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."))
}
}