mirror of
https://github.com/home-assistant/iOS.git
synced 2026-04-12 15:26:45 -05:00
## Summary Follow-up fixes after #4444 merged, addressing issues found during Mac Catalyst testing: - **Fix `›` character in Frequent Updates footer** — `.strings` files don't interpret `\uXXXX` escapes at runtime; replaced with the literal `›` character - **Fix Mac Catalyst compilation** — `ActivityKit` APIs (`ActivityAttributes`, `Activity`, `ActivityUIDismissalPolicy`, etc.) are marked unavailable on Mac Catalyst even though `canImport(ActivityKit)` returns true there. Replaced all `#if canImport(ActivityKit)` and bare `#if os(iOS)` guards around ActivityKit code with `#if os(iOS) && !targetEnvironment(macCatalyst)`. Files affected: - `HALiveActivityAttributes.swift` - `LiveActivityRegistry.swift` - `HandlerLiveActivity.swift` - `LiveActivitySettingsView.swift` - `HADynamicIslandView.swift` - `HALockScreenView.swift` - `HALiveActivityConfiguration.swift` - `Widgets.swift` (three `HALiveActivityConfiguration()` call sites) - `Environment.swift`, `AppDelegate.swift`, `HAAPI.swift`, `NotificationsCommandManager.swift`, `SettingsItem.swift` (inline guards) ## Test plan - [x] iOS builds and runs - [x] macOS (Mac Catalyst) builds and launches - [ ] Live Activities settings entry does not appear on macOS (filtered by `isTestFlight` + `#available(iOS 17.2, *)`) - [ ] Live Activities work as expected on iOS TestFlight build 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.9 KiB
Swift
110 lines
3.9 KiB
Swift
#if os(iOS) && !targetEnvironment(macCatalyst)
|
||
import ActivityKit
|
||
import SwiftUI
|
||
|
||
/// ActivityAttributes for Home Assistant Live Activities.
|
||
///
|
||
/// Field names intentionally mirror the Android companion app's notification fields
|
||
/// so that automations can target both platforms with minimal differences.
|
||
///
|
||
/// ⚠️ NEVER rename this struct or its fields post-ship.
|
||
/// The `attributes-type` string in APNs push-to-start payloads must exactly match
|
||
/// the Swift struct name (case-sensitive). Renaming breaks all in-flight activities.
|
||
@available(iOS 17.2, *)
|
||
public struct HALiveActivityAttributes: ActivityAttributes {
|
||
// MARK: - Static Attributes (set once at creation, cannot change)
|
||
|
||
/// Unique identifier for this Live Activity. Maps to `tag` in the notification payload.
|
||
/// Same semantics as Android's `tag`: the same tag value updates in-place.
|
||
public let tag: String
|
||
|
||
/// Display title for the activity. Maps to `title` in the notification payload.
|
||
public let title: String
|
||
|
||
// MARK: - Dynamic State
|
||
|
||
/// Codable state that can be updated via push or local update.
|
||
/// Field names map to Android companion app notification data fields.
|
||
public struct ContentState: Codable, Hashable {
|
||
/// Primary body text. Maps to `message` in the notification payload.
|
||
public var message: String
|
||
|
||
/// Short text for Dynamic Island compact trailing view.
|
||
/// Maps to `critical_text` in the notification payload (≤ ~10 chars recommended).
|
||
public var criticalText: String?
|
||
|
||
/// Current progress value (raw integer). Maps to `progress`.
|
||
public var progress: Int?
|
||
|
||
/// Maximum progress value (raw integer). Maps to `progress_max`.
|
||
public var progressMax: Int?
|
||
|
||
/// If true, show a countdown timer instead of static text. Maps to `chronometer`.
|
||
public var chronometer: Bool?
|
||
|
||
/// Absolute end date for the countdown timer.
|
||
/// Computed from `when` + `when_relative` in the notification payload:
|
||
/// - `when_relative: true` → `Date().addingTimeInterval(Double(when))`
|
||
/// - `when_relative: false` → `Date(timeIntervalSince1970: Double(when))`
|
||
public var countdownEnd: Date?
|
||
|
||
/// MDI icon slug for display. Maps to `notification_icon`.
|
||
public var icon: String?
|
||
|
||
/// Hex color string for icon accent. Maps to `notification_icon_color`.
|
||
public var color: String?
|
||
|
||
// MARK: - Computed helpers (not sent over wire)
|
||
|
||
/// Progress as a fraction in [0, 1] for use in SwiftUI ProgressView.
|
||
public var progressFraction: Double? {
|
||
guard let p = progress, let m = progressMax, m > 0 else { return nil }
|
||
return Double(p) / Double(m)
|
||
}
|
||
|
||
// MARK: - CodingKeys
|
||
|
||
/// Explicit coding keys so that JSON field names match the Android notification fields.
|
||
enum CodingKeys: String, CodingKey {
|
||
case message
|
||
case criticalText = "critical_text"
|
||
case progress
|
||
case progressMax = "progress_max"
|
||
case chronometer
|
||
case countdownEnd = "countdown_end"
|
||
case icon
|
||
case color
|
||
}
|
||
|
||
// MARK: - Init
|
||
|
||
public init(
|
||
message: String,
|
||
criticalText: String? = nil,
|
||
progress: Int? = nil,
|
||
progressMax: Int? = nil,
|
||
chronometer: Bool? = nil,
|
||
countdownEnd: Date? = nil,
|
||
icon: String? = nil,
|
||
color: String? = nil
|
||
) {
|
||
self.message = message
|
||
self.criticalText = criticalText
|
||
self.progress = progress
|
||
self.progressMax = progressMax
|
||
self.chronometer = chronometer
|
||
self.countdownEnd = countdownEnd
|
||
self.icon = icon
|
||
self.color = color
|
||
}
|
||
}
|
||
|
||
// MARK: - Init
|
||
|
||
public init(tag: String, title: String) {
|
||
self.tag = tag
|
||
self.title = title
|
||
}
|
||
}
|
||
#endif
|