mirror of
https://github.com/home-assistant/iOS.git
synced 2026-06-19 07:24:05 -05:00
<!-- Thank you for submitting a Pull Request and helping to improve Home Assistant. Please complete the following sections to help the processing and review of your changes. Please do not delete anything from this template. --> ## Summary <!-- Provide a brief summary of the changes you have made and most importantly what they aim to achieve --> This PR is a massive refactor of how the app handles UI presentation and navigation, goin from the UIKit based apps style to SwiftUI. ## Screenshots <!-- If this is a user-facing change not in the frontend, please include screenshots in light and dark mode. --> ## Link to pull request in Documentation repository <!-- Pull requests that add, change or remove functionality must have a corresponding pull request in the Companion App Documentation repository (https://github.com/home-assistant/companion.home-assistant). Please add the number of this pull request after the "#" --> Documentation: home-assistant/companion.home-assistant# ## Any other notes <!-- If there is any other information of note, like if this Pull Request is part of a bigger change, please include it here. --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
98 lines
4.3 KiB
Swift
98 lines
4.3 KiB
Swift
import Combine
|
|
import Shared
|
|
|
|
/// Drives the content presented over the web frontend — launch messages (What's-New, then TestFlight),
|
|
/// Settings, the download manager (sheets), and the forced onboarding-permissions decision (full-screen
|
|
/// cover). Owns the launch queue and publishes the currently-presented content for `ContainerView`.
|
|
@MainActor
|
|
final class ContainerViewModel: ObservableObject {
|
|
/// A sheet presented over the web view. A single `.sheet(item:)` switches on this, so only one can be
|
|
/// presented at a time regardless of how many qualify.
|
|
enum PresentedSheet: Identifiable {
|
|
case whatsNew(WhatsNewRelease)
|
|
case testFlight(TestFlightMessage)
|
|
case settings
|
|
case assistSettings
|
|
case downloadManager(DownloadManagerViewModel)
|
|
case serverSelect(prompt: String?, includeSettings: Bool, onSelect: (Server) -> Void)
|
|
|
|
var id: String {
|
|
switch self {
|
|
case let .whatsNew(release): return "whatsNew-\(release.id)"
|
|
case let .testFlight(message): return "testFlight-\(message.id.rawValue)"
|
|
case .settings: return "settings"
|
|
case .assistSettings: return "assistSettings"
|
|
case .downloadManager: return "downloadManager"
|
|
case .serverSelect: return "serverSelect"
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A forced, full-screen flow presented over the web view via `.fullScreenCover` (no swipe-to-dismiss).
|
|
enum FullScreenCover: Identifiable {
|
|
case onboardingPermissions(server: Server, steps: [OnboardingPermissionsNavigationViewModel.StepID])
|
|
|
|
var id: String {
|
|
switch self {
|
|
case .onboardingPermissions: return "onboardingPermissions"
|
|
}
|
|
}
|
|
}
|
|
|
|
@Published var presentedSheet: PresentedSheet?
|
|
@Published var fullScreenCover: FullScreenCover?
|
|
|
|
private var pendingLaunchMessages: [PresentedSheet] = []
|
|
private var didEvaluateLaunchMessages = false
|
|
|
|
/// Queues the launch messages (What's-New, then TestFlight) to present the first time the web view appears
|
|
/// — the first thing the user sees — via SwiftUI rather than a UIKit overlay.
|
|
func presentLaunchMessagesIfNeeded(isShowingWebView: Bool) {
|
|
guard !didEvaluateLaunchMessages, isShowingWebView else { return }
|
|
didEvaluateLaunchMessages = true
|
|
|
|
var queue: [PresentedSheet] = []
|
|
if let release = WhatsNewEngine().releaseToShow() {
|
|
queue.append(.whatsNew(release))
|
|
}
|
|
if let message = TestFlightCommunicationEngine().messageToShow() {
|
|
queue.append(.testFlight(message))
|
|
}
|
|
pendingLaunchMessages = queue
|
|
showNextLaunchMessage()
|
|
}
|
|
|
|
/// Presents the next queued launch message, if any. Called on first evaluation and on each sheet dismiss so
|
|
/// a single `.sheet` shows them in sequence — only one is ever bound, avoiding competing-binding races.
|
|
func showNextLaunchMessage() {
|
|
guard presentedSheet == nil, !pendingLaunchMessages.isEmpty else { return }
|
|
presentedSheet = pendingLaunchMessages.removeFirst()
|
|
}
|
|
|
|
/// Presents app Settings as a sheet over the web view (user-triggered, e.g. a gesture or the re-auth gear).
|
|
func presentSettings() {
|
|
presentedSheet = .settings
|
|
}
|
|
|
|
/// Presents Assist settings as a sheet over the web view (triggered by the frontend's external bus).
|
|
func presentAssistSettings() {
|
|
presentedSheet = .assistSettings
|
|
}
|
|
|
|
/// Presents the download manager as a sheet (iOS 17+, a download began). The view model must be the same
|
|
/// instance the web view set as the `WKDownload` delegate.
|
|
func presentDownloadManager(_ viewModel: DownloadManagerViewModel) {
|
|
presentedSheet = .downloadManager(viewModel)
|
|
}
|
|
|
|
/// Presents the forced onboarding-permissions decision as a full-screen cover.
|
|
func presentOnboardingPermissions(server: Server, steps: [OnboardingPermissionsNavigationViewModel.StepID]) {
|
|
fullScreenCover = .onboardingPermissions(server: server, steps: steps)
|
|
}
|
|
|
|
/// Presents the server picker as a sheet (e.g. a server-less deep link, or the "show servers" gesture).
|
|
func presentServerSelect(prompt: String?, includeSettings: Bool, onSelect: @escaping (Server) -> Void) {
|
|
presentedSheet = .serverSelect(prompt: prompt, includeSettings: includeSettings, onSelect: onSelect)
|
|
}
|
|
}
|