casa/Casa/ContentView.swift
Shadow 66d84d91c2
Some checks failed
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled
Fix scene selection state
2026-02-07 13:56:11 -06:00

233 lines
7.4 KiB
Swift

import SwiftUI
import HomeKit
struct ContentView: View {
@EnvironmentObject private var model: CasaAppModel
@State private var mainSelection: MainSelection = .apiDocs
@State private var accessorySelection: UUID? = nil
@State private var sceneSelection: UUID? = nil
var body: some View {
ZStack(alignment: .topTrailing) {
if model.settings.onboardingComplete {
tabView
} else {
OnboardingView()
.environmentObject(model)
}
if let toast = model.toastMessage {
ToastView(message: toast)
.padding(.top, 8)
.padding(.trailing, 8)
.transition(.move(edge: .top).combined(with: .opacity))
}
}
.animation(.easeOut(duration: 0.2), value: model.toastMessage)
}
private var accessoryBinding: Binding<UUID?> {
Binding<UUID?>(
get: { accessorySelection },
set: { newValue in
accessorySelection = newValue
if newValue != nil {
sceneSelection = nil
mainSelection = .apiDocs
}
}
)
}
private var sceneBinding: Binding<UUID?> {
Binding<UUID?>(
get: { sceneSelection },
set: { newValue in
sceneSelection = newValue
if newValue != nil {
accessorySelection = nil
mainSelection = .apiDocs
}
}
)
}
private var tabView: some View {
TabView(selection: $mainSelection) {
apiDocsView
.tabItem {
Label("HomeKit API", systemImage: "doc.text.magnifyingglass")
}
.tag(MainSelection.apiDocs)
LogsView()
.tabItem {
Label("Diagnostics & Logs", systemImage: "waveform.path.ecg")
}
.tag(MainSelection.logs)
SettingsView()
.tabItem {
Label("Settings", systemImage: "gearshape")
}
.tag(MainSelection.settings)
}
}
private var apiDocsView: some View {
HStack(spacing: 0) {
if model.settings.homeKitEnabled {
SidebarView(
accessories: model.homeKit.accessories,
scenes: model.homeKit.scenes,
accessorySelection: accessoryBinding,
sceneSelection: sceneBinding
)
.frame(minWidth: 220, idealWidth: 240, maxWidth: 300)
.background(Color(UIColor.systemGroupedBackground))
Divider()
}
ApiDocsView(
accessories: model.homeKit.accessories,
scenes: model.homeKit.scenes,
selectedAccessoryId: accessoryBinding,
selectedSceneId: sceneBinding
)
}
}
}
private struct ToastView: View {
let message: String
var body: some View {
Text(message)
.font(.callout)
.foregroundColor(.primary)
.padding(.horizontal, 16)
.padding(.vertical, 12)
.background(.ultraThinMaterial)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.primary.opacity(0.08), lineWidth: 1)
)
.cornerRadius(14)
.shadow(color: Color.black.opacity(0.1), radius: 8, x: 0, y: 4)
.accessibilityLabel(message)
}
}
private enum MainSelection: Hashable {
case apiDocs
case logs
case settings
}
private struct SidebarView: View {
@EnvironmentObject private var model: CasaAppModel
@ObservedObject private var settings = CasaSettings.shared
let accessories: [HMAccessory]
let scenes: [HMActionSet]
@Binding var accessorySelection: UUID?
@Binding var sceneSelection: UUID?
var body: some View {
List {
Section("Status") {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 8) {
StatusBadge(status: serverStatus)
Text("Port \(settings.port, format: .number.grouping(.never))")
.font(.caption)
.foregroundColor(.secondary)
}
Text(settings.authToken.isEmpty ? "Auth: off" : "Auth: on")
.font(.caption)
.foregroundColor(.secondary)
HStack(spacing: 8) {
Text("HomeKit")
.font(.caption)
.foregroundColor(.secondary)
StatusBadge(status: moduleStatusHomeKit())
}
}
.padding(.vertical, 4)
}
Section("Accessories") {
Button {
accessorySelection = nil
sceneSelection = nil
} label: {
sidebarRow(title: "All accessories", isSelected: accessorySelection == nil && sceneSelection == nil)
}
.buttonStyle(.plain)
ForEach(accessories, id: \.uniqueIdentifier) { accessory in
Button {
accessorySelection = accessory.uniqueIdentifier
sceneSelection = nil
} label: {
sidebarRow(title: accessory.name, isSelected: accessorySelection == accessory.uniqueIdentifier)
}
.buttonStyle(.plain)
}
}
Section("Scenes") {
Button {
sceneSelection = SceneSelection.allScenesId
accessorySelection = nil
} label: {
sidebarRow(title: "All scenes", isSelected: sceneSelection == SceneSelection.allScenesId)
}
.buttonStyle(.plain)
ForEach(scenes, id: \.uniqueIdentifier) { scene in
Button {
sceneSelection = scene.uniqueIdentifier
accessorySelection = nil
} label: {
sidebarRow(title: scene.name, isSelected: sceneSelection == scene.uniqueIdentifier)
}
.buttonStyle(.plain)
}
}
}
.listStyle(.insetGrouped)
}
private func sidebarRow(title: String, isSelected: Bool) -> some View {
HStack {
Text(title)
.foregroundColor(.primary)
Spacer()
if isSelected {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}
.contentShape(Rectangle())
}
private var serverStatus: ModuleStatus {
if model.server.isRunning {
return ModuleStatus(text: "Running", tone: .ok)
}
return ModuleStatus(text: "Stopped", tone: .muted)
}
private func moduleStatusHomeKit() -> ModuleStatus {
if settings.homeKitEnabled {
return ModuleStatus(text: "Enabled", tone: .ok)
}
return ModuleStatus(text: "Disabled", tone: .muted)
}
}