GarminManager.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import Combine
  2. import ConnectIQ
  3. import Foundation
  4. import Swinject
  5. protocol GarminManager {
  6. func selectDevices() -> AnyPublisher<[IQDevice], Never>
  7. func updateListDevices(devices: [IQDevice])
  8. var devices: [IQDevice] { get }
  9. func sendState(_ data: Data)
  10. var stateRequet: (() -> (Data))? { get set }
  11. }
  12. extension Notification.Name {
  13. static let openFromGarminConnect = Notification.Name("Notification.Name.openFromGarminConnect")
  14. }
  15. final class BaseGarminManager: NSObject, GarminManager, Injectable {
  16. private enum Config {
  17. static let watchfaceUUID = UUID(uuidString: "EC3420F6-027D-49B3-B45F-D81D6D3ED90A")
  18. static let watchdataUUID = UUID(uuidString: "71CF0982-CA41-42A5-8441-EA81D36056C3")
  19. }
  20. private let connectIQ = ConnectIQ.sharedInstance()
  21. private let router = FreeAPSApp.resolver.resolve(Router.self)!
  22. @Injected() private var notificationCenter: NotificationCenter!
  23. @Persisted(key: "BaseGarminManager.persistedDevices") private var persistedDevices: [CodableDevice] = []
  24. private var watchfaces: [IQApp] = []
  25. var stateRequet: (() -> (Data))?
  26. private let stateSubject = PassthroughSubject<NSDictionary, Never>()
  27. private(set) var devices: [IQDevice] = [] {
  28. didSet {
  29. persistedDevices = devices.map(CodableDevice.init)
  30. watchfaces = []
  31. devices.forEach { device in
  32. connectIQ?.register(forDeviceEvents: device, delegate: self)
  33. let watchfaceApp = IQApp(
  34. uuid: Config.watchfaceUUID,
  35. store: UUID(),
  36. device: device
  37. )
  38. let watchDataFieldApp = IQApp(
  39. uuid: Config.watchdataUUID,
  40. store: UUID(),
  41. device: device
  42. )
  43. watchfaces.append(watchfaceApp!)
  44. watchfaces.append(watchDataFieldApp!)
  45. connectIQ?.register(forAppMessages: watchfaceApp, delegate: self)
  46. }
  47. }
  48. }
  49. private var lifetime = Lifetime()
  50. private var selectPromise: Future<[IQDevice], Never>.Promise?
  51. init(resolver: Resolver) {
  52. super.init()
  53. connectIQ?.initialize(withUrlScheme: "Trio", uiOverrideDelegate: self)
  54. injectServices(resolver)
  55. restoreDevices()
  56. subscribeToOpenFromGarminConnect()
  57. setupApplications()
  58. subscribeState()
  59. }
  60. private func subscribeToOpenFromGarminConnect() {
  61. notificationCenter
  62. .publisher(for: .openFromGarminConnect)
  63. .sink { notification in
  64. guard let url = notification.object as? URL else { return }
  65. self.parseDevicesFor(url: url)
  66. }
  67. .store(in: &lifetime)
  68. }
  69. private func subscribeState() {
  70. func sendToWatchface(state: NSDictionary) {
  71. watchfaces.forEach { app in
  72. connectIQ?.getAppStatus(app) { status in
  73. guard status?.isInstalled ?? false else {
  74. debug(.service, "Garmin: watchface app not installed")
  75. return
  76. }
  77. debug(.service, "Garmin: sending message to watchface")
  78. self.sendMessage(state, to: app)
  79. }
  80. }
  81. }
  82. stateSubject
  83. .throttle(for: .seconds(10), scheduler: DispatchQueue.main, latest: true)
  84. .sink { state in
  85. sendToWatchface(state: state)
  86. }
  87. .store(in: &lifetime)
  88. }
  89. private func restoreDevices() {
  90. devices = persistedDevices.map(\.iqDevice)
  91. }
  92. private func parseDevicesFor(url: URL) {
  93. devices = connectIQ?.parseDeviceSelectionResponse(from: url) as? [IQDevice] ?? []
  94. selectPromise?(.success(devices))
  95. selectPromise = nil
  96. }
  97. private func setupApplications() {
  98. devices.forEach { _ in
  99. }
  100. }
  101. func selectDevices() -> AnyPublisher<[IQDevice], Never> {
  102. Future { promise in
  103. self.selectPromise = promise
  104. self.connectIQ?.showDeviceSelection()
  105. }
  106. .timeout(120, scheduler: DispatchQueue.main)
  107. .replaceEmpty(with: [])
  108. .eraseToAnyPublisher()
  109. }
  110. func updateListDevices(devices: [IQDevice]) {
  111. self.devices = devices
  112. }
  113. func sendState(_ data: Data) {
  114. guard let object = try? JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary else {
  115. return
  116. }
  117. stateSubject.send(object)
  118. }
  119. private func sendMessage(_ msg: NSDictionary, to app: IQApp) {
  120. connectIQ?.sendMessage(msg, to: app, progress: { _, _ in
  121. // debug(.service, "Garmin: sending progress: \(Int(Double(sent) / Double(all) * 100)) %")
  122. }, completion: { result in
  123. if result == .success {
  124. debug(.service, "Garmin: message sent")
  125. } else {
  126. debug(.service, "Garmin: message failed")
  127. }
  128. })
  129. }
  130. }
  131. extension BaseGarminManager: IQUIOverrideDelegate {
  132. func needsToInstallConnectMobile() {
  133. debug(.apsManager, NSLocalizedString("Garmin is not available", comment: ""))
  134. let messageCont = MessageContent(
  135. content: NSLocalizedString(
  136. "The app Garmin Connect must be installed to use for Trio.\n Go to App Store to download it",
  137. comment: ""
  138. ),
  139. type: .warning
  140. )
  141. router.alertMessage.send(messageCont)
  142. }
  143. }
  144. extension BaseGarminManager: IQDeviceEventDelegate {
  145. func deviceStatusChanged(_ device: IQDevice, status: IQDeviceStatus) {
  146. switch status {
  147. case .invalidDevice:
  148. debug(.service, "Garmin: invalidDevice, Device: \(device.uuid!)")
  149. case .bluetoothNotReady:
  150. debug(.service, "Garmin: bluetoothNotReady, Device: \(device.uuid!)")
  151. case .notFound:
  152. debug(.service, "Garmin: notFound, Device: \(device.uuid!)")
  153. case .notConnected:
  154. debug(.service, "Garmin: notConnected, Device: \(device.uuid!)")
  155. case .connected:
  156. debug(.service, "Garmin: connected, Device: \(device.uuid!)")
  157. @unknown default:
  158. debug(.service, "Garmin: unknown state, Device: \(device.uuid!)")
  159. }
  160. }
  161. }
  162. extension BaseGarminManager: IQAppMessageDelegate {
  163. func receivedMessage(_ message: Any, from app: IQApp) {
  164. print("ASDF: got message: \(message) from app: \(app.uuid!)")
  165. if let status = message as? String, status == "status", let watchState = stateRequet?() {
  166. sendState(watchState)
  167. }
  168. }
  169. }
  170. struct CodableDevice: Codable, Equatable {
  171. let id: UUID
  172. let modelName: String
  173. let friendlyName: String
  174. init(iqDevice: IQDevice) {
  175. id = iqDevice.uuid
  176. modelName = iqDevice.modelName
  177. friendlyName = iqDevice.modelName
  178. }
  179. var iqDevice: IQDevice {
  180. IQDevice(id: id, modelName: modelName, friendlyName: friendlyName)
  181. }
  182. }