| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- import CoreData
- import SwiftUI
- import Swinject
- extension OverrideProfilesConfig {
- struct RootView: BaseView {
- let resolver: Resolver
- @StateObject var state = StateModel()
- @State private var isEditing = false
- @State private var showAlert = false
- @State private var showingDetail = false
- @State private var alertSring = ""
- @State var isSheetPresented: Bool = false
- @Environment(\.dismiss) var dismiss
- @Environment(\.managedObjectContext) var moc
- @Environment(\.colorScheme) var colorScheme
- var color: LinearGradient {
- colorScheme == .dark ? LinearGradient(
- gradient: Gradient(colors: [
- Color.bgDarkBlue,
- Color.bgDarkerDarkBlue
- ]),
- startPoint: .top,
- endPoint: .bottom
- )
- :
- LinearGradient(
- gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
- startPoint: .top,
- endPoint: .bottom
- )
- }
- @FetchRequest(
- entity: OverridePresets.entity(),
- sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)], predicate: NSPredicate(
- format: "name != %@", "" as String
- )
- ) var fetchedProfiles: FetchedResults<OverridePresets>
- private var formatter: NumberFormatter {
- let formatter = NumberFormatter()
- formatter.numberStyle = .decimal
- formatter.maximumFractionDigits = 0
- return formatter
- }
- 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
- }
- var presetPopover: some View {
- Form {
- Section {
- TextField("Name Of Profile", text: $state.profileName)
- } header: { Text("Enter Name of Profile") }
- Section {
- Button("Save") {
- state.savePreset()
- isSheetPresented = false
- }
- .disabled(state.profileName.isEmpty || fetchedProfiles.filter({ $0.name == state.profileName }).isNotEmpty)
- Button("Cancel") {
- isSheetPresented = false
- }
- }
- }
- }
- var body: some View {
- Form {
- if state.presets.isNotEmpty {
- Section {
- ForEach(fetchedProfiles) { preset in
- profilesView(for: preset)
- }.onDelete(perform: removeProfile)
- }
- }
- Section {
- VStack {
- Spacer()
- Text("\(state.percentage.formatted(.number)) %")
- .foregroundColor(
- state
- .percentage >= 130 ? .red :
- (isEditing ? .orange : .blue)
- )
- .font(.largeTitle)
- Slider(
- value: $state.percentage,
- in: 10 ... 200,
- step: 1,
- onEditingChanged: { editing in
- isEditing = editing
- }
- ).accentColor(state.percentage >= 130 ? .red : .blue)
- Spacer()
- Toggle(isOn: $state._indefinite) {
- Text("Enable indefinitely")
- }
- }
- if !state._indefinite {
- HStack {
- Text("Duration")
- DecimalTextField("0", value: $state.duration, formatter: formatter, cleanInput: false)
- Text("minutes").foregroundColor(.secondary)
- }
- }
- HStack {
- Toggle(isOn: $state.override_target) {
- Text("Override Profile Target")
- }
- }
- if state.override_target {
- HStack {
- Text("Target Glucose")
- DecimalTextField("0", value: $state.target, formatter: glucoseFormatter, cleanInput: false)
- Text(state.units.rawValue).foregroundColor(.secondary)
- }
- }
- HStack {
- Toggle(isOn: $state.advancedSettings) {
- Text("More options")
- }
- }
- if state.advancedSettings {
- HStack {
- Toggle(isOn: $state.smbIsOff) {
- Text("Disable SMBs")
- }
- }
- HStack {
- Toggle(isOn: $state.smbIsAlwaysOff) {
- Text("Schedule when SMBs are Off")
- }.disabled(!state.smbIsOff)
- }
- if state.smbIsAlwaysOff {
- HStack {
- Text("First Hour SMBs are Off (24 hours)")
- DecimalTextField("0", value: $state.start, formatter: formatter, cleanInput: false)
- Text("hour").foregroundColor(.secondary)
- }
- HStack {
- Text("Last Hour SMBs are Off (24 hours)")
- DecimalTextField("0", value: $state.end, formatter: formatter, cleanInput: false)
- Text("hour").foregroundColor(.secondary)
- }
- }
- HStack {
- Toggle(isOn: $state.isfAndCr) {
- Text("Change ISF and CR")
- }
- }
- if !state.isfAndCr {
- HStack {
- Toggle(isOn: $state.isf) {
- Text("Change ISF")
- }
- }
- HStack {
- Toggle(isOn: $state.cr) {
- Text("Change CR")
- }
- }
- }
- HStack {
- Text("SMB Minutes")
- DecimalTextField(
- "0",
- value: $state.smbMinutes,
- formatter: formatter,
- cleanInput: false
- )
- Text("minutes").foregroundColor(.secondary)
- }
- HStack {
- Text("UAM SMB Minutes")
- DecimalTextField(
- "0",
- value: $state.uamMinutes,
- formatter: formatter,
- cleanInput: false
- )
- Text("minutes").foregroundColor(.secondary)
- }
- }
- HStack {
- Button("Start new Profile") {
- showAlert.toggle()
- alertSring = "\(state.percentage.formatted(.number)) %, " +
- (
- state.duration > 0 || !state
- ._indefinite ?
- (
- state
- .duration
- .formatted(.number.grouping(.never).rounded().precision(.fractionLength(0))) +
- " min."
- ) :
- NSLocalizedString(" infinite duration.", comment: "")
- ) +
- (
- (state.target == 0 || !state.override_target) ? "" :
- (" Target: " + state.target.formatted() + " " + state.units.rawValue + ".")
- )
- +
- (
- state
- .smbIsOff ?
- NSLocalizedString(
- " SMBs are disabled either by schedule or during the entire duration.",
- comment: ""
- ) : ""
- )
- +
- "\n\n"
- +
- NSLocalizedString(
- "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.",
- comment: ""
- )
- }
- .disabled(unChanged())
- .buttonStyle(BorderlessButtonStyle())
- .font(.callout)
- .controlSize(.mini)
- .alert(
- "Start Profile",
- isPresented: $showAlert,
- actions: {
- Button("Cancel", role: .cancel) { state.isEnabled = false }
- Button("Start Profile", role: .destructive) {
- if state._indefinite { state.duration = 0 }
- state.isEnabled.toggle()
- state.saveSettings()
- dismiss()
- }
- },
- message: {
- Text(alertSring)
- }
- )
- Button {
- isSheetPresented = true
- }
- label: { Text("Save as Profile") }
- .tint(.orange)
- .frame(maxWidth: .infinity, alignment: .trailing)
- .buttonStyle(BorderlessButtonStyle())
- .controlSize(.mini)
- .disabled(unChanged())
- }
- .sheet(isPresented: $isSheetPresented) {
- presetPopover
- }
- }
- header: {
- HStack {
- Text("Insulin")
- Spacer()
- Button {
- state.showModal(for: .addTempTarget)
- } label: {
- HStack {
- Image(systemName: "plus.circle.fill")
- .font(.system(size: 20))
- Text("add temp target")
- .font(.caption)
- .foregroundStyle(Color.blue)
- }
- }
- }
- }
- footer: {
- Text(
- "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage."
- )
- }
- Button("Return to Normal") {
- state.cancelProfile()
- dismiss()
- }
- .frame(maxWidth: .infinity, alignment: .center)
- .buttonStyle(BorderlessButtonStyle())
- .disabled(!state.isEnabled)
- .tint(.red)
- }.scrollContentBackground(.hidden).background(color)
- .onAppear(perform: configureView)
- .onAppear { state.savedSettings() }
- .navigationBarTitle("Profiles")
- .navigationBarTitleDisplayMode(.inline)
- .toolbar {
- ToolbarItem(placement: .navigationBarLeading) {
- Button("Close") {
- state.hideModal()
- }
- }
- }
- }
- @ViewBuilder private func profilesView(for preset: OverridePresets) -> some View {
- let target = state.units == .mmolL ? (((preset.target ?? 0) as NSDecimalNumber) as Decimal)
- .asMmolL : (preset.target ?? 0) as Decimal
- let duration = (preset.duration ?? 0) as Decimal
- let name = ((preset.name ?? "") == "") || (preset.name?.isEmpty ?? true) ? "" : preset.name!
- let percent = preset.percentage / 100
- let perpetual = preset.indefinite
- let durationString = perpetual ? "" : "\(formatter.string(from: duration as NSNumber)!)"
- let scheduledSMBstring = (preset.smbIsOff && preset.smbIsAlwaysOff) ? "Scheduled SMBs" : ""
- let smbString = (preset.smbIsOff && scheduledSMBstring == "") ? "SMBs are off" : ""
- let targetString = target != 0 ? "\(glucoseFormatter.string(from: target as NSNumber)!)" : ""
- let maxMinutesSMB = (preset.smbMinutes as Decimal?) != nil ? (preset.smbMinutes ?? 0) as Decimal : 0
- let maxMinutesUAM = (preset.uamMinutes as Decimal?) != nil ? (preset.uamMinutes ?? 0) as Decimal : 0
- let isfString = preset.isf ? "ISF" : ""
- let crString = preset.cr ? "CR" : ""
- let dash = crString != "" ? "/" : ""
- let isfAndCRstring = isfString + dash + crString
- if name != "" {
- HStack {
- VStack {
- HStack {
- Text(name)
- Spacer()
- }
- HStack(spacing: 5) {
- Text(percent.formatted(.percent.grouping(.never).rounded().precision(.fractionLength(0))))
- if targetString != "" {
- Text(targetString)
- Text(targetString != "" ? state.units.rawValue : "")
- }
- if durationString != "" { Text(durationString + (perpetual ? "" : "min")) }
- if smbString != "" { Text(smbString).foregroundColor(.secondary).font(.caption) }
- if scheduledSMBstring != "" { Text(scheduledSMBstring) }
- if preset.advancedSettings {
- Text(maxMinutesSMB == 0 ? "" : maxMinutesSMB.formatted() + " SMB")
- Text(maxMinutesUAM == 0 ? "" : maxMinutesUAM.formatted() + " UAM")
- Text(isfAndCRstring)
- }
- Spacer()
- }
- .padding(.top, 2)
- .foregroundColor(.secondary)
- .font(.caption)
- }
- .contentShape(Rectangle())
- .onTapGesture {
- state.selectProfile(id_: preset.id ?? "")
- state.hideModal()
- }
- }
- }
- }
- private func unChanged() -> Bool {
- let isChanged = (state.percentage == 100 && !state.override_target && !state.smbIsOff && !state.advancedSettings) ||
- (!state._indefinite && state.duration == 0) || (state.override_target && state.target == 0) ||
- (
- state.percentage == 100 && !state.override_target && !state.smbIsOff && state.isf && state.cr && state
- .smbMinutes == state.defaultSmbMinutes && state.uamMinutes == state.defaultUamMinutes
- )
- return isChanged
- }
- private func removeProfile(at offsets: IndexSet) {
- for index in offsets {
- let language = fetchedProfiles[index]
- moc.delete(language)
- }
- do {
- try moc.save()
- } catch {
- // To do: add error
- }
- }
- }
- }
|