352 lines
16 KiB
Swift
352 lines
16 KiB
Swift
//
|
|
// ServerSetupView.swift
|
|
// Zashi
|
|
//
|
|
// Created by Lukáš Korba on 2024-02-07.
|
|
//
|
|
|
|
import SwiftUI
|
|
import ComposableArchitecture
|
|
import ZcashLightClientKit
|
|
|
|
import Generated
|
|
import UIComponents
|
|
import ZcashSDKEnvironment
|
|
|
|
public struct ServerSetupView: View {
|
|
@Environment(\.colorScheme) var colorScheme
|
|
|
|
var customDismiss: (() -> Void)? = nil
|
|
|
|
@Perception.Bindable var store: StoreOf<ServerSetup>
|
|
|
|
public init(store: StoreOf<ServerSetup>, customDismiss: (() -> Void)? = nil) {
|
|
self.store = store
|
|
self.customDismiss = customDismiss
|
|
}
|
|
|
|
public var body: some View {
|
|
WithPerceptionTracking {
|
|
ScrollViewReader { value in
|
|
WithPerceptionTracking {
|
|
VStack(alignment: .center, spacing: 0) {
|
|
ScrollView {
|
|
if store.topKServers.isEmpty {
|
|
VStack(spacing: 15) {
|
|
progressView()
|
|
|
|
Text(L10n.ServerSetup.performingTest)
|
|
.zFont(.semiBold, size: 20, style: Design.Text.primary)
|
|
|
|
Text(L10n.ServerSetup.couldTakeTime)
|
|
.zFont(size: 14, style: Design.Text.tertiary)
|
|
}
|
|
.frame(height: 136)
|
|
} else {
|
|
HStack {
|
|
Text(L10n.ServerSetup.fastestServers)
|
|
.zFont(.semiBold, size: 18, style: Design.Text.primary)
|
|
|
|
Spacer()
|
|
|
|
Button {
|
|
store.send(.refreshServersTapped)
|
|
} label: {
|
|
HStack(spacing: 4) {
|
|
Text(L10n.ServerSetup.refresh)
|
|
.zFont(.semiBold, size: 14, style: Design.Text.primary)
|
|
|
|
if store.isEvaluatingServers {
|
|
progressView()
|
|
.scaleEffect(0.7)
|
|
} else {
|
|
Asset.Assets.refreshCCW2.image
|
|
.zImage(size: 20, style: Design.Text.primary)
|
|
}
|
|
}
|
|
.padding(5)
|
|
}
|
|
}
|
|
.screenHorizontalPadding()
|
|
.padding(.top, 20)
|
|
|
|
listOfServers(store.topKServers)
|
|
}
|
|
|
|
HStack {
|
|
Text(
|
|
store.topKServers.isEmpty
|
|
? L10n.ServerSetup.allServers
|
|
: L10n.ServerSetup.otherServers
|
|
)
|
|
.zFont(.semiBold, size: 18, style: Design.Text.primary)
|
|
|
|
Spacer()
|
|
}
|
|
.screenHorizontalPadding()
|
|
.padding(.top, store.topKServers.isEmpty ? 0 : 15)
|
|
|
|
listOfServers(store.servers)
|
|
}
|
|
.padding(.vertical, 1)
|
|
|
|
WithPerceptionTracking {
|
|
ZStack {
|
|
Asset.Colors.background.color
|
|
.frame(height: 72)
|
|
.cornerRadius(32)
|
|
.shadow(color: .black.opacity(0.02), radius: 4, x: 0, y: -8)
|
|
|
|
Button {
|
|
store.send(.setServerTapped)
|
|
} label: {
|
|
if store.isUpdatingServer {
|
|
HStack(spacing: 8) {
|
|
Text(L10n.ServerSetup.save)
|
|
.zFont(.semiBold, size: 16,
|
|
style: store.selectedServer == nil
|
|
? Design.Btns.Primary.fgDisabled
|
|
: Design.Btns.Primary.fg
|
|
)
|
|
|
|
progressView(invertTint: true)
|
|
}
|
|
.frame(height: 48)
|
|
.frame(maxWidth: .infinity)
|
|
.background(
|
|
store.selectedServer == nil
|
|
? Design.Btns.Primary.bgDisabled.color(colorScheme)
|
|
: Design.Btns.Primary.bg.color(colorScheme)
|
|
)
|
|
.cornerRadius(10)
|
|
.screenHorizontalPadding()
|
|
} else {
|
|
Text(L10n.ServerSetup.save)
|
|
.zFont(.semiBold, size: 16,
|
|
style: store.selectedServer == nil
|
|
? Design.Btns.Primary.fgDisabled
|
|
: Design.Btns.Primary.fg
|
|
)
|
|
.frame(height: 48)
|
|
.frame(maxWidth: .infinity)
|
|
.background(
|
|
store.selectedServer == nil
|
|
? Design.Btns.Primary.bgDisabled.color(colorScheme)
|
|
: Design.Btns.Primary.bg.color(colorScheme)
|
|
)
|
|
.cornerRadius(10)
|
|
.screenHorizontalPadding()
|
|
}
|
|
}
|
|
.disabled(store.isUpdatingServer)
|
|
}
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.zashiBack(store.isUpdatingServer, customDismiss: customDismiss)
|
|
.screenTitle(L10n.ServerSetup.title)
|
|
.onAppear { store.send(.onAppear) }
|
|
.onDisappear { store.send(.onDisappear) }
|
|
.alert($store.scope(state: \.alert, action: \.alert))
|
|
}
|
|
}
|
|
.applyScreenBackground()
|
|
}
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
|
|
private func listOfServers(_ servers: [ZcashSDKEnvironment.Server]) -> some View {
|
|
ForEach(servers, id: \.self) { server in
|
|
WithPerceptionTracking {
|
|
VStack {
|
|
HStack(spacing: 0) {
|
|
Button {
|
|
store.send(.someServerTapped(server))
|
|
} label: {
|
|
HStack(
|
|
alignment:
|
|
isCustom(server) ? .top : .center,
|
|
spacing: 10
|
|
) {
|
|
WithPerceptionTracking {
|
|
if server.value(for: store.network) == store.activeServer && store.selectedServer == nil {
|
|
Circle()
|
|
.fill(Design.Surfaces.brandPrimary.color(colorScheme))
|
|
.frame(width: 20, height: 20)
|
|
.overlay {
|
|
Asset.Assets.check.image
|
|
.zImage(size: 14, style: Design.Surfaces.brandFg)
|
|
}
|
|
} else if server.value(for: store.network) == store.selectedServer {
|
|
Circle()
|
|
.fill(Design.Checkboxes.onBg.color(colorScheme))
|
|
.frame(width: 20, height: 20)
|
|
.overlay {
|
|
Asset.Assets.check.image
|
|
.zImage(size: 14, style: Design.Checkboxes.onFg)
|
|
}
|
|
} else {
|
|
Circle()
|
|
.fill(Design.Checkboxes.offBg.color(colorScheme))
|
|
.frame(width: 20, height: 20)
|
|
.overlay {
|
|
Circle()
|
|
.stroke(Design.Checkboxes.offStroke.color(colorScheme))
|
|
.frame(width: 20, height: 20)
|
|
}
|
|
}
|
|
}
|
|
.padding(.top, isCustom(server) ? 16 : 0)
|
|
|
|
if isCustom(server) {
|
|
VStack(alignment: .leading) {
|
|
HStack {
|
|
Text(server.value(for: store.network))
|
|
.zFont(.medium, size: 14, style: Design.Text.primary)
|
|
.multilineTextAlignment(.leading)
|
|
|
|
Spacer()
|
|
|
|
if server.value(for: store.network) == store.activeServer {
|
|
activeBadge()
|
|
}
|
|
|
|
chevronDown()
|
|
}
|
|
|
|
WithPerceptionTracking {
|
|
TextField(L10n.ServerSetup.placeholder, text: $store.customServer)
|
|
.zFont(.medium, size: 14, style: Design.Text.primary)
|
|
.frame(height: 40)
|
|
.autocapitalization(.none)
|
|
.multilineTextAlignment(.leading)
|
|
.padding(.leading, 10)
|
|
.background {
|
|
RoundedRectangle(cornerRadius: Design.Radius._md)
|
|
.fill(Design.Surfaces.bgPrimary.color(colorScheme))
|
|
}
|
|
.overlay {
|
|
RoundedRectangle(cornerRadius: Design.Radius._md)
|
|
.stroke(Design.Inputs.Default.stroke.color(colorScheme), lineWidth: 1)
|
|
}
|
|
.padding(.vertical, 8)
|
|
}
|
|
}
|
|
.padding(.vertical, 16)
|
|
} else {
|
|
VStack(alignment: .leading) {
|
|
Text(
|
|
isCustomButNotSelected(server) && !store.customServer.isEmpty
|
|
? store.customServer
|
|
: server.value(for: store.network)
|
|
)
|
|
.zFont(.medium, size: 14, style: Design.Text.primary)
|
|
.multilineTextAlignment(.leading)
|
|
|
|
if let desc = server.desc(for: store.network) {
|
|
Text(desc)
|
|
.zFont(size: 14, style: Design.Text.tertiary)
|
|
}
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
|
|
if server.value(for: store.network) == store.activeServer && !isCustom(server) {
|
|
activeBadge()
|
|
}
|
|
|
|
if isCustomButNotSelected(server) {
|
|
chevronDown()
|
|
}
|
|
}
|
|
.frame(minHeight: 48)
|
|
.padding(.leading, 24)
|
|
.padding(.trailing, isCustom(server) ? 0 : 24)
|
|
.background {
|
|
RoundedRectangle(cornerRadius: Design.Radius._xl)
|
|
.fill(
|
|
server.value(for: store.network) == store.selectedServer
|
|
? Design.Surfaces.bgSecondary.color(colorScheme)
|
|
: Asset.Colors.background.color
|
|
)
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.leading, 8)
|
|
|
|
if let last = servers.last, last != server {
|
|
Design.Surfaces.divider.color(colorScheme)
|
|
.frame(height: 1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func isCustom(_ server: ZcashSDKEnvironment.Server) -> Bool {
|
|
store.selectedServer == L10n.ServerSetup.custom && server.value(for: store.network) == L10n.ServerSetup.custom
|
|
}
|
|
|
|
private func isCustomButNotSelected(_ server: ZcashSDKEnvironment.Server) -> Bool {
|
|
store.selectedServer != L10n.ServerSetup.custom && server.value(for: store.network) == L10n.ServerSetup.custom
|
|
}
|
|
|
|
private func chevronDown() -> some View {
|
|
Asset.Assets.chevronDown.image
|
|
.zImage(size: 20, style: Design.Text.primary)
|
|
}
|
|
|
|
private func activeBadge() -> some View {
|
|
Text(L10n.ServerSetup.active)
|
|
.zFont(.medium, size: 14, style: Design.Utility.SuccessGreen._700)
|
|
.frame(height: 20)
|
|
.padding(.horizontal, 10)
|
|
.padding(.vertical, 2)
|
|
.zBackground(Design.Utility.SuccessGreen._50)
|
|
.cornerRadius(16)
|
|
.overlay {
|
|
RoundedRectangle(cornerRadius: Design.Radius._2xl)
|
|
.inset(by: 0.5)
|
|
.stroke(Design.Utility.SuccessGreen._200.color(colorScheme), lineWidth: 1)
|
|
}
|
|
}
|
|
|
|
private func progressView(invertTint: Bool = false) -> some View {
|
|
ProgressView()
|
|
.progressViewStyle(
|
|
CircularProgressViewStyle(
|
|
tint: colorScheme == .dark
|
|
? (invertTint ? .black : .white) : (invertTint ? .white : .black)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
#Preview {
|
|
NavigationView {
|
|
ServerSetupView(
|
|
store: ServerSetup.placeholder
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: Placeholders
|
|
|
|
extension ServerSetup.State {
|
|
public static var initial = ServerSetup.State()
|
|
}
|
|
|
|
extension ServerSetup {
|
|
public static let placeholder = StoreOf<ServerSetup>(
|
|
initialState: .initial
|
|
) {
|
|
ServerSetup()
|
|
}
|
|
}
|