Simplify iOS network add flow
This commit is contained in:
parent
35f3b3ce4e
commit
36a54628ba
2 changed files with 181 additions and 42 deletions
|
|
@ -18,10 +18,13 @@ public struct BurrowView: View {
|
||||||
Text("Burrow")
|
Text("Burrow")
|
||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
|
if showsHeaderSubtitle {
|
||||||
Text("Networks and accounts")
|
Text("Networks and accounts")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if showsToolbarAddMenu {
|
||||||
Spacer()
|
Spacer()
|
||||||
Menu {
|
Menu {
|
||||||
Button("Add WireGuard Network") {
|
Button("Add WireGuard Network") {
|
||||||
|
|
@ -39,12 +42,19 @@ public struct BurrowView: View {
|
||||||
.accessibilityLabel("Add")
|
.accessibilityLabel("Add")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
|
|
||||||
|
if showsInlineQuickActions {
|
||||||
|
quickAddSection
|
||||||
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
sectionHeader(
|
sectionHeader(
|
||||||
title: "Networks",
|
title: "Networks",
|
||||||
detail: "Stored daemon networks and their active account selectors"
|
detail: showsInlineQuickActions
|
||||||
|
? nil
|
||||||
|
: "Stored daemon networks and their active account selectors"
|
||||||
)
|
)
|
||||||
if let connectionError = networkViewModel.connectionError {
|
if let connectionError = networkViewModel.connectionError {
|
||||||
Text(connectionError)
|
Text(connectionError)
|
||||||
|
|
@ -54,10 +64,13 @@ public struct BurrowView: View {
|
||||||
NetworkCarouselView(networks: networkViewModel.cards)
|
NetworkCarouselView(networks: networkViewModel.cards)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if showsAccountsSection {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
sectionHeader(
|
sectionHeader(
|
||||||
title: "Accounts",
|
title: "Accounts",
|
||||||
detail: "Per-network identities and sign-in state"
|
detail: showsInlineQuickActions
|
||||||
|
? nil
|
||||||
|
: "Per-network identities and sign-in state"
|
||||||
)
|
)
|
||||||
if accountStore.accounts.isEmpty {
|
if accountStore.accounts.isEmpty {
|
||||||
ContentUnavailableView(
|
ContentUnavailableView(
|
||||||
|
|
@ -77,11 +90,12 @@ public struct BurrowView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
sectionHeader(
|
sectionHeader(
|
||||||
title: "Tunnel",
|
title: "Tunnel",
|
||||||
detail: "Current system extension state"
|
detail: showsInlineQuickActions ? nil : "Current system extension state"
|
||||||
)
|
)
|
||||||
TunnelStatusView()
|
TunnelStatusView()
|
||||||
TunnelButton()
|
TunnelButton()
|
||||||
|
|
@ -120,18 +134,58 @@ public struct BurrowView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func sectionHeader(title: String, detail: String) -> some View {
|
private var quickAddSection: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
sectionHeader(title: "Add", detail: nil)
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
ForEach(ConfigurationSheet.allCases) { sheet in
|
||||||
|
QuickAddButton(sheet: sheet) {
|
||||||
|
activeSheet = sheet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func sectionHeader(title: String, detail: String?) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.title2.weight(.semibold))
|
.font(.title2.weight(.semibold))
|
||||||
|
if let detail, !detail.isEmpty {
|
||||||
Text(detail)
|
Text(detail)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var showsInlineQuickActions: Bool {
|
||||||
|
#if os(iOS)
|
||||||
|
true
|
||||||
|
#else
|
||||||
|
false
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private var showsToolbarAddMenu: Bool {
|
||||||
|
!showsInlineQuickActions
|
||||||
|
}
|
||||||
|
|
||||||
|
private var showsHeaderSubtitle: Bool {
|
||||||
|
!showsInlineQuickActions
|
||||||
|
}
|
||||||
|
|
||||||
|
private var showsAccountsSection: Bool {
|
||||||
|
#if os(iOS)
|
||||||
|
!accountStore.accounts.isEmpty
|
||||||
|
#else
|
||||||
|
true
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ConfigurationSheet: String, Identifiable {
|
private enum ConfigurationSheet: String, CaseIterable, Identifiable {
|
||||||
case wireGuard
|
case wireGuard
|
||||||
case tor
|
case tor
|
||||||
case tailnet
|
case tailnet
|
||||||
|
|
@ -145,6 +199,75 @@ private enum ConfigurationSheet: String, Identifiable {
|
||||||
case .tailnet: .headscale
|
case .tailnet: .headscale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var iconName: String {
|
||||||
|
switch self {
|
||||||
|
case .wireGuard:
|
||||||
|
"wave.3.right"
|
||||||
|
case .tor:
|
||||||
|
"shield.lefthalf.filled.badge.checkmark"
|
||||||
|
case .tailnet:
|
||||||
|
"network.badge.shield.half.filled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var quickActionTitle: String {
|
||||||
|
switch self {
|
||||||
|
case .wireGuard:
|
||||||
|
"WireGuard"
|
||||||
|
case .tor:
|
||||||
|
"Tor"
|
||||||
|
case .tailnet:
|
||||||
|
"Tailnet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var quickActionSubtitle: String {
|
||||||
|
switch self {
|
||||||
|
case .wireGuard:
|
||||||
|
"Import a tunnel"
|
||||||
|
case .tor:
|
||||||
|
"Save an Arti profile"
|
||||||
|
case .tailnet:
|
||||||
|
"Sign in or save a control plane"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var quickActionColor: Color {
|
||||||
|
switch self {
|
||||||
|
case .wireGuard:
|
||||||
|
.blue
|
||||||
|
case .tor, .tailnet:
|
||||||
|
kind.accentColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct QuickAddButton: View {
|
||||||
|
let sheet: ConfigurationSheet
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: action) {
|
||||||
|
HStack(spacing: 14) {
|
||||||
|
Image(systemName: sheet.iconName)
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.frame(width: 24)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(sheet.quickActionTitle)
|
||||||
|
.font(.headline)
|
||||||
|
Text(sheet.quickActionSubtitle)
|
||||||
|
.font(.caption)
|
||||||
|
.opacity(0.88)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, minHeight: 64, alignment: .leading)
|
||||||
|
}
|
||||||
|
.buttonStyle(.floating(color: sheet.quickActionColor, cornerRadius: 18))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct AccountDraft {
|
private struct AccountDraft {
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,28 @@ struct NetworkCarouselView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if networks.isEmpty {
|
if networks.isEmpty {
|
||||||
|
#if os(iOS)
|
||||||
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
|
Text("No stored networks yet")
|
||||||
|
.font(.headline)
|
||||||
|
Text("WireGuard and Tailnet networks show up here as soon as you add one.")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding()
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 18)
|
||||||
|
.fill(.thinMaterial)
|
||||||
|
)
|
||||||
|
#else
|
||||||
ContentUnavailableView(
|
ContentUnavailableView(
|
||||||
"No Networks Yet",
|
"No Networks Yet",
|
||||||
systemImage: "network.slash",
|
systemImage: "network.slash",
|
||||||
description: Text("Add a WireGuard network, or save a Tailnet account so Burrow can store a managed network when the daemon is reachable.")
|
description: Text("Add a WireGuard network, or save a Tailnet account so Burrow can store a managed network when the daemon is reachable.")
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity, minHeight: 175)
|
.frame(maxWidth: .infinity, minHeight: 175)
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
ScrollView(.horizontal) {
|
ScrollView(.horizontal) {
|
||||||
LazyHStack {
|
LazyHStack {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue