RemoteSettingsView.swift 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. //
  2. // RemoteSettingsView.swift
  3. // LoopFollow
  4. //
  5. // Created by Jonas Björkert on 2024-08-25.
  6. // Updated on 2024-09-16.
  7. // Copyright © 2024 Jon Fawcett. All rights reserved.
  8. //
  9. import SwiftUI
  10. import HealthKit
  11. struct RemoteSettingsView: View {
  12. @ObservedObject var viewModel: RemoteSettingsViewModel
  13. @Environment(\.presentationMode) var presentationMode
  14. @FocusState private var focusedField: Field?
  15. @State private var showAlert: Bool = false
  16. @State private var alertType: AlertType? = nil
  17. @State private var alertMessage: String? = nil
  18. enum Field: Hashable {
  19. case user
  20. case deviceToken
  21. case sharedSecret
  22. case apnsKey
  23. case teamId
  24. case keyId
  25. case bundleId
  26. case maxBolus
  27. }
  28. enum AlertType {
  29. case validation
  30. }
  31. var body: some View {
  32. NavigationView {
  33. Form {
  34. // Remote Type Section
  35. Section {
  36. Picker("Remote Type", selection: $viewModel.remoteType) {
  37. Text("None").tag(RemoteType.none)
  38. Text("Nightscout").tag(RemoteType.nightscout)
  39. Text("Trio Remote Control").tag(RemoteType.trc)
  40. }
  41. .pickerStyle(MenuPickerStyle())
  42. }
  43. // User Information Section
  44. if viewModel.remoteType != .none {
  45. Section(header: Text("User Information")) {
  46. HStack {
  47. Text("User")
  48. TextField("Enter User", text: $viewModel.user)
  49. .autocapitalization(.none)
  50. .disableAutocorrection(true)
  51. .focused($focusedField, equals: .user)
  52. .multilineTextAlignment(.trailing)
  53. }
  54. }
  55. }
  56. // Trio Remote Control Settings Section
  57. if viewModel.remoteType == .trc {
  58. Section(header: Text("Trio Remote Control Settings")) {
  59. HStack {
  60. Text("Shared Secret")
  61. TextField("Enter Shared Secret", text: $viewModel.sharedSecret)
  62. .autocapitalization(.none)
  63. .disableAutocorrection(true)
  64. .focused($focusedField, equals: .sharedSecret)
  65. .multilineTextAlignment(.trailing)
  66. }
  67. HStack {
  68. Text("APNS Key ID")
  69. TextField("Enter APNS Key ID", text: $viewModel.keyId)
  70. .autocapitalization(.none)
  71. .disableAutocorrection(true)
  72. .focused($focusedField, equals: .keyId)
  73. .multilineTextAlignment(.trailing)
  74. }
  75. VStack(alignment: .leading) {
  76. Text("APNS Key")
  77. TextEditor(text: $viewModel.apnsKey)
  78. .frame(height: 100)
  79. .autocapitalization(.none)
  80. .disableAutocorrection(true)
  81. .overlay(
  82. RoundedRectangle(cornerRadius: 8)
  83. .stroke(Color.gray.opacity(0.5), lineWidth: 1)
  84. )
  85. .focused($focusedField, equals: .apnsKey)
  86. }
  87. }
  88. // Guardrails Section
  89. Section(header: Text("Guardrails")) {
  90. HStack {
  91. Text("Max Bolus")
  92. Spacer()
  93. TextFieldWithToolBar(
  94. quantity: $viewModel.maxBolus,
  95. maxLength: 4,
  96. unit: HKUnit.internationalUnit(),
  97. allowDecimalSeparator: true,
  98. minValue: HKQuantity(unit: .internationalUnit(), doubleValue: 0.0),
  99. maxValue: HKQuantity(unit: .internationalUnit(), doubleValue: 10.0),
  100. onValidationError: { message in
  101. handleValidationError(message)
  102. }
  103. )
  104. .frame(width: 100)
  105. Text("U")
  106. .foregroundColor(.secondary)
  107. }
  108. HStack {
  109. Text("Max Carbs")
  110. Spacer()
  111. TextFieldWithToolBar(
  112. quantity: $viewModel.maxCarbs,
  113. maxLength: 4,
  114. unit: HKUnit.gram(),
  115. allowDecimalSeparator: true,
  116. minValue: HKQuantity(unit: .gram(), doubleValue: 0),
  117. maxValue: HKQuantity(unit: .gram(), doubleValue: 100),
  118. onValidationError: { message in
  119. handleValidationError(message)
  120. }
  121. )
  122. .frame(width: 100)
  123. Text("g")
  124. .foregroundColor(.secondary)
  125. }
  126. HStack {
  127. Text("Max Protein")
  128. Spacer()
  129. TextFieldWithToolBar(
  130. quantity: $viewModel.maxProtein,
  131. maxLength: 4,
  132. unit: HKUnit.gram(),
  133. allowDecimalSeparator: true,
  134. minValue: HKQuantity(unit: .gram(), doubleValue: 0),
  135. maxValue: HKQuantity(unit: .gram(), doubleValue: 100),
  136. onValidationError: { message in
  137. handleValidationError(message)
  138. }
  139. )
  140. .frame(width: 100)
  141. Text("g")
  142. .foregroundColor(.secondary)
  143. }
  144. HStack {
  145. Text("Max Fat")
  146. Spacer()
  147. TextFieldWithToolBar(
  148. quantity: $viewModel.maxFat,
  149. maxLength: 4,
  150. unit: HKUnit.gram(),
  151. allowDecimalSeparator: true,
  152. minValue: HKQuantity(unit: .gram(), doubleValue: 0),
  153. maxValue: HKQuantity(unit: .gram(), doubleValue: 100),
  154. onValidationError: { message in
  155. handleValidationError(message)
  156. }
  157. )
  158. .frame(width: 100)
  159. Text("g")
  160. .foregroundColor(.secondary)
  161. }
  162. }
  163. }
  164. }
  165. .navigationBarTitle("Remote Settings", displayMode: .inline)
  166. .toolbar {
  167. ToolbarItem(placement: .navigationBarTrailing) {
  168. Button("Done") {
  169. presentationMode.wrappedValue.dismiss()
  170. }
  171. }
  172. }
  173. .onTapGesture {
  174. focusedField = nil
  175. }
  176. .alert(isPresented: $showAlert) {
  177. switch alertType {
  178. case .validation:
  179. return Alert(
  180. title: Text("Validation Error"),
  181. message: Text(alertMessage ?? "Invalid input."),
  182. dismissButton: .default(Text("OK"))
  183. )
  184. case .none:
  185. return Alert(title: Text("Unknown Alert"))
  186. }
  187. }
  188. }
  189. }
  190. private func handleValidationError(_ message: String) {
  191. alertMessage = message
  192. alertType = .validation
  193. showAlert = true
  194. }
  195. }