Simplify iOS network add flow

This commit is contained in:
Conrad Kramer 2026-03-31 13:40:13 -07:00
parent 35f3b3ce4e
commit 36a54628ba
2 changed files with 181 additions and 42 deletions

View file

@ -18,10 +18,13 @@ public struct BurrowView: View {
Text("Burrow")
.font(.largeTitle)
.fontWeight(.bold)
if showsHeaderSubtitle {
Text("Networks and accounts")
.font(.headline)
.foregroundStyle(.secondary)
}
}
if showsToolbarAddMenu {
Spacer()
Menu {
Button("Add WireGuard Network") {
@ -39,12 +42,19 @@ public struct BurrowView: View {
.accessibilityLabel("Add")
}
}
}
.padding(.top)
if showsInlineQuickActions {
quickAddSection
}
VStack(alignment: .leading, spacing: 12) {
sectionHeader(
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 {
Text(connectionError)
@ -54,10 +64,13 @@ public struct BurrowView: View {
NetworkCarouselView(networks: networkViewModel.cards)
}
if showsAccountsSection {
VStack(alignment: .leading, spacing: 12) {
sectionHeader(
title: "Accounts",
detail: "Per-network identities and sign-in state"
detail: showsInlineQuickActions
? nil
: "Per-network identities and sign-in state"
)
if accountStore.accounts.isEmpty {
ContentUnavailableView(
@ -77,11 +90,12 @@ public struct BurrowView: View {
}
}
}
}
VStack(alignment: .leading, spacing: 8) {
sectionHeader(
title: "Tunnel",
detail: "Current system extension state"
detail: showsInlineQuickActions ? nil : "Current system extension state"
)
TunnelStatusView()
TunnelButton()
@ -120,10 +134,25 @@ public struct BurrowView: View {
}
@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) {
Text(title)
.font(.title2.weight(.semibold))
if let detail, !detail.isEmpty {
Text(detail)
.font(.subheadline)
.foregroundStyle(.secondary)
@ -131,7 +160,32 @@ public struct BurrowView: View {
}
}
private enum ConfigurationSheet: String, Identifiable {
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, CaseIterable, Identifiable {
case wireGuard
case tor
case tailnet
@ -145,6 +199,75 @@ private enum ConfigurationSheet: String, Identifiable {
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 {

View file

@ -6,12 +6,28 @@ struct NetworkCarouselView: View {
var body: some View {
Group {
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(
"No Networks Yet",
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.")
)
.frame(maxWidth: .infinity, minHeight: 175)
#endif
} else {
ScrollView(.horizontal) {
LazyHStack {