RemoteCommandSettings.swift 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. // LoopFollow
  2. // RemoteCommandSettings.swift
  3. import Foundation
  4. import HealthKit
  5. struct RemoteCommandSettings: Codable {
  6. let remoteType: RemoteType
  7. let user: String
  8. let sharedSecret: String
  9. let apnsKey: String
  10. let keyId: String
  11. let teamId: String?
  12. let maxBolus: Double
  13. let maxCarbs: Double
  14. let maxProtein: Double
  15. let maxFat: Double
  16. let mealWithBolus: Bool
  17. let mealWithFatProtein: Bool
  18. let productionEnvironment: Bool
  19. let loopAPNSQrCodeURL: String
  20. let url: String
  21. let token: String
  22. let version: String
  23. init(
  24. remoteType: RemoteType,
  25. user: String,
  26. sharedSecret: String,
  27. apnsKey: String,
  28. keyId: String,
  29. teamId: String?,
  30. maxBolus: Double,
  31. maxCarbs: Double,
  32. maxProtein: Double,
  33. maxFat: Double,
  34. mealWithBolus: Bool,
  35. mealWithFatProtein: Bool,
  36. productionEnvironment: Bool,
  37. loopAPNSQrCodeURL: String,
  38. url: String,
  39. token: String
  40. ) {
  41. self.remoteType = remoteType
  42. self.user = user
  43. self.sharedSecret = sharedSecret
  44. self.apnsKey = apnsKey
  45. self.keyId = keyId
  46. self.teamId = teamId
  47. self.maxBolus = maxBolus
  48. self.maxCarbs = maxCarbs
  49. self.maxProtein = maxProtein
  50. self.maxFat = maxFat
  51. self.mealWithBolus = mealWithBolus
  52. self.mealWithFatProtein = mealWithFatProtein
  53. self.productionEnvironment = productionEnvironment
  54. self.loopAPNSQrCodeURL = loopAPNSQrCodeURL
  55. self.url = url
  56. self.token = token
  57. version = "1.0"
  58. }
  59. /// Creates RemoteCommandSettings from the current Storage values
  60. static func fromCurrentStorage() -> RemoteCommandSettings {
  61. let storage = Storage.shared
  62. return RemoteCommandSettings(
  63. remoteType: storage.remoteType.value,
  64. user: storage.user.value,
  65. sharedSecret: storage.sharedSecret.value,
  66. apnsKey: storage.apnsKey.value,
  67. keyId: storage.keyId.value,
  68. teamId: storage.teamId.value,
  69. maxBolus: storage.maxBolus.value.doubleValue(for: .internationalUnit()),
  70. maxCarbs: storage.maxCarbs.value.doubleValue(for: .gram()),
  71. maxProtein: storage.maxProtein.value.doubleValue(for: .gram()),
  72. maxFat: storage.maxFat.value.doubleValue(for: .gram()),
  73. mealWithBolus: storage.mealWithBolus.value,
  74. mealWithFatProtein: storage.mealWithFatProtein.value,
  75. productionEnvironment: storage.productionEnvironment.value,
  76. loopAPNSQrCodeURL: storage.loopAPNSQrCodeURL.value,
  77. url: storage.url.value,
  78. token: storage.token.value
  79. )
  80. }
  81. /// Applies the settings to the current Storage
  82. func applyToStorage() {
  83. let storage = Storage.shared
  84. storage.remoteType.value = remoteType
  85. storage.user.value = user
  86. storage.sharedSecret.value = sharedSecret
  87. storage.apnsKey.value = apnsKey
  88. storage.keyId.value = keyId
  89. storage.teamId.value = teamId
  90. storage.maxBolus.value = HKQuantity(unit: .internationalUnit(), doubleValue: maxBolus)
  91. storage.maxCarbs.value = HKQuantity(unit: .gram(), doubleValue: maxCarbs)
  92. storage.maxProtein.value = HKQuantity(unit: .gram(), doubleValue: maxProtein)
  93. storage.maxFat.value = HKQuantity(unit: .gram(), doubleValue: maxFat)
  94. storage.mealWithBolus.value = mealWithBolus
  95. storage.mealWithFatProtein.value = mealWithFatProtein
  96. storage.productionEnvironment.value = productionEnvironment
  97. storage.loopAPNSQrCodeURL.value = loopAPNSQrCodeURL
  98. storage.url.value = url
  99. storage.token.value = token
  100. // Automatically set device type based on remote type
  101. switch remoteType {
  102. case .loopAPNS:
  103. storage.device.value = "Loop"
  104. case .trc:
  105. storage.device.value = "Trio"
  106. case .nightscout:
  107. // For Nightscout, we don't automatically set device type
  108. // as it should be determined by the actual connection
  109. break
  110. case .none:
  111. // For none, we don't change the device type
  112. break
  113. }
  114. }
  115. /// Encodes the settings to a JSON string for QR code generation
  116. func encodeToJSON() -> String? {
  117. do {
  118. let data = try JSONEncoder().encode(self)
  119. return String(data: data, encoding: .utf8)
  120. } catch {
  121. return nil
  122. }
  123. }
  124. /// Decodes settings from a JSON string
  125. static func decodeFromJSON(_ jsonString: String) -> RemoteCommandSettings? {
  126. guard let data = jsonString.data(using: .utf8) else {
  127. return nil
  128. }
  129. do {
  130. return try JSONDecoder().decode(RemoteCommandSettings.self, from: data)
  131. } catch {
  132. return nil
  133. }
  134. }
  135. /// Checks if the settings are valid for the given remote type
  136. func isValid() -> Bool {
  137. switch remoteType {
  138. case .none:
  139. return true
  140. case .nightscout:
  141. return !user.isEmpty
  142. case .trc:
  143. return !user.isEmpty && !sharedSecret.isEmpty && !apnsKey.isEmpty && !keyId.isEmpty
  144. case .loopAPNS:
  145. return !keyId.isEmpty && !apnsKey.isEmpty && teamId != nil && !loopAPNSQrCodeURL.isEmpty
  146. }
  147. }
  148. /// Validates URL and token compatibility with current storage
  149. /// Returns a tuple with (isCompatible, shouldPromptForURL, shouldPromptForToken, message)
  150. func validateCompatibilityWithCurrentStorage() -> (isCompatible: Bool, shouldPromptForURL: Bool, shouldPromptForToken: Bool, message: String) {
  151. let storage = Storage.shared
  152. let currentURL = storage.url.value
  153. let currentToken = storage.token.value
  154. var shouldPromptForURL = false
  155. var shouldPromptForToken = false
  156. var message = ""
  157. // Check if current user has URL set
  158. let hasCurrentURL = !currentURL.isEmpty
  159. let hasCurrentToken = !currentToken.isEmpty
  160. // Check if scanned settings have URL/token
  161. let hasScannedURL = !url.isEmpty
  162. let hasScannedToken = !token.isEmpty
  163. // If current user doesn't have URL but scanned settings do, prompt to set it
  164. if !hasCurrentURL, hasScannedURL {
  165. shouldPromptForURL = true
  166. message = "The scanned settings include a Nightscout URL. Would you like to set this as your Nightscout address?"
  167. }
  168. // If current user doesn't have token but scanned settings do, prompt to set it
  169. if !hasCurrentToken, hasScannedToken {
  170. shouldPromptForToken = true
  171. if !message.isEmpty {
  172. message += "\n\nThe scanned settings also include a token. Would you like to set this as your access token?"
  173. } else {
  174. message = "The scanned settings include a token. Would you like to set this as your access token?"
  175. }
  176. }
  177. // If both have URLs but they don't match, show warning
  178. if hasCurrentURL, hasScannedURL, currentURL != url {
  179. shouldPromptForURL = true
  180. message = "The scanned Nightscout URL (\(url)) doesn't match your current Nightscout address (\(currentURL)). Would you like to change your Nightscout address to match the scanned settings?"
  181. }
  182. // If both have tokens but they don't match, show warning
  183. if hasCurrentToken, hasScannedToken, currentToken != token {
  184. shouldPromptForToken = true
  185. if !message.isEmpty {
  186. message += "\n\nThe scanned token doesn't match your current access token. Would you like to update your token?"
  187. } else {
  188. message = "The scanned token doesn't match your current access token. Would you like to update your token?"
  189. }
  190. }
  191. let isCompatible = !shouldPromptForURL && !shouldPromptForToken
  192. return (isCompatible, shouldPromptForURL, shouldPromptForToken, message)
  193. }
  194. }