mirror of
https://github.com/home-assistant/iOS.git
synced 2026-06-18 11:15:36 -05:00
Fixes #1552. ## Summary Only fires on the watch if sending via phone is not an option, and only allows ephemerally sending an action. ## Any other notes Ephemerally sending doesn't retry nearly as much, and doesn't enter into any background URL session for possible persistent retry.
181 lines
6.2 KiB
Swift
181 lines
6.2 KiB
Swift
import Communicator
|
|
import EMTLoadingIndicator
|
|
import Foundation
|
|
import PromiseKit
|
|
import RealmSwift
|
|
import Shared
|
|
import WatchKit
|
|
|
|
class InterfaceController: WKInterfaceController {
|
|
@IBOutlet var tableView: WKInterfaceTable!
|
|
@IBOutlet var noActionsLabel: WKInterfaceLabel!
|
|
|
|
var notificationToken: NotificationToken?
|
|
|
|
var actions: Results<Action>?
|
|
|
|
override func awake(withContext context: Any?) {
|
|
super.awake(withContext: context)
|
|
|
|
MaterialDesignIcons.register()
|
|
|
|
setupTable()
|
|
}
|
|
|
|
override func willActivate() {
|
|
// This method is called when watch view controller is about to be visible to user
|
|
super.willActivate()
|
|
}
|
|
|
|
override func didDeactivate() {
|
|
// This method is called when watch view controller is no longer visible
|
|
super.didDeactivate()
|
|
}
|
|
|
|
func setupTable() {
|
|
let realm = Realm.live()
|
|
|
|
noActionsLabel.setText(L10n.Watch.Labels.noAction)
|
|
|
|
let actions = realm.objects(Action.self).sorted(byKeyPath: "Position")
|
|
self.actions = actions
|
|
|
|
notificationToken = actions.observe { (changes: RealmCollectionChange) in
|
|
guard let tableView = self.tableView else { return }
|
|
|
|
self.noActionsLabel.setHidden(actions.count > 0)
|
|
|
|
switch changes {
|
|
case .initial:
|
|
// Results are now populated and can be accessed without blocking the UI
|
|
self.tableView.setNumberOfRows(actions.count, withRowType: "actionRowType")
|
|
|
|
for idx in actions.indices {
|
|
self.setupRow(idx)
|
|
}
|
|
case let .update(_, deletions, insertions, modifications):
|
|
let insertionsSet = NSMutableIndexSet()
|
|
insertions.forEach(insertionsSet.add)
|
|
|
|
tableView.insertRows(at: IndexSet(insertionsSet), withRowType: "actionRowType")
|
|
|
|
insertions.forEach(self.setupRow)
|
|
|
|
let deletionsSet = NSMutableIndexSet()
|
|
deletions.forEach(deletionsSet.add)
|
|
|
|
tableView.removeRows(at: IndexSet(deletionsSet))
|
|
|
|
modifications.forEach(self.setupRow)
|
|
case let .error(error):
|
|
// An error occurred while opening the Realm file on the background worker thread
|
|
Current.Log.error("Error during Realm notifications! \(error)")
|
|
}
|
|
}
|
|
}
|
|
|
|
func setupRow(_ index: Int) {
|
|
DispatchQueue.main.async {
|
|
guard let row = self.tableView.rowController(at: index) as? ActionRowType,
|
|
let action = self.actions?[index] else { return }
|
|
Current.Log.verbose("Setup row \(index) with action \(action)")
|
|
row.group.setBackgroundColor(UIColor(hex: action.BackgroundColor))
|
|
row.indicator = EMTLoadingIndicator(
|
|
interfaceController: self,
|
|
interfaceImage: row.image,
|
|
width: 24,
|
|
height: 24,
|
|
style: .dot
|
|
)
|
|
row.icon = MaterialDesignIcons(named: action.IconName)
|
|
let iconColor = UIColor(hex: action.IconColor)
|
|
row.image.setImage(row.icon.image(ofSize: CGSize(width: 24, height: 24), color: iconColor))
|
|
row.image.setAlpha(1)
|
|
row.label.setText(action.Text)
|
|
row.label.setTextColor(UIColor(hex: action.TextColor))
|
|
}
|
|
}
|
|
|
|
override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) {
|
|
let selectedAction = actions![rowIndex]
|
|
|
|
Current.Log.verbose("Selected action row at index \(rowIndex), \(selectedAction)")
|
|
|
|
guard let row = tableView.rowController(at: rowIndex) as? ActionRowType else {
|
|
Current.Log.warning("Row at \(rowIndex) is not ActionRowType")
|
|
return
|
|
}
|
|
|
|
row.indicator?.prepareImagesForWait()
|
|
row.indicator?.showWait()
|
|
|
|
enum SendError: Error {
|
|
case notImmediate
|
|
case phoneFailed
|
|
}
|
|
|
|
firstly { () -> Promise<Void> in
|
|
Promise { seal in
|
|
guard Communicator.shared.currentReachability == .immediatelyReachable else {
|
|
seal.reject(SendError.notImmediate)
|
|
return
|
|
}
|
|
|
|
Current.Log.verbose("Signaling action pressed via phone")
|
|
let actionMessage = InteractiveImmediateMessage(
|
|
identifier: "ActionRowPressed",
|
|
content: ["ActionID": selectedAction.ID],
|
|
reply: { message in
|
|
Current.Log.verbose("Received reply dictionary \(message)")
|
|
if message.content["fired"] as? Bool == true {
|
|
seal.fulfill(())
|
|
} else {
|
|
seal.reject(SendError.phoneFailed)
|
|
}
|
|
}
|
|
)
|
|
|
|
Current.Log.verbose("Sending ActionRowPressed message \(actionMessage)")
|
|
Communicator.shared.send(actionMessage, errorHandler: { error in
|
|
Current.Log.error("Received error when sending immediate message \(error)")
|
|
seal.reject(error)
|
|
})
|
|
}
|
|
}.recover { error -> Promise<Void> in
|
|
guard error == SendError.notImmediate else {
|
|
throw error
|
|
}
|
|
|
|
Current.Log.error("recovering error \(error) by trying locally")
|
|
return Current.api.then(on: nil) { api -> Promise<Void> in
|
|
api.HandleAction(actionID: selectedAction.ID, source: .Watch)
|
|
}
|
|
}.done {
|
|
self.handleActionSuccess(row, rowIndex)
|
|
}.catch { err -> Void in
|
|
Current.Log.error("Error during action event fire: \(err)")
|
|
self.handleActionFailure(row, rowIndex)
|
|
}
|
|
}
|
|
|
|
func handleActionSuccess(_ row: ActionRowType, _ index: Int) {
|
|
WKInterfaceDevice.current().play(.success)
|
|
|
|
row.image.stopAnimating()
|
|
|
|
setupRow(index)
|
|
}
|
|
|
|
func handleActionFailure(_ row: ActionRowType, _ index: Int) {
|
|
WKInterfaceDevice.current().play(.failure)
|
|
|
|
row.image.stopAnimating()
|
|
|
|
setupRow(index)
|
|
}
|
|
|
|
deinit {
|
|
notificationToken?.invalidate()
|
|
}
|
|
}
|