Files
iOS/Sources/App/Scenes/AssistSceneDelegate.swift
Copilot 0c0c2533e5 Add scene support for AssistView on macOS (#3965)
## Summary
Enables AssistView to open in a dedicated window on macOS while
maintaining modal presentation on iOS/iPad.

**Implementation:**
- Added `.assist` case to `SceneActivity` enum with `ha.assist`
identifier
- Created `AssistSceneDelegate` extending `BasicSceneDelegate` to manage
Assist windows
- Updated `Info.plist` with Assist scene configuration
- Added `SceneManager.activateAnyScene(for:with:)` to pass parameters
via `NSUserActivity.userInfo`
- Modified `WebViewExternalMessageHandler.showAssist()` to branch on
`Current.isCatalyst`:
  - macOS: `SceneManager.activateAnyScene(for: .assist, with: userInfo)`
  - iOS/iPad: Existing modal presentation (unchanged)
- Window centering: Assist window opens centered on screen (600x600)
using `UIWindowScene.MacGeometryPreferences` (iOS 17+)
- Close button: Removed toolbar close button in scene mode since window
has native close button; modal presentation on iOS/iPad retains close
button

**Parameters passed via userInfo:**
- `server`: Server identifier string
- `pipelineId`: Pipeline ID string
- `autoStartRecording`: Boolean

## Screenshots

N/A - Scene system change, requires macOS build to screenshot

## Link to pull request in Documentation repository
Documentation: home-assistant/companion.home-assistant#

## Any other notes
Follows existing scene patterns from `SettingsSceneDelegate` and
`AboutSceneDelegate`. No breaking changes to iOS/iPad behavior. The
`showCloseButton` parameter in `AssistView` allows flexible control of
close button visibility - `false` for scene mode (window has native
controls), `true` for modal presentation.

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> Turn AssistView into a scene, so it can be initiated in a new window
on macOS


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com>
2025-11-12 17:16:50 +00:00

80 lines
2.9 KiB
Swift

import Foundation
import Shared
import SwiftUI
import UIKit
@objc class AssistSceneDelegate: BasicSceneDelegate {
private var server: Server?
private var preferredPipelineId: String = ""
private var autoStartRecording: Bool = false
override func basicConfig(in traitCollection: UITraitCollection) -> BasicSceneDelegate.BasicConfig {
let server = server ?? Current.servers.all.first!
let assistView = AssistView.build(
server: server,
preferredPipelineId: preferredPipelineId,
autoStartRecording: autoStartRecording,
showCloseButton: false
)
let hostingController = UIHostingController(rootView: assistView)
return .init(
title: "Assist",
rootViewController: hostingController
)
}
override func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
// Extract parameters from userInfo if available
if let userActivity = connectionOptions.userActivities.first,
let userInfo = userActivity.userInfo {
if let serverIdentifier = userInfo["server"] as? String,
let server = Current.servers.all.first(where: { $0.identifier.rawValue == serverIdentifier }) {
self.server = server
}
preferredPipelineId = userInfo["pipelineId"] as? String ?? ""
autoStartRecording = userInfo["autoStartRecording"] as? Bool ?? false
}
super.scene(scene, willConnectTo: session, options: connectionOptions)
#if targetEnvironment(macCatalyst)
// Center the window on screen
if let windowScene = scene as? UIWindowScene {
let screen = windowScene.screen
let screenBounds = screen.bounds
let windowSize = CGSize(width: 400, height: 600)
let centeredFrame = CGRect(
x: (screenBounds.width - windowSize.width) / 2,
y: (screenBounds.height - windowSize.height) / 2,
width: windowSize.width,
height: windowSize.height
)
if #available(macCatalyst 16.0, *) {
windowScene.requestGeometryUpdate(.Mac(systemFrame: centeredFrame)) { error in
Current.Log.info(["Failed to request geometry": error.localizedDescription])
}
} else {
// For earlier versions, we can't set the initial frame directly
// The window will use default positioning
}
}
#endif
}
func sceneDidDisconnect(_ scene: UIScene) {
// Clean up when scene is destroyed
Current.Log.info("Assist scene disconnected")
window = nil
self.scene = nil
server = nil
preferredPipelineId = ""
autoStartRecording = false
}
}