NightscoutConfigRootView.swift 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import CoreData
  2. import SwiftUI
  3. import Swinject
  4. extension NightscoutConfig {
  5. struct RootView: BaseView {
  6. let resolver: Resolver
  7. @StateObject var state = StateModel()
  8. @State var importAlert: Alert?
  9. @State var isImportAlertPresented = false
  10. @State var importedHasRun = false
  11. @Environment(\.colorScheme) var colorScheme
  12. var color: LinearGradient {
  13. colorScheme == .dark ? LinearGradient(
  14. gradient: Gradient(colors: [
  15. Color.bgDarkBlue,
  16. Color.bgDarkerDarkBlue
  17. ]),
  18. startPoint: .top,
  19. endPoint: .bottom
  20. )
  21. :
  22. LinearGradient(
  23. gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
  24. startPoint: .top,
  25. endPoint: .bottom
  26. )
  27. }
  28. @FetchRequest(
  29. entity: ImportError.entity(),
  30. sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], predicate: NSPredicate(
  31. format: "date > %@", Date().addingTimeInterval(-1.minutes.timeInterval) as NSDate
  32. )
  33. ) var fetchedErrors: FetchedResults<ImportError>
  34. private var portFormater: NumberFormatter {
  35. let formatter = NumberFormatter()
  36. formatter.allowsFloats = false
  37. formatter.usesGroupingSeparator = false
  38. return formatter
  39. }
  40. var body: some View {
  41. Form {
  42. Section {
  43. TextField("URL", text: $state.url)
  44. .disableAutocorrection(true)
  45. .textContentType(.URL)
  46. .autocapitalization(.none)
  47. .keyboardType(.URL)
  48. SecureField("API secret", text: $state.secret)
  49. .disableAutocorrection(true)
  50. .autocapitalization(.none)
  51. .textContentType(.password)
  52. .keyboardType(.asciiCapable)
  53. if !state.message.isEmpty {
  54. Text(state.message)
  55. }
  56. if state.connecting {
  57. HStack {
  58. Text("Connecting...")
  59. Spacer()
  60. ProgressView()
  61. }
  62. }
  63. }
  64. Section {
  65. Button("Connect") { state.connect() }
  66. .disabled(state.url.isEmpty || state.connecting)
  67. Button("Delete") { state.delete() }.foregroundColor(.red).disabled(state.connecting)
  68. }
  69. Section {
  70. Toggle("Upload", isOn: $state.isUploadEnabled)
  71. if state.isUploadEnabled {
  72. Toggle("Statistics", isOn: $state.uploadStats)
  73. HStack(alignment: .top) {
  74. Image(systemName: "pencil.circle.fill")
  75. VStack {
  76. Text(
  77. "This enables uploading of statistics.json to Nightscout, which can be used by the Community Statistics and Demographics Project.\n\nParticipation in Community Statistics is opt-in, and requires separate registration at:\n"
  78. )
  79. .font(.caption)
  80. Text(
  81. "https://iaps-stats.hub.org"
  82. )
  83. .font(.caption)
  84. .multilineTextAlignment(.center)
  85. }
  86. }
  87. .foregroundColor(Color.secondary)
  88. Toggle("Glucose", isOn: $state.uploadGlucose)
  89. }
  90. } header: {
  91. Text("Allow Uploads")
  92. }
  93. Section {
  94. Button("Import settings from Nightscout") {
  95. importAlert = Alert(
  96. title: Text("Import settings?"),
  97. message: Text(
  98. "\n" +
  99. NSLocalizedString(
  100. "This will replace some or all of your current pump settings. Are you sure you want to import profile settings from Nightscout?",
  101. comment: "Profile Import Alert"
  102. ) +
  103. "\n"
  104. ),
  105. primaryButton: .destructive(
  106. Text("Yes, Import"),
  107. action: {
  108. state.importSettings()
  109. importedHasRun = true
  110. }
  111. ),
  112. secondaryButton: .cancel()
  113. )
  114. isImportAlertPresented.toggle()
  115. }.disabled(state.url.isEmpty || state.connecting)
  116. } header: { Text("Import from Nightscout") }
  117. .alert(isPresented: $importedHasRun) {
  118. Alert(
  119. title: Text((fetchedErrors.first?.error ?? "").count < 4 ? "Settings imported" : "Import Error"),
  120. message: Text(
  121. (fetchedErrors.first?.error ?? "").count < 4 ?
  122. NSLocalizedString(
  123. "\nNow please verify all of your new settings thoroughly:\n\n* Basal Settings\n * Carb Ratios\n * Glucose Targets\n * Insulin Sensitivities\n * DIA\n\n in iAPS Settings > Configuration.\n\nBad or invalid profile settings could have disatrous effects.",
  124. comment: "Imported Profiles Alert"
  125. ) :
  126. NSLocalizedString(fetchedErrors.first?.error ?? "", comment: "Import Error")
  127. ),
  128. primaryButton: .destructive(
  129. Text("OK")
  130. ),
  131. secondaryButton: .cancel()
  132. )
  133. }
  134. Section {
  135. Toggle("Use local glucose server", isOn: $state.useLocalSource)
  136. HStack {
  137. Text("Port")
  138. TextFieldWithToolBar(
  139. text: $state.localPort,
  140. placeholder: "",
  141. keyboardType: .numberPad,
  142. numberFormatter: portFormater,
  143. allowDecimalSeparator: false
  144. )
  145. }
  146. } header: { Text("Local glucose source") }
  147. Section {
  148. Button("Backfill glucose") {
  149. Task {
  150. await state.backfillGlucose()
  151. }
  152. }
  153. .disabled(state.url.isEmpty || state.connecting || state.backfilling)
  154. }
  155. Section {
  156. Toggle("Remote control", isOn: $state.allowAnnouncements)
  157. } header: { Text("Allow Remote control of iAPS") }
  158. }
  159. .scrollContentBackground(.hidden).background(color)
  160. .onAppear(perform: configureView)
  161. .navigationBarTitle("Nightscout Config")
  162. .navigationBarTitleDisplayMode(.automatic)
  163. .alert(isPresented: $isImportAlertPresented) {
  164. importAlert!
  165. }
  166. }
  167. }
  168. }