Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concept of Assist on Apple Watch #2400

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e40d89c
Bump Build to 2023.12 (2023)
homeassistant Nov 6, 2023
4972cd7
Concept of Assist on Apple Watch
bgoncal Aug 20, 2023
1a5bb54
bundle exec fastlane autocorrect
bgoncal Aug 20, 2023
44a0f4b
Add the ability to record the audio in watchOS and process it in iOS
bgoncal Aug 25, 2023
fc8d89d
Add ability to launch assist from complication and stop recording on …
bgoncal Aug 27, 2023
d5edd02
Clean up AppDelegate
bgoncal Aug 28, 2023
a8afe54
Decoupling dependencies and add tests
bgoncal Aug 28, 2023
5b2c182
Add one more test
bgoncal Aug 28, 2023
8958a85
Fix linter issues
bgoncal Aug 28, 2023
bdfeed7
Require watchOS 6 for assist
bgoncal Aug 29, 2023
c5268db
Avoid importing mac catalyst libraries when targetting iOS
bgoncal Nov 8, 2023
fb5bd42
WIP auto generate Assist complication for all sizes
bgoncal Nov 8, 2023
af277bb
Use SwiftUI instead or WKInterfaceControllers
bgoncal Nov 13, 2023
51ee6c2
Add animation to assist button
bgoncal Nov 13, 2023
d48ddd8
Concept of Assist on Apple Watch
bgoncal Aug 20, 2023
d2f5292
bundle exec fastlane autocorrect
bgoncal Aug 20, 2023
eab3b6a
Add the ability to record the audio in watchOS and process it in iOS
bgoncal Aug 25, 2023
b06f3ed
Add ability to launch assist from complication and stop recording on …
bgoncal Aug 27, 2023
c7369b5
Clean up AppDelegate
bgoncal Aug 28, 2023
9dbbe11
Decoupling dependencies and add tests
bgoncal Aug 28, 2023
ed4aa92
Add one more test
bgoncal Aug 28, 2023
a47650d
Fix linter issues
bgoncal Aug 28, 2023
af86ed7
Require watchOS 6 for assist
bgoncal Aug 29, 2023
8871954
Avoid importing mac catalyst libraries when targetting iOS
bgoncal Nov 8, 2023
0dced62
WIP auto generate Assist complication for all sizes
bgoncal Nov 8, 2023
950ffd5
Use SwiftUI instead or WKInterfaceControllers
bgoncal Nov 13, 2023
67ac37c
Add animation to assist button
bgoncal Nov 13, 2023
bc01015
Merge branch 'assist-apple-watch' of github.com:bgoncal/HA-iOS into a…
bgoncal Nov 13, 2023
cc4a95e
Linter
bgoncal Nov 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions HomeAssistant.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

57 changes: 15 additions & 42 deletions Sources/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import PromiseKit
import RealmSwift
import SafariServices
import Shared
import Speech
import UIKit
import XCGLogger

Expand Down Expand Up @@ -52,6 +53,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
let notificationManager = NotificationManager()

