Просмотр исходного кода

Improve warning labels in bolus calc popup & Improve unit centering

Mike Plante 1 год назад
Родитель
Сommit
7d5109f03d

Разница между файлами не показана из-за своего большого размера
+ 23 - 7
Trio/Sources/Localizations/Main/Localizable.xcstrings


+ 93 - 49
Trio/Sources/Modules/Treatments/View/PopupView.swift

@@ -69,7 +69,7 @@ private extension View {
         }
 
         return font(.system(.headline, weight: .bold))
-            .frame(minWidth: 50)
+            .frame(minWidth: 45, alignment: .trailing)
             .foregroundStyle(solutionColor)
             .allowsTightening(true)
             .fixedSize(horizontal: true, vertical: true)
@@ -90,6 +90,15 @@ private extension View {
             .lineLimit(1)
     }
 
+    /// Applies styling for warning labels.
+    /// - Parameter warningColor: The color of the text.
+    func warningStyle(_ warningColor: Color) -> some View {
+        font(.subheadline)
+            .foregroundStyle(warningColor)
+            .allowsTightening(true)
+            .minimumScaleFactor(0.5)
+    }
+
     /// Reduces the default inset padding of List Sections for more compact presentation.
     /// Creates tighter spacing in the calculation cards.
     func listRowStyle() -> some View {
@@ -104,6 +113,8 @@ private extension View {
 // with a sticky recommendation card at the bottom.
 
 struct PopupView: View {
+    @Environment(\.colorScheme) var colorScheme
+
     /// State model containing all calculation parameters and results.
     var state: Treatments.StateModel
 
@@ -244,7 +255,7 @@ struct PopupView: View {
                 Text("\(state.units.rawValue)/U")
                 Text("")
                     .layoutPriority(-15)
-                Text("U")
+                Text(padForNegative(state.targetDifferenceInsulin) + String(localized: "U"))
             }
             .unitStyle()
         }
@@ -289,7 +300,7 @@ struct PopupView: View {
                 Text("U")
                 Text("")
                     .layoutPriority(-15)
-                Text("U")
+                Text(padForNegative(-1 * state.iob) + String(localized: "U"))
             }
             .unitStyle()
         }
@@ -360,7 +371,7 @@ struct PopupView: View {
                     if !hasExceededMaxCOB {
                         Text("")
                             .layoutPriority(-15)
-                        Text("U")
+                        Text(padForNegative(state.wholeCobInsulin) + String(localized: "U"))
                     }
                 }
                 .unitStyle()
@@ -384,7 +395,7 @@ struct PopupView: View {
                     GridRow {
                         Text(Int(state.wholeCob).description)
                             .valueStyle()
-                            .foregroundStyle(.red)
+                            .foregroundStyle(.orange)
                         Text("/")
                             .operatorStyle()
                         Text(state.carbRatio.formatted())
@@ -404,7 +415,7 @@ struct PopupView: View {
                         Text("g/U")
                         Text("")
                             .layoutPriority(-15)
-                        Text("U")
+                        Text(padForNegative(state.wholeCobInsulin) + String(localized: "U"))
                     }
                     .unitStyle()
                 }
@@ -450,7 +461,7 @@ struct PopupView: View {
                 Text("\(state.units.rawValue)/U")
                 Text("")
                     .layoutPriority(-15)
-                Text("U")
+                Text(padForNegative(state.fifteenMinInsulin) + String(localized: "U"))
             }
             .unitStyle()
         }
@@ -615,7 +626,7 @@ struct PopupView: View {
                     Text("")
                         .layoutPriority(-15)
                         .gridCellColumns(3)
-                    Text("U")
+                    Text(padForNegative(state.factoredInsulin) + String(localized: "U"))
                 }
                 .unitStyle()
 
@@ -657,7 +668,7 @@ struct PopupView: View {
                     Text("U")
                     Text("")
                         .layoutPriority(-15)
-                    Text("U")
+                    Text(padForNegative(state.factoredInsulin) + String(localized: "U"))
                 }
                 .unitStyle()
 
@@ -710,7 +721,7 @@ struct PopupView: View {
                         Text("U")
                         Text("")
                             .layoutPriority(-15)
-                        Text("U")
+                        Text(padForNegative(state.factoredInsulin) + String(localized: "U"))
                     }
                     .unitStyle()
                 } else {
@@ -746,7 +757,7 @@ struct PopupView: View {
                         Text("U")
                         Text("")
                             .layoutPriority(-15)
-                        Text("U")
+                        Text(padForNegative(state.factoredInsulin) + String(localized: "U"))
                     }
                     .unitStyle()
                 }
