Преглед изворни кода

port PRed changes to textfield from Trio, use numberPad instead of decimalPad for carbs/fpus

polscm32 aka Marvout пре 1 година
родитељ
комит
40911c5f19

+ 4 - 4
FreeAPS.xcodeproj/project.pbxproj

@@ -144,7 +144,7 @@
 		3870FF4725EC187A0088248F /* BloodGlucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3870FF4225EC13F40088248F /* BloodGlucose.swift */; };
 		3871F39C25ED892B0013ECB5 /* TempTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3871F39B25ED892B0013ECB5 /* TempTarget.swift */; };
 		3871F39F25ED895A0013ECB5 /* Decimal+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3871F39E25ED895A0013ECB5 /* Decimal+Extensions.swift */; };
-		3883581C25EE79BB00E024B2 /* DecimalTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3883581B25EE79BB00E024B2 /* DecimalTextField.swift */; };
+		3883581C25EE79BB00E024B2 /* TextFieldWithToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3883581B25EE79BB00E024B2 /* TextFieldWithToolBar.swift */; };
 		3883583425EEB38000E024B2 /* PumpSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3883583325EEB38000E024B2 /* PumpSettings.swift */; };
 		388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388358C725EEF6D200E024B2 /* BasalProfileEntry.swift */; };
 		38887CCE25F5725200944304 /* IOBEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38887CCD25F5725200944304 /* IOBEntry.swift */; };
@@ -750,7 +750,7 @@
 		3870FF4225EC13F40088248F /* BloodGlucose.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloodGlucose.swift; sourceTree = "<group>"; };
 		3871F39B25ED892B0013ECB5 /* TempTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTarget.swift; sourceTree = "<group>"; };
 		3871F39E25ED895A0013ECB5 /* Decimal+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+Extensions.swift"; sourceTree = "<group>"; };
-		3883581B25EE79BB00E024B2 /* DecimalTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalTextField.swift; sourceTree = "<group>"; };
+		3883581B25EE79BB00E024B2 /* TextFieldWithToolBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldWithToolBar.swift; sourceTree = "<group>"; };
 		3883583325EEB38000E024B2 /* PumpSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpSettings.swift; sourceTree = "<group>"; };
 		388358C725EEF6D200E024B2 /* BasalProfileEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalProfileEntry.swift; sourceTree = "<group>"; };
 		38887CCD25F5725200944304 /* IOBEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOBEntry.swift; sourceTree = "<group>"; };
@@ -1726,7 +1726,7 @@
 			isa = PBXGroup;
 			children = (
 				3811DE5925C9D4D500A708ED /* ViewModifiers.swift */,
-				3883581B25EE79BB00E024B2 /* DecimalTextField.swift */,
+				3883581B25EE79BB00E024B2 /* TextFieldWithToolBar.swift */,
 				383420D825FFEB3F002D46C1 /* Popup.swift */,
 				389ECDFD2601061500D86C4F /* View+Snapshot.swift */,
 				38EA05FF262091870064E39B /* BolusProgressViewStyle.swift */,
@@ -3041,7 +3041,7 @@
 				38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */,
 				38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */,
 				FE66D16B291F74F8005D6F77 /* Bundle+Extensions.swift in Sources */,
-				3883581C25EE79BB00E024B2 /* DecimalTextField.swift in Sources */,
+				3883581C25EE79BB00E024B2 /* TextFieldWithToolBar.swift in Sources */,
 				6B1A8D2E2B156EEF00E76752 /* LiveActivityBridge.swift in Sources */,
 				581516A92BCEEDF800BF67D7 /* NSPredicates.swift in Sources */,
 				38DAB28A260D349500F74C1A /* FetchGlucoseManager.swift in Sources */,

+ 8 - 3
FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift

@@ -259,13 +259,18 @@ extension Bolus {
             HStack {
                 Text("Fat").foregroundColor(.orange)
                 Spacer()
-                TextFieldWithToolBar(text: $state.fat, placeholder: "0", numberFormatter: mealFormatter)
+                TextFieldWithToolBar(text: $state.fat, placeholder: "0", keyboardType: .numberPad, numberFormatter: mealFormatter)
                 Text("g").foregroundColor(.secondary)
             }
             HStack {
                 Text("Protein").foregroundColor(.red)
                 Spacer()
-                TextFieldWithToolBar(text: $state.protein, placeholder: "0", numberFormatter: mealFormatter)
+                TextFieldWithToolBar(
+                    text: $state.protein,
+                    placeholder: "0",
+                    keyboardType: .numberPad,
+                    numberFormatter: mealFormatter
+                )
                 Text("g").foregroundColor(.secondary)
             }
         }
@@ -277,7 +282,7 @@ extension Bolus {
                 TextFieldWithToolBar(
                     text: $state.carbs,
                     placeholder: "0",
-                    shouldBecomeFirstResponder: false,
+                    keyboardType: .numberPad,
                     numberFormatter: mealFormatter
                 )
                 .onChange(of: state.carbs) { _ in

+ 8 - 1
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -38,6 +38,7 @@ extension NightscoutConfig {
         private var portFormater: NumberFormatter {
             let formatter = NumberFormatter()
             formatter.allowsFloats = false
+            formatter.usesGroupingSeparator = false
             return formatter
         }
 
@@ -145,7 +146,13 @@ extension NightscoutConfig {
                     Toggle("Use local glucose server", isOn: $state.useLocalSource)
                     HStack {
                         Text("Port")
-                        TextFieldWithToolBar(text: $state.localPort, placeholder: "", numberFormatter: portFormater)
+                        TextFieldWithToolBar(
+                            text: $state.localPort,
+                            placeholder: "",
+                            keyboardType: .numberPad,
+                            numberFormatter: portFormater,
+                            allowDecimalSeparator: false
+                        )
                     }
                 } header: { Text("Local glucose source") }
                 Section {

+ 127 - 3
FreeAPS/Sources/Views/DecimalTextField.swift

@@ -14,6 +14,7 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
     var isDismissible: Bool
     var textFieldDidBeginEditing: (() -> Void)?
     var numberFormatter: NumberFormatter
+    var allowDecimalSeparator: Bool
 
     public init(
         text: Binding<Decimal>,
@@ -27,7 +28,8 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
         maxLength: Int? = nil,
         isDismissible: Bool = true,
         textFieldDidBeginEditing: (() -> Void)? = nil,
-        numberFormatter: NumberFormatter
+        numberFormatter: NumberFormatter,
+        allowDecimalSeparator: Bool = true
     ) {
         _text = text
         self.placeholder = placeholder
@@ -42,6 +44,7 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
         self.textFieldDidBeginEditing = textFieldDidBeginEditing
         self.numberFormatter = numberFormatter
         self.numberFormatter.numberStyle = .decimal
+        self.allowDecimalSeparator = allowDecimalSeparator
     }
 
     public func makeUIView(context: Context) -> UITextField {
@@ -146,7 +149,7 @@ extension TextFieldWithToolBar.Coordinator: UITextFieldDelegate {
         let isDecimalSeparator = (string == decimalFormatter.decimalSeparator && textField.text?.contains(string) == false)
 
         // Only proceed if the input is a valid number or decimal separator
-        if isNumber || isDecimalSeparator,
+        if isNumber || isDecimalSeparator && parent.allowDecimalSeparator,
            let currentText = textField.text as NSString?
         {
             // Get the proposed new text
@@ -164,7 +167,7 @@ extension TextFieldWithToolBar.Coordinator: UITextFieldDelegate {
         }
 
         // Allow the change if it's a valid number or decimal separator
-        return isNumber || isDecimalSeparator
+        return isNumber || isDecimalSeparator && parent.allowDecimalSeparator
     }
 
     public func textFieldDidBeginEditing(_: UITextField) {
@@ -185,3 +188,124 @@ extension UIApplication {
         sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
     }
 }
+
+public struct TextFieldWithToolBarString: UIViewRepresentable {
+    @Binding var text: String
+    var placeholder: String
+    var textAlignment: NSTextAlignment = .right
+    var keyboardType: UIKeyboardType = .default
+    var autocapitalizationType: UITextAutocapitalizationType = .none
+    var autocorrectionType: UITextAutocorrectionType = .no
+    var shouldBecomeFirstResponder: Bool = false
+    var maxLength: Int? = nil
+    var isDismissible: Bool = true
+
+    public func makeUIView(context: Context) -> UITextField {
+        let textField = UITextField()
+        context.coordinator.textField = textField
+        textField.inputAccessoryView = isDismissible ? makeDoneToolbar(for: textField, context: context) : nil
+        textField.addTarget(context.coordinator, action: #selector(Coordinator.editingDidBegin), for: .editingDidBegin)
+        textField.delegate = context.coordinator
+        textField.text = text
+        textField.placeholder = placeholder
+        textField.textAlignment = textAlignment
+        textField.keyboardType = keyboardType
+        textField.autocapitalizationType = autocapitalizationType
+        textField.autocorrectionType = autocorrectionType
+        textField.adjustsFontSizeToFitWidth = true
+        return textField
+    }
+
+    private func makeDoneToolbar(for textField: UITextField, context: Context) -> UIToolbar {
+        let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
+        let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
+        let doneButton = UIBarButtonItem(
+            image: UIImage(systemName: "keyboard.chevron.compact.down"),
+            style: .done,
+            target: textField,
+            action: #selector(UITextField.resignFirstResponder)
+        )
+        let clearButton = UIBarButtonItem(
+            image: UIImage(systemName: "trash"),
+            style: .plain,
+            target: context.coordinator,
+            action: #selector(Coordinator.clearText)
+        )
+
+        toolbar.items = [clearButton, flexibleSpace, doneButton]
+        toolbar.sizeToFit()
+        return toolbar
+    }
+
+    public func updateUIView(_ textField: UITextField, context: Context) {
+        if textField.text != text {
+            textField.text = text
+        }
+
+        textField.textAlignment = textAlignment
+        textField.keyboardType = keyboardType
+        textField.autocapitalizationType = autocapitalizationType
+        textField.autocorrectionType = autocorrectionType
+
+        if shouldBecomeFirstResponder, !context.coordinator.didBecomeFirstResponder {
+            if textField.window != nil, textField.becomeFirstResponder() {
+                context.coordinator.didBecomeFirstResponder = true
+            }
+        } else if !shouldBecomeFirstResponder, context.coordinator.didBecomeFirstResponder {
+            context.coordinator.didBecomeFirstResponder = false
+        }
+    }
+
+    public func makeCoordinator() -> Coordinator {
+        Coordinator(self, maxLength: maxLength)
+    }
+
+    public final class Coordinator: NSObject {
+        var parent: TextFieldWithToolBarString
+        var textField: UITextField?
+        let maxLength: Int?
+        var didBecomeFirstResponder = false
+
+        init(_ parent: TextFieldWithToolBarString, maxLength: Int?) {
+            self.parent = parent
+            self.maxLength = maxLength
+        }
+
+        @objc fileprivate func clearText() {
+            parent.text = ""
+            textField?.text = ""
+        }
+
+        @objc fileprivate func editingDidBegin(_ textField: UITextField) {
+            DispatchQueue.main.async {
+                textField.moveCursorToEnd()
+            }
+        }
+    }
+}
+
+extension TextFieldWithToolBarString.Coordinator: UITextFieldDelegate {
+    public func textField(
+        _ textField: UITextField,
+        shouldChangeCharactersIn range: NSRange,
+        replacementString string: String
+    ) -> Bool {
+        if let maxLength = parent.maxLength {
+            // Get the current text, including the proposed change
+            let currentText = textField.text ?? ""
+            let newLength = currentText.count + string.count - range.length
+            if newLength > maxLength {
+                return false
+            }
+        }
+
+        DispatchQueue.main.async {
+            if let textFieldText = textField.text as NSString? {
+                let newText = textFieldText.replacingCharacters(in: range, with: string)
+                self.parent.text = newText
+            }
+        }
+
+        return true
+    }
+}