SettingsRootView.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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. @StateObject private var viewModel = SettingsRootViewModel()
  12. @State private var searchText: String = ""
  13. @Environment(\.colorScheme) var colorScheme
  14. @EnvironmentObject var appIcons: Icons
  15. private var color: LinearGradient {
  16. colorScheme == .dark ? LinearGradient(
  17. gradient: Gradient(colors: [
  18. Color.bgDarkBlue,
  19. Color.bgDarkerDarkBlue
  20. ]),
  21. startPoint: .top,
  22. endPoint: .bottom
  23. )
  24. :
  25. LinearGradient(
  26. gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
  27. startPoint: .top,
  28. endPoint: .bottom
  29. )
  30. }
  31. var body: some View {
  32. Form {
  33. // Section {
  34. // Toggle("Closed loop", isOn: $state.closedLoop)
  35. // }
  36. // header: {
  37. // Text(viewModel.headerText).textCase(nil)
  38. // }.listRowBackground(Color.chart)
  39. Section {
  40. let buildDetails = BuildDetails.default
  41. let versionNumber = Bundle.main.releaseVersionNumber ?? "Unknown"
  42. let buildNumber = Bundle.main.buildVersionNumber ?? "Unknown"
  43. let branch = buildDetails.branchAndSha
  44. Group {
  45. HStack {
  46. Image(uiImage: UIImage(named: appIcons.appIcon.rawValue) ?? UIImage())
  47. .resizable()
  48. .aspectRatio(contentMode: .fit)
  49. .frame(width: 50, height: 50)
  50. .padding(.trailing, 10)
  51. VStack(alignment: .leading) {
  52. Text("Trio v\(versionNumber) (\(buildNumber))")
  53. .font(.headline)
  54. Text("Branch: \(branch)")
  55. .font(.footnote)
  56. .foregroundColor(.secondary)
  57. if let expirationDate = buildDetails.calculateExpirationDate() {
  58. let formattedDate = DateFormatter.localizedString(
  59. from: expirationDate,
  60. dateStyle: .medium,
  61. timeStyle: .none
  62. )
  63. Text("\(buildDetails.expirationHeaderString): \(formattedDate)")
  64. .font(.footnote)
  65. .foregroundColor(.secondary)
  66. }
  67. }
  68. }
  69. Text("Statistics").navigationLink(to: .statistics, from: self)
  70. }
  71. }.listRowBackground(Color.chart)
  72. Section {
  73. VStack {
  74. Toggle("Closed Loop", isOn: $state.closedLoop)
  75. Spacer()
  76. (
  77. Text("Running Trio in")
  78. +
  79. Text(" closed loop mode ").bold()
  80. +
  81. Text("requires an active CGM session sensor session and a connected pump.")
  82. +
  83. Text("This enables automated insulin delivery.").bold()
  84. )
  85. .foregroundColor(.secondary)
  86. .font(.footnote)
  87. }.padding(.vertical)
  88. }.listRowBackground(Color.chart)
  89. Section {
  90. Text("Pump").navigationLink(to: .pumpConfig, from: self)
  91. Text("CGM").navigationLink(to: .cgm, from: self)
  92. Text("Watch").navigationLink(to: .watch, from: self)
  93. // TODO: combine pump + pump settings?!
  94. Text("Pump Settings").navigationLink(to: .pumpSettingsEditor, from: self)
  95. } header: { Text("Devices") }.listRowBackground(Color.chart)
  96. Section {
  97. Text("Basal Profile").navigationLink(to: .basalProfileEditor, from: self)
  98. Text("Insulin Sensitivities").navigationLink(to: .isfEditor, from: self)
  99. Text("Carb Ratios").navigationLink(to: .crEditor, from: self)
  100. Text("Target Glucose").navigationLink(to: .targetsEditor, from: self)
  101. Text("Autotune").navigationLink(to: .autotuneConfig, from: self)
  102. } header: { Text("Profiles") }.listRowBackground(Color.chart)
  103. Section {
  104. Text("Preferences").navigationLink(to: .preferencesEditor, from: self)
  105. Text("Dynamic Settings").navigationLink(to: .dynamicISF, from: self)
  106. } header: { Text("Algorithm") }.listRowBackground(Color.chart)
  107. Section {
  108. Text("UI/UX").navigationLink(to: .statisticsConfig, from: self)
  109. Text("Meal Settings").navigationLink(to: .fpuConfig, from: self)
  110. Text("Bolus Calculator").navigationLink(to: .bolusCalculatorConfig, from: self)
  111. Text("App Icons").navigationLink(to: .iconConfig, from: self)
  112. } header: { Text("App Configuration") }.listRowBackground(Color.chart)
  113. Section {
  114. Text("App Notifications").navigationLink(to: .notificationsConfig, from: self)
  115. Text("Live Activity")
  116. } header: { Text("Notifications") }.listRowBackground(Color.chart)
  117. Section {
  118. Text("Nightscout").navigationLink(to: .nighscoutConfig, from: self)
  119. NavigationLink(destination: TidepoolStartView(state: state)) {
  120. Text("Tidepool")
  121. }
  122. if HKHealthStore.isHealthDataAvailable() {
  123. Text("Apple Health").navigationLink(to: .healthkit, from: self)
  124. }
  125. Text("Shortcuts", tableName: "ShortcutsDetail").navigationLink(to: .shortcutsConfig, from: self)
  126. } header: { Text("Services") }.listRowBackground(Color.chart)
  127. Section {
  128. HStack {
  129. Text("Share Logs")
  130. .onTapGesture {
  131. showShareSheet.toggle()
  132. }
  133. Spacer()
  134. Image(systemName: "chevron.right").foregroundColor(.secondary)
  135. }
  136. HStack {
  137. Text("Submit Ticket on GitHub")
  138. .onTapGesture {
  139. if let url = URL(string: "https://github.com/nightscout/Trio/issues/new/choose") {
  140. UIApplication.shared.open(url)
  141. }
  142. }
  143. Spacer()
  144. Image(systemName: "chevron.right").foregroundColor(.secondary)
  145. }
  146. HStack {
  147. Text("Trio Discord")
  148. .onTapGesture {
  149. if let url = URL(string: "https://discord.gg/FnwFEFUwXE") {
  150. UIApplication.shared.open(url)
  151. }
  152. }
  153. Spacer()
  154. Image(systemName: "chevron.right").foregroundColor(.secondary)
  155. }
  156. HStack {
  157. Text("Trio Facebook")
  158. .onTapGesture {
  159. if let url = URL(string: "https://m.facebook.com/groups/1351938092206709/") {
  160. UIApplication.shared.open(url)
  161. }
  162. }
  163. Spacer()
  164. Image(systemName: "chevron.right").foregroundColor(.secondary)
  165. }
  166. } header: { Text("Support") }.listRowBackground(Color.chart)
  167. Section {
  168. Toggle("Debug options", isOn: $state.debugOptions)
  169. if state.debugOptions {
  170. Group {
  171. HStack {
  172. Text("NS Upload Profile and Settings")
  173. Button("Upload") { state.uploadProfileAndSettings(true) }
  174. .frame(maxWidth: .infinity, alignment: .trailing)
  175. .buttonStyle(.borderedProminent)
  176. }
  177. // 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
  178. // Leaving it in here, as it may be a handy functionality for further testing or developers.
  179. // See https://github.com/nightscout/Trio/pull/277 for more information
  180. //
  181. // HStack {
  182. // Text("Delete Stored Pump State Binary Files")
  183. // Button("Delete") { state.resetLoopDocuments() }
  184. // .frame(maxWidth: .infinity, alignment: .trailing)
  185. // .buttonStyle(.borderedProminent)
  186. // }
  187. }
  188. Group {
  189. Text("Preferences")
  190. .navigationLink(to: .configEditor(file: OpenAPS.Settings.preferences), from: self)
  191. Text("Pump Settings")
  192. .navigationLink(to: .configEditor(file: OpenAPS.Settings.settings), from: self)
  193. Text("Autosense")
  194. .navigationLink(to: .configEditor(file: OpenAPS.Settings.autosense), from: self)
  195. // Text("Pump History")
  196. // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.pumpHistory), from: self)
  197. Text("Basal profile")
  198. .navigationLink(to: .configEditor(file: OpenAPS.Settings.basalProfile), from: self)
  199. Text("Targets ranges")
  200. .navigationLink(to: .configEditor(file: OpenAPS.Settings.bgTargets), from: self)
  201. Text("Temp targets")
  202. .navigationLink(to: .configEditor(file: OpenAPS.Settings.tempTargets), from: self)
  203. }
  204. Group {
  205. Text("Pump profile")
  206. .navigationLink(to: .configEditor(file: OpenAPS.Settings.pumpProfile), from: self)
  207. Text("Profile")
  208. .navigationLink(to: .configEditor(file: OpenAPS.Settings.profile), from: self)
  209. // Text("Carbs")
  210. // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.carbHistory), from: self)
  211. // Text("Announcements")
  212. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcements), from: self)
  213. // Text("Enacted announcements")
  214. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcementsEnacted), from: self)
  215. Text("Autotune")
  216. .navigationLink(to: .configEditor(file: OpenAPS.Settings.autotune), from: self)
  217. }
  218. Group {
  219. Text("Target presets")
  220. .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.tempTargetsPresets), from: self)
  221. Text("Calibrations")
  222. .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.calibrations), from: self)
  223. Text("Middleware")
  224. .navigationLink(to: .configEditor(file: OpenAPS.Middleware.determineBasal), from: self)
  225. // Text("Statistics")
  226. // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.statistics), from: self)
  227. Text("Edit settings json")
  228. .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
  229. }
  230. }
  231. } header: { Text("Developer") }.listRowBackground(Color.chart)
  232. }.scrollContentBackground(.hidden).background(color)
  233. .sheet(isPresented: $showShareSheet) {
  234. ShareSheet(activityItems: state.logItems())
  235. }
  236. .onAppear(perform: configureView)
  237. .navigationTitle("Settings")
  238. .navigationBarTitleDisplayMode(.automatic)
  239. .toolbar {
  240. ToolbarItem(placement: .topBarTrailing) {
  241. Button(
  242. action: {
  243. if let url = URL(string: "https://triodocs.org/") {
  244. UIApplication.shared.open(url)
  245. }
  246. },
  247. label: {
  248. HStack {
  249. Text("Trio Docs")
  250. Image(systemName: "questionmark.circle")
  251. }
  252. }
  253. )
  254. }
  255. }
  256. // .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
  257. .onDisappear(perform: { state.uploadProfileAndSettings(false) })
  258. .screenNavigation(self)
  259. }
  260. }
  261. }