mirror of
https://github.com/home-assistant/iOS.git
synced 2026-02-17 12:46:47 -06: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 --> Align implementation to use Current.connectivity.currentSSID() a source of truth for macOS and iOS ## 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. -->
172 lines
5.4 KiB
Swift
172 lines
5.4 KiB
Swift
import Communicator
|
|
import Foundation
|
|
import NetworkExtension
|
|
import PromiseKit
|
|
import Shared
|
|
|
|
final class WatchMagicViewRowViewModel: ObservableObject {
|
|
enum RowState {
|
|
case idle
|
|
case loading
|
|
case success
|
|
case failure
|
|
}
|
|
|
|
enum MagicItemResponse {
|
|
case success
|
|
case failed
|
|
case tookLonger
|
|
|
|
var rowState: RowState {
|
|
switch self {
|
|
case .success:
|
|
return .success
|
|
case .failed:
|
|
return .failure
|
|
case .tookLonger:
|
|
return .idle
|
|
}
|
|
}
|
|
}
|
|
|
|
@Published private(set) var state: RowState = .idle
|
|
@Published var showConfirmationDialog = false
|
|
|
|
@Published private(set) var item: MagicItem
|
|
@Published private(set) var itemInfo: MagicItem.Info
|
|
|
|
private var timeoutWorkItem: DispatchWorkItem?
|
|
|
|
init(item: MagicItem, itemInfo: MagicItem.Info) {
|
|
self.item = item
|
|
self.itemInfo = itemInfo
|
|
}
|
|
|
|
func executeItem() {
|
|
if itemInfo.customization?.requiresConfirmation ?? true {
|
|
showConfirmationDialog = true
|
|
} else {
|
|
executeItemAction()
|
|
}
|
|
}
|
|
|
|
func confirmationAction() {
|
|
executeItemAction()
|
|
}
|
|
|
|
private func executeItemAction() {
|
|
state = .loading
|
|
executeMagicItem { [weak self] response in
|
|
DispatchQueue.main.async { [weak self] in
|
|
self?.state = response.rowState
|
|
}
|
|
self?.resetState()
|
|
}
|
|
}
|
|
|
|
private func resetState() {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
|
|
self?.state = .idle
|
|
}
|
|
}
|
|
|
|
private func fetchNetworkInfo(completion: (() -> Void)? = nil) {
|
|
Current.networkInformation { hotspotNetwork in
|
|
WatchUserDefaults.shared.set(hotspotNetwork?.ssid, key: .watchSSID)
|
|
completion?()
|
|
}
|
|
}
|
|
|
|
private func executeMagicItemUsingiPhone(magicItem: MagicItem, completion: @escaping (Bool) -> Void) {
|
|
Current.Log.verbose("Signaling magic item pressed via phone")
|
|
let itemMessage = InteractiveImmediateMessage(
|
|
identifier: InteractiveImmediateMessages.magicItemPressed.rawValue,
|
|
content: [
|
|
"itemId": magicItem.id,
|
|
"serverId": magicItem.serverId,
|
|
"itemType": magicItem.type.rawValue,
|
|
],
|
|
reply: { message in
|
|
Current.Log.verbose("Received reply dictionary \(message)")
|
|
if message.content["fired"] as? Bool == true {
|
|
completion(true)
|
|
} else {
|
|
completion(false)
|
|
}
|
|
}
|
|
)
|
|
|
|
Current.Log
|
|
.verbose(
|
|
"Sending \(InteractiveImmediateMessages.magicItemPressed.rawValue) message \(itemMessage)"
|
|
)
|
|
Communicator.shared.send(itemMessage, errorHandler: { error in
|
|
Current.Log.error("Received error when sending immediate message \(error)")
|
|
completion(false)
|
|
})
|
|
}
|
|
|
|
private func executeMagicItemUsingAPI(magicItem: MagicItem, completion: @escaping (Bool) -> Void) {
|
|
guard let server = Current.servers.all.first(where: { $0.identifier.rawValue == magicItem.serverId }) else {
|
|
completion(false)
|
|
return
|
|
}
|
|
Current.Log.error("Executing watch magic item directly via API")
|
|
|
|
magicItem.execute(on: server, source: .Watch) { success in
|
|
completion(success)
|
|
}
|
|
}
|
|
|
|
private func executeMagicItem(completion: @escaping (MagicItemResponse) -> Void) {
|
|
let timeTriggered = Date()
|
|
let magicItem = item
|
|
Current.Log.verbose("Selected magic item id: \(magicItem.id)")
|
|
fetchNetworkInfo { [weak self] in
|
|
guard let self else { return }
|
|
if Communicator.shared.currentReachability == .immediatelyReachable {
|
|
executeMagicItemUsingiPhone(magicItem: magicItem) { success in
|
|
// Avoid haptics in background
|
|
guard self.isLessThan30Seconds(from: timeTriggered) else {
|
|
completion(.tookLonger)
|
|
return
|
|
}
|
|
if success {
|
|
self.cancelTimeout()
|
|
completion(success ? .success : .failed)
|
|
} else {
|
|
completion(.failed)
|
|
}
|
|
}
|
|
} else {
|
|
executeMagicItemUsingAPI(magicItem: magicItem) { success in
|
|
self.cancelTimeout()
|
|
completion(success ? .success : .failed)
|
|
}
|
|
}
|
|
startTimeoutTimerWhichResetsState(completion: completion)
|
|
}
|
|
}
|
|
|
|
// Given date returns if is less than 30 seconds from now
|
|
private func isLessThan30Seconds(from date: Date) -> Bool {
|
|
Date().timeIntervalSince(date) < 30
|
|
}
|
|
|
|
private func startTimeoutTimerWhichResetsState(completion: @escaping (MagicItemResponse) -> Void) {
|
|
timeoutWorkItem?.cancel()
|
|
|
|
timeoutWorkItem = DispatchWorkItem {
|
|
completion(.tookLonger)
|
|
}
|
|
|
|
if let workItem = timeoutWorkItem {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: workItem)
|
|
}
|
|
}
|
|
|
|
private func cancelTimeout() {
|
|
timeoutWorkItem?.cancel()
|
|
}
|
|
}
|