EditOverrideForm.swift 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. import Foundation
  2. import SwiftUI
  3. struct EditOverrideForm: View {
  4. var override: OverrideStored
  5. @Environment(\.presentationMode) var presentationMode
  6. @Environment(\.colorScheme) var colorScheme
  7. @Environment(AppState.self) var appState
  8. @Bindable var state: Adjustments.StateModel
  9. @State private var name: String
  10. @State private var percentage: Double
  11. @State private var indefinite: Bool
  12. @State private var duration: Decimal
  13. @State private var target: Decimal?
  14. @State private var advancedSettings: Bool
  15. @State private var smbIsOff: Bool
  16. @State private var smbIsScheduledOff: Bool
  17. @State private var start: Decimal?
  18. @State private var end: Decimal?
  19. @State private var isfAndCr: Bool
  20. @State private var isf: Bool
  21. @State private var cr: Bool
  22. @State private var smbMinutes: Decimal?
  23. @State private var uamMinutes: Decimal?
  24. @State private var selectedIsfCrOption: IsfAndOrCrOptions
  25. @State private var selectedDisableSmbOption: DisableSmbOptions
  26. @State private var hasChanges = false
  27. @State private var isEditing = false
  28. @State private var target_override = false
  29. @State private var percentageStep: Int = 1
  30. @State private var displayPickerPercentage: Bool = false
  31. @State private var displayPickerDuration: Bool = false
  32. @State private var targetStep: Decimal = 1
  33. @State private var displayPickerTarget: Bool = false
  34. @State private var displayPickerDisableSmbSchedule: Bool = false
  35. @State private var displayPickerSmbMinutes: Bool = false
  36. init(overrideToEdit: OverrideStored, state: Adjustments.StateModel) {
  37. override = overrideToEdit
  38. _state = Bindable(wrappedValue: state)
  39. _name = State(initialValue: overrideToEdit.name ?? "")
  40. _percentage = State(initialValue: overrideToEdit.percentage)
  41. _indefinite = State(initialValue: overrideToEdit.indefinite)
  42. _duration = State(initialValue: overrideToEdit.duration?.decimalValue ?? 0)
  43. _target = State(initialValue: overrideToEdit.target?.decimalValue)
  44. _target_override = State(initialValue: overrideToEdit.target != nil && overrideToEdit.target?.decimalValue != 0)
  45. _advancedSettings = State(initialValue: overrideToEdit.advancedSettings)
  46. _smbIsOff = State(initialValue: overrideToEdit.smbIsOff)
  47. _smbIsScheduledOff = State(initialValue: overrideToEdit.smbIsScheduledOff)
  48. _start = State(initialValue: overrideToEdit.start?.decimalValue)
  49. _end = State(initialValue: overrideToEdit.end?.decimalValue)
  50. _isfAndCr = State(initialValue: overrideToEdit.isfAndCr)
  51. _isf = State(initialValue: overrideToEdit.isf)
  52. _cr = State(initialValue: overrideToEdit.cr)
  53. _selectedIsfCrOption = State(
  54. initialValue: overrideToEdit.isfAndCr ? .isfAndCr
  55. : (overrideToEdit.isf ? .isf : (overrideToEdit.cr ? .cr : .nothing))
  56. )
  57. _selectedDisableSmbOption = State(
  58. initialValue: overrideToEdit.smbIsScheduledOff ? .disableOnSchedule
  59. : (overrideToEdit.smbIsOff ? .disable : .dontDisable)
  60. )
  61. _smbMinutes = State(initialValue: overrideToEdit.smbMinutes?.decimalValue)
  62. _uamMinutes = State(initialValue: overrideToEdit.uamMinutes?.decimalValue)
  63. }
  64. private var percentageSelection: Binding<Double> {
  65. Binding<Double>(
  66. get: {
  67. let value = floor(percentage / Double(percentageStep)) * Double(percentageStep)
  68. return max(10, min(value, 200))
  69. },
  70. set: {
  71. percentage = $0
  72. hasChanges = true
  73. }
  74. )
  75. }
  76. var body: some View {
  77. NavigationView {
  78. List {
  79. editOverride()
  80. saveButton
  81. }
  82. .listSectionSpacing(10)
  83. .padding(.top, 30)
  84. .ignoresSafeArea(edges: .top)
  85. .scrollContentBackground(.hidden)
  86. .background(appState.trioBackgroundColor(for: colorScheme))
  87. .navigationTitle("Edit Override")
  88. .navigationBarTitleDisplayMode(.inline)
  89. .toolbar {
  90. ToolbarItem(placement: .topBarLeading) {
  91. Button(action: {
  92. presentationMode.wrappedValue.dismiss()
  93. }, label: {
  94. Text("Cancel")
  95. })
  96. }
  97. ToolbarItem(placement: .topBarTrailing) {
  98. Button(
  99. action: {
  100. state.isHelpSheetPresented.toggle()
  101. },
  102. label: {
  103. Image(systemName: "questionmark.circle")
  104. }
  105. )
  106. }
  107. }
  108. .onDisappear {
  109. if !hasChanges {
  110. // Reset UI changes
  111. resetValues()
  112. }
  113. }
  114. .sheet(isPresented: $state.isHelpSheetPresented) {
  115. NavigationStack {
  116. List {
  117. VStack(alignment: .leading, spacing: 10) {
  118. VStack(alignment: .leading, spacing: 0) {
  119. Text("This feature can be used to override these therapy settings for a chosen length of time:")
  120. .fixedSize(horizontal: false, vertical: true)
  121. Text("• Basal Rate")
  122. Text(" ◦ Insulin Sensitivity")
  123. Text(" ◦ Carb Ratio")
  124. Text("• Glucose Target")
  125. }
  126. Text(
  127. "There are also options to override your Max SMB Minutes and Max UAM SMB Minutes, as well as to disable SMBs."
  128. )
  129. Text(
  130. "Select \"Start Override\" to immediately start using the Override, or select \"Save as Preset\" to be able to easily start the Override at a later time."
  131. )
  132. Text(
  133. "If an active override preset is edited, the changes will also apply to the currently running override. However, if you edit the currently running override directly, the preset stays unchanged."
  134. )
  135. Text(
  136. "If using Dynamic ISF (without Sigmoid), overriding your ISF will only adjust the limits of the ISF the algorithm is allowed to set."
  137. )
  138. Text(
  139. "If using Dynamic ISF (with Sigmoid), overriding your ISF will adjust the ISF used at your glucose target which extends to the ISF used at other glucose. Overriding your glucose target will change glucose level your ISF will be set to your profile ISF. Both of these can be combined in a single Override."
  140. )
  141. }
  142. }
  143. .padding(.trailing, 10)
  144. .navigationBarTitle("Help", displayMode: .inline)
  145. Button { state.isHelpSheetPresented.toggle() }
  146. label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
  147. .buttonStyle(.bordered)
  148. .padding(.top)
  149. }
  150. .padding()
  151. .presentationDetents(
  152. [.fraction(0.9), .large],
  153. selection: $state.helpSheetDetent
  154. )
  155. }
  156. }
  157. }
  158. @ViewBuilder private func editOverride() -> some View {
  159. Group {
  160. if override.name != nil {
  161. Section {
  162. HStack {
  163. Text("Name")
  164. Spacer()
  165. TextField("Name", text: $name)
  166. .onChange(of: name) { hasChanges = true }
  167. .multilineTextAlignment(.trailing)
  168. }
  169. }
  170. .listRowBackground(Color.chart)
  171. }
  172. // Percentage Picker
  173. Section(footer: state.percentageDescription(percentage)) {
  174. HStack {
  175. Text("Change Basal Rate by")
  176. Spacer()
  177. Text("\(percentage.formatted(.number)) %")
  178. .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)
  179. .onTapGesture {
  180. displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
  181. }
  182. }
  183. if displayPickerPercentage {
  184. HStack {
  185. // Radio buttons and text on the left side
  186. VStack(alignment: .leading) {
  187. // Radio buttons for step iteration
  188. ForEach([1, 5], id: \.self) { step in
  189. RadioButton(isSelected: percentageStep == step, label: "\(step) %") {
  190. percentageStep = step
  191. percentage = Adjustments.StateModel.roundOverridePercentageToStep(percentage, step)
  192. }
  193. .padding(.top, 10)
  194. }
  195. }
  196. .frame(maxWidth: .infinity)
  197. Spacer()
  198. // Picker on the right side
  199. Picker(
  200. selection: percentageSelection,
  201. label: Text("")
  202. ) {
  203. ForEach(
  204. Array(stride(from: 40.0, through: 150.0, by: Double(percentageStep))),
  205. id: \.self
  206. ) { percent in
  207. Text("\(Int(percent)) %").tag(percent)
  208. }
  209. }
  210. .pickerStyle(WheelPickerStyle())
  211. .frame(maxWidth: .infinity)
  212. }
  213. .listRowSeparator(.hidden, edges: .top)
  214. }
  215. // Picker for ISF/CR settings
  216. Picker("Also Change", selection: $selectedIsfCrOption) {
  217. ForEach(IsfAndOrCrOptions.allCases, id: \.self) { option in
  218. Text(option.rawValue).tag(option)
  219. }
  220. }
  221. .pickerStyle(MenuPickerStyle())
  222. .onChange(of: selectedIsfCrOption) { _, newValue in
  223. switch newValue {
  224. case .isfAndCr:
  225. isfAndCr = true
  226. isf = false
  227. cr = false
  228. case .isf:
  229. isfAndCr = false
  230. isf = true
  231. cr = false
  232. case .cr:
  233. isfAndCr = false
  234. isf = false
  235. cr = true
  236. case .nothing:
  237. isfAndCr = false
  238. isf = false
  239. cr = false
  240. }
  241. hasChanges = true
  242. }
  243. }
  244. .listRowBackground(Color.chart)
  245. Section {
  246. Toggle(isOn: $target_override) {
  247. Text("Override Target")
  248. }
  249. .onChange(of: target_override) {
  250. hasChanges = true
  251. }
  252. // Target Glucose Picker
  253. if target_override {
  254. let settingsProvider = PickerSettingsProvider.shared
  255. let glucoseSetting = PickerSetting(value: 0, step: targetStep, min: 72, max: 270, type: .glucose)
  256. TargetPicker(
  257. label: "Target Glucose",
  258. selection: Binding(
  259. get: { target ?? 100 },
  260. set: { target = $0 }
  261. ),
  262. options: settingsProvider.generatePickerValues(
  263. from: glucoseSetting,
  264. units: state.units,
  265. roundMinToStep: true
  266. ),
  267. units: state.units,
  268. hasChanges: $hasChanges,
  269. targetStep: $targetStep,
  270. displayPickerTarget: $displayPickerTarget,
  271. toggleScrollWheel: toggleScrollWheel
  272. )
  273. .onAppear {
  274. if target == 0 || target == nil {
  275. target = 100
  276. }
  277. }
  278. }
  279. }
  280. .listRowBackground(Color.chart)
  281. Section {
  282. // Picker for Disable SMB settings
  283. Picker("Disable SMBs", selection: $selectedDisableSmbOption) {
  284. ForEach(DisableSmbOptions.allCases, id: \.self) { option in
  285. Text(option.rawValue).tag(option)
  286. }
  287. }
  288. .pickerStyle(MenuPickerStyle())
  289. .onChange(of: selectedDisableSmbOption) { _, newValue in
  290. switch newValue {
  291. case .dontDisable:
  292. smbIsOff = false
  293. smbIsScheduledOff = false
  294. case .disable:
  295. smbIsOff = true
  296. smbIsScheduledOff = false
  297. case .disableOnSchedule:
  298. smbIsOff = false
  299. smbIsScheduledOff = true
  300. }
  301. hasChanges = true
  302. }
  303. if smbIsScheduledOff {
  304. // First Hour SMBs Are Disabled
  305. HStack {
  306. Text("From")
  307. Spacer()
  308. Text(
  309. state.is24HourFormat() ? state.format24Hour(Int(truncating: start! as NSNumber)) + ":00" :
  310. state.convertTo12HourFormat(Int(truncating: start! as NSNumber))
  311. )
  312. .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
  313. .onTapGesture {
  314. displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
  315. }
  316. Spacer()
  317. Divider().frame(width: 1, height: 20)
  318. Spacer()
  319. Text("To")
  320. Spacer()
  321. Text(
  322. state.is24HourFormat() ? state.format24Hour(Int(truncating: end! as NSNumber)) + ":00" :
  323. state.convertTo12HourFormat(Int(truncating: end! as NSNumber))
  324. )
  325. .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
  326. .onTapGesture {
  327. displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
  328. }
  329. }
  330. if displayPickerDisableSmbSchedule {
  331. HStack {
  332. Picker(selection: Binding(
  333. get: { Int(truncating: start! as NSNumber) },
  334. set: {
  335. start = Decimal($0)
  336. hasChanges = true
  337. }
  338. ), label: Text("")) {
  339. if state.is24HourFormat() {
  340. ForEach(0 ..< 24, id: \.self) { hour in
  341. Text(state.format24Hour(hour) + ":00").tag(hour)
  342. }
  343. } else {
  344. ForEach(0 ..< 24, id: \.self) { hour in
  345. Text(state.convertTo12HourFormat(hour)).tag(hour)
  346. }
  347. }
  348. }
  349. .pickerStyle(WheelPickerStyle())
  350. .frame(maxWidth: .infinity)
  351. Picker(selection: Binding(
  352. get: { Int(truncating: end! as NSNumber) },
  353. set: {
  354. end = Decimal($0)
  355. hasChanges = true
  356. }
  357. ), label: Text("")) {
  358. if state.is24HourFormat() {
  359. ForEach(0 ..< 24, id: \.self) { hour in
  360. Text(state.format24Hour(hour) + ":00").tag(hour)
  361. }
  362. } else {
  363. ForEach(0 ..< 24, id: \.self) { hour in
  364. Text(state.convertTo12HourFormat(hour)).tag(hour)
  365. }
  366. }
  367. }
  368. .pickerStyle(WheelPickerStyle())
  369. .frame(maxWidth: .infinity)
  370. }
  371. .listRowSeparator(.hidden, edges: .top)
  372. }
  373. }
  374. }
  375. .listRowBackground(Color.chart)
  376. if !smbIsOff {
  377. Section {
  378. Toggle(isOn: $advancedSettings) {
  379. Text("Change Max SMB Minutes")
  380. }
  381. .onChange(of: advancedSettings) { hasChanges = true }
  382. if advancedSettings {
  383. // SMB Minutes Picker
  384. HStack {
  385. Text("SMB")
  386. Spacer()
  387. Text("\(smbMinutes?.formatted(.number) ?? "\(state.defaultSmbMinutes)") min")
  388. .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
  389. .onTapGesture {
  390. displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
  391. }
  392. Spacer()
  393. Divider().frame(width: 1, height: 20)
  394. Spacer()
  395. Text("UAM")
  396. Spacer()
  397. Text("\(uamMinutes?.formatted(.number) ?? "\(state.defaultUamMinutes)") min")
  398. .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
  399. .onTapGesture {
  400. displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
  401. }
  402. }
  403. if displayPickerSmbMinutes {
  404. HStack {
  405. Picker(
  406. selection: Binding(
  407. get: { smbMinutes ?? state.defaultSmbMinutes },
  408. set: {
  409. smbMinutes = $0
  410. hasChanges = true
  411. }
  412. ),
  413. label: Text("")
  414. ) {
  415. ForEach(Array(stride(from: 0, through: 180, by: 5)), id: \.self) { minute in
  416. Text("\(minute) min").tag(Decimal(minute))
  417. }
  418. }
  419. .pickerStyle(WheelPickerStyle())
  420. .frame(maxWidth: .infinity)
  421. Picker(
  422. selection: Binding(
  423. get: { uamMinutes ?? state.defaultUamMinutes },
  424. set: {
  425. uamMinutes = $0
  426. hasChanges = true
  427. }
  428. ),
  429. label: Text("")
  430. ) {
  431. ForEach(Array(stride(from: 0, through: 180, by: 5)), id: \.self) { minute in
  432. Text("\(minute) min").tag(Decimal(minute))
  433. }
  434. }
  435. .pickerStyle(WheelPickerStyle())
  436. .frame(maxWidth: .infinity)
  437. }
  438. .listRowSeparator(.hidden, edges: .top)
  439. }
  440. }
  441. }
  442. .listRowBackground(Color.chart)
  443. }
  444. Section {
  445. Toggle(isOn: $indefinite) { Text("Enable Indefinitely") }
  446. .onChange(of: indefinite) { hasChanges = true }
  447. if !indefinite {
  448. HStack {
  449. Text("Duration")
  450. Spacer()
  451. Text(state.formatHrMin(Int(truncating: duration as NSNumber)))
  452. .foregroundColor(!displayPickerDuration ? .primary : .accentColor)
  453. .onTapGesture {
  454. displayPickerDuration = toggleScrollWheel(displayPickerDuration)
  455. }
  456. }
  457. if displayPickerDuration {
  458. HStack {
  459. Picker(
  460. selection: Binding(
  461. get: {
  462. Int(truncating: duration as NSNumber) / 60
  463. },
  464. set: {
  465. let minutes = Int(truncating: duration as NSNumber) % 60
  466. let totalMinutes = $0 * 60 + minutes
  467. duration = Decimal(totalMinutes)
  468. hasChanges = true
  469. }
  470. ),
  471. label: Text("")
  472. ) {
  473. ForEach(0 ..< 24) { hour in
  474. Text("\(hour) hr").tag(hour)
  475. }
  476. }
  477. .pickerStyle(WheelPickerStyle())
  478. .frame(maxWidth: .infinity)
  479. Picker(
  480. selection: Binding(
  481. get: {
  482. Int(truncating: duration as NSNumber) %
  483. 60 // Convert Decimal to Int for modulus operation
  484. },
  485. set: {
  486. duration = Decimal((Int(truncating: duration as NSNumber) / 60) * 60 + $0)
  487. hasChanges = true
  488. }
  489. ),
  490. label: Text("")
  491. ) {
  492. ForEach(Array(stride(from: 0, through: 55, by: 5)), id: \.self) { minute in
  493. Text("\(minute) min").tag(minute)
  494. }
  495. }
  496. .pickerStyle(WheelPickerStyle())
  497. .frame(maxWidth: .infinity)
  498. }
  499. .listRowSeparator(.hidden, edges: .top)
  500. }
  501. }
  502. }
  503. .listRowBackground(Color.chart)
  504. }
  505. }
  506. private var saveButton: some View {
  507. let (isInvalid, errorMessage) = isOverrideInvalid()
  508. return Section(
  509. header:
  510. HStack {
  511. Spacer()
  512. Text(errorMessage ?? "").textCase(nil)
  513. .foregroundColor(colorScheme == .dark ? .orange : .accentColor)
  514. Spacer()
  515. },
  516. content: {
  517. Button(action: {
  518. saveChanges()
  519. do {
  520. guard let moc = override.managedObjectContext else { return }
  521. guard moc.hasChanges else { return }
  522. try moc.save()
  523. Task {
  524. await state.nightscoutManager.uploadProfiles()
  525. }
  526. // Disable previous active Override
  527. if let currentActiveOverride = state.currentActiveOverride {
  528. Task {
  529. await state.disableAllActiveOverrides(
  530. except: currentActiveOverride.objectID,
  531. createOverrideRunEntry: false
  532. )
  533. // Update View
  534. state.updateLatestOverrideConfiguration()
  535. }
  536. }
  537. hasChanges = false
  538. presentationMode.wrappedValue.dismiss()
  539. } catch {
  540. debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to edit Override")
  541. }
  542. }, label: {
  543. Text("Save Override")
  544. })
  545. .disabled(isInvalid) // Disable button if changes are invalid
  546. .frame(maxWidth: .infinity, alignment: .center)
  547. .tint(.white)
  548. }
  549. )
  550. .listRowBackground(isInvalid ? Color(.systemGray4) : Color(.systemBlue))
  551. }
  552. private func isOverrideInvalid() -> (Bool, String?) {
  553. let noDurationSpecified = !indefinite && duration == 0
  554. let targetZeroWithOverride = target_override && (target ?? 0 < 72 || target ?? 0 > 270)
  555. let allSettingsDefault = percentage == 100 && !target_override && !advancedSettings &&
  556. !smbIsOff && !smbIsScheduledOff
  557. if noDurationSpecified {
  558. return (true, "Enable indefinitely or set a duration.")
  559. }
  560. if targetZeroWithOverride {
  561. return (true, "Target glucose is out of range (\(state.units == .mgdL ? "72-270" : "4-14")).")
  562. }
  563. if allSettingsDefault {
  564. return (true, "All settings are at default values.")
  565. }
  566. if !hasChanges {
  567. return (true, nil)
  568. }
  569. return (false, nil)
  570. }
  571. private func saveChanges() {
  572. if !override.isPreset, hasChanges, name == (override.name ?? "") {
  573. override.name = "Custom Override"
  574. } else {
  575. override.name = name
  576. }
  577. override.percentage = percentage
  578. override.indefinite = indefinite
  579. override.duration = NSDecimalNumber(decimal: duration)
  580. override.target = target_override ? NSDecimalNumber(decimal: target ?? 100) : nil
  581. override.advancedSettings = advancedSettings
  582. override.smbIsOff = smbIsOff
  583. override.smbIsScheduledOff = smbIsScheduledOff
  584. override.start = start.map { NSDecimalNumber(decimal: $0) }
  585. override.end = end.map { NSDecimalNumber(decimal: $0) }
  586. override.isfAndCr = isfAndCr
  587. override.isf = isf
  588. override.cr = cr
  589. override.smbMinutes = smbMinutes.map { NSDecimalNumber(decimal: $0) }
  590. override.uamMinutes = uamMinutes.map { NSDecimalNumber(decimal: $0) }
  591. override.isUploadedToNS = false
  592. }
  593. private func resetValues() {
  594. name = override.name ?? ""
  595. percentage = override.percentage
  596. indefinite = override.indefinite
  597. duration = override.duration?.decimalValue ?? 0
  598. target = override.target?.decimalValue
  599. advancedSettings = override.advancedSettings
  600. smbIsOff = override.smbIsOff
  601. smbIsScheduledOff = override.smbIsScheduledOff
  602. start = override.start?.decimalValue
  603. end = override.end?.decimalValue
  604. isfAndCr = override.isfAndCr
  605. isf = override.isf
  606. cr = override.cr
  607. smbMinutes = override.smbMinutes?.decimalValue ?? state.defaultSmbMinutes
  608. uamMinutes = override.uamMinutes?.decimalValue ?? state.defaultUamMinutes
  609. }
  610. private func toggleScrollWheel(_ toggle: Bool) -> Bool {
  611. displayPickerDuration = false
  612. displayPickerPercentage = false
  613. displayPickerTarget = false
  614. displayPickerDisableSmbSchedule = false
  615. displayPickerSmbMinutes = false
  616. return !toggle
  617. }
  618. }