RemoteSettingsViewModel.swift 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // LoopFollow
  2. // RemoteSettingsViewModel.swift
  3. import Combine
  4. import Foundation
  5. import HealthKit
  6. class RemoteSettingsViewModel: ObservableObject {
  7. @Published var remoteType: RemoteType
  8. @Published var user: String
  9. @Published var sharedSecret: String
  10. @Published var apnsKey: String
  11. @Published var keyId: String
  12. @Published var maxBolus: HKQuantity
  13. @Published var maxCarbs: HKQuantity
  14. @Published var maxProtein: HKQuantity
  15. @Published var maxFat: HKQuantity
  16. @Published var mealWithBolus: Bool
  17. @Published var mealWithFatProtein: Bool
  18. @Published var isTrioDevice: Bool = (Storage.shared.device.value == "Trio")
  19. @Published var isLoopDevice: Bool = (Storage.shared.device.value == "Loop")
  20. // MARK: - Loop APNS Setup Properties
  21. @Published var loopDeveloperTeamId: String
  22. @Published var loopAPNSQrCodeURL: String
  23. @Published var productionEnvironment: Bool
  24. @Published var isShowingLoopAPNSScanner: Bool = false
  25. @Published var loopAPNSErrorMessage: String?
  26. // MARK: - Computed property for Loop APNS Setup validation
  27. var loopAPNSSetup: Bool {
  28. !keyId.isEmpty &&
  29. !apnsKey.isEmpty &&
  30. !loopDeveloperTeamId.isEmpty &&
  31. !loopAPNSQrCodeURL.isEmpty &&
  32. !Storage.shared.deviceToken.value.isEmpty &&
  33. !Storage.shared.bundleId.value.isEmpty
  34. }
  35. private var storage = Storage.shared
  36. private var cancellables = Set<AnyCancellable>()
  37. init() {
  38. // Initialize published properties from storage
  39. remoteType = storage.remoteType.value
  40. user = storage.user.value
  41. sharedSecret = storage.sharedSecret.value
  42. apnsKey = storage.apnsKey.value
  43. keyId = storage.keyId.value
  44. maxBolus = storage.maxBolus.value
  45. maxCarbs = storage.maxCarbs.value
  46. maxProtein = storage.maxProtein.value
  47. maxFat = storage.maxFat.value
  48. mealWithBolus = storage.mealWithBolus.value
  49. mealWithFatProtein = storage.mealWithFatProtein.value
  50. loopDeveloperTeamId = storage.teamId.value ?? ""
  51. loopAPNSQrCodeURL = storage.loopAPNSQrCodeURL.value
  52. productionEnvironment = storage.productionEnvironment.value
  53. setupBindings()
  54. }
  55. private func setupBindings() {
  56. // Basic property bindings
  57. $remoteType
  58. .dropFirst()
  59. .sink { [weak self] in self?.storage.remoteType.value = $0 }
  60. .store(in: &cancellables)
  61. $user
  62. .dropFirst()
  63. .sink { [weak self] in self?.storage.user.value = $0 }
  64. .store(in: &cancellables)
  65. $sharedSecret
  66. .dropFirst()
  67. .sink { [weak self] in self?.storage.sharedSecret.value = $0 }
  68. .store(in: &cancellables)
  69. $apnsKey
  70. .dropFirst()
  71. .sink { [weak self] newValue in
  72. // Validate and fix the APNS key format using the service
  73. let apnsService = LoopAPNSService()
  74. let fixedKey = apnsService.validateAndFixAPNSKey(newValue)
  75. self?.storage.apnsKey.value = fixedKey
  76. }
  77. .store(in: &cancellables)
  78. $keyId
  79. .dropFirst()
  80. .sink { [weak self] in self?.storage.keyId.value = $0 }
  81. .store(in: &cancellables)
  82. $maxBolus
  83. .dropFirst()
  84. .sink { [weak self] in self?.storage.maxBolus.value = $0 }
  85. .store(in: &cancellables)
  86. $maxCarbs
  87. .dropFirst()
  88. .sink { [weak self] in self?.storage.maxCarbs.value = $0 }
  89. .store(in: &cancellables)
  90. $maxProtein
  91. .dropFirst()
  92. .sink { [weak self] in self?.storage.maxProtein.value = $0 }
  93. .store(in: &cancellables)
  94. $maxFat
  95. .dropFirst()
  96. .sink { [weak self] in self?.storage.maxFat.value = $0 }
  97. .store(in: &cancellables)
  98. $mealWithBolus
  99. .dropFirst()
  100. .sink { [weak self] in self?.storage.mealWithBolus.value = $0 }
  101. .store(in: &cancellables)
  102. $mealWithFatProtein
  103. .dropFirst()
  104. .sink { [weak self] in self?.storage.mealWithFatProtein.value = $0 }
  105. .store(in: &cancellables)
  106. // Device type monitoring
  107. Storage.shared.device.$value
  108. .receive(on: DispatchQueue.main)
  109. .sink { [weak self] newValue in
  110. self?.isTrioDevice = (newValue == "Trio")
  111. self?.isLoopDevice = (newValue == "Loop")
  112. }
  113. .store(in: &cancellables)
  114. // Loop APNS bindings
  115. $loopDeveloperTeamId
  116. .dropFirst()
  117. .sink { [weak self] in self?.storage.teamId.value = $0 }
  118. .store(in: &cancellables)
  119. $loopAPNSQrCodeURL
  120. .dropFirst()
  121. .sink { [weak self] in self?.storage.loopAPNSQrCodeURL.value = $0 }
  122. .store(in: &cancellables)
  123. $productionEnvironment
  124. .dropFirst()
  125. .sink { [weak self] in self?.storage.productionEnvironment.value = $0 }
  126. .store(in: &cancellables)
  127. }
  128. func handleLoopAPNSQRCodeScanResult(_ result: Result<String, Error>) {
  129. DispatchQueue.main.async {
  130. switch result {
  131. case let .success(code):
  132. self.loopAPNSQrCodeURL = code
  133. LogManager.shared.log(category: .apns, message: "Loop APNS QR code scanned: \(code)")
  134. case let .failure(error):
  135. self.loopAPNSErrorMessage = "Scanning failed: \(error.localizedDescription)"
  136. }
  137. self.isShowingLoopAPNSScanner = false
  138. }
  139. }
  140. }