iOS/Sources/Extensions/Watch/Home/MagicItemRow/WatchMagicViewRowViewModel....

170 lines
5.3 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) {
NEHotspotNetwork.fetchCurrent { 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")
guard let api = Current.api(for: server) else {
Current.Log.error("No API available to execute watch magic item")
completion(false)
return
}
api.executeMagicItem(item: magicItem) { success in
completion(success)
}
}
private func executeMagicItem(completion: @escaping (MagicItemResponse) -> Void) {
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
if success {
self.cancelTimeout()
completion(success ? .success : .failed)
} else {
self.executeMagicItemUsingAPI(magicItem: magicItem) { success in
self.cancelTimeout()
completion(success ? .success : .failed)
}
}
}
} else {
executeMagicItemUsingAPI(magicItem: magicItem) { success in
self.cancelTimeout()
completion(success ? .success : .failed)
}
}
startTimeoutTimerWhichResetsState(completion: completion)
}
}
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()
}
}