@@ -795,33 +806,78 @@ struct PopupView: View {
         }
 
         return VStack(alignment: .center, spacing: 4) {
+            let warningColor: Color = colorScheme == .dark ? .orange : .accentColor
+
             // Display appropriate warnings based on current conditions as a header on this card.
             // Each warning indicates a specific safety concern.
             if isLoopStale {
-                limitWarning(String(localized: "Last loop was > 15 mins ago."))
+                Text("Last loop was > 15 m ago.")
+                    .warningStyle(warningColor)
             } else if state.currentBG < 54 {
-                limitWarning(String(localized: "Glucose is very low."))
+                Text("Glucose is very low.")
+                    .warningStyle(.red)
             } else if state.minPredBG < 54 {
-                limitWarning(String(localized: "Glucose forecast is very low."))
+                Text("Glucose forecast is very low.")
+                    .warningStyle(warningColor)
             } else if state.factoredInsulin > state.maxBolus, state.maxBolus <= iobAvailable {
-                limitWarning(String(localized: "Max Bolus = \(insulinFormatter(state.maxBolus)) U"))
+                Text("Max Bolus = \(insulinFormatter(state.maxBolus)) U")
+                    .warningStyle(warningColor)
             } else if state.factoredInsulin > 0, state.factoredInsulin > iobAvailable {
                 // Available IOB warning with detailed breakdown.
                 // Shows calculation: Max IOB - IOB = Available IOB
-                limitWarning(String(localized: "Available IOB:"))
-                HStack(alignment: .firstTextBaseline, spacing: 0) {
-                    Text("Max IOB - IOB = ").secondaryStyle()
-                    Text(insulinFormatter(state.maxIOB)).font(.footnote)
-                    Text(" U - ").secondaryStyle()
-                    Text(wrapNegative(state.iob)).font(.footnote)
-                    Text(" U = ").secondaryStyle()
-                    Text(insulinFormatter(iobAvailable))
-                        .font(.footnote)
-                        .fontWeight(.bold)
-                        .foregroundStyle(.red)
-                    Text(" U").secondaryStyle()
+                if state.iob > state.maxIOB {
+                    Text("Current IOB (\(insulinFormatter(state.iob)) U) > Max IOB (\(insulinFormatter(state.maxIOB)) U)")
+                        .warningStyle(warningColor)
+                } else {
+                    Text("Limited by Max IOB.")
+                        .warningStyle(warningColor)
+                    ViewThatFits(in: .horizontal) {
+                        // Option 1: Everything on one line (preferred if it fits)
+                        HStack(alignment: .firstTextBaseline, spacing: 0) {
+                            Text("Max IOB (")
+                            Text(insulinFormatter(state.maxIOB))
+                                .foregroundStyle(.primary)
+                            Text(" U) - Current IOB (")
+                            Text(insulinFormatter(state.iob))
+                                .foregroundStyle(.primary)
+                            Text(" U) = ")
+                            Text(insulinFormatter(iobAvailable))
+                                .foregroundStyle(.orange)
+                            Text(" U")
+                        }
+
+                        // Option 2: Two lines
+                        Grid {
+                            GridRow {
+                                Text("Max IOB")
+                                Text("")
+                                Text("IOB")
+                                Text("")
+                                Text("Limit")
+                            }
+                            GridRow {
+                                HStack(alignment: .firstTextBaseline, spacing: 0) {
+                                    Text(insulinFormatter(state.maxIOB))
+                                        .foregroundStyle(.primary)
+                                    Text(" U")
+                                }
+                                Text("-")
+                                HStack(alignment: .firstTextBaseline, spacing: 0) {
+                                    Text(wrapNegative(state.iob))
+                                        .foregroundStyle(.primary)
+                                    Text(" U")
+                                }
+                                Text("=")
+                                HStack(alignment: .firstTextBaseline, spacing: 0) {
+                                    Text(insulinFormatter(iobAvailable))
+                                        .foregroundStyle(.orange)
+                                    Text(" U")
+                                }
+                            }
+                        }
+                    }
+                    .secondaryStyle()
                 }
-                .fixedSize()
             }
 
             // Recommended Bolus card with accent-colored background
@@ -861,27 +917,6 @@ struct PopupView: View {
         }
     }
 
-    // MARK: - Helper Views
-
-    // Support views for warnings and other UI elements
-
-    /// Creates a standardized warning display with triangle icon and red text used for rec bolus card's header.
-    /// - Parameter text: The warning message to display
-    /// - Returns: A formatted warning view
-    private func limitWarning(_ text: String) -> some View {
-        HStack {
-            Image(systemName: "exclamationmark.triangle.fill")
-                .foregroundStyle(.orange)
-                .font(.subheadline)
-
-            Text(text)
-                .fontWeight(.medium)
-                .foregroundStyle(.red)
-                .minimumScaleFactor(0.5)
-                .font(.subheadline)
-        }
-    }
-
     // MARK: - Helper Formatters
 
     // Functions for consistent number formatting throughout the view
@@ -922,4 +957,13 @@ struct PopupView: View {
     private func wrapNegative(_ value: Decimal, _ roundingMode: NSDecimalNumber.RoundingMode = .down) -> String {
         value < 0 ? "(" + insulinFormatter(value, roundingMode) + ")" : insulinFormatter(value, roundingMode)
     }
+
+    /// Adds a leading space before unit symbols for negative values to ensure proper center alignment.
+    /// This compensates for the negative sign's width, maintaining consistent text alignment
+    /// when displaying both positive and negative values with their units.
+    /// - Parameter value: The decimal value to check for negativity
+    /// - Returns: A single space string if the value is negative, otherwise an empty string
+    private func padForNegative(_ value: Decimal) -> String {
+        value < 0 ? " " : ""
+    }
 }

+ 6 - 4
Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -421,22 +421,24 @@ extension Treatments {
 
         @State private var showConfirmDialogForBolusing = false
 
-        private var bolusWarning: (shouldConfirm: Bool, warningMessage: String) {
+        private var bolusWarning: (shouldConfirm: Bool, warningMessage: String, color: Color) {
             let isGlucoseVeryLow = state.currentBG < 54
             let isForecastVeryLow = state.minPredBG < 54
 
             // Only warn when enacting a bolus via pump
             guard !state.externalInsulin, state.amount > 0 else {
-                return (false, "")
+                return (false, "", .primary)
             }
 
             let warningMessage = isGlucoseVeryLow ? String(localized: "Glucose is very low.") :
                 isForecastVeryLow ? String(localized: "Glucose forecast is very low.") :
                 ""
 
+            let warningColor: Color = isGlucoseVeryLow ? .red : colorScheme == .dark ? .orange : .accentColor
+
             let shouldConfirm = state.confirmBolus && (isGlucoseVeryLow || isForecastVeryLow)
 
-            return (shouldConfirm, warningMessage)
+            return (shouldConfirm, warningMessage, warningColor)
         }
 
         var treatmentButton: some View {
@@ -490,7 +492,7 @@ extension Treatments {
                     Text(bolusWarning.warningMessage)
                         .textCase(nil)
                         .font(.subheadline)
-                        .foregroundColor(Color.loopRed)
+                        .foregroundColor(bolusWarning.color)
                         .frame(maxWidth: .infinity, alignment: .center)
                         .padding(.top, -22)
                 }