private var zoneManager: ZoneManager?
private var currentWatchMessageHandler: WatchCommunicationProtocol?
private var titleSubscription: MenuManagerTitleSubscription? {
didSet {
if oldValue != titleSubscription {
Expand Down Expand Up @@ -497,50 +499,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
Current.Log.verbose("Reachability changed: \(reachability)")
}

InteractiveImmediateMessage.observations.store[.init(queue: .main)] = { message in
InteractiveImmediateMessage.observations.store[.init(queue: .main)] = { [weak self] message in
Current.Log.verbose("Received message: \(message.identifier)")

// TODO: move all these to something more strongly typed

if message.identifier == "ActionRowPressed" {
Current.Log.verbose("Received ActionRowPressed \(message) \(message.content)")
let responseIdentifier = "ActionRowPressedResponse"

guard let actionID = message.content["ActionID"] as? String,
let action = Current.realm().object(ofType: Action.self, forPrimaryKey: actionID),
let server = Current.servers.server(for: action) else {
Current.Log.warning("ActionID either does not exist or is not a string in the payload")
message.reply(.init(identifier: responseIdentifier, content: ["fired": false]))
return
}

firstly {
Current.api(for: server).HandleAction(actionID: actionID, source: .Watch)
}.done {
message.reply(.init(identifier: responseIdentifier, content: ["fired": true]))
}.catch { err in
Current.Log.error("Error during action event fire: \(err)")
message.reply(.init(identifier: responseIdentifier, content: ["fired": false]))
}
} else if message.identifier == "PushAction" {
Current.Log.verbose("Received PushAction \(message) \(message.content)")
let responseIdentifier = "PushActionResponse"

if let infoJSON = message.content["PushActionInfo"] as? [String: Any],
let info = Mapper<HomeAssistantAPI.PushActionInfo>().map(JSON: infoJSON),
let serverIdentifier = message.content["Server"] as? String,
let server = Current.servers.server(forServerIdentifier: serverIdentifier) {
Current.backgroundTask(withName: "watch-push-action") { _ in
firstly {
Current.api(for: server).handlePushAction(for: info)
}.ensure {
message.reply(.init(identifier: responseIdentifier))
}
}.catch { error in
Current.Log.error("error handling push action: \(error)")
}
}
guard let self else { return }
switch message.identifier {
case WatchCommunicationKey.actionRow.rawValue:
self.currentWatchMessageHandler = Current.watchActionService
case WatchCommunicationKey.pushAction.rawValue:
self.currentWatchMessageHandler = Current.watchPushActionService
case WatchCommunicationKey.assist.rawValue:
self.currentWatchMessageHandler = Current.watchAssistService
default:
break
}

self.currentWatchMessageHandler?.handle(message: message)
}

Blob.observations.store[.init(queue: .main)] = { blob in
Expand Down
113 changes: 55 additions & 58 deletions Sources/App/Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<string>$(PRODUCT_NAME)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleGetInfoString</key>
<string></string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundleAlternateIcons</key>
Expand Down Expand Up @@ -547,41 +549,6 @@
</array>
</dict>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>WebView</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).WebViewSceneDelegate</string>
</dict>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SettingsSceneDelegate</string>
<key>UISceneConfigurationName</key>
<string>Settings</string>
</dict>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).AboutSceneDelegate</string>
<key>UISceneConfigurationName</key>
<string>About</string>
</dict>
</array>
</dict>
</dict>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>FIREBASE_ANALYTICS_COLLECTION_ENABLED</key>
Expand Down Expand Up @@ -623,6 +590,8 @@
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NFCReaderUsageDescription</key>
<string>Reading and writing NFC tags allows you to trigger events.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
Expand All @@ -634,6 +603,12 @@
<string>_home-assistant._tcp</string>
<string>_matter._tcp</string>
</array>
<key>NSCameraUsageDescription</key>
<string>Take photos and send them to your Home Assistant server.</string>
<key>NSCrossWebsiteTrackingUsageDescription</key>
<string>Optionally enable cross-website tracking if your configuration requires it.</string>
<key>NSFocusStatusUsageDescription</key>
<string>Report your focus status as a sensor.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Locate and communicate with your Home Assistant instance.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
Expand All @@ -649,6 +624,8 @@
<key>NSLocationUsageDescription</key>
<string>When enabled, your device location is sent to your Home Assistant server as a device
tracker.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Record audio using your Home Assistant frontend.</string>
<key>NSMotionUsageDescription</key>
<string>Motion is used to improve location updates with current motion type, as well as
provide basic pedometer data.</string>
Expand All @@ -658,24 +635,8 @@
<string>Photo Library access is needed to allow saving photos from the web view.</string>
<key>NSSiriUsageDescription</key>
<string>We use Siri to allow created shortcuts to interact with the app.</string>
<key>NFCReaderUsageDescription</key>
<string>Reading and writing NFC tags allows you to trigger events.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Used to dictate text to Assist.</string>
<key>NSCameraUsageDescription</key>
<string>Take photos and send them to your Home Assistant server.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Record audio using your Home Assistant frontend.</string>
<key>NSFocusStatusUsageDescription</key>
<string>Report your focus status as a sensor.</string>
<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
<array>
<string>12FC</string>
</array>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>D2760000850101</string>
</array>
<key>NSUserActivityTypes</key>
<array>
<string>CallServiceIntent</string>
Expand All @@ -690,6 +651,41 @@
<string>WidgetOpenPageIntent</string>
<string>AssistIntent</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>WebView</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).WebViewSceneDelegate</string>
</dict>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>Settings</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SettingsSceneDelegate</string>
</dict>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>About</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).AboutSceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationShortcutItems</key>
<array>
<dict>
Expand Down Expand Up @@ -731,12 +727,13 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSCrossWebsiteTrackingUsageDescription</key>
<string>Optionally enable cross-website tracking if your configuration requires it.</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
<key>TemporaryFullAccuracyReasonManualUpdate</key>
<string>[localized in strings file]</string>
</dict>
<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
<array>
<string>12FC</string>
</array>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>D2760000850101</string>
</array>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class ComplicationListViewController: HAFormViewController {
$0.value = URL(string: "https://companion.home-assistant.io/app/ios/apple-watch")!
}

enableAssistComplication()

form +++ Section(
header: L10n.Watch.Configurator.List.ManualUpdates.title,
footer: L10n.Watch.Configurator.List.ManualUpdates.footer
Expand Down Expand Up @@ -151,4 +153,35 @@ class ComplicationListViewController: HAFormViewController {
)
}
}

private func enableAssistComplication() {
let realm = Current.realm()

// TODO: reduce complexity
realm.reentrantWrite {
ComplicationGroup.allCases.sorted().forEach { group in
group.members.forEach { family in
Current.servers.all.forEach { server in
let config = WatchComplication()
config.name = "Assist on \(server.info.name)"
config.identifier = "assist-\(server.identifier.rawValue)-\(family.rawValue)"
config.IsPublic = true
config.Family = family
config.Template = family.templates.first!
config.serverIdentifier = server.identifier.rawValue
config.Data = [
"icon": [
"icon": "microphone_message",
"icon_color": "#17bcf2",
],
]
Current.Log.verbose("COMPLICATION \(config)")
realm.add(config, update: .all)
}
}
}
}.done {
Current.apis.forEach({ $0.updateComplications(passively: false) })
}.cauterize()
}
}
6 changes: 6 additions & 0 deletions Sources/App/WebView/WebViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import KeychainAccess
import MBProgressHUD
import PromiseKit
import Shared
import Speech
import SwiftMessages
import UIKit
import WebKit
Expand Down Expand Up @@ -247,6 +248,11 @@ class WebViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, U

styleUI()
updateWebViewForServerValues()

// TODO: Move this block to a proper place
SFSpeechRecognizer.requestAuthorization { status in
print(status)
}
}

public func showSettingsViewController() {
Expand Down
Loading