184 lines
5.8 KiB
Swift
184 lines
5.8 KiB
Swift
import EMTLoadingIndicator
|
|
import Foundation
|
|
import PromiseKit
|
|
import Shared
|
|
import UserNotifications
|
|
import WatchKit
|
|
|
|
class DynamicNotificationController: WKUserNotificationInterfaceController {
|
|
@IBOutlet var loadingImage: WKInterfaceImage!
|
|
@IBOutlet var errorLabel: WKInterfaceLabel!
|
|
@IBOutlet var notificationTitleLabel: WKInterfaceLabel!
|
|
@IBOutlet var notificationSubtitleLabel: WKInterfaceLabel!
|
|
@IBOutlet var notificationAlertLabel: WKInterfaceLabel!
|
|
@IBOutlet var notificationImage: WKInterfaceImage!
|
|
@IBOutlet var notificationVideo: WKInterfaceInlineMovie!
|
|
@IBOutlet var notificationMap: WKInterfaceMap!
|
|
|
|
private var loadingIndicator: EMTLoadingIndicator?
|
|
private var activeSubController: NotificationSubController? {
|
|
willSet {
|
|
if activeSubController !== newValue {
|
|
activeSubController?.stop()
|
|
}
|
|
}
|
|
didSet {
|
|
if isActive {
|
|
start()
|
|
}
|
|
}
|
|
}
|
|
|
|
private lazy var dynamicElements: NotificationElements = .init(
|
|
image: notificationImage,
|
|
map: notificationMap,
|
|
movie: notificationVideo
|
|
)
|
|
|
|
private var isActive: Bool = false {
|
|
didSet {
|
|
if isActive {
|
|
start()
|
|
} else if let subController = activeSubController {
|
|
subController.stop()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var isLoading: Bool = false {
|
|
didSet {
|
|
if isLoading {
|
|
if loadingIndicator == nil {
|
|
loadingIndicator = with(EMTLoadingIndicator(
|
|
interfaceController: self,
|
|
interfaceImage: loadingImage,
|
|
width: 40,
|
|
height: 40,
|
|
style: .dot
|
|
)) {
|
|
$0.showWait()
|
|
}
|
|
}
|
|
} else {
|
|
loadingIndicator?.hide()
|
|
loadingImage.setHidden(true)
|
|
loadingIndicator = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
override func willActivate() {
|
|
super.willActivate()
|
|
isActive = true
|
|
}
|
|
|
|
override func didDeactivate() {
|
|
super.didDeactivate()
|
|
isActive = false
|
|
}
|
|
|
|
private func start() {
|
|
guard let subController = activeSubController else { return }
|
|
|
|
isLoading = true
|
|
|
|
firstly {
|
|
subController.start(with: dynamicElements)
|
|
}.ensure { [self] in
|
|
isLoading = false
|
|
}.catch { [self] error in
|
|
show(error: error)
|
|
}
|
|
}
|
|
|
|
private func show(error: Error) {
|
|
errorLabel.setTextColor(.red)
|
|
errorLabel.setTextAndHideIfEmpty(L10n.NotificationService.failedToLoad + "\n" + error.localizedDescription)
|
|
}
|
|
|
|
private var possibleSubControllers: [NotificationSubController.Type] { [
|
|
NotificationSubControllerMJPEG.self,
|
|
NotificationSubControllerMap.self,
|
|
NotificationSubControllerMedia.self,
|
|
] }
|
|
|
|
private func subController(for notification: UNNotification, api: HomeAssistantAPI) -> NotificationSubController? {
|
|
for potential in possibleSubControllers {
|
|
if let controller = potential.init(api: api, notification: notification) {
|
|
return controller
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
private func subController(for url: URL, api: HomeAssistantAPI) -> NotificationSubController? {
|
|
for potential in possibleSubControllers {
|
|
if let controller = potential.init(api: api, url: url) {
|
|
return controller
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
override func didReceive(_ notification: UNNotification) {
|
|
super.didReceive(notification)
|
|
|
|
notificationTitleLabel.setTextAndHideIfEmpty(notification.request.content.title)
|
|
notificationSubtitleLabel.setTextAndHideIfEmpty(notification.request.content.subtitle)
|
|
notificationAlertLabel.setTextAndHideIfEmpty(notification.request.content.body)
|
|
|
|
errorLabel.setHidden(true)
|
|
dynamicElements.hide()
|
|
|
|
guard let server = Current.servers.server(for: notification.request.content) else {
|
|
return
|
|
}
|
|
|
|
guard let api = Current.api(for: server) else {
|
|
Current.Log.error("No API available to handle didReceive(_ notification: UNNotification)")
|
|
return
|
|
}
|
|
|
|
notificationActions = notification.request.content.userInfoActions
|
|
|
|
if let active = subController(for: notification, api: api) {
|
|
activeSubController = active
|
|
} else {
|
|
isLoading = true
|
|
|
|
firstly {
|
|
Current.notificationAttachmentManager.downloadAttachment(from: notification.request.content, api: api)
|
|
}.tap { [self] result in
|
|
if case .rejected = result {
|
|
// we don't stop loading on success 'cause it's just gonna turn it on again
|
|
isLoading = false
|
|
}
|
|
}.map { [self] url in
|
|
subController(for: url, api: api)
|
|
}.done { [self] controller in
|
|
activeSubController = controller
|
|
}.catch { [self] error in
|
|
Current.Log.info("no attachments downloaded: \(error)")
|
|
|
|
if error as? NotificationAttachmentManagerServiceError == .noAttachment {
|
|
// can make the above != but it doesn't clearly indicate
|
|
} else {
|
|
show(error: error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override func suggestionsForResponseToAction(
|
|
withIdentifier identifier: String,
|
|
for notification: UNNotification,
|
|
inputLanguage: String
|
|
) -> [String] {
|
|
// if not implemented, this returns `nil` by default, which causes it to not prompt
|
|
// last tested: watchOS 7.5
|
|
[]
|
|
}
|
|
}
|