Kaynağa Gözat

Merge pull request #712 from aidanlane/dev

fix: Accidentally tapping on suffix of a field does not focus input
Deniz Cengiz 9 ay önce
ebeveyn
işleme
a6fb896d41

+ 2 - 2
Trio/Sources/Modules/Calibrations/View/CalibrationsRootView.swift

@@ -48,9 +48,9 @@ extension Calibrations {
                             TextFieldWithToolBar(
                                 text: $state.newCalibration,
                                 placeholder: "0",
-                                numberFormatter: manualGlucoseFormatter
+                                numberFormatter: manualGlucoseFormatter,
+                                unitsText: state.units.rawValue
                             )
-                            Text(state.units.rawValue).foregroundColor(.secondary)
                         }
                         Button {
                             Task {

+ 6 - 6
Trio/Sources/Modules/DataTable/View/CarbEntryEditorView.swift

@@ -138,9 +138,9 @@ struct CarbEntryEditorView: View {
                             text: $editedCarbs,
                             placeholder: "0",
                             keyboardType: .numberPad,
-                            numberFormatter: mealFormatter
+                            numberFormatter: mealFormatter,
+                            unitsText: String(localized: "g", comment: "Units for carbs")
                         )
-                        Text("g").foregroundStyle(.secondary)
                     }
 
                     if state.settingsManager.settings.useFPUconversion {
@@ -150,9 +150,9 @@ struct CarbEntryEditorView: View {
                                 text: $editedProtein,
                                 placeholder: "0",
                                 keyboardType: .numberPad,
-                                numberFormatter: mealFormatter
+                                numberFormatter: mealFormatter,
+                                unitsText: String(localized: "g", comment: "Units for carbs")
                             )
-                            Text("g").foregroundStyle(.secondary)
                         }
 
                         HStack {
@@ -161,9 +161,9 @@ struct CarbEntryEditorView: View {
                                 text: $editedFat,
                                 placeholder: "0",
                                 keyboardType: .numberPad,
-                                numberFormatter: mealFormatter
+                                numberFormatter: mealFormatter,
+                                unitsText: String(localized: "g", comment: "Units for carbs")
                             )
-                            Text("g").foregroundStyle(.secondary)
                         }
                     }
 

+ 2 - 2
Trio/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -440,9 +440,9 @@ extension DataTable {
                                     placeholder: " ... ",
                                     keyboardType: state.units == .mgdL ? .numberPad : .decimalPad,
                                     numberFormatter: manualGlucoseFormatter,
-                                    initialFocus: true
+                                    initialFocus: true,
+                                    unitsText: state.units.rawValue
                                 )
-                                Text(state.units.rawValue).foregroundStyle(.secondary)
                             }
                         }.listRowBackground(Color.chart)
 

+ 2 - 2
Trio/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift

@@ -27,9 +27,9 @@ extension ManualTempBasal {
                             text: $state.rate,
                             placeholder: "0",
                             numberFormatter: formatter,
-                            initialFocus: true
+                            initialFocus: true,
+                            unitsText: String(localized: "U/hr", comment: "Units text for temporary basal rate")
                         )
-                        Text("U/hr").foregroundColor(.secondary)
                     }
                     Picker(selection: $state.durationIndex, label: Text("Duration")) {
                         ForEach(0 ..< state.durationValues.count) { index in

+ 6 - 6
Trio/Sources/Modules/Treatments/View/MealPreset/AddMealPresetView.swift

@@ -75,9 +75,9 @@ struct AddMealPresetView: View {
                 text: $presetCarbs,
                 placeholder: "0",
                 keyboardType: .numberPad,
-                numberFormatter: mealFormatter
+                numberFormatter: mealFormatter,
+                unitsText: String(localized: "g", comment: "Units for carbs")
             )
-            Text("g").foregroundColor(.secondary)
         }
     }
 
@@ -89,9 +89,9 @@ struct AddMealPresetView: View {
                 text: $presetProtein,
                 placeholder: "0",
                 keyboardType: .numberPad,
-                numberFormatter: mealFormatter
+                numberFormatter: mealFormatter,
+                unitsText: String(localized: "g", comment: "Units for carbs")
             )
-            Text("g").foregroundColor(.secondary)
         }
         HStack {
             Text("Fat").foregroundColor(.orange)
@@ -100,9 +100,9 @@ struct AddMealPresetView: View {
                 text: $presetFat,
                 placeholder: "0",
                 keyboardType: .numberPad,
-                numberFormatter: mealFormatter
+                numberFormatter: mealFormatter,
+                unitsText: String(localized: "g", comment: "Units for carbs")
             )
-            Text("g").foregroundColor(.secondary)
         }
     }
 

