RemoteCommandSettings.swift 7.9 KB

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