99import SwiftUI
1010import Combine
1111import LoopCore
12+ import LoopKit
13+ import HealthKit
1214
1315struct MicrobolusView : View {
1416 final class ViewModel : ObservableObject {
@@ -19,6 +21,8 @@ struct MicrobolusView: View {
1921 @Published var safeMode : Microbolus . SafeMode
2022 @Published var microbolusesMinimumBolusSize : Double
2123 @Published var openBolusScreen : Bool
24+ @Published var disableByOverride : Bool
25+ @Published var lowerBound : Double
2226
2327 @Published fileprivate var pickerWithCOBIndex : Int
2428 @Published fileprivate var pickerWithoutCOBIndex : Int
@@ -29,19 +33,25 @@ struct MicrobolusView: View {
2933 fileprivate let minimumBolusSizeValues = stride ( from: 0.0 , to: 0.51 , by: 0.05 ) . map { $0 }
3034
3135 private var cancellable : AnyCancellable !
36+ fileprivate let formatter = QuantityFormatter ( )
37+ fileprivate let unit : HKUnit
3238
33- init ( microbolusesWithCOB: Bool , withCOBValue: Double , microbolusesWithoutCOB: Bool , withoutCOBValue: Double , safeMode: Microbolus . SafeMode , microbolusesMinimumBolusSize: Double , openBolusScreen: Bool ) {
34- self . microbolusesWithCOB = microbolusesWithCOB
35- self . withCOBValue = withCOBValue
36- self . microbolusesWithoutCOB = microbolusesWithoutCOB
37- self . withoutCOBValue = withoutCOBValue
38- self . safeMode = safeMode
39- self . microbolusesMinimumBolusSize = microbolusesMinimumBolusSize
40- self . openBolusScreen = openBolusScreen
39+ init ( settings: Microbolus . Settings , glucoseUnit: HKUnit ) {
40+ self . microbolusesWithCOB = settings. enabled
41+ self . withCOBValue = settings. size
42+ self . microbolusesWithoutCOB = settings. enabledWithoutCarbs
43+ self . withoutCOBValue = settings. sizeWithoutCarbs
44+ self . safeMode = settings. safeMode
45+ self . microbolusesMinimumBolusSize = settings. minimumBolusSize
46+ self . openBolusScreen = settings. shouldOpenBolusScreen
47+ self . disableByOverride = settings. disableByOverride
48+ self . lowerBound = settings. overrideLowerBound
49+ self . unit = glucoseUnit
50+ formatter. setPreferredNumberFormatter ( for: glucoseUnit)
4151
42- pickerWithCOBIndex = values. firstIndex ( of: Int ( withCOBValue ) ) ?? 0
43- pickerWithoutCOBIndex = values. firstIndex ( of: Int ( withoutCOBValue ) ) ?? 0
44- pickerMinimumBolusSizeIndex = minimumBolusSizeValues. firstIndex ( of: Double ( microbolusesMinimumBolusSize ) ) ?? 0
52+ pickerWithCOBIndex = values. firstIndex ( of: Int ( settings . size ) ) ?? 0
53+ pickerWithoutCOBIndex = values. firstIndex ( of: Int ( settings . sizeWithoutCarbs ) ) ?? 0
54+ pickerMinimumBolusSizeIndex = minimumBolusSizeValues. firstIndex ( of: Double ( settings . minimumBolusSize ) ) ?? 0
4555
4656 let withCOBCancellable = $pickerWithCOBIndex
4757 . map { Double ( self . values [ $0] ) }
@@ -63,33 +73,40 @@ struct MicrobolusView: View {
6373 }
6474
6575 func changes( ) -> AnyPublisher < Microbolus . Settings , Never > {
66- Publishers . CombineLatest4 (
76+ Publishers . CombineLatest3 (
6777 Publishers . CombineLatest4 (
6878 $microbolusesWithCOB,
6979 $withCOBValue,
7080 $microbolusesWithoutCOB,
7181 $withoutCOBValue
7282 ) ,
73- $safeMode,
74- $microbolusesMinimumBolusSize,
75- $openBolusScreen
83+ Publishers . CombineLatest4 (
84+ $safeMode,
85+ $microbolusesMinimumBolusSize,
86+ $openBolusScreen,
87+ $disableByOverride
88+ ) ,
89+ $lowerBound
7690 )
7791 . map {
7892 Microbolus . Settings (
7993 enabled: $0. 0 . 0 ,
8094 size: $0. 0 . 1 ,
8195 enabledWithoutCarbs: $0. 0 . 2 ,
8296 sizeWithoutCarb: $0. 0 . 3 ,
83- safeMode: $0. 1 ,
84- minimumBolusSize: $0. 2 ,
85- shouldOpenBolusScreen: $0. 3
97+ safeMode: $0. 1 . 0 ,
98+ minimumBolusSize: $0. 1 . 1 ,
99+ shouldOpenBolusScreen: $0. 1 . 2 ,
100+ disableByOverride: $0. 1 . 3 ,
101+ overrideLowerBound: $0. 2
86102 )
87103 }
88104 . eraseToAnyPublisher ( )
89105 }
90106 }
91107
92108 @ObservedObject var viewModel : ViewModel
109+ @ObservedObject private var keyboard = KeyboardResponder ( )
93110
94111 init ( viewModel: ViewModel ) {
95112 self . viewModel = viewModel
@@ -152,6 +169,28 @@ struct MicrobolusView: View {
152169 Toggle ( isOn: $viewModel. openBolusScreen) {
153170 Text ( " Open Bolus screen after Carbs " )
154171 }
172+
173+ Toggle ( isOn: $viewModel. disableByOverride) {
174+ Text ( " Disable microboluses by override " )
175+ }
176+
177+ if viewModel. disableByOverride {
178+ Text ( " Blablabla " )
179+ . font ( . caption)
180+
181+ HStack {
182+ Text ( " Lower bound " )
183+
184+ TextField ( " 0 " , value: $viewModel. lowerBound, formatter: viewModel. formatter. numberFormatter, onEditingChanged: { changed in
185+
186+ } ) { self . dismissKeyboard ( ) }
187+ . keyboardType ( . numberPad)
188+ . multilineTextAlignment ( . trailing)
189+
190+ Text ( viewModel. unit. localizedShortUnitString)
191+ }
192+ }
193+
155194 Picker ( selection: $viewModel. pickerMinimumBolusSizeIndex, label: Text ( " Minimum Bolus Size " ) ) {
156195 ForEach ( 0 ..< viewModel. minimumBolusSizeValues. count) { index in Text ( String ( format: " %.2f U " , self . viewModel. minimumBolusSizeValues [ index] ) ) . tag ( index)
157196 }
@@ -160,6 +199,11 @@ struct MicrobolusView: View {
160199
161200 }
162201 . navigationBarTitle ( " Microboluses " )
202+ . padding ( . bottom, keyboard. currentHeight)
203+ . animation ( . easeOut( duration: 0.16 ) )
204+ . onTapGesture {
205+ self . dismissKeyboard ( )
206+ }
163207 }
164208}
165209
@@ -179,16 +223,50 @@ private extension Microbolus.SafeMode {
179223struct MicrobolusView_Previews : PreviewProvider {
180224 static var previews : some View {
181225 MicrobolusView ( viewModel: . init(
182- microbolusesWithCOB: true ,
183- withCOBValue: 30 ,
184- microbolusesWithoutCOB: false ,
185- withoutCOBValue: 30 ,
186- safeMode: . enabled,
187- microbolusesMinimumBolusSize: 0.0 ,
188- openBolusScreen: false
226+ settings: Microbolus . Settings ( ) ,
227+ glucoseUnit: HKUnit ( from: " mmol/L " )
189228 )
190229 )
191230 . environment ( \. colorScheme, . dark)
192231 . previewLayout ( . sizeThatFits)
193232 }
194233}
234+
235+ // MARK: - Helpers
236+
237+ extension UIApplication {
238+ func endEditing( ) {
239+ sendAction ( #selector( UIResponder . resignFirstResponder) , to: nil , from: nil , for: nil )
240+ }
241+ }
242+
243+ extension View {
244+ func dismissKeyboard( ) {
245+ UIApplication . shared. endEditing ( )
246+ }
247+ }
248+
249+ final class KeyboardResponder : ObservableObject {
250+ private var notificationCenter : NotificationCenter
251+ @Published private( set) var currentHeight : CGFloat = 0
252+
253+ init ( center: NotificationCenter = . default) {
254+ notificationCenter = center
255+ notificationCenter. addObserver ( self , selector: #selector( keyBoardWillShow ( notification: ) ) , name: UIResponder . keyboardWillShowNotification, object: nil )
256+ notificationCenter. addObserver ( self , selector: #selector( keyBoardWillHide ( notification: ) ) , name: UIResponder . keyboardWillHideNotification, object: nil )
257+ }
258+
259+ deinit {
260+ notificationCenter. removeObserver ( self )
261+ }
262+
263+ @objc func keyBoardWillShow( notification: Notification ) {
264+ if let keyboardSize = ( notification. userInfo ? [ UIResponder . keyboardFrameEndUserInfoKey] as? NSValue ) ? . cgRectValue {
265+ currentHeight = keyboardSize. height
266+ }
267+ }
268+
269+ @objc func keyBoardWillHide( notification: Notification ) {
270+ currentHeight = 0
271+ }
272+ }
0 commit comments