SettingsRootView.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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. private var filteredItems: [FilteredSettingItem] {
  31. SettingItems.filteredItems(searchText: searchText)
  32. }
  33. var body: some View {
  34. Form {
  35. if searchText.isEmpty {
  36. let buildDetails = BuildDetails.default
  37. Section(
  38. header: Text("BRANCH: \(buildDetails.branchAndSha)").textCase(nil),
  39. content: {
  40. let versionNumber = Bundle.main.releaseVersionNumber ?? "Unknown"
  41. let buildNumber = Bundle.main.buildVersionNumber ?? "Unknown"
  42. Group {
  43. HStack {
  44. Image(uiImage: UIImage(named: appIcons.appIcon.rawValue) ?? UIImage())
  45. .resizable()
  46. .aspectRatio(contentMode: .fit)
  47. .frame(width: 50, height: 50)
  48. .padding(.trailing, 10)
  49. VStack(alignment: .leading) {
  50. Text("Trio v\(versionNumber) (\(buildNumber))")
  51. .font(.headline)
  52. if let expirationDate = buildDetails.calculateExpirationDate() {
  53. let formattedDate = DateFormatter.localizedString(
  54. from: expirationDate,
  55. dateStyle: .medium,
  56. timeStyle: .none
  57. )
  58. Text("\(buildDetails.expirationHeaderString): \(formattedDate)")
  59. .font(.footnote)
  60. .foregroundColor(.secondary)
  61. } else {
  62. Text("Simulator Build has no expiry")
  63. .font(.footnote)
  64. .foregroundColor(.secondary)
  65. }
  66. }
  67. }
  68. Text("Statistics").navigationLink(to: .statistics, from: self)
  69. }
  70. }
  71. ).listRowBackground(Color.chart)
  72. Section(
  73. header: Text("Automated Insulin Delivery"),
  74. content: {
  75. VStack {
  76. Toggle("Closed Loop", isOn: $state.closedLoop)
  77. Spacer()
  78. (
  79. Text("Running Trio in")
  80. +
  81. Text(" closed loop mode ").bold()
  82. +
  83. Text("requires an active CGM sensor session and a connected pump.")
  84. +
  85. Text("This enables automated insulin delivery.").bold()
  86. )
  87. .foregroundColor(.secondary)
  88. .font(.footnote)
  89. }.padding(.vertical)
  90. }
  91. ).listRowBackground(Color.chart)
  92. Section(
  93. header: Text("Trio Configuration"),
  94. content: {
  95. ForEach(SettingItems.trioConfig) { item in
  96. Text(item.title).navigationLink(to: item.view, from: self)
  97. }
  98. }
  99. )
  100. .listRowBackground(Color.chart)
  101. Section(
  102. header: Text("Support & Community"),
  103. content: {
  104. Button {
  105. showShareSheet.toggle()
  106. } label: {
  107. HStack {
  108. Text("Share Logs")
  109. .foregroundColor(.white)
  110. Spacer()
  111. Image(systemName: "chevron.right")
  112. .foregroundColor(.secondary)
  113. .font(.footnote)
  114. }
  115. }
  116. .frame(maxWidth: .infinity, alignment: .leading)
  117. Button {
  118. if let url = URL(string: "https://github.com/nightscout/Trio/issues/new/choose") {
  119. UIApplication.shared.open(url)
  120. }
  121. } label: {
  122. HStack {
  123. Text("Submit Ticket on GitHub")
  124. .foregroundColor(.white)
  125. Spacer()
  126. Image(systemName: "chevron.right")
  127. .foregroundColor(.secondary)
  128. .font(.footnote)
  129. }
  130. }
  131. .frame(maxWidth: .infinity, alignment: .leading)
  132. Button {
  133. if let url = URL(string: "https://discord.gg/FnwFEFUwXE") {
  134. UIApplication.shared.open(url)
  135. }
  136. } label: {
  137. HStack {
  138. Text("Trio Discord")
  139. .foregroundColor(.white)
  140. Spacer()
  141. Image(systemName: "chevron.right")
  142. .foregroundColor(.secondary)
  143. .font(.footnote)
  144. }
  145. }
  146. .frame(maxWidth: .infinity, alignment: .leading)
  147. Button {
  148. if let url = URL(string: "https://m.facebook.com/groups/1351938092206709/") {
  149. UIApplication.shared.open(url)
  150. }
  151. } label: {
  152. HStack {
  153. Text("Trio Facebook")
  154. .foregroundColor(.white)
  155. Spacer()
  156. Image(systemName: "chevron.right")
  157. .foregroundColor(.secondary)
  158. .font(.footnote)
  159. }
  160. }
  161. .frame(maxWidth: .infinity, alignment: .leading)
  162. Button {
  163. if let url = URL(string: "https://diy-trio.org/") {
  164. UIApplication.shared.open(url)
  165. }
  166. } label: {
  167. HStack {
  168. Text("Trio Website")
  169. .foregroundColor(.white)
  170. Spacer()
  171. Image(systemName: "chevron.right")
  172. .foregroundColor(.secondary)
  173. .font(.footnote)
  174. }
  175. }
  176. .frame(maxWidth: .infinity, alignment: .leading)
  177. }
  178. ).listRowBackground(Color.chart)
  179. } else {
  180. Section(
  181. header: Text("Search Results"),
  182. content: {
  183. ForEach(filteredItems) { filteredItem in
  184. VStack(alignment: .leading) {
  185. Text(filteredItem.matchedContent).bold()
  186. if let path = filteredItem.settingItem.path {
  187. Text(path.map(\.stringValue).joined(separator: " > "))
  188. .font(.caption)
  189. .foregroundColor(.secondary)
  190. }
  191. }.navigationLink(to: filteredItem.settingItem.view, from: self)
  192. }
  193. }
  194. ).listRowBackground(Color.chart)
  195. }
  196. // TODO: remove this more or less entirely; add build-time flag to enable Middleware; add settings export feature
  197. // Section {
  198. // Toggle("Developer Options", isOn: $state.debugOptions)
  199. // if state.debugOptions {
  200. // Group {
  201. // HStack {
  202. // Text("NS Upload Profile and Settings")
  203. // Button("Upload") { state.uploadProfileAndSettings(true) }
  204. // .frame(maxWidth: .infinity, alignment: .trailing)
  205. // .buttonStyle(.borderedProminent)
  206. // }
  207. // // 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
  208. // // Leaving it in here, as it may be a handy functionality for further testing or developers.
  209. // // See https://github.com/nightscout/Trio/pull/277 for more information
  210. // //
  211. // // HStack {
  212. // // Text("Delete Stored Pump State Binary Files")
  213. // // Button("Delete") { state.resetLoopDocuments() }
  214. // // .frame(maxWidth: .infinity, alignment: .trailing)
  215. // // .buttonStyle(.borderedProminent)
  216. // // }
  217. // }
  218. // Group {
  219. // Text("Preferences")
  220. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.preferences), from: self)
  221. // Text("Pump Settings")
  222. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.settings), from: self)
  223. // Text("Autosense")
  224. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.autosense), from: self)
  225. // // Text("Pump History")
  226. // // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.pumpHistory), from: self)
  227. // Text("Basal profile")
  228. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.basalProfile), from: self)
  229. // Text("Targets ranges")
  230. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.bgTargets), from: self)
  231. // Text("Temp targets")
  232. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.tempTargets), from: self)
  233. // }
  234. //
  235. // Group {
  236. // Text("Pump profile")
  237. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.pumpProfile), from: self)
  238. // Text("Profile")
  239. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.profile), from: self)
  240. // // Text("Carbs")
  241. // // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.carbHistory), from: self)
  242. // // Text("Announcements")
  243. // // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcements), from: self)
  244. // // Text("Enacted announcements")
  245. // // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcementsEnacted), from: self)
  246. // Text("Autotune")
  247. // .navigationLink(to: .configEditor(file: OpenAPS.Settings.autotune), from: self)
  248. // }
  249. //
  250. // Group {
  251. // Text("Target presets")
  252. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.tempTargetsPresets), from: self)
  253. // Text("Calibrations")
  254. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.calibrations), from: self)
  255. // Text("Middleware")
  256. // .navigationLink(to: .configEditor(file: OpenAPS.Middleware.determineBasal), from: self)
  257. // // Text("Statistics")
  258. // // .navigationLink(to: .configEditor(file: OpenAPS.Monitor.statistics), from: self)
  259. // Text("Edit settings json")
  260. // .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
  261. // }
  262. // }
  263. // }.listRowBackground(Color.chart)
  264. }.scrollContentBackground(.hidden).background(color)
  265. .sheet(isPresented: $showShareSheet) {
  266. ShareSheet(activityItems: state.logItems())
  267. }
  268. .onAppear(perform: configureView)
  269. .navigationTitle("Settings")
  270. .navigationBarTitleDisplayMode(.automatic)
  271. .toolbar {
  272. ToolbarItem(placement: .topBarTrailing) {
  273. Button(
  274. action: {
  275. if let url = URL(string: "https://triodocs.org/") {
  276. UIApplication.shared.open(url)
  277. }
  278. },
  279. label: {
  280. HStack {
  281. Text("Trio Docs")
  282. Image(systemName: "questionmark.circle")
  283. }
  284. }
  285. )
  286. }
  287. }
  288. .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
  289. .onDisappear(perform: { state.uploadProfileAndSettings(false) })
  290. .screenNavigation(self)
  291. }
  292. }
  293. }