| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- import Combine
- import ConnectIQ
- import Foundation
- import Swinject
- protocol GarminManager {
- func selectDevices() -> AnyPublisher<[IQDevice], Never>
- func updateListDevices(devices: [IQDevice])
- var devices: [IQDevice] { get }
- func sendState(_ data: Data)
- var stateRequet: (() -> (Data))? { get set }
- }
- extension Notification.Name {
- static let openFromGarminConnect = Notification.Name("Notification.Name.openFromGarminConnect")
- }
- final class BaseGarminManager: NSObject, GarminManager, Injectable {
- private enum Config {
- static let watchfaceUUID = UUID(uuidString: "EC3420F6-027D-49B3-B45F-D81D6D3ED90A")
- static let watchdataUUID = UUID(uuidString: "71CF0982-CA41-42A5-8441-EA81D36056C3")
- }
- private let connectIQ = ConnectIQ.sharedInstance()
- private let router = FreeAPSApp.resolver.resolve(Router.self)!
- @Injected() private var notificationCenter: NotificationCenter!
- @Persisted(key: "BaseGarminManager.persistedDevices") private var persistedDevices: [CodableDevice] = []
- private var watchfaces: [IQApp] = []
- var stateRequet: (() -> (Data))?
- private let stateSubject = PassthroughSubject<NSDictionary, Never>()
- private(set) var devices: [IQDevice] = [] {
- didSet {
- persistedDevices = devices.map(CodableDevice.init)
- watchfaces = []
- devices.forEach { device in
- connectIQ?.register(forDeviceEvents: device, delegate: self)
- let watchfaceApp = IQApp(
- uuid: Config.watchfaceUUID,
- store: UUID(),
- device: device
- )
- let watchDataFieldApp = IQApp(
- uuid: Config.watchdataUUID,
- store: UUID(),
- device: device
- )
- watchfaces.append(watchfaceApp!)
- watchfaces.append(watchDataFieldApp!)
- connectIQ?.register(forAppMessages: watchfaceApp, delegate: self)
- }
- }
- }
- private var lifetime = Lifetime()
- private var selectPromise: Future<[IQDevice], Never>.Promise?
- init(resolver: Resolver) {
- super.init()
- connectIQ?.initialize(withUrlScheme: "freeaps-x", uiOverrideDelegate: self)
- injectServices(resolver)
- restoreDevices()
- subscribeToOpenFromGarminConnect()
- setupApplications()
- subscribeState()
- }
- private func subscribeToOpenFromGarminConnect() {
- notificationCenter
- .publisher(for: .openFromGarminConnect)
- .sink { notification in
- guard let url = notification.object as? URL else { return }
- self.parseDevicesFor(url: url)
- }
- .store(in: &lifetime)
- }
- private func subscribeState() {
- func sendToWatchface(state: NSDictionary) {
- watchfaces.forEach { app in
- connectIQ?.getAppStatus(app) { status in
- guard status?.isInstalled ?? false else {
- debug(.service, "Garmin: watchface app not installed")
- return
- }
- debug(.service, "Garmin: sending message to watchface")
- self.sendMessage(state, to: app)
- }
- }
- }
- stateSubject
- .throttle(for: .seconds(10), scheduler: DispatchQueue.main, latest: true)
- .sink { state in
- sendToWatchface(state: state)
- }
- .store(in: &lifetime)
- }
- private func restoreDevices() {
- devices = persistedDevices.map(\.iqDevice)
- }
- private func parseDevicesFor(url: URL) {
- devices = connectIQ?.parseDeviceSelectionResponse(from: url) as? [IQDevice] ?? []
- selectPromise?(.success(devices))
- selectPromise = nil
- }
- private func setupApplications() {
- devices.forEach { _ in
- }
- }
- func selectDevices() -> AnyPublisher<[IQDevice], Never> {
- Future { promise in
- self.selectPromise = promise
- self.connectIQ?.showDeviceSelection()
- }
- .timeout(120, scheduler: DispatchQueue.main)
- .replaceEmpty(with: [])
- .eraseToAnyPublisher()
- }
- func updateListDevices(devices: [IQDevice]) {
- self.devices = devices
- }
- func sendState(_ data: Data) {
- guard let object = try? JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary else {
- return
- }
- stateSubject.send(object)
- }
- private func sendMessage(_ msg: NSDictionary, to app: IQApp) {
- connectIQ?.sendMessage(msg, to: app, progress: { _, _ in
- // debug(.service, "Garmin: sending progress: \(Int(Double(sent) / Double(all) * 100)) %")
- }, completion: { result in
- if result == .success {
- debug(.service, "Garmin: message sent")
- } else {
- debug(.service, "Garmin: message failed")
- }
- })
- }
- }
- extension BaseGarminManager: IQUIOverrideDelegate {
- func needsToInstallConnectMobile() {
- debug(.apsManager, NSLocalizedString("Garmin is not available", comment: ""))
- let messageCont = MessageContent(
- content: NSLocalizedString(
- "The app Garmin Connect must be installed to use for iAPS.\n Go to App Store to download it",
- comment: ""
- ),
- type: .warning
- )
- router.alertMessage.send(messageCont)
- }
- }
- extension BaseGarminManager: IQDeviceEventDelegate {
- func deviceStatusChanged(_ device: IQDevice, status: IQDeviceStatus) {
- switch status {
- case .invalidDevice:
- debug(.service, "Garmin: invalidDevice, Device: \(device.uuid!)")
- case .bluetoothNotReady:
- debug(.service, "Garmin: bluetoothNotReady, Device: \(device.uuid!)")
- case .notFound:
- debug(.service, "Garmin: notFound, Device: \(device.uuid!)")
- case .notConnected:
- debug(.service, "Garmin: notConnected, Device: \(device.uuid!)")
- case .connected:
- debug(.service, "Garmin: connected, Device: \(device.uuid!)")
- @unknown default:
- debug(.service, "Garmin: unknown state, Device: \(device.uuid!)")
- }
- }
- }
- extension BaseGarminManager: IQAppMessageDelegate {
- func receivedMessage(_ message: Any, from app: IQApp) {
- print("ASDF: got message: \(message) from app: \(app.uuid!)")
- if let status = message as? String, status == "status", let watchState = stateRequet?() {
- sendState(watchState)
- }
- }
- }
- struct CodableDevice: Codable, Equatable {
- let id: UUID
- let modelName: String
- let friendlyName: String
- init(iqDevice: IQDevice) {
- id = iqDevice.uuid
- modelName = iqDevice.modelName
- friendlyName = iqDevice.modelName
- }
- var iqDevice: IQDevice {
- IQDevice(id: id, modelName: modelName, friendlyName: friendlyName)
- }
- }
|