| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- import CoreData
- import SwiftUI
- import Swinject
- extension Adjustments {
- struct RootView: BaseView {
- let resolver: Resolver
- @State var state = StateModel()
- @State var isEditing = false
- @State var showOverrideCreationSheet = false
- @State var showTempTargetCreationSheet = false
- @State var showingDetail = false
- @State var showOverrideCheckmark: Bool = false
- @State var showTempTargetCheckmark: Bool = false
- @State var selectedOverridePresetID: String?
- @State var selectedTempTargetPresetID: String?
- @State var selectedOverride: OverrideStored?
- @State var selectedTempTarget: TempTargetStored?
- @State var isConfirmDeletePresented = false
- @State var isPromptPresented = false
- @State var isRemoveAlertPresented = false
- @State var removeAlert: Alert?
- @State var isEditingTT = false
- @State var showCancelOverrideConfirmDialog = false
- @State var showCancelTempTargetConfirmDialog = false
- @State var pendingPresetActivation: PendingPresetActivation?
- private var shouldDisplayStickyOverrideStopButton: Bool {
- state.isOverrideEnabled && state.activeOverrideName.isNotEmpty
- }
- private var shouldDisplayStickyTempTargetStopButton: Bool {
- state.isTempTargetEnabled && state.activeTempTargetName.isNotEmpty
- }
- @Environment(\.colorScheme) var colorScheme
- @Environment(AppState.self) var appState
- func formattedGlucose(glucose: Decimal) -> String {
- let formattedValue: String
- if state.units == .mgdL {
- formattedValue = Formatter.glucoseFormatter(for: state.units)
- .string(from: glucose as NSDecimalNumber) ?? "\(glucose)"
- } else {
- formattedValue = glucose.formattedAsMmolL
- }
- return "\(formattedValue) \(state.units.rawValue)"
- }
- var body: some View {
- ZStack(alignment: .center, content: {
- VStack {
- Picker("Adjustment Tabs", selection: $state.selectedTab) {
- ForEach(Adjustments.Tab.allCases.indexed(), id: \.1) { index, item in
- Text(item.name).tag(index)
- }
- }
- .pickerStyle(SegmentedPickerStyle())
- .padding(.horizontal)
- List {
- switch state.selectedTab {
- case .overrides: overrides()
- case .tempTargets: tempTargets() }
- }
- .scrollContentBackground(.hidden)
- .background(appState.trioBackgroundColor(for: colorScheme))
- }
- .listSectionSpacing(10)
- .safeAreaInset(
- edge: .bottom,
- spacing: shouldDisplayStickyOverrideStopButton || shouldDisplayStickyTempTargetStopButton ? 30 : 0
- ) {
- if shouldDisplayStickyOverrideStopButton, state.selectedTab == .overrides {
- stickyStopOverrideButton
- } else if shouldDisplayStickyTempTargetStopButton, state.selectedTab == .tempTargets {
- stickyStopTempTargetButton
- } else {
- EmptyView()
- }
- }
- .scrollContentBackground(.hidden)
- .background(appState.trioBackgroundColor(for: colorScheme))
- .onAppear(perform: configureView)
- .navigationBarTitle("Adjustments")
- .navigationBarTitleDisplayMode(.large)
- .toolbar {
- ToolbarItem(placement: .topBarTrailing) {
- switch state.selectedTab {
- case .overrides:
- Button(action: {
- showOverrideCreationSheet = true
- }, label: {
- HStack {
- Text("Add Override")
- Image(systemName: "plus")
- }
- })
- case .tempTargets:
- Button(action: {
- showTempTargetCreationSheet = true
- }, label: {
- HStack {
- Text("Add Temp Target")
- Image(systemName: "plus")
- }
- })
- }
- }
- }
- .sheet(isPresented: $state.showOverrideEditSheet, onDismiss: {
- Task {
- await state.resetStateVariables()
- state.showOverrideEditSheet = false
- }
- }) {
- if let override = selectedOverride {
- EditOverrideForm(overrideToEdit: override, state: state)
- }
- }
- .sheet(isPresented: $showOverrideCreationSheet, onDismiss: {
- Task {
- await state.resetStateVariables()
- showOverrideCreationSheet = false
- }
- }) {
- AddOverrideForm(state: state)
- }
- .sheet(isPresented: $showTempTargetCreationSheet, onDismiss: {
- Task {
- await state.resetTempTargetState()
- showTempTargetCreationSheet = false
- }
- }) {
- AddTempTargetForm(state: state)
- }
- .sheet(isPresented: $state.showTempTargetEditSheet, onDismiss: {
- Task {
- await state.resetTempTargetState()
- state.showTempTargetEditSheet = false
- }
- }) {
- if let tempTarget = selectedTempTarget {
- EditTempTargetForm(tempTargetToEdit: tempTarget, state: state)
- }
- }
- .confirmationDialog("Override to Stop", isPresented: $showCancelOverrideConfirmDialog) {
- Button("Stop", role: .destructive) {
- Task {
- // Save cancelled Override in OverrideRunStored Entity
- // Cancel ALL active Override
- await state.disableAllActiveOverrides(createOverrideRunEntry: true)
- }
- }
- Button("Cancel", role: .cancel) {}
- } message: {
- Text("Stop the Override \"\(state.currentActiveOverride?.name ?? "")\"?")
- }
- .confirmationDialog("Temp Target to Stop", isPresented: $showCancelTempTargetConfirmDialog) {
- Button("Stop", role: .destructive) {
- Task {
- // Save cancelled Temp Targets in TempTargetRunStored Entity
- // Cancel ALL active Temp Targets
- await state.disableAllActiveTempTargets(createTempTargetRunEntry: true)
- // Update View
- state.updateLatestTempTargetConfiguration()
- }
- }
- Button("Cancel", role: .cancel) {}
- } message: {
- Text("Stop the Temp Target \"\(state.currentActiveTempTarget?.name ?? "")\"?")
- }
- .confirmationDialog(
- "Activate Preset",
- isPresented: presetActivationConfirmationBinding
- ) {
- Button("Activate") {
- if let activation = pendingPresetActivation {
- activatePreset(activation)
- }
- }
- Button("Cancel", role: .cancel) {
- state.shouldDisplayPresetStartConfirmDialog = false
- pendingPresetActivation = nil
- }
- } message: {
- if let activation = pendingPresetActivation {
- Text(activation.confirmationMessage)
- }
- }
- }).background(appState.trioBackgroundColor(for: colorScheme))
- }
- var defaultText: some View {
- switch state.selectedTab {
- case .overrides:
- Section {} header: {
- Text("Add Preset or Override by tapping 'Add Override +' in the top right-hand corner of the screen.")
- .textCase(nil)
- .foregroundStyle(.secondary)
- }
- case .tempTargets:
- Section {} header: {
- Text(
- "Add Preset or Temp Target by tapping 'Add Temp Target +' in the top right-hand corner of the screen."
- )
- .textCase(nil)
- .foregroundStyle(.secondary)
- }
- }
- }
- var currentActiveAdjustment: some View {
- switch state.selectedTab {
- case .overrides:
- Section {
- HStack {
- Text("\(state.activeOverrideName) is running")
- Spacer()
- Image(systemName: "square.and.pencil")
- .foregroundStyle(Color.primary)
- }
- .contentShape(Rectangle())
- .onTapGesture {
- Task {
- /// To avoid editing the Preset when a Preset-Override is running we first duplicate the Preset-Override as a non-Preset Override
- /// The currentActiveOverride variable in the State will update automatically via MOC notification
- await state.duplicateOverridePresetAndCancelPreviousOverride()
- /// selectedOverride is used for passing the chosen Override to the EditSheet so we have to set the updated currentActiveOverride to be the selectedOverride
- selectedOverride = state.currentActiveOverride
- /// Now we can show the Edit sheet
- state.showOverrideEditSheet = true
- }
- }
- }
- .listRowBackground(Color.purple.opacity(0.8))
- case .tempTargets:
- Section {
- HStack {
- Text("\(state.activeTempTargetName) is running")
- Spacer()
- Image(systemName: "square.and.pencil")
- .foregroundStyle(Color.primary)
- }
- .contentShape(Rectangle())
- .onTapGesture {
- Task {
- /// To avoid editing the Preset when a Preset-Override is running we first duplicate the Preset-Override as a non-Preset Override
- /// The currentActiveOverride variable in the State will update automatically via MOC notification
- await state.duplicateTempTargetPresetAndCancelPreviousTempTarget()
- /// selectedOverride is used for passing the chosen Override to the EditSheet so we have to set the updated currentActiveOverride to be the selectedOverride
- selectedTempTarget = state.currentActiveTempTarget
- /// Now we can show the Edit sheet
- state.showTempTargetEditSheet = true
- }
- }
- }
- .listRowBackground(Color.loopGreen.opacity(0.8))
- }
- }
- var cancelAdjustmentButton: some View {
- switch state.selectedTab {
- case .overrides:
- Button(action: {
- showCancelOverrideConfirmDialog = true
- }, label: {
- Text("Stop Override")
- })
- .frame(maxWidth: .infinity, alignment: .center)
- .disabled(!state.isOverrideEnabled)
- .listRowBackground(!state.isOverrideEnabled ? Color(.systemGray4) : Color(.systemRed))
- .tint(.white)
- case .tempTargets:
- Button(action: {
- showCancelTempTargetConfirmDialog = true
- }, label: {
- Text("Stop Temp Target")
- })
- .frame(maxWidth: .infinity, alignment: .center)
- .disabled(!state.isTempTargetEnabled)
- .listRowBackground(!state.isTempTargetEnabled ? Color(.systemGray4) : Color(.systemRed))
- .tint(.white)
- }
- }
- func formattedTimeRemaining(_ timeInterval: TimeInterval) -> String {
- let totalSeconds = Int(timeInterval)
- let hours = totalSeconds / 3600
- let minutes = (totalSeconds % 3600) / 60
- let seconds = totalSeconds % 60
- if hours > 0 {
- return "\(hours)h \(minutes)m \(seconds)s"
- } else if minutes > 0 {
- return "\(minutes)m \(seconds)s"
- } else {
- return "<1m"
- }
- }
- }
- }
- // MARK: Preset Activation Handling
- extension Adjustments.RootView: View {
- enum PendingPresetActivation {
- case override(objectID: NSManagedObjectID, presetID: String?, name: String)
- case tempTarget(objectID: NSManagedObjectID, presetID: String?, name: String)
- var name: String {
- switch self {
- case let .override(_, _, name),
- let .tempTarget(_, _, name):
- return name
- }
- }
- var adjustmentType: String {
- switch self {
- case .override:
- return String(localized: "Override")
- case .tempTarget:
- return String(localized: "Temp Target")
- }
- }
- var confirmationMessage: String {
- String(localized: "Start the \(adjustmentType) \"\(name)\"?", comment: "Confirmation message for starting a preset")
- }
- }
- private var presetActivationConfirmationBinding: Binding<Bool> {
- Binding(
- get: {
- state.requireAdjustmentsConfirmation &&
- state.shouldDisplayPresetStartConfirmDialog &&
- pendingPresetActivation != nil
- },
- set: { isPresented in
- if !isPresented {
- state.shouldDisplayPresetStartConfirmDialog = false
- pendingPresetActivation = nil
- }
- }
- )
- }
- func requestPresetActivation(_ activation: PendingPresetActivation) {
- if state.requireAdjustmentsConfirmation {
- pendingPresetActivation = activation
- state.shouldDisplayPresetStartConfirmDialog = true
- } else {
- activatePreset(activation)
- }
- }
- func activatePreset(_ activation: PendingPresetActivation) {
- Task {
- switch activation {
- case let .override(objectID, presetID, _):
- await state.enactOverridePreset(withID: objectID)
- await MainActor.run {
- state.hideModal()
- selectedOverridePresetID = presetID
- showOverrideCheckmark = true
- state.shouldDisplayPresetStartConfirmDialog = false
- pendingPresetActivation = nil
- }
- DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
- showOverrideCheckmark = false
- }
- case let .tempTarget(objectID, presetID, _):
- await state.enactTempTargetPreset(withID: objectID)
- await MainActor.run {
- selectedTempTargetPresetID = presetID
- showTempTargetCheckmark = true
- state.shouldDisplayPresetStartConfirmDialog = false
- pendingPresetActivation = nil
- }
- DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
- showTempTargetCheckmark = false
- }
- }
- }
- }
- }
|