| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- import LoopKit
- import SwiftUI
- struct DeviceAlarmEditorView: View {
- @ObservedObject var store: DeviceAlertsStore
- let configID: UUID
- let isNew: Bool
- var onDone: () -> Void
- var onCancel: () -> Void
- @Environment(\.dismiss) private var dismiss
- @Environment(\.colorScheme) private var colorScheme
- @Environment(AppState.self) private var appState
- @State private var working: DeviceAlertSeverityConfig
- init(
- store: DeviceAlertsStore,
- initial: DeviceAlertSeverityConfig,
- isNew: Bool,
- onDone: @escaping () -> Void,
- onCancel: @escaping () -> Void = {}
- ) {
- self.store = store
- configID = initial.id
- self.isNew = isNew
- self.onDone = onDone
- self.onCancel = onCancel
- _working = State(initialValue: initial)
- }
- var body: some View {
- NavigationStack {
- Form {
- Section(
- header: Text("Behavior"),
- footer: Text(working.severity.blurb)
- ) {
- HStack {
- Text(working.severity.displayName).font(.headline)
- Spacer()
- Text(activeLabel)
- .font(.footnote)
- .foregroundColor(.secondary)
- }
- // Critical-tier configs are always armed — the user can
- // mute the sound via the Audio section but not turn the
- // alarm itself off. Other tiers expose the toggle.
- if working.severity != .critical {
- Toggle(String(localized: "Enabled"), isOn: $working.isEnabled)
- }
- Toggle(
- String(localized: "Override Silence & Focus Mode"),
- isOn: $working.overridesSilenceAndDND
- )
- }.listRowBackground(Color.chart)
- AlarmActiveSection(activeOption: $working.activeOption)
- AlarmAudioSection(
- playsSound: $working.playsSound,
- soundFilename: $working.soundFilename
- )
- Section(header: Text("Applies To (Cannot be changed)")) {
- ForEach(conceptsForTier(working.severity), id: \.self) { concept in
- Text(concept.displayTitle)
- .font(.footnote)
- .foregroundColor(.secondary)
- }
- }.listRowBackground(Color.chart)
- if !isNew, store.canDelete(working) {
- Section {
- Button(role: .destructive) {
- store.remove(working)
- dismiss()
- } label: {
- Text("Delete Variant")
- }
- }.listRowBackground(Color.chart)
- }
- }
- .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
- .navigationTitle(working.severity.displayName)
- .navigationBarTitleDisplayMode(.inline)
- .toolbar {
- ToolbarItem(placement: .confirmationAction) {
- Button(isNew ? String(localized: "Add") : String(localized: "Done")) {
- if isNew {
- store.add(working)
- } else {
- store.update(working)
- }
- onDone()
- dismiss()
- }
- }
- ToolbarItem(placement: .cancellationAction) {
- Button(String(localized: "Cancel")) {
- onCancel()
- dismiss()
- }
- }
- }
- }
- }
- private var activeLabel: String {
- switch working.activeOption {
- case .always: return String(localized: "Day & Night")
- case .day: return String(localized: "Day only")
- case .night: return String(localized: "Night only")
- }
- }
- /// Distinct alarm concepts whose catalog entries fall into this tier.
- /// Sorted by display title so the list is stable across plugin changes.
- private func conceptsForTier(_ tier: DeviceAlertSeverity) -> [LoopKit.Alert.CatalogConcept] {
- var seen: Set<LoopKit.Alert.CatalogConcept> = []
- var ordered: [LoopKit.Alert.CatalogConcept] = []
- for entry in AlertCatalogRegistry.entries
- where DeviceAlertSeverity(level: entry.interruptionLevel) == tier
- {
- if seen.insert(entry.concept).inserted {
- ordered.append(entry.concept)
- }
- }
- return ordered.sorted { $0.displayTitle < $1.displayTitle }
- }
- }
|