import SwiftUI import Swinject extension UserInterfaceSettings { struct RootView: BaseView { let resolver: Resolver @StateObject var state = StateModel() @State private var shouldDisplayHint: Bool = false @State var hintDetent = PresentationDetent.large @State var selectedVerboseHint: AnyView? @State var hintLabel: String? @State private var decimalPlaceholder: Decimal = 0.0 @State private var booleanPlaceholder: Bool = false @State private var displayPickerLowThreshold: Bool = false @State private var displayPickerHighThreshold: Bool = false @AppStorage("colorSchemePreference") private var colorSchemePreference: ColorSchemeOption = .systemDefault @Environment(\.colorScheme) var colorScheme @Environment(AppState.self) var appState private var glucoseFormatter: NumberFormatter { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.maximumFractionDigits = 0 if state.units == .mmolL { formatter.maximumFractionDigits = 1 } formatter.roundingMode = .halfUp return formatter } private var carbsFormatter: NumberFormatter { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.maximumFractionDigits = 0 return formatter } var body: some View { List { Section( header: Text("General Appearance"), content: { VStack { Picker( selection: $colorSchemePreference, label: Text("Trio Color Scheme") ) { ForEach(ColorSchemeOption.allCases) { selection in Text(selection.displayName).tag(selection) } }.padding(.top) HStack(alignment: .center) { Text( "Choose between Light, Dark, or System Default for the app color scheme." ) .font(.footnote) .foregroundColor(.secondary) .lineLimit(nil) Spacer() Button( action: { hintLabel = "Color Scheme Preference" selectedVerboseHint = AnyView( VStack(alignment: .leading, spacing: 10) { Text( "Set the app color scheme using the following options:" ) VStack(alignment: .leading, spacing: 10) { Text( "System Default: Follows the phone's current color scheme setting at that time" ) Text("Light: Always in Light mode") Text("Dark: Always in Dark mode") } } ) shouldDisplayHint.toggle() }, label: { HStack { Image(systemName: "questionmark.circle") } } ).buttonStyle(BorderlessButtonStyle()) }.padding(.top) }.padding(.bottom) } ).listRowBackground(Color.chart) Section { VStack { Picker( selection: $state.glucoseColorScheme, label: Text("Glucose Color Scheme") ) { ForEach(GlucoseColorScheme.allCases) { selection in Text(selection.displayName).tag(selection) } }.padding(.top) HStack(alignment: .center) { Text( "Choose between Static or Dynamic coloring for glucose readings." ) .font(.footnote) .foregroundColor(.secondary) .lineLimit(nil) Spacer() Button( action: { hintLabel = "Glucose Color Scheme" selectedVerboseHint = AnyView( VStack(spacing: 10) { Text( "Set the color scheme for glucose readings on the main glucose graph, live activities, and bolus calculator using the following options:" ) VStack { Text( "Static: Below-Range Target readings will be in RED, In-Range will be GREEN, Above-Range will be YELLOW." ) Text( "Dynamic: Readings on Target will be GREEN. As readings approach and exceed below target, they become more RED. As readings approach and exceed above targer, they become more PURPLE." ) } } ) shouldDisplayHint.toggle() }, label: { HStack { Image(systemName: "questionmark.circle") } } ).buttonStyle(BorderlessButtonStyle()) }.padding(.top) }.padding(.bottom) }.listRowBackground(Color.chart) Section( header: Text("Home View Settings"), content: { VStack { Toggle("Show X-Axis Grid Lines", isOn: $state.xGridLines) Toggle("Show Y-Axis Grid Lines", isOn: $state.yGridLines) HStack(alignment: .center) { Text( "Display the grid lines behind the glucose graph." ) .font(.footnote) .foregroundColor(.secondary) .lineLimit(nil) Spacer() Button( action: { hintLabel = "Show Main Chart X- and Y-Axis Grid Lines" selectedVerboseHint = AnyView( Text("Choose whether or not to display one or both X- and Y-Axis grid lines.") ) shouldDisplayHint.toggle() }, label: { HStack { Image(systemName: "questionmark.circle") } } ).buttonStyle(BorderlessButtonStyle()) }.padding(.top) }.padding(.vertical) } ).listRowBackground(Color.chart) SettingInputSection( decimalValue: $decimalPlaceholder, booleanValue: $state.rulerMarks, shouldDisplayHint: $shouldDisplayHint, selectedVerboseHint: Binding( get: { selectedVerboseHint }, set: { selectedVerboseHint = $0.map { AnyView($0) } hintLabel = "Show Low and High Thresholds" } ), units: state.units, type: .boolean, label: "Show Low and High Thresholds", miniHint: "Display the Low and High glucose thresholds set below.", verboseHint: VStack(alignment: .leading, spacing: 10) { Text("This setting displays the upper and lower values for your glucose target range.") Text("This range is for display and statistical purposes only and does not influence insulin dosing.") } ) if state.rulerMarks { Section { VStack { VStack { HStack { Text("Low Threshold") Spacer() Group { Text(state.units == .mgdL ? state.low.description : state.low.asMmolL.description) .foregroundColor(!displayPickerLowThreshold ? .primary : .accentColor) Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary) } } .onTapGesture { displayPickerLowThreshold.toggle() } } .padding(.top) if displayPickerLowThreshold { let setting = PickerSettingsProvider.shared.settings.low Picker(selection: $state.low, label: Text("")) { ForEach( PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units), id: \.self ) { value in let displayValue = state.units == .mgdL ? value : value.asMmolL Text("\(displayValue.description)").tag(value) } } .pickerStyle(WheelPickerStyle()) .frame(maxWidth: .infinity) } VStack { HStack { Text("High Threshold") Spacer() Group { Text(state.units == .mgdL ? state.high.description : state.high.asMmolL.description) .foregroundColor(!displayPickerHighThreshold ? .primary : .accentColor) Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary) } } .onTapGesture { displayPickerHighThreshold.toggle() } } .padding(.top) if displayPickerHighThreshold { let setting = PickerSettingsProvider.shared.settings.high Picker(selection: $state.high, label: Text("")) { ForEach( PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units), id: \.self ) { value in let displayValue = state.units == .mgdL ? value : value.asMmolL Text("\(displayValue.description)").tag(value) } } .pickerStyle(WheelPickerStyle()) .frame(maxWidth: .infinity) } HStack(alignment: .center) { Text( "Set low and high glucose values for the main screen glucose graph and statistics." ) .lineLimit(nil) .font(.footnote) .foregroundColor(.secondary) Spacer() Button( action: { hintLabel = "Low and High Thresholds" selectedVerboseHint = AnyView( VStack(alignment: .leading, spacing: 10) { Text( "Default values are based on internationally accepted Time in Range values of \(state.units == .mgdL ? "70" : 70.formattedAsMmolL)-\(state.units == .mgdL ? "180" : 180.formattedAsMmolL) \(state.units.rawValue)." ) Text( "Set the values used in the main screen glucose graph and to determine Time in Range for Statistics." ) Text("Note: These values are not used to calculate insulin dosing.") } ) shouldDisplayHint.toggle() }, label: { HStack { Image(systemName: "questionmark.circle") } } ).buttonStyle(BorderlessButtonStyle()) }.padding(.top) }.padding(.bottom) }.listRowBackground(Color.chart) } Section { VStack { Picker( selection: $state.forecastDisplayType, label: Text("Forecast Display Type") ) { ForEach(ForecastDisplayType.allCases) { selection in Text(selection.displayName).tag(selection) } }.padding(.top) HStack(alignment: .center) { Text( "Choose between the Cone of Uncertainty or the OpenAPS colored lines for the algorithm's forecast." ) .font(.footnote) .foregroundColor(.secondary) .lineLimit(nil) Spacer() Button( action: { hintLabel = "Forecast Display Type" selectedVerboseHint = AnyView( VStack(alignment: .leading, spacing: 10) { Text( "This setting allows you to choose between the following two options for the glucose forecast:" ) Text( "Cone of Uncertainty: Uses a combined range of all possible forecasts from the OpenAPS lines and provides you with a range of possible forecasts. This option has shown to reduce confusion and stress around algorithm forecasts by providing a less concerning visual representation." ) Text( "Forecast Lines: Uses the IOB, COB, UAM, and ZT forecast lines from OpenAPS. This option provides a more detailed view of the algorithm's forecast, but may be more confusing for some users." ) } ) shouldDisplayHint.toggle() }, label: { HStack { Image(systemName: "questionmark.circle") } } ).buttonStyle(BorderlessButtonStyle()) }.padding(.top) }.padding(.bottom) }.listRowBackground(Color.chart) Section { VStack(alignment: .leading) { Picker( selection: $state.totalInsulinDisplayType, label: Text("Total Insulin Display Type").multilineTextAlignment(.leading) ) { ForEach(TotalInsulinDisplayType.allCases) { selection in Text(selection.displayName).tag(selection) } }.padding(.top) HStack(alignment: .center) { Text( "Choose between Total Daily Dose (TDD) or Total Insulin in Scope (TINS) to be displayed above the main glucose graph." ) .font(.footnote) .foregroundColor(.secondary) .lineLimit(nil) Spacer() Button( action: { hintLabel = "Total Insulin Display Type" selectedVerboseHint = AnyView( VStack(alignment: .leading, spacing: 10) { Text( "Choose between Total Daily Dose (TDD) or Total Insulin in Scope (TINS) to be displayed above the main glucose graph." ) Text( "Total Daily Dose: Displays the last 24 hours of total insulin administered, both basal and bolus." ) Text( "Total Insulin in Scope: Displays the total insulin administered since midnight, both basal and bolus." ) } ) shouldDisplayHint.toggle() }, label: { HStack { Image(systemName: "questionmark.circle") } } ).buttonStyle(BorderlessButtonStyle()) }.padding(.top) }.padding(.bottom) }.listRowBackground(Color.chart) Section( header: Text("Trio Statistics"), content: { VStack { Picker( selection: $state.hbA1cDisplayUnit, label: Text("HbA1c Display Unit") ) { ForEach(HbA1cDisplayUnit.allCases) { selection in Text(selection.displayName).tag(selection) } }.padding(.top) HStack(alignment: .center) { Text( "Choose to display HbA1c in % or mmol/mol." ) .font(.footnote) .foregroundColor(.secondary) .lineLimit(nil) Spacer() Button( action: { hintLabel = "HbA1c Display Unit" selectedVerboseHint = AnyView( Text( "Choose which format you'd prefer the HbA1c value in the statistics view as a percentage (Example: 6.5%) or mmol/mol (Example: 48 mmol/mol)." ) ) shouldDisplayHint.toggle() }, label: { HStack { Image(systemName: "questionmark.circle") } } ).buttonStyle(BorderlessButtonStyle()) }.padding(.top) }.padding(.bottom) } ).listRowBackground(Color.chart) Section { VStack(alignment: .leading) { Picker( selection: $state.timeInRangeChartStyle, label: Text("Time in Range Chart Style").multilineTextAlignment(.leading) ) { ForEach(TimeInRangeChartStyle.allCases) { selection in Text(selection.displayName).tag(selection) } }.padding(.top) HStack(alignment: .center) { Text( "Choose to display the Time in Range chart as a vertical bar chart or horizontal line chart." ) .font(.footnote) .foregroundColor(.secondary) .lineLimit(nil) Spacer() Button( action: { hintLabel = "Time in Range Chart Style" selectedVerboseHint = AnyView( Text( "Choose which style for the time in range chart you'd prefer: a standing, i.e., vertical, bar chart or a laying, i.e., horizontal, line chart." ) ) shouldDisplayHint.toggle() }, label: { HStack { Image(systemName: "questionmark.circle") } } ).buttonStyle(BorderlessButtonStyle()) }.padding(.top) }.padding(.bottom) }.listRowBackground(Color.chart) SettingInputSection( decimalValue: $state.carbsRequiredThreshold, booleanValue: $state.showCarbsRequiredBadge, shouldDisplayHint: $shouldDisplayHint, selectedVerboseHint: Binding( get: { selectedVerboseHint }, set: { selectedVerboseHint = $0.map { AnyView($0) } hintLabel = "Show Carbs Required Badge" } ), units: state.units, type: .conditionalDecimal("carbsRequiredThreshold"), label: "Show Carbs Required Badge", conditionalLabel: "Carbs Required Threshold", miniHint: "Show carbs required as a notification badge on the home screen.", verboseHint: Text( "Turning this on will show the grams of carbs needed to prevent a low as a notification badge on the Trio home screen located above the main icon." ), headerText: "Carbs Required Badge" ) } .listSectionSpacing(sectionSpacing) .sheet(isPresented: $shouldDisplayHint) { SettingInputHintView( hintDetent: $hintDetent, shouldDisplayHint: $shouldDisplayHint, hintLabel: hintLabel ?? "", hintText: selectedVerboseHint ?? AnyView(EmptyView()), sheetTitle: "Help" ) } .scrollContentBackground(.hidden) .background(appState.trioBackgroundColor(for: colorScheme)) .onAppear(perform: configureView) .navigationBarTitle("User Interface") .navigationBarTitleDisplayMode(.automatic) } } }