SettingsRootView.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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 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").navigationLink(to: .therapySettings, from: self)
  93. Text("Algorithm").navigationLink(to: .algorithmSettings, from: self)
  94. Text("Features").navigationLink(to: .featureSettings, from: self)
  95. Text("Notifications").navigationLink(to: .notificationSettings, from: self)
  96. Text("Services").navigationLink(to: .serviceSettings, from: self)
  97. }
  98. ).listRowBackground(Color.chart)
  99. Section(
  100. header: Text("Support & Community"),
  101. content: {
  102. HStack {
  103. Text("Share Logs")
  104. .onTapGesture {
  105. showShareSheet.toggle()
  106. }
  107. Spacer()
  108. Image(systemName: "chevron.right").foregroundColor(.secondary)
  109. }
  110. HStack {
  111. Text("Submit Ticket on GitHub")
  112. .onTapGesture {
  113. if let url = URL(string: "https://github.com/nightscout/Trio/issues/new/choose") {
  114. UIApplication.shared.open(url)
  115. }
  116. }
  117. Spacer()
  118. Image(systemName: "chevron.right").foregroundColor(.secondary)
  119. }
  120. HStack {
  121. Text("Trio Discord")
  122. .onTapGesture {
  123. if let url = URL(string: "https://discord.gg/FnwFEFUwXE") {
  124. UIApplication.shared.open(url)
  125. }
  126. }
  127. Spacer()
  128. Image(systemName: "chevron.right").foregroundColor(.secondary)
  129. }
  130. HStack {
  131. Text("Trio Facebook")
  132. .onTapGesture {
  133. if let url = URL(string: "https://m.facebook.com/groups/1351938092206709/") {
  134. UIApplication.shared.open(url)
  135. }
  136. }
  137. Spacer()
  138. Image(systemName: "chevron.right").foregroundColor(.secondary)
  139. }
  140. HStack {
  141. Text("Trio Website")
  142. .onTapGesture {
  143. if let url = URL(string: "https://diy-trio.org/") {
  144. UIApplication.shared.open(url)
  145. }
  146. }
  147. Spacer()
  148. Image(systemName: "chevron.right").foregroundColor(.secondary)
  149. }
  150. }
  151. ).listRowBackground(Color.chart)
  152. // TODO: remove this more or less entirely; add build-time flag to enable Middleware; add settings export feature
  153. // Section {
  154. // Toggle("Developer Options", isOn: $state.debugOptions)
  155. // if state.debugOptions {
  156. // Group {
  157. // HStack {
  158. // Text("NS Upload Profile and Settings")
  159. // Button("Upload") { state.uploadProfileAndSettings(true) }
  160. // .frame(maxWidth: .infinity, alignment: .trailing)
  161. // .buttonStyle(.borderedProminent)
  162. // }
  163. // // 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
  164. // // Leaving it in here, as it may be a handy functionality for further testing or developers.
  165. // // See https://github.com/nightscout/Trio/pull/277 for more information
  166. // //
  167. // // HStack {
  168. // // Text("Delete Stored Pump State Binary Files")
  169. // // Button("Delete") { state.resetLoopDocuments() }
  170. // // .frame(maxWidth: .infinity, alignment: .trailing)
  171. // // .buttonStyle(.borderedProminent)
  172. // // }
  173. // }
  174. // Group {
  175. // Text("Preferences")
  176. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.preferences), from: self)
  177. // Text("Pump Settings")
  178. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.settings), from: self)
  179. // Text("Autosense")
  180. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.autosense), from: self)
  181. // // Text("Pump History")
  182. // // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.pumpHistory), from: self)
  183. // Text("Basal profile")
  184. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.basalProfile), from: self)
  185. // Text("Targets ranges")
  186. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.bgTargets), from: self)
  187. // Text("Temp targets")
  188. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.tempTargets), from: self)
  189. // }
  190. //
  191. // Group {
  192. // Text("Pump profile")
  193. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.pumpProfile), from: self)
  194. // Text("Profile")
  195. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.profile), from: self)
  196. // // Text("Carbs")
  197. // // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.carbHistory), from: self)
  198. // // Text("Announcements")
  199. // // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcements), from: self)
  200. // // Text("Enacted announcements")
  201. // // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcementsEnacted), from: self)
  202. // Text("Autotune")
  203. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.autotune), from: self)
  204. // }
  205. //
  206. // Group {
  207. // Text("Target presets")
  208. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.tempTargetsPresets), from: self)
  209. // Text("Calibrations")
  210. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.calibrations), from: self)
  211. // Text("Middleware")
  212. // .navigationLink(to: .configEditor(file: OpenAPS.Middleware.determineBasal), from: self)
  213. // // Text("Statistics")
  214. // // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.statistics), from: self)
  215. // Text("Edit settings json")
  216. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
  217. // }
  218. // }
  219. // }.listRowBackground(Color.chart)
  220. }.scrollContentBackground(.hidden).background(color)
  221. .sheet(isPresented: $showShareSheet) {
  222. ShareSheet(activityItems: state.logItems())
  223. }
  224. .onAppear(perform: configureView)
  225. .navigationTitle("Settings")
  226. .navigationBarTitleDisplayMode(.automatic)
  227. .toolbar {
  228. ToolbarItem(placement: .topBarTrailing) {
  229. Button(
  230. action: {
  231. if let url = URL(string: "https://triodocs.org/") {
  232. UIApplication.shared.open(url)
  233. }
  234. },
  235. label: {
  236. HStack {
  237. Text("Trio Docs")
  238. Image(systemName: "questionmark.circle")
  239. }
  240. }
  241. )
  242. }
  243. }
  244. // TODO: check how to implement intuitive search
  245. // .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic))
  246. .onDisappear(perform: { state.uploadProfileAndSettings(false) })
  247. .screenNavigation(self)
  248. }
  249. }
  250. }