SettingsRootView.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import HealthKit
  2. import LoopKit
  3. import LoopKitUI
  4. import SwiftUI
  5. import Swinject
  6. extension Settings {
  7. struct RootView: BaseView {
  8. let resolver: Resolver
  9. @StateObject var state = StateModel()
  10. @State private var showShareSheet = false
  11. @State private var searchText: String = ""
  12. @Environment(\.colorScheme) var colorScheme
  13. @EnvironmentObject var appIcons: Icons
  14. private var color: LinearGradient {
  15. colorScheme == .dark ? LinearGradient(
  16. gradient: Gradient(colors: [
  17. Color.bgDarkBlue,
  18. Color.bgDarkerDarkBlue
  19. ]),
  20. startPoint: .top,
  21. endPoint: .bottom
  22. )
  23. :
  24. LinearGradient(
  25. gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
  26. startPoint: .top,
  27. endPoint: .bottom
  28. )
  29. }
  30. var body: some View {
  31. Form {
  32. let buildDetails = BuildDetails.default
  33. Section(
  34. header: Text("BRANCH: \(buildDetails.branchAndSha)").textCase(nil),
  35. content: {
  36. let versionNumber = Bundle.main.releaseVersionNumber ?? "Unknown"
  37. let buildNumber = Bundle.main.buildVersionNumber ?? "Unknown"
  38. Group {
  39. HStack {
  40. Image(uiImage: UIImage(named: appIcons.appIcon.rawValue) ?? UIImage())
  41. .resizable()
  42. .aspectRatio(contentMode: .fit)
  43. .frame(width: 50, height: 50)
  44. .padding(.trailing, 10)
  45. VStack(alignment: .leading) {
  46. Text("Trio v\(versionNumber) (\(buildNumber))")
  47. .font(.headline)
  48. if let expirationDate = buildDetails.calculateExpirationDate() {
  49. let formattedDate = DateFormatter.localizedString(
  50. from: expirationDate,
  51. dateStyle: .medium,
  52. timeStyle: .none
  53. )
  54. Text("\(buildDetails.expirationHeaderString): \(formattedDate)")
  55. .font(.footnote)
  56. .foregroundColor(.secondary)
  57. } else {
  58. Text("Simulator Build has no expiry")
  59. .font(.footnote)
  60. .foregroundColor(.secondary)
  61. }
  62. }
  63. }
  64. Text("Statistics").navigationLink(to: .statistics, from: self)
  65. }
  66. }
  67. ).listRowBackground(Color.chart)
  68. Section(
  69. header: Text("Automated Insulin Delivery"),
  70. content: {
  71. VStack {
  72. Toggle("Closed Loop", isOn: $state.closedLoop)
  73. Spacer()
  74. (
  75. Text("Running Trio in")
  76. +
  77. Text(" closed loop mode ").bold()
  78. +
  79. Text("requires an active CGM session sensor session and a connected pump.")
  80. +
  81. Text("This enables automated insulin delivery.").bold()
  82. )
  83. .foregroundColor(.secondary)
  84. .font(.footnote)
  85. }.padding(.vertical)
  86. }
  87. ).listRowBackground(Color.chart)
  88. Section(
  89. header: Text("Trio Configuration"),
  90. content: {
  91. Text("Devices").navigationLink(to: .devices, from: self)
  92. Text("Therapy Settings").navigationLink(to: .therapySettings, from: self)
  93. Text("Feature Settings").navigationLink(to: .featureSettings, from: self)
  94. Text("Notifications").navigationLink(to: .notificationSettings, from: self)
  95. Text("Services").navigationLink(to: .serviceSettings, from: self)
  96. }
  97. ).listRowBackground(Color.chart)
  98. Section(
  99. header: Text("Support & Community"),
  100. content: {
  101. HStack {
  102. Text("Share Logs")
  103. .onTapGesture {
  104. showShareSheet.toggle()
  105. }
  106. Spacer()
  107. Image(systemName: "chevron.right").foregroundColor(.secondary)
  108. }
  109. HStack {
  110. Text("Submit Ticket on GitHub")
  111. .onTapGesture {
  112. if let url = URL(string: "https://github.com/nightscout/Trio/issues/new/choose") {
  113. UIApplication.shared.open(url)
  114. }
  115. }
  116. Spacer()
  117. Image(systemName: "chevron.right").foregroundColor(.secondary)
  118. }
  119. HStack {
  120. Text("Trio Discord")
  121. .onTapGesture {
  122. if let url = URL(string: "https://discord.gg/FnwFEFUwXE") {
  123. UIApplication.shared.open(url)
  124. }
  125. }
  126. Spacer()
  127. Image(systemName: "chevron.right").foregroundColor(.secondary)
  128. }
  129. HStack {
  130. Text("Trio Facebook")
  131. .onTapGesture {
  132. if let url = URL(string: "https://m.facebook.com/groups/1351938092206709/") {
  133. UIApplication.shared.open(url)
  134. }
  135. }
  136. Spacer()
  137. Image(systemName: "chevron.right").foregroundColor(.secondary)
  138. }
  139. }
  140. ).listRowBackground(Color.chart)
  141. // TODO: remove this more or less entirely; add build-time flag to enable Middleware; add settings export feature
  142. // Section {
  143. // Toggle("Developer Options", isOn: $state.debugOptions)
  144. // if state.debugOptions {
  145. // Group {
  146. // HStack {
  147. // Text("NS Upload Profile and Settings")
  148. // Button("Upload") { state.uploadProfileAndSettings(true) }
  149. // .frame(maxWidth: .infinity, alignment: .trailing)
  150. // .buttonStyle(.borderedProminent)
  151. // }
  152. // // Commenting this out for now, as not needed and possibly dangerous for users to be able to nuke their pump pairing informations via the debug menu
  153. // // Leaving it in here, as it may be a handy functionality for further testing or developers.
  154. // // See https://github.com/nightscout/Trio/pull/277 for more information
  155. // //
  156. // // HStack {
  157. // // Text("Delete Stored Pump State Binary Files")
  158. // // Button("Delete") { state.resetLoopDocuments() }
  159. // // .frame(maxWidth: .infinity, alignment: .trailing)
  160. // // .buttonStyle(.borderedProminent)
  161. // // }
  162. // }
  163. // Group {
  164. // Text("Preferences")
  165. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.preferences), from: self)
  166. // Text("Pump Settings")
  167. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.settings), from: self)
  168. // Text("Autosense")
  169. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.autosense), from: self)
  170. // // Text("Pump History")
  171. // // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.pumpHistory), from: self)
  172. // Text("Basal profile")
  173. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.basalProfile), from: self)
  174. // Text("Targets ranges")
  175. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.bgTargets), from: self)
  176. // Text("Temp targets")
  177. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.tempTargets), from: self)
  178. // }
  179. //
  180. // Group {
  181. // Text("Pump profile")
  182. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.pumpProfile), from: self)
  183. // Text("Profile")
  184. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.profile), from: self)
  185. // // Text("Carbs")
  186. // // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.carbHistory), from: self)
  187. // // Text("Announcements")
  188. // // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcements), from: self)
  189. // // Text("Enacted announcements")
  190. // // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcementsEnacted), from: self)
  191. // Text("Autotune")
  192. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.autotune), from: self)
  193. // }
  194. //
  195. // Group {
  196. // Text("Target presets")
  197. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.tempTargetsPresets), from: self)
  198. // Text("Calibrations")
  199. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.calibrations), from: self)
  200. // Text("Middleware")
  201. // .navigationLink(to: .configEditor(file: OpenAPS.Middleware.determineBasal), from: self)
  202. // // Text("Statistics")
  203. // // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.statistics), from: self)
  204. // Text("Edit settings json")
  205. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
  206. // }
  207. // }
  208. // }.listRowBackground(Color.chart)
  209. }.scrollContentBackground(.hidden).background(color)
  210. .sheet(isPresented: $showShareSheet) {
  211. ShareSheet(activityItems: state.logItems())
  212. }
  213. .onAppear(perform: configureView)
  214. .navigationTitle("Settings")
  215. .navigationBarTitleDisplayMode(.automatic)
  216. .toolbar {
  217. ToolbarItem(placement: .topBarTrailing) {
  218. Button(
  219. action: {
  220. if let url = URL(string: "https://triodocs.org/") {
  221. UIApplication.shared.open(url)
  222. }
  223. },
  224. label: {
  225. HStack {
  226. Text("Trio Docs")
  227. Image(systemName: "questionmark.circle")
  228. }
  229. }
  230. )
  231. }
  232. }
  233. // TODO: check how to implement intuitive search
  234. // .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic))
  235. .onDisappear(perform: { state.uploadProfileAndSettings(false) })
  236. .screenNavigation(self)
  237. }
  238. }
  239. }