mirror of
https://github.com/home-assistant/iOS.git
synced 2026-06-16 23:33:36 -05:00
Move live activity settings back to root settings and add samples
This commit is contained in:
@@ -145,21 +145,6 @@ struct DebugView: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(iOS) && !targetEnvironment(macCatalyst)
|
||||
if #available(iOS 17.2, *) {
|
||||
Section {
|
||||
NavigationLink {
|
||||
LiveActivitySettingsView()
|
||||
} label: {
|
||||
linkContent(
|
||||
image: .init(systemSymbol: .livephoto),
|
||||
title: L10n.LiveActivity.title
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
criticalSection
|
||||
|
||||
if tapsOnCasitaLogo < 10 {
|
||||
|
||||
@@ -27,6 +27,7 @@ struct LiveActivitySettingsView: View {
|
||||
)
|
||||
|
||||
statusSection
|
||||
frequentUpdatesSection
|
||||
|
||||
if activities.isEmpty {
|
||||
Section(L10n.LiveActivity.Section.active) {
|
||||
@@ -62,15 +63,8 @@ struct LiveActivitySettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
debugSection
|
||||
#endif
|
||||
|
||||
privacySection
|
||||
|
||||
if #available(iOS 17.2, *) {
|
||||
frequentUpdatesSection
|
||||
}
|
||||
samplesSection
|
||||
}
|
||||
.navigationTitle(L10n.LiveActivity.title)
|
||||
.task { await loadActivities() }
|
||||
@@ -101,7 +95,7 @@ struct LiveActivitySettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Debug (DEBUG builds only)
|
||||
// MARK: - Samples
|
||||
|
||||
//
|
||||
// Two sections: Static (fixed snapshots to verify layout) and Animated (multi-stage
|
||||
@@ -121,131 +115,139 @@ struct LiveActivitySettingsView: View {
|
||||
// It does NOT appear on the lock screen. Use a Dynamic Island device or
|
||||
// simulator (iPhone 14 Pro+) to see it.
|
||||
|
||||
#if DEBUG
|
||||
private var debugSection: some View {
|
||||
Group {
|
||||
Section {
|
||||
// Minimum viable layout — only the message field is set.
|
||||
// Verifies the bare layout renders without icon, progress, or timer.
|
||||
Button("Plain Message") {
|
||||
startTestActivity(
|
||||
tag: "debug-plain",
|
||||
title: "Home Assistant",
|
||||
state: .init(message: "Everything looks good at home.")
|
||||
)
|
||||
private var samplesSection: some View {
|
||||
Section {
|
||||
NavigationLink("Samples") {
|
||||
List {
|
||||
staticSamplesSection
|
||||
animatedSamplesSection
|
||||
}
|
||||
|
||||
// icon = nil code path. Layout must not shift or break when no icon is provided.
|
||||
// color = nil so the progress bar uses the default HA-blue tint.
|
||||
// criticalText ("Active") visible in DI compact trailing only.
|
||||
Button("No Icon · Default Color") {
|
||||
startTestActivity(
|
||||
tag: "debug-no-icon",
|
||||
title: "Script Running",
|
||||
state: .init(
|
||||
message: "Irrigation zone 3 is active",
|
||||
criticalText: "Active",
|
||||
progress: 35,
|
||||
progressMax: 100
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Short 60-second countdown with no progress bar.
|
||||
// Red color communicates urgency. Watch the timer count down in real time.
|
||||
// Represents automations like alarm arming delays or reminder countdowns.
|
||||
Button("Alarm · 60 sec Countdown") {
|
||||
startTestActivity(
|
||||
tag: "debug-alarm",
|
||||
title: "Security Alarm",
|
||||
state: .init(
|
||||
message: "Motion at back door · Arms in 60 seconds",
|
||||
criticalText: "60 sec",
|
||||
chronometer: true,
|
||||
countdownEnd: Date().addingTimeInterval(60),
|
||||
icon: "mdi:alarm-light",
|
||||
color: "#F44336"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Every ContentState field active at the same time.
|
||||
// Lock screen shows: icon → live countdown → progress bar.
|
||||
// criticalText ("5 min") visible in DI compact trailing only.
|
||||
// Use this to confirm no layout collisions when all fields are populated.
|
||||
Button("All Fields · Max Load") {
|
||||
startTestActivity(
|
||||
tag: "debug-all",
|
||||
title: "All Fields",
|
||||
state: .init(
|
||||
message: "All content state fields active",
|
||||
criticalText: "5 min",
|
||||
progress: 42,
|
||||
progressMax: 100,
|
||||
chronometer: true,
|
||||
countdownEnd: Date().addingTimeInterval(5 * 60),
|
||||
icon: "mdi:home-assistant",
|
||||
color: "#03A9F4"
|
||||
)
|
||||
)
|
||||
}
|
||||
} header: {
|
||||
Text("Debug · Static")
|
||||
} footer: {
|
||||
Text("Fixed state — no updates after start. Good for checking layout at a glance.")
|
||||
}
|
||||
|
||||
Section {
|
||||
// Progress bar advances through five named stages.
|
||||
// criticalText tracks the current stage name in the DI compact trailing slot.
|
||||
// Icon swaps from washing-machine to check-circle on the final update.
|
||||
// Represents any multi-step appliance cycle automation.
|
||||
Button("Washing Machine · Stage Labels (~12 s)") { startWashingMachineCycle() }
|
||||
|
||||
// Numeric percentage in criticalText updates alongside the progress bar.
|
||||
// Color shifts from green to yellow-green as the charge nears 100 %.
|
||||
// Represents any "% complete with time remaining" automation pattern.
|
||||
Button("EV Charging · Numeric % (~16 s)") { startEVChargingSimulation() }
|
||||
|
||||
// The only scenario where both progress (playback position) and a live countdown
|
||||
// (time remaining in track) are active and updating at the same time.
|
||||
// Simulates a track change mid-sequence: progress resets, countdown resets.
|
||||
Button("Media Player · Progress + Timer (~20 s)") { startMediaNowPlaying() }
|
||||
|
||||
// Message, criticalText, and icon all change on every update — no progress bar.
|
||||
// Represents automations where the status category itself changes (not just a value).
|
||||
Button("Package Delivery · All Text Fields (~15 s)") { startPackageJourney() }
|
||||
|
||||
// No progress bar — state communicated entirely through color and icon.
|
||||
// Escalates orange (motion) → red (person) → green (all clear).
|
||||
// Represents any alert-and-resolve automation pattern.
|
||||
Button("Security Escalation · Color + Icon (~8 s)") { startSecuritySequence() }
|
||||
|
||||
// Cycles through wash stages then calls activity.end() with .default dismissal.
|
||||
// The only scenario that tests the full lifecycle: start → update → end.
|
||||
// After ending, the final "Done" state lingers on the lock screen (up to 4 h).
|
||||
Button("Dishwasher · Full Lifecycle, Ends Itself (~12 s)") { startDishwasherAutoComplete() }
|
||||
|
||||
// Fires 6 updates 2 seconds apart (12 s total).
|
||||
// On iOS 18 the system enforces ~15 s between rendered updates — some will be
|
||||
// silently dropped. Watch the counter skip values to see the rate limit in action.
|
||||
// On the simulator and iOS 17 all 6 updates should render.
|
||||
Button("Rate Limit · 6 Rapid Updates, 2 s Apart (~12 s)") { startRapidUpdateStressTest() }
|
||||
} header: {
|
||||
Text("Debug · Animated")
|
||||
} footer: {
|
||||
Text(
|
||||
"Activity updates itself after you tap. Tap, then immediately lock (⌘L) " +
|
||||
"to watch updates on the lock screen in real time."
|
||||
)
|
||||
.navigationTitle("Samples")
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
private var staticSamplesSection: some View {
|
||||
Section {
|
||||
// Minimum viable layout — only the message field is set.
|
||||
// Verifies the bare layout renders without icon, progress, or timer.
|
||||
Button("Plain Message") {
|
||||
startTestActivity(
|
||||
tag: "debug-plain",
|
||||
title: "Home Assistant",
|
||||
state: .init(message: "Everything looks good at home.")
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Debug helpers
|
||||
// icon = nil code path. Layout must not shift or break when no icon is provided.
|
||||
// color = nil so the progress bar uses the default HA-blue tint.
|
||||
// criticalText ("Active") visible in DI compact trailing only.
|
||||
Button("No Icon · Default Color") {
|
||||
startTestActivity(
|
||||
tag: "debug-no-icon",
|
||||
title: "Script Running",
|
||||
state: .init(
|
||||
message: "Irrigation zone 3 is active",
|
||||
criticalText: "Active",
|
||||
progress: 35,
|
||||
progressMax: 100
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Short 60-second countdown with no progress bar.
|
||||
// Red color communicates urgency. Watch the timer count down in real time.
|
||||
// Represents automations like alarm arming delays or reminder countdowns.
|
||||
Button("Alarm · 60 sec Countdown") {
|
||||
startTestActivity(
|
||||
tag: "debug-alarm",
|
||||
title: "Security Alarm",
|
||||
state: .init(
|
||||
message: "Motion at back door · Arms in 60 seconds",
|
||||
criticalText: "60 sec",
|
||||
chronometer: true,
|
||||
countdownEnd: Date().addingTimeInterval(60),
|
||||
icon: "mdi:alarm-light",
|
||||
color: "#F44336"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Every ContentState field active at the same time.
|
||||
// Lock screen shows: icon → live countdown → progress bar.
|
||||
// criticalText ("5 min") visible in DI compact trailing only.
|
||||
// Use this to confirm no layout collisions when all fields are populated.
|
||||
Button("All Fields · Max Load") {
|
||||
startTestActivity(
|
||||
tag: "debug-all",
|
||||
title: "All Fields",
|
||||
state: .init(
|
||||
message: "All content state fields active",
|
||||
criticalText: "5 min",
|
||||
progress: 42,
|
||||
progressMax: 100,
|
||||
chronometer: true,
|
||||
countdownEnd: Date().addingTimeInterval(5 * 60),
|
||||
icon: "mdi:home-assistant",
|
||||
color: "#03A9F4"
|
||||
)
|
||||
)
|
||||
}
|
||||
} header: {
|
||||
Text("Sample · Static")
|
||||
} footer: {
|
||||
Text("Fixed state — no updates after start. Good for checking layout at a glance.")
|
||||
}
|
||||
}
|
||||
|
||||
private var animatedSamplesSection: some View {
|
||||
Section {
|
||||
// Progress bar advances through five named stages.
|
||||
// criticalText tracks the current stage name in the DI compact trailing slot.
|
||||
// Icon swaps from washing-machine to check-circle on the final update.
|
||||
// Represents any multi-step appliance cycle automation.
|
||||
Button("Washing Machine · Stage Labels (~12 s)") { startWashingMachineCycle() }
|
||||
|
||||
// Numeric percentage in criticalText updates alongside the progress bar.
|
||||
// Color shifts from green to yellow-green as the charge nears 100 %.
|
||||
// Represents any "% complete with time remaining" automation pattern.
|
||||
Button("EV Charging · Numeric % (~16 s)") { startEVChargingSimulation() }
|
||||
|
||||
// The only scenario where both progress (playback position) and a live countdown
|
||||
// (time remaining in track) are active and updating at the same time.
|
||||
// Simulates a track change mid-sequence: progress resets, countdown resets.
|
||||
Button("Media Player · Progress + Timer (~20 s)") { startMediaNowPlaying() }
|
||||
|
||||
// Message, criticalText, and icon all change on every update — no progress bar.
|
||||
// Represents automations where the status category itself changes (not just a value).
|
||||
Button("Package Delivery · All Text Fields (~15 s)") { startPackageJourney() }
|
||||
|
||||
// No progress bar — state communicated entirely through color and icon.
|
||||
// Escalates orange (motion) → red (person) → green (all clear).
|
||||
// Represents any alert-and-resolve automation pattern.
|
||||
Button("Security Escalation · Color + Icon (~8 s)") { startSecuritySequence() }
|
||||
|
||||
// Cycles through wash stages then calls activity.end() with .default dismissal.
|
||||
// The only scenario that tests the full lifecycle: start → update → end.
|
||||
// After ending, the final "Done" state lingers on the lock screen (up to 4 h).
|
||||
Button("Dishwasher · Full Lifecycle, Ends Itself (~12 s)") { startDishwasherAutoComplete() }
|
||||
|
||||
// Fires 6 updates 2 seconds apart (12 s total).
|
||||
// On iOS 18 the system enforces ~15 s between rendered updates — some will be
|
||||
// silently dropped. Watch the counter skip values to see the rate limit in action.
|
||||
// On the simulator and iOS 17 all 6 updates should render.
|
||||
Button("Rate Limit · 6 Rapid Updates, 2 s Apart (~12 s)") { startRapidUpdateStressTest() }
|
||||
} header: {
|
||||
Text("Sample · Animated")
|
||||
} footer: {
|
||||
Text(
|
||||
"Activity updates itself after you tap. Tap, then immediately lock (⌘L) " +
|
||||
"to watch updates on the lock screen in real time."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sample helpers
|
||||
|
||||
/// Starts a single-state activity (no subsequent updates).
|
||||
private func startTestActivity(tag: String, title: String, state: HALiveActivityAttributes.ContentState) {
|
||||
@@ -565,8 +567,6 @@ struct LiveActivitySettingsView: View {
|
||||
)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private var privacySection: some View {
|
||||
Section {
|
||||
Label(L10n.LiveActivity.Privacy.message, systemSymbol: .lockShield)
|
||||
|
||||
@@ -149,22 +149,30 @@ enum SettingsItem: String, Hashable, CaseIterable {
|
||||
allCases.filter { item in
|
||||
// Filter based on platform
|
||||
#if targetEnvironment(macCatalyst)
|
||||
if item == .servers || item == .gestures || item == .kiosk || item == .watch || item == .carPlay ||
|
||||
item == .complications || item == .nfc || item == .help ||
|
||||
item == .whatsNew {
|
||||
let hiddenItems: [SettingsItem] = [
|
||||
.servers,
|
||||
.gestures,
|
||||
.kiosk,
|
||||
.watch,
|
||||
.carPlay,
|
||||
.complications,
|
||||
.nfc,
|
||||
.help,
|
||||
.whatsNew,
|
||||
.liveActivities,
|
||||
]
|
||||
|
||||
if hiddenItems.contains(item) {
|
||||
return false
|
||||
}
|
||||
#endif
|
||||
// Live Activities are shown in DebugView
|
||||
if item == .liveActivities {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
static var generalItems: [SettingsItem] {
|
||||
[.general, .gestures, .location, .notifications, .kiosk]
|
||||
[.general, .gestures, .location, .notifications, .kiosk, .liveActivities]
|
||||
}
|
||||
|
||||
static var integrationItems: [SettingsItem] {
|
||||
|
||||
@@ -246,7 +246,7 @@ struct SettingsView: View {
|
||||
Label {
|
||||
HStack(spacing: DesignSystem.Spaces.one) {
|
||||
Text(item.title)
|
||||
if item == .kiosk {
|
||||
if [.kiosk, .liveActivities].contains(item) {
|
||||
LabsLabel()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user