+ 8 - 8
Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -93,10 +93,10 @@ extension Treatments {
                         numberFormatter: mealFormatter,
                         showArrows: true,
                         previousTextField: { focusedField = previousField(from: .protein) },
-                        nextTextField: { focusedField = nextField(from: .protein) }
+                        nextTextField: { focusedField = nextField(from: .protein) },
+                        unitsText: String(localized: "g", comment: "Units for carbs")
                     )
                     .focused($focusedField, equals: .protein)
-                    Text("g").foregroundColor(.secondary)
                 }
 
                 Divider().foregroundStyle(.primary).fontWeight(.bold).frame(width: 10)
@@ -110,10 +110,10 @@ extension Treatments {
                         numberFormatter: mealFormatter,
                         showArrows: true,
                         previousTextField: { focusedField = previousField(from: .fat) },
-                        nextTextField: { focusedField = nextField(from: .fat) }
+                        nextTextField: { focusedField = nextField(from: .fat) },
+                        unitsText: String(localized: "g", comment: "Units for carbs")
                     )
                     .focused($focusedField, equals: .fat)
-                    Text("g").foregroundColor(.secondary)
                 }
             }
         }
@@ -129,13 +129,13 @@ extension Treatments {
                     numberFormatter: mealFormatter,
                     showArrows: true,
                     previousTextField: { focusedField = previousField(from: .carbs) },
-                    nextTextField: { focusedField = nextField(from: .carbs) }
+                    nextTextField: { focusedField = nextField(from: .carbs) },
+                    unitsText: String(localized: "g", comment: "Units for carbs")
                 )
                 .focused($focusedField, equals: .carbs)
                 .onChange(of: state.carbs) {
                     handleDebouncedInput()
                 }
-                Text("g").foregroundColor(.secondary)
             }
         }
 
@@ -331,14 +331,14 @@ extension Treatments {
                                     numberFormatter: formatter,
                                     showArrows: true,
                                     previousTextField: { focusedField = previousField(from: .bolus) },
-                                    nextTextField: { focusedField = nextField(from: .bolus) }
+                                    nextTextField: { focusedField = nextField(from: .bolus) },
+                                    unitsText: String(localized: "U", comment: "Units for bolus amount")
                                 ).focused($focusedField, equals: .bolus)
                                     .onChange(of: state.amount) {
                                         Task {
                                             await state.updateForecasts()
                                         }
                                     }
-                                Text(" U").foregroundColor(.secondary)
                             }
 
                             HStack {

+ 129 - 115
Trio/Sources/Views/TextFieldWithToolBar.swift

@@ -18,6 +18,8 @@ public struct TextFieldWithToolBar: View {
     var previousTextField: (() -> Void)?
     var nextTextField: (() -> Void)?
     var initialFocus: Bool
+    var unitsText: String?
+    var unitsTextColor: Color
 
     @FocusState private var isFocused: Bool
     @State private var localText: String = ""
@@ -40,7 +42,9 @@ public struct TextFieldWithToolBar: View {
         showArrows: Bool = false,
         previousTextField: (() -> Void)? = nil,
         nextTextField: (() -> Void)? = nil,
-        initialFocus: Bool = false
+        initialFocus: Bool = false,
+        unitsText: String? = nil,
+        unitsTextColor: Color = .secondary
     ) {
         _text = text
         self.placeholder = placeholder
@@ -59,150 +63,160 @@ public struct TextFieldWithToolBar: View {
         self.previousTextField = previousTextField
         self.nextTextField = nextTextField
         self.initialFocus = initialFocus
+        self.unitsText = unitsText
+        self.unitsTextColor = unitsTextColor
     }
 
     public var body: some View {
-        TextField(placeholder, text: $localText)
-            .focused($isFocused)
-            .multilineTextAlignment(textAlignment)
-            .foregroundColor(textColor)
-            .keyboardType(keyboardType)
-            .toolbar {
-                if isFocused {
-                    ToolbarItemGroup(placement: .keyboard) {
-                        Button(action: {
-                            localText = ""
-                            text = 0
-                            isZeroCleared = true // Mark as cleared to prevent showing "0"
-                            textDidChange?(0)
-                        }) {
-                            Image(systemName: "trash")
-                        }
-
-                        if showArrows {
-                            Button(action: { previousTextField?() }) {
-                                Image(systemName: "chevron.up")
+        HStack {
+            TextField(placeholder, text: $localText)
+                .focused($isFocused)
+                .multilineTextAlignment(textAlignment)
+                .foregroundColor(textColor)
+                .keyboardType(keyboardType)
+                .toolbar {
+                    if isFocused {
+                        ToolbarItemGroup(placement: .keyboard) {
+                            Button(action: {
+                                localText = ""
+                                text = 0
+                                isZeroCleared = true // Mark as cleared to prevent showing "0"
+                                textDidChange?(0)
+                            }) {
+                                Image(systemName: "trash")
                             }
-                            Button(action: { nextTextField?() }) {
-                                Image(systemName: "chevron.down")
+
+                            if showArrows {
+                                Button(action: { previousTextField?() }) {
+                                    Image(systemName: "chevron.up")
+                                }
+                                Button(action: { nextTextField?() }) {
+                                    Image(systemName: "chevron.down")
+                                }
                             }
-                        }
 
-                        Spacer()
+                            Spacer()
 
-                        if isDismissible {
-                            Button(action: { isFocused = false }) {
-                                Image(systemName: "keyboard.chevron.compact.down")
+                            if isDismissible {
+                                Button(action: { isFocused = false }) {
+                                    Image(systemName: "keyboard.chevron.compact.down")
+                                }
                             }
                         }
                     }
                 }
-            }
-            .onChange(of: isFocused) { _, newValue in
-                if newValue {
-                    textFieldDidBeginEditing?()
-                    // When gaining focus: if the value is zero and was previously cleared,
-                    // keep the text field empty to show placeholder instead of "0"
-                    if isZeroCleared, text == 0 {
-                        localText = ""
-                    }
-                } else {
-                    // When losing focus: handle formatting and validation
-                    if localText.isEmpty {
-                        // If field is empty, maintain zero value but mark as cleared
-                        // so we can show placeholder instead of "0"
-                        text = 0
-                        isZeroCleared = true
-                    } else if let decimal = Decimal(string: localText, locale: numberFormatter.locale) {
-                        if decimal != 0 {
-                            // For non-zero values, format normally and update binding
-                            text = decimal
-                            localText = numberFormatter.string(from: decimal as NSNumber) ?? ""
-                            isZeroCleared = false
-                        } else {
-                            // If user explicitly entered zero, store the value but
-                            // keep display empty to show placeholder
-                            text = 0
+                .onChange(of: isFocused) { _, newValue in
+                    if newValue {
+                        textFieldDidBeginEditing?()
+                        // When gaining focus: if the value is zero and was previously cleared,
+                        // keep the text field empty to show placeholder instead of "0"
+                        if isZeroCleared, text == 0 {
                             localText = ""
+                        }
+                    } else {
+                        // When losing focus: handle formatting and validation
+                        if localText.isEmpty {
+                            // If field is empty, maintain zero value but mark as cleared
+                            // so we can show placeholder instead of "0"
+                            text = 0
                             isZeroCleared = true
+                        } else if let decimal = Decimal(string: localText, locale: numberFormatter.locale) {
+                            if decimal != 0 {
+                                // For non-zero values, format normally and update binding
+                                text = decimal
+                                localText = numberFormatter.string(from: decimal as NSNumber) ?? ""
+                                isZeroCleared = false
+                            } else {
+                                // If user explicitly entered zero, store the value but
+                                // keep display empty to show placeholder
+                                text = 0
+                                localText = ""
+                                isZeroCleared = true
+                            }
                         }
                     }
                 }
-            }
-            .onChange(of: localText) { oldValue, newValue in
-                // Reset zero-cleared state as soon as user starts typing anything
-                if !newValue.isEmpty {
-                    isZeroCleared = false
-                }
-
-                // Special handling for backspace operations to maintain decimal format
-                if oldValue.count == newValue.count + 1 {
-                    let decimalSeparator = numberFormatter.decimalSeparator ?? "."
-
-                    // Special case: When backspacing to leave only a decimal point
-                    // e.g., "10.1" -> "10." - Keep decimal separator without adding trailing zero
-                    if newValue.hasSuffix(decimalSeparator) {
-                        if let decimal = Decimal(string: newValue + "0", locale: numberFormatter.locale) {
-                            text = decimal
-                            textDidChange?(decimal)
-                        }
-                        return
+                .onChange(of: localText) { oldValue, newValue in
+                    // Reset zero-cleared state as soon as user starts typing anything
+                    if !newValue.isEmpty {
+                        isZeroCleared = false
                     }
 
-                    // Special case: When backspacing the last digit after a decimal point
-                    // e.g., "10.0" -> "10." - Ensure we keep proper decimal format
-                    if oldValue.contains(decimalSeparator), newValue.contains(decimalSeparator) {
-                        let oldParts = oldValue.components(separatedBy: decimalSeparator)
-                        let newParts = newValue.components(separatedBy: decimalSeparator)
-
-                        // Check if we've removed the last digit after decimal point
-                        if oldParts.count > 1, newParts.count > 1,
-                           oldParts[1].count == 1, newParts[1].isEmpty
-                        {
-                            // Keep proper decimal format by adding trailing zero
-                            localText = newParts[0] + decimalSeparator + "0"
+                    // Special handling for backspace operations to maintain decimal format
+                    if oldValue.count == newValue.count + 1 {
+                        let decimalSeparator = numberFormatter.decimalSeparator ?? "."
 
-                            if let decimal = Decimal(string: localText, locale: numberFormatter.locale) {
+                        // Special case: When backspacing to leave only a decimal point
+                        // e.g., "10.1" -> "10." - Keep decimal separator without adding trailing zero
+                        if newValue.hasSuffix(decimalSeparator) {
+                            if let decimal = Decimal(string: newValue + "0", locale: numberFormatter.locale) {
                                 text = decimal
                                 textDidChange?(decimal)
                             }
                             return
                         }
+
+                        // Special case: When backspacing the last digit after a decimal point
+                        // e.g., "10.0" -> "10." - Ensure we keep proper decimal format
+                        if oldValue.contains(decimalSeparator), newValue.contains(decimalSeparator) {
+                            let oldParts = oldValue.components(separatedBy: decimalSeparator)
+                            let newParts = newValue.components(separatedBy: decimalSeparator)
+
+                            // Check if we've removed the last digit after decimal point
+                            if oldParts.count > 1, newParts.count > 1,
+                               oldParts[1].count == 1, newParts[1].isEmpty
+                            {
+                                // Keep proper decimal format by adding trailing zero
+                                localText = newParts[0] + decimalSeparator + "0"
+
+                                if let decimal = Decimal(string: localText, locale: numberFormatter.locale) {
+                                    text = decimal
+                                    textDidChange?(decimal)
+                                }
+                                return
+                            }
+                        }
                     }
-                }
 
-                // Process normal text input changes
-                handleTextChange(oldValue, newValue)
-            }
-            .onChange(of: text) { oldValue, newValue in
-                // Handle external changes to the text binding
-                // (changes not initiated by typing, like programmatic changes)
-                if oldValue != newValue,
-                   Decimal(string: localText, locale: numberFormatter.locale) != newValue
-                {
-                    if newValue == 0, isZeroCleared {
-                        // If value is zero and field was cleared, keep display empty to show placeholder
-                        localText = ""
-                    } else {
-                        // Otherwise format and display the new value
-                        localText = numberFormatter.string(from: newValue as NSNumber) ?? ""
-                        isZeroCleared = false
+                    // Process normal text input changes
+                    handleTextChange(oldValue, newValue)
+                }
+                .onChange(of: text) { oldValue, newValue in
+                    // Handle external changes to the text binding
+                    // (changes not initiated by typing, like programmatic changes)
+                    if oldValue != newValue,
+                       Decimal(string: localText, locale: numberFormatter.locale) != newValue
+                    {
+                        if newValue == 0, isZeroCleared {
+                            // If value is zero and field was cleared, keep display empty to show placeholder
+                            localText = ""
+                        } else {
+                            // Otherwise format and display the new value
+                            localText = numberFormatter.string(from: newValue as NSNumber) ?? ""
+                            isZeroCleared = false
+                        }
                     }
                 }
-            }
-            .onAppear {
-                if text != 0 {
-                    // Initialize with formatted non-zero value
-                    localText = numberFormatter.string(from: text as NSNumber) ?? ""
-                    isZeroCleared = false
-                } else {
-                    // For zero values, start with empty field to show placeholder
-                    localText = ""
-                    isZeroCleared = true
+                .onAppear {
+                    if text != 0 {
+                        // Initialize with formatted non-zero value
+                        localText = numberFormatter.string(from: text as NSNumber) ?? ""
+                        isZeroCleared = false
+                    } else {
+                        // For zero values, start with empty field to show placeholder
+                        localText = ""
+                        isZeroCleared = true
+                    }
+                    // Set initial focus if requested
+                    isFocused = initialFocus
                 }
-                // Set initial focus if requested
-                isFocused = initialFocus
+            if unitsText != nil {
+                Text(unitsText ?? "").foregroundColor(unitsTextColor)
+                    .onTapGesture {
+                        isFocused = true
+                    }
             }
+        }
     }
 
     private func handleTextChange(_ oldValue: String, _ newValue: String) {