89 lines
2.9 KiB
Swift
89 lines
2.9 KiB
Swift
import SwiftUI
|
||
|
||
/// The native Chat/Terminal switcher rendered over the bottom of the web view.
|
||
///
|
||
/// On iOS 26+ the capsule uses the system Liquid Glass material; on iOS 18–25 it
|
||
/// falls back to `.ultraThinMaterial`, matching the look of `ServerSwitcher`.
|
||
struct ChatTerminalBar: View {
|
||
@Binding var mode: WebViewMode
|
||
let terminalEnabled: Bool
|
||
let terminalStartingUp: Bool
|
||
let onSelect: (WebViewMode) -> Void
|
||
|
||
@Environment(\.colorScheme) private var colorScheme
|
||
@Namespace private var selection
|
||
|
||
var body: some View {
|
||
HStack(spacing: 4) {
|
||
segment(.chat, title: "Chat", systemImage: "message")
|
||
segment(.terminal, title: "Terminal", systemImage: "terminal")
|
||
}
|
||
.padding(InsetMetrics.barCapsulePadding)
|
||
.modifier(GlassCapsule(colorScheme: colorScheme))
|
||
.animation(.easeInOut(duration: 0.18), value: mode)
|
||
.accessibilityElement(children: .contain)
|
||
.accessibilityLabel("View mode")
|
||
}
|
||
|
||
@ViewBuilder
|
||
private func segment(_ target: WebViewMode, title: String, systemImage: String) -> some View {
|
||
let isSelected = mode == target
|
||
let isDisabled = target == .terminal && !terminalEnabled
|
||
|
||
Button {
|
||
guard !isDisabled, mode != target else { return }
|
||
onSelect(target)
|
||
} label: {
|
||
HStack(spacing: 5) {
|
||
if target == .terminal && terminalStartingUp {
|
||
ProgressView()
|
||
.controlSize(.mini)
|
||
} else {
|
||
Image(systemName: systemImage)
|
||
.font(.system(size: 13, weight: .medium))
|
||
}
|
||
Text(title)
|
||
.font(.system(size: 13, weight: .medium))
|
||
}
|
||
.foregroundStyle(
|
||
isSelected
|
||
? DesignTokens.foreground(colorScheme) : DesignTokens.mutedForeground(colorScheme)
|
||
)
|
||
.padding(.horizontal, 14)
|
||
.frame(height: InsetMetrics.barSegmentHeight)
|
||
.background {
|
||
if isSelected {
|
||
Capsule(style: .continuous)
|
||
.fill(Color.primary.opacity(colorScheme == .dark ? 0.16 : 0.08))
|
||
.matchedGeometryEffect(id: "selection", in: selection)
|
||
}
|
||
}
|
||
.contentShape(Capsule(style: .continuous))
|
||
}
|
||
.buttonStyle(.plain)
|
||
.disabled(isDisabled)
|
||
.opacity(isDisabled ? 0.4 : 1)
|
||
.accessibilityAddTraits(isSelected ? [.isSelected] : [])
|
||
}
|
||
}
|
||
|
||
/// Wraps the bar in the system glass material where available, otherwise a
|
||
/// hand-rolled material capsule that mirrors `ServerSwitcher`'s styling.
|
||
private struct GlassCapsule: ViewModifier {
|
||
let colorScheme: ColorScheme
|
||
|
||
func body(content: Content) -> some View {
|
||
if #available(iOS 26.0, *) {
|
||
content.glassEffect(.regular.interactive(), in: .capsule)
|
||
} else {
|
||
content
|
||
.background(.ultraThinMaterial, in: Capsule(style: .continuous))
|
||
.overlay {
|
||
Capsule(style: .continuous)
|
||
.stroke(Color.primary.opacity(colorScheme == .dark ? 0.16 : 0.10), lineWidth: 0.5)
|
||
}
|
||
.shadow(color: .black.opacity(colorScheme == .dark ? 0.22 : 0.08), radius: 10, y: 4)
|
||
}
|
||
}
|
||
}
|