SettingsRootView.swift 18 KB

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