Parcourir la source

Cleanup + add missing localizations

Deniz Cengiz il y a 1 an
Parent
commit
f84433d4e1

+ 39 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -15554,6 +15554,9 @@
         }
       }
     },
+    "3 M" : {
+      "comment" : "Abbreviation for three months"
+    },
     "3. Tap on \"Data Access & Devices\"." : {
       "localizations" : {
         "bg" : {
@@ -39906,6 +39909,9 @@
         }
       }
     },
+    "Bolus Distribution" : {
+
+    },
     "Bolus enacted successfully." : {
       "comment" : "Success message for enacting a bolus",
       "localizations" : {
@@ -45961,6 +45967,9 @@
         }
       }
     },
+    "CGM Connection Trace" : {
+
+    },
     "CGM Connection Trace Chart" : {
 
     },
@@ -55485,6 +55494,9 @@
         }
       }
     },
+    "D" : {
+      "comment" : "Abbreviation for day"
+    },
     "Dana(RS/-i)" : {
       "localizations" : {
         "bg" : {
@@ -69320,6 +69332,9 @@
         }
       }
     },
+    "Distribution" : {
+
+    },
     "Do not enable this feature until you have optimized your CR (carb ratio) setting." : {
       "localizations" : {
         "bg" : {
@@ -109303,6 +109318,9 @@
     "Looping Chart Type" : {
 
     },
+    "Looping Performance" : {
+
+    },
     "Loops" : {
       "comment" : "Nr of Loops in statPanel",
       "localizations" : {
@@ -111167,6 +111185,9 @@
         }
       }
     },
+    "M" : {
+      "comment" : "Abbreviation for month"
+    },
     "Macro Nutrients (g)" : {
 
     },
@@ -116391,6 +116412,9 @@
         }
       }
     },
+    "Meal to Hypo/Hyper" : {
+
+    },
     "Meal to Hypoglycemia/Hyperglycemia Distribution Chart" : {
 
     },
@@ -133673,6 +133697,9 @@
         }
       }
     },
+    "Percentile" : {
+
+    },
     "Period:" : {
       "localizations" : {
         "bg" : {
@@ -179721,6 +179748,9 @@
         }
       }
     },
+    "Total Daily Dose" : {
+
+    },
     "Total Daily Dose (TDD)" : {
       "extractionState" : "stale",
       "localizations" : {
@@ -180447,6 +180477,9 @@
         }
       }
     },
+    "Total Meals" : {
+
+    },
     "Total:" : {
 
     },
@@ -183078,6 +183111,9 @@
         }
       }
     },
+    "Trio Up-Time" : {
+
+    },
     "Trio Up-Time Chart" : {
 
     },
@@ -189423,6 +189459,9 @@
     "View" : {
 
     },
+    "W" : {
+      "comment" : "Abbreviation for week"
+    },
     "Wait please" : {
       "extractionState" : "manual",
       "localizations" : {

+ 19 - 18
Trio/Sources/Modules/Stat/StatStateModel+Setup/LoopChartSetup.swift

@@ -53,7 +53,7 @@ extension Stat.StateModel {
                 let stats = try await self.getLoopStats(
                     allLoopIds: recordIDs,
                     failedLoopIds: failedRecordIDs,
-                    duration: selectedDurationForLoopStats
+                    interval: selectedDurationForLoopStats
                 )
 
                 await MainActor.run {
@@ -66,22 +66,24 @@ extension Stat.StateModel {
     }
 
     /// Fetches loop statistics records for the specified duration
-    /// - Parameter duration: The time period to fetch records for
+    /// - Parameter interval: The time period to fetch records for
     /// - Returns: A tuple containing arrays of NSManagedObjectIDs for (all loops, failed loops)
-    func fetchLoopStatRecords(for duration: Duration) async throws -> ([NSManagedObjectID], [NSManagedObjectID]) {
+    func fetchLoopStatRecords(for interval: StatsTimeIntervalWithToday) async throws
+        -> ([NSManagedObjectID], [NSManagedObjectID])
+    {
         // Calculate the date range based on selected duration
         let now = Date()
         let startDate: Date
-        switch duration {
-        case .Day:
+        switch interval {
+        case .day:
             startDate = now.addingTimeInterval(-24.hours.timeInterval)
-        case .Today:
+        case .today:
             startDate = Calendar.current.startOfDay(for: now)
-        case .Week:
+        case .week:
             startDate = now.addingTimeInterval(-7.days.timeInterval)
-        case .Month:
+        case .month:
             startDate = now.addingTimeInterval(-30.days.timeInterval)
-        case .Total:
+        case .total:
             startDate = now.addingTimeInterval(-90.days.timeInterval)
         }
 
@@ -133,29 +135,28 @@ extension Stat.StateModel {
     /// - Parameters:
     ///   - allLoopIds: Array of NSManagedObjectIDs for all loop records
     ///   - failedLoopIds: Array of NSManagedObjectIDs for failed loop records
-    ///   - duration: The time period for statistics calculation
+    ///   - interval: The time period for statistics calculation
     /// - Returns: Array of tuples containing category, count and percentage for each statistic
     func getLoopStats(
         allLoopIds: [NSManagedObjectID],
         failedLoopIds: [NSManagedObjectID],
-        duration: Duration
-//    ) async throws -> [(category: LoopStatusType, count: Int, percentage: Double)] {
+        interval: StatsTimeIntervalWithToday
     ) async throws
         -> [(category: LoopStatsDataType, count: Int, percentage: Double, medianDuration: Double, medianInterval: Double)]
     {
         // Calculate the date range for glucose readings
         let now = Date()
         let startDate: Date
-        switch duration {
-        case .Day:
+        switch interval {
+        case .day:
             startDate = now.addingTimeInterval(-24.hours.timeInterval)
-        case .Today:
+        case .today:
             startDate = Calendar.current.startOfDay(for: now)
-        case .Week:
+        case .week:
             startDate = now.addingTimeInterval(-7.days.timeInterval)
-        case .Month:
+        case .month:
             startDate = now.addingTimeInterval(-30.days.timeInterval)
-        case .Total:
+        case .total:
             startDate = now.addingTimeInterval(-90.days.timeInterval)
         }
 

+ 171 - 101
Trio/Sources/Modules/Stat/StatStateModel.swift

@@ -5,40 +5,6 @@ import SwiftUI
 import Swinject
 
 extension Stat {
-    /// Defines the available types of glucose charts
-    enum GlucoseChartType: String, CaseIterable {
-        /// Ambulatory Glucose Profile showing percentile ranges
-        case percentile = "Percentile"
-        /// Time-based distribution of glucose ranges
-        case distribution = "Distribution"
-    }
-
-    /// Defines the available types of insulin charts
-    enum InsulinChartType: String, CaseIterable {
-        /// Shows total daily insulin doses
-        case totalDailyDose = "Total Daily Dose"
-        /// Shows distribution of bolus types
-        case bolusDistribution = "Bolus Distribution"
-    }
-
-    /// Defines the available types of looping charts
-    enum LoopingChartType: String, CaseIterable {
-        /// Shows loop completion and success rates
-        case loopingPerformance = "Looping Performance"
-        /// Shows CGM connection status over time
-        case cgmConnectionTrace = "CGM Connection Trace"
-        /// Shows Trio pump uptime statistics
-        case trioUpTime = "Trio Up-Time"
-    }
-
-    /// Defines the available types of meal charts
-    enum MealChartType: String, CaseIterable {
-        /// Shows total meal statistics
-        case totalMeals = "Total Meals"
-        /// Shows correlation between meals and glucose excursions
-        case mealToHypoHyperDistribution = "Meal to Hypo/Hyper"
-    }
-
     @Observable final class StateModel: BaseStateModel<Provider> {
         @ObservationIgnored @Injected() var settings: SettingsManager!
         var highLimit: Decimal = 180
@@ -77,20 +43,20 @@ extension Stat {
         var bolusAveragesCache: [Date: (manual: Double, smb: Double, external: Double)] = [:]
 
         // Selected Duration for Glucose Stats
-        var selectedDurationForGlucoseStats: Duration = .Today {
+        var selectedDurationForGlucoseStats: StatsTimeIntervalWithToday = .today {
             didSet {
                 setupGlucoseArray(for: selectedDurationForGlucoseStats)
             }
         }
 
         // Selected Duration for Insulin Stats
-        var selectedDurationForInsulinStats: StatsTimeInterval = .Day
+        var selectedDurationForInsulinStats: StatsTimeInterval = .day
 
         // Selected Duration for Meal Stats
-        var selectedDurationForMealStats: StatsTimeInterval = .Day
+        var selectedDurationForMealStats: StatsTimeInterval = .day
 
         // Selected Duration for Loop Stats
-        var selectedDurationForLoopStats: Duration = .Today {
+        var selectedDurationForLoopStats: StatsTimeIntervalWithToday = .today {
             didSet {
                 setupLoopStatRecords()
             }
@@ -116,61 +82,8 @@ extension Stat {
         let mealTaskContext = CoreDataStack.shared.newTaskContext()
         let bolusTaskContext = CoreDataStack.shared.newTaskContext()
 
-        /// Defines the available time periods for duration-based statistics
-        enum Duration: String, CaseIterable, Identifiable {
-            /// Current day
-            case Today
-            /// Single day view
-            case Day = "D"
-            /// Week view
-            case Week = "W"
-            /// Month view
-            case Month = "M"
-            /// Three month view
-            case Total = "3 M"
-
-            var id: Self { self }
-        }
-
-        /// Defines the available time intervals for statistical analysis
-        enum StatsTimeInterval: String, CaseIterable, Identifiable {
-            /// Single day interval
-            case Day = "D"
-            /// Week interval
-            case Week = "W"
-            /// Month interval
-            case Month = "M"
-            /// Three month interval
-            case Total = "3 M"
-
-            var id: Self { self }
-        }
-
-        /// Defines the main categories of statistics available in the app
-        enum StatisticViewType: String, CaseIterable, Identifiable {
-            /// Glucose-related statistics including AGP and distributions
-            case glucose
-            /// Insulin delivery statistics including TDD and bolus distributions
-            case insulin
-            /// Loop performance and system status statistics
-            case looping
-            /// Meal-related statistics and correlations
-            case meals
-
-            var id: String { rawValue }
-
-            var title: String {
-                switch self {
-                case .glucose: return "Glucose"
-                case .insulin: return "Insulin"
-                case .looping: return "Looping"
-                case .meals: return "Meals"
-                }
-            }
-        }
-
         override func subscribe() {
-            setupGlucoseArray(for: .Today)
+            setupGlucoseArray(for: .today)
             setupTDDStats()
             setupBolusStats()
             setupLoopStatRecords()
@@ -181,9 +94,9 @@ extension Stat {
             useFPUconversion = settingsManager.settings.useFPUconversion
         }
 
-        func setupGlucoseArray(for duration: Duration) {
+        func setupGlucoseArray(for interval: StatsTimeIntervalWithToday) {
             Task {
-                let ids = await fetchGlucose(for: duration)
+                let ids = await fetchGlucose(for: interval)
                 await updateGlucoseArray(with: ids)
 
                 // Calculate hourly stats and glucose range stats asynchronously with fetched glucose IDs
@@ -193,20 +106,20 @@ extension Stat {
             }
         }
 
-        private func fetchGlucose(for duration: Duration) async -> [NSManagedObjectID] {
+        private func fetchGlucose(for interval: StatsTimeIntervalWithToday) async -> [NSManagedObjectID] {
             do {
                 let predicate: NSPredicate
 
-                switch duration {
-                case .Day:
+                switch interval {
+                case .day:
                     predicate = NSPredicate.glucoseForStatsDay
-                case .Week:
+                case .week:
                     predicate = NSPredicate.glucoseForStatsWeek
-                case .Today:
+                case .today:
                     predicate = NSPredicate.glucoseForStatsToday
-                case .Month:
+                case .month:
                     predicate = NSPredicate.glucoseForStatsMonth
-                case .Total:
+                case .total:
                     predicate = NSPredicate.glucoseForStatsTotal
                 }
 
@@ -264,3 +177,160 @@ extension Stat {
         }
     }
 }
+
+// MARK: Stats Types + Enums
+
+extension Stat.StateModel {
+    /// Defines the available types of glucose charts
+    enum GlucoseChartType: String, CaseIterable {
+        /// Ambulatory Glucose Profile showing percentile ranges
+        case percentile = "Percentile"
+        /// Time-based distribution of glucose ranges
+        case distribution = "Distribution"
+
+        var displayName: String {
+            switch self {
+            case .percentile:
+                return String(localized: "Percentile")
+            case .distribution:
+                return String(localized: "Distribution")
+            }
+        }
+    }
+
+    /// Defines the available types of insulin charts
+    enum InsulinChartType: String, CaseIterable {
+        /// Shows total daily insulin doses
+        case totalDailyDose = "Total Daily Dose"
+        /// Shows distribution of bolus types
+        case bolusDistribution = "Bolus Distribution"
+
+        var displayName: String {
+            switch self {
+            case .totalDailyDose:
+                return String(localized: "Total Daily Dose")
+            case .bolusDistribution:
+                return String(localized: "Bolus Distribution")
+            }
+        }
+    }
+
+    /// Defines the available types of looping charts
+    enum LoopingChartType: String, CaseIterable {
+        /// Shows loop completion and success rates
+        case loopingPerformance = "Looping Performance"
+        /// Shows CGM connection status over time
+        case cgmConnectionTrace = "CGM Connection Trace"
+        /// Shows Trio pump uptime statistics
+        case trioUpTime = "Trio Up-Time"
+
+        var displayName: String {
+            switch self {
+            case .loopingPerformance:
+                return String(localized: "Looping Performance")
+            case .cgmConnectionTrace:
+                return String(localized: "CGM Connection Trace")
+            case .trioUpTime:
+                return String(localized: "Trio Up-Time")
+            }
+        }
+    }
+
+    /// Defines the available types of meal charts
+    enum MealChartType: String, CaseIterable {
+        /// Shows total meal statistics
+        case totalMeals = "Total Meals"
+        /// Shows correlation between meals and glucose excursions
+        case mealToHypoHyperDistribution = "Meal to Hypo/Hyper"
+
+        var displayName: String {
+            switch self {
+            case .totalMeals:
+                return String(localized: "Total Meals")
+            case .mealToHypoHyperDistribution:
+                return String(localized: "Meal to Hypo/Hyper")
+            }
+        }
+    }
+
+    /// Defines the available time periods for duration-based statistics including 'Today' (time since midnight until now)
+    enum StatsTimeIntervalWithToday: String, CaseIterable, Identifiable {
+        /// Current day
+        case today
+        /// Single day view
+        case day = "D"
+        /// Week view
+        case week = "W"
+        /// Month view
+        case month = "M"
+        /// Three month view
+        case total = "3 M"
+
+        var id: Self { self }
+
+        var displayName: String {
+            switch self {
+            case .today:
+                return String(localized: "Today")
+            case .day:
+                return String(localized: "D", comment: "Abbreviation for day")
+            case .week:
+                return String(localized: "W", comment: "Abbreviation for week")
+            case .month:
+                return String(localized: "M", comment: "Abbreviation for month")
+            case .total:
+                return String(localized: "3 M", comment: "Abbreviation for three months")
+            }
+        }
+    }
+
+    /// Defines the available time periods for duration-based statistics
+    enum StatsTimeInterval: String, CaseIterable, Identifiable {
+        /// Single day interval
+        case day = "D"
+        /// Week interval
+        case week = "W"
+        /// Month interval
+        case month = "M"
+        /// Three month interval
+        case total = "3 M"
+
+        var id: Self { self }
+
+        var displayName: String {
+            switch self {
+            case .day:
+                return String(localized: "D", comment: "Abbreviation for day")
+            case .week:
+                return String(localized: "W", comment: "Abbreviation for week")
+            case .month:
+                return String(localized: "M", comment: "Abbreviation for month")
+            case .total:
+                return String(localized: "3 M", comment: "Abbreviation for three months")
+            }
+        }
+    }
+
+    /// Defines the main categories of statistics available in the app
+    enum StatisticViewType: String, CaseIterable, Identifiable {
+        /// Glucose-related statistics including AGP and distributions
+        case glucose
+        /// Insulin delivery statistics including TDD and bolus distributions
+        case insulin
+        /// Loop performance and system status statistics
+        case looping
+        /// Meal-related statistics and correlations
+        case meals
+
+        var id: String { rawValue }
+
+        var displayName: String {
+            switch self {
+            case .glucose: return "Glucose"
+            case .insulin: return "Insulin"
+            case .looping: return "Looping"
+            case .meals: return "Meals"
+            }
+        }
+    }
+}

+ 19 - 20
Trio/Sources/Modules/Stat/View/StatChartUtils.swift

@@ -7,10 +7,10 @@ struct StatChartUtils {
     /// - Returns: The time interval in seconds.
     static func visibleDomainLength(for selectedDuration: Stat.StateModel.StatsTimeInterval) -> TimeInterval {
         switch selectedDuration {
-        case .Day: return 24 * 3600
-        case .Week: return 7 * 24 * 3600
-        case .Month: return 30 * 24 * 3600
-        case .Total: return 90 * 24 * 3600
+        case .day: return 24 * 3600
+        case .week: return 7 * 24 * 3600
+        case .month: return 30 * 24 * 3600
+        case .total: return 90 * 24 * 3600
         }
     }
 
@@ -32,10 +32,10 @@ struct StatChartUtils {
     /// - Returns: A Date.FormatStyle configured for the current time interval.
     static func dateFormat(for selectedDuration: Stat.StateModel.StatsTimeInterval) -> Date.FormatStyle {
         switch selectedDuration {
-        case .Day: return .dateTime.hour()
-        case .Week: return .dateTime.weekday(.abbreviated)
-        case .Month: return .dateTime.day()
-        case .Total: return .dateTime.month(.abbreviated)
+        case .day: return .dateTime.hour()
+        case .week: return .dateTime.weekday(.abbreviated)
+        case .month: return .dateTime.day()
+        case .total: return .dateTime.month(.abbreviated)
         }
     }
 
@@ -44,10 +44,10 @@ struct StatChartUtils {
     /// - Returns: DateComponents configured for the appropriate alignment.
     static func alignmentComponents(for selectedDuration: Stat.StateModel.StatsTimeInterval) -> DateComponents {
         switch selectedDuration {
-        case .Day: return DateComponents(hour: 0)
-        case .Week: return DateComponents(weekday: 2)
-        case .Month,
-             .Total: return DateComponents(day: 1)
+        case .day: return DateComponents(hour: 0)
+        case .week: return DateComponents(weekday: 2)
+        case .month,
+             .total: return DateComponents(day: 1)
         }
     }
 
@@ -59,10 +59,10 @@ struct StatChartUtils {
         let now = Date()
 
         switch selectedDuration {
-        case .Day: return calendar.date(byAdding: .day, value: -1, to: now)!
-        case .Week: return calendar.date(byAdding: .day, value: -7, to: now)!
-        case .Month: return calendar.date(byAdding: .month, value: -1, to: now)!
-        case .Total: return calendar.date(byAdding: .month, value: -3, to: now)!
+        case .day: return calendar.date(byAdding: .day, value: -1, to: now)!
+        case .week: return calendar.date(byAdding: .day, value: -7, to: now)!
+        case .month: return calendar.date(byAdding: .month, value: -1, to: now)!
+        case .total: return calendar.date(byAdding: .month, value: -3, to: now)!
         }
     }
 
@@ -75,7 +75,7 @@ struct StatChartUtils {
     static func isSameTimeUnit(_ date1: Date, _ date2: Date, for selectedDuration: Stat.StateModel.StatsTimeInterval) -> Bool {
         let calendar = Calendar.current
         switch selectedDuration {
-        case .Day:
+        case .day:
             return calendar.isDate(date1, equalTo: date2, toGranularity: .hour)
         default:
             return calendar.isDate(date1, inSameDayAs: date2)
@@ -124,7 +124,7 @@ struct StatChartUtils {
             Text(value)
         }
     }
-    
+
     /// Computes the median value of an array of integers.
     ///
     /// - Parameter array: An array of integers.
@@ -161,8 +161,7 @@ struct StatChartUtils {
     ///   - label: The text label for the legend item.
     ///   - color: The color associated with the legend item.
     /// - Returns: A SwiftUI view displaying a colored symbol and a label.
-    @ViewBuilder
-    static func legendItem(label: String, color: Color) -> some View {
+    @ViewBuilder static func legendItem(label: String, color: Color) -> some View {
         HStack(spacing: 4) {
             Image(systemName: "circle.fill").foregroundStyle(color)
             Text(label).foregroundStyle(Color.secondary)

+ 49 - 49
Trio/Sources/Modules/Stat/View/StatRootView.swift

@@ -22,7 +22,7 @@ extension Stat {
             VStack {
                 Picker("View", selection: $selectedView) {
                     ForEach(StateModel.StatisticViewType.allCases) { viewType in
-                        Text(viewType.title).tag(viewType)
+                        Text(viewType.displayName).tag(viewType)
                     }
                 }
                 .pickerStyle(.segmented)
@@ -68,16 +68,16 @@ extension Stat {
                 Spacer()
 
                 Picker("Glucose Chart Type", selection: $state.selectedGlucoseChartType) {
-                    ForEach(GlucoseChartType.allCases, id: \.self) { type in
-                        Text(type.rawValue)
+                    ForEach(StateModel.GlucoseChartType.allCases, id: \.self) { type in
+                        Text(type.displayName)
                     }
                 }
                 .pickerStyle(.menu)
             }.padding(.horizontal)
 
             Picker("Duration", selection: $state.selectedDurationForGlucoseStats) {
-                ForEach(StateModel.Duration.allCases, id: \.self) { timeInterval in
-                    Text(timeInterval.rawValue)
+                ForEach(StateModel.StatsTimeIntervalWithToday.allCases, id: \.self) { timeInterval in
+                    Text(timeInterval.displayName)
                 }
             }
             .pickerStyle(.segmented)
@@ -122,7 +122,7 @@ extension Stat {
                             lowLimit: state.lowLimit,
                             units: state.units,
                             hourlyStats: state.hourlyStats,
-                            isToday: state.selectedDurationForGlucoseStats == .Today
+                            isToday: state.selectedDurationForGlucoseStats == .today
                         )
                     case .distribution:
                         GlucoseDistributionChart(
@@ -168,8 +168,8 @@ extension Stat {
                 Spacer()
 
                 Picker("Insulin Chart Type", selection: $state.selectedInsulinChartType) {
-                    ForEach(InsulinChartType.allCases, id: \.self) { type in
-                        Text(type.rawValue)
+                    ForEach(StateModel.InsulinChartType.allCases, id: \.self) { type in
+                        Text(type.displayName)
                     }
                 }.pickerStyle(.menu)
             }.padding(.horizontal)
@@ -193,7 +193,7 @@ extension Stat {
                     } else {
                         TotalDailyDoseChart(
                             selectedDuration: $state.selectedDurationForInsulinStats,
-                            tddStats: state.selectedDurationForInsulinStats == .Day ?
+                            tddStats: state.selectedDurationForInsulinStats == .day ?
                                 state.hourlyTDDStats : state.dailyTDDStats,
                             state: state
                         )
@@ -213,7 +213,7 @@ extension Stat {
                     } else {
                         BolusStatsView(
                             selectedDuration: $state.selectedDurationForInsulinStats,
-                            bolusStats: state.selectedDurationForInsulinStats == .Day ?
+                            bolusStats: state.selectedDurationForInsulinStats == .day ?
                                 state.hourlyBolusStats : state.dailyBolusStats,
                             state: state
                         )
@@ -238,57 +238,57 @@ extension Stat {
                 Spacer()
 
                 Picker("Looping Chart Type", selection: $state.selectedLoopingChartType) {
-                    ForEach(LoopingChartType.allCases, id: \.self) { type in
-                        Text(type.rawValue)
+                    ForEach(StateModel.LoopingChartType.allCases, id: \.self) { type in
+                        Text(type.displayName)
                     }
                 }.pickerStyle(.menu)
             }.padding(.horizontal)
 
             Picker("Duration", selection: $state.selectedDurationForLoopStats) {
-                ForEach(StateModel.Duration.allCases, id: \.self) { duration in
-                    Text(duration.rawValue)
+                ForEach(StateModel.StatsTimeIntervalWithToday.allCases, id: \.self) { interval in
+                    Text(interval.displayName)
                 }
             }
             .pickerStyle(.segmented)
 
-            switch state.selectedLoopingChartType {
-            case .loopingPerformance:
-                if state.loopStatRecords.isEmpty {
+            StatCard {
+                switch state.selectedLoopingChartType {
+                case .loopingPerformance:
+                    if state.loopStatRecords.isEmpty {
+                        ContentUnavailableView(
+                            String(localized: "No Loop Data"),
+                            systemImage: "clock.arrow.2.circlepath",
+                            description: Text("Loop statistics will appear here once data is available.")
+                        )
+                    } else {
+                        loopingChartView
+                        loopStats
+                    }
+                case .trioUpTime:
+                    // TODO: Trio Up-Time Chart
                     ContentUnavailableView(
-                        String(localized: "No Loop Data"),
-                        systemImage: "clock.arrow.2.circlepath",
-                        description: Text("Loop statistics will appear here once data is available.")
+                        String(localized: "Coming soon."),
+                        systemImage: "hourglass",
+                        description: Text("Trio Up-Time Chart")
+                    )
+                case .cgmConnectionTrace:
+                    // TODO: CGM Connection Trace Chart
+                    ContentUnavailableView(
+                        String(localized: "Coming soon."),
+                        systemImage: "hourglass",
+                        description: Text("CGM Connection Trace Chart")
                     )
-                } else {
-                    loopsCard
-                    loopStats
                 }
-            case .trioUpTime:
-                // TODO: Trio Up-Time Chart
-                ContentUnavailableView(
-                    String(localized: "Coming soon."),
-                    systemImage: "hourglass",
-                    description: Text("Trio Up-Time Chart")
-                )
-            case .cgmConnectionTrace:
-                // TODO: CGM Connection Trace Chart
-                ContentUnavailableView(
-                    String(localized: "Coming soon."),
-                    systemImage: "hourglass",
-                    description: Text("CGM Connection Trace Chart")
-                )
             }
         }
 
-        private var loopsCard: some View {
-            StatCard {
-                VStack(spacing: Constants.spacing) {
-                    LoopBarChartView(
-                        loopStatRecords: state.loopStatRecords,
-                        selectedDuration: state.selectedDurationForLoopStats,
-                        statsData: state.loopStats
-                    )
-                }
+        private var loopingChartView: some View {
+            VStack(spacing: Constants.spacing) {
+                LoopBarChartView(
+                    loopStatRecords: state.loopStatRecords,
+                    selectedDuration: state.selectedDurationForLoopStats,
+                    statsData: state.loopStats
+                )
             }
         }
 
@@ -310,8 +310,8 @@ extension Stat {
                 Spacer()
 
                 Picker("Meal Chart Type", selection: $state.selectedMealChartType) {
-                    ForEach(MealChartType.allCases, id: \.self) { type in
-                        Text(type.rawValue)
+                    ForEach(StateModel.MealChartType.allCases, id: \.self) { type in
+                        Text(type.displayName)
                     }
                 }.pickerStyle(.menu)
             }.padding(.horizontal)
@@ -339,7 +339,7 @@ extension Stat {
                     } else {
                         MealStatsView(
                             selectedDuration: $state.selectedDurationForMealStats,
-                            mealStats: state.selectedDurationForMealStats == .Day ?
+                            mealStats: state.selectedDurationForMealStats == .day ?
                                 state.hourlyMealStats : state.dailyMealStats,
                             state: state
                         )

+ 9 - 9
Trio/Sources/Modules/Stat/View/ViewElements/Insulin/BolusStatsView.swift

@@ -110,7 +110,7 @@ struct BolusStatsView: View {
             ForEach(bolusStats) { stat in
                 // Total Bolus Bar
                 BarMark(
-                    x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                    x: .value("Date", stat.date, unit: selectedDuration == .day ? .hour : .day),
                     y: .value("Amount", stat.manualBolus)
                 )
                 .foregroundStyle(by: .value("Type", "Manual"))
@@ -123,7 +123,7 @@ struct BolusStatsView: View {
 
                 // Carb Bolus Bar
                 BarMark(
-                    x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                    x: .value("Date", stat.date, unit: selectedDuration == .day ? .hour : .day),
                     y: .value("Amount", stat.smb)
                 )
                 .foregroundStyle(by: .value("Type", "SMB"))
@@ -135,7 +135,7 @@ struct BolusStatsView: View {
                 )
                 // Correction Bolus Bar
                 BarMark(
-                    x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                    x: .value("Date", stat.date, unit: selectedDuration == .day ? .hour : .day),
                     y: .value("Amount", stat.external)
                 )
                 .foregroundStyle(by: .value("Type", "External"))
@@ -195,25 +195,25 @@ struct BolusStatsView: View {
             }
         }
         .chartXAxis {
-            AxisMarks(preset: .aligned, values: .stride(by: selectedDuration == .Day ? .hour : .day)) { value in
+            AxisMarks(preset: .aligned, values: .stride(by: selectedDuration == .day ? .hour : .day)) { value in
                 if let date = value.as(Date.self) {
                     let day = Calendar.current.component(.day, from: date)
                     let hour = Calendar.current.component(.hour, from: date)
 
                     switch selectedDuration {
-                    case .Day:
+                    case .day:
                         if hour % 6 == 0 { // Show only every 6 hours
                             AxisValueLabel(format: StatChartUtils.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
-                    case .Month:
+                    case .month:
                         if day % 3 == 0 { // Only show every 3rd day
                             AxisValueLabel(format: StatChartUtils.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
-                    case .Total:
+                    case .total:
                         // Only show every other month
                         if day == 1 && Calendar.current.component(.month, from: date) % 2 == 1 {
                             AxisValueLabel(format: StatChartUtils.dateFormat(for: selectedDuration), centered: true)
@@ -233,7 +233,7 @@ struct BolusStatsView: View {
         .chartScrollPosition(x: $scrollPosition)
         .chartScrollTargetBehavior(
             .valueAligned(
-                matching: selectedDuration == .Day ?
+                matching: selectedDuration == .day ?
                     DateComponents(minute: 0) : // Align to next hour for Day view
                     DateComponents(hour: 0), // Align to start of day for other views
                 majorAlignment: .matching(
@@ -252,7 +252,7 @@ private struct BolusSelectionPopover: View {
     let selectedDuration: Stat.StateModel.StatsTimeInterval
 
     private var timeText: String {
-        if selectedDuration == .Day {
+        if selectedDuration == .day {
             let hour = Calendar.current.component(.hour, from: date)
             return "\(hour):00-\(hour + 1):00"
         } else {

+ 10 - 10
Trio/Sources/Modules/Stat/View/ViewElements/Insulin/TotalDailyDoseChart.swift

@@ -72,7 +72,7 @@ struct TotalDailyDoseChart: View {
         .onChange(of: scrollPosition) {
             updateTimer.scheduleUpdate {
                 updateAverages()
-                if selectedDuration == .Day {
+                if selectedDuration == .day {
                     updateTotalDoses()
                 }
             }
@@ -81,7 +81,7 @@ struct TotalDailyDoseChart: View {
             Task {
                 scrollPosition = StatChartUtils.getInitialScrollPosition(for: selectedDuration)
                 updateAverages()
-                if selectedDuration == .Day {
+                if selectedDuration == .day {
                     updateTotalDoses()
                 }
             }
@@ -91,7 +91,7 @@ struct TotalDailyDoseChart: View {
     /// A view displaying the statistics summary including average TDD.
     private var statsView: some View {
         HStack {
-            if selectedDuration == .Day {
+            if selectedDuration == .day {
                 Grid(alignment: .leading) {
                     GridRow {
                         Text("Total:")
@@ -135,7 +135,7 @@ struct TotalDailyDoseChart: View {
         Chart {
             ForEach(tddStats) { stat in
                 BarMark(
-                    x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                    x: .value("Date", stat.date, unit: selectedDuration == .day ? .hour : .day),
                     y: .value("Amount", stat.amount)
                 )
                 .foregroundStyle(Color.insulin)
@@ -175,25 +175,25 @@ struct TotalDailyDoseChart: View {
             }
         }
         .chartXAxis {
-            AxisMarks(preset: .aligned, values: .stride(by: selectedDuration == .Day ? .hour : .day)) { value in
+            AxisMarks(preset: .aligned, values: .stride(by: selectedDuration == .day ? .hour : .day)) { value in
                 if let date = value.as(Date.self) {
                     let day = Calendar.current.component(.day, from: date)
                     let hour = Calendar.current.component(.hour, from: date)
 
                     switch selectedDuration {
-                    case .Day:
+                    case .day:
                         if hour % 6 == 0 { // Show only every 6 hours
                             AxisValueLabel(format: StatChartUtils.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
-                    case .Month:
+                    case .month:
                         if day % 3 == 0 { // Only show every 3rd day
                             AxisValueLabel(format: StatChartUtils.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
-                    case .Total:
+                    case .total:
                         // Only show every other month
                         if day == 1 && Calendar.current.component(.month, from: date) % 2 == 1 {
                             AxisValueLabel(format: StatChartUtils.dateFormat(for: selectedDuration), centered: true)
@@ -213,7 +213,7 @@ struct TotalDailyDoseChart: View {
         .chartScrollPosition(x: $scrollPosition)
         .chartScrollTargetBehavior(
             .valueAligned(
-                matching: selectedDuration == .Day ?
+                matching: selectedDuration == .day ?
                     DateComponents(minute: 0) :
                     DateComponents(hour: 0),
                 majorAlignment: .matching(StatChartUtils.alignmentComponents(for: selectedDuration))
@@ -237,7 +237,7 @@ private struct TDDSelectionPopover: View {
     let selectedDuration: Stat.StateModel.StatsTimeInterval
 
     private var timeText: String {
-        if selectedDuration == .Day {
+        if selectedDuration == .day {
             let hour = Calendar.current.component(.hour, from: date)
             return "\(hour):00-\(hour + 1):00"
         } else {

+ 11 - 11
Trio/Sources/Modules/Stat/View/ViewElements/Looping/LoopBarChartView.swift

@@ -3,7 +3,7 @@ import SwiftUI
 
 struct LoopBarChartView: View {
     let loopStatRecords: [LoopStatRecord]
-    let selectedDuration: Stat.StateModel.Duration
+    let selectedDuration: Stat.StateModel.StatsTimeIntervalWithToday
     let statsData: [(category: LoopStatsDataType, count: Int, percentage: Double, medianDuration: Double, medianInterval: Double)]
 
     var body: some View {
@@ -59,23 +59,23 @@ struct LoopBarChartView: View {
     )) -> String {
         if data.category == .successfulLoop {
             switch selectedDuration {
-            case .Day,
-                 .Today:
+            case .day,
+                 .today:
                 return "\(data.count) " + String(localized: "Loops")
-            case .Month,
-                 .Total,
-                 .Week:
+            case .month,
+                 .total,
+                 .week:
                 return "\(data.count) " + String(localized: "Loops per Day")
             }
         } else {
             // For Glucose Count, show different text based on duration
             switch selectedDuration {
-            case .Day,
-                 .Today:
+            case .day,
+                 .today:
                 return "\(data.count) " + String(localized: "Readings")
-            case .Month,
-                 .Total,
-                 .Week:
+            case .month,
+                 .total,
+                 .week:
                 return "\(data.count) " + String(localized: "Readings per Day")
             }
         }

+ 9 - 9
Trio/Sources/Modules/Stat/View/ViewElements/Meal/MealStatsView.swift

@@ -112,7 +112,7 @@ struct MealStatsView: View {
             ForEach(mealStats) { stat in
                 // Carbs Bar (bottom)
                 BarMark(
-                    x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                    x: .value("Date", stat.date, unit: selectedDuration == .day ? .hour : .day),
                     y: .value("Amount", stat.carbs)
                 )
                 .foregroundStyle(by: .value("Type", "Carbs"))
@@ -125,7 +125,7 @@ struct MealStatsView: View {
                 if state.useFPUconversion {
                     // Fat Bar (middle)
                     BarMark(
-                        x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                        x: .value("Date", stat.date, unit: selectedDuration == .day ? .hour : .day),
                         y: .value("Amount", stat.fat)
                     )
                     .foregroundStyle(by: .value("Type", "Fat"))
@@ -137,7 +137,7 @@ struct MealStatsView: View {
                     )
                     // Protein Bar (top)
                     BarMark(
-                        x: .value("Date", stat.date, unit: selectedDuration == .Day ? .hour : .day),
+                        x: .value("Date", stat.date, unit: selectedDuration == .day ? .hour : .day),
                         y: .value("Amount", stat.protein)
                     )
                     .foregroundStyle(by: .value("Type", "Protein"))
@@ -204,25 +204,25 @@ struct MealStatsView: View {
             }
         }
         .chartXAxis {
-            AxisMarks(preset: .aligned, values: .stride(by: selectedDuration == .Day ? .hour : .day)) { value in
+            AxisMarks(preset: .aligned, values: .stride(by: selectedDuration == .day ? .hour : .day)) { value in
                 if let date = value.as(Date.self) {
                     let day = Calendar.current.component(.day, from: date)
                     let hour = Calendar.current.component(.hour, from: date)
 
                     switch selectedDuration {
-                    case .Day:
+                    case .day:
                         if hour % 6 == 0 { // Show only every 6 hours
                             AxisValueLabel(format: StatChartUtils.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
-                    case .Month:
+                    case .month:
                         if day % 3 == 0 { // Only show every 3rd day
                             AxisValueLabel(format: StatChartUtils.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
-                    case .Total:
+                    case .total:
                         // Only show every other month
                         if day == 1 && Calendar.current.component(.month, from: date) % 2 == 1 {
                             AxisValueLabel(format: StatChartUtils.dateFormat(for: selectedDuration), centered: true)
@@ -242,7 +242,7 @@ struct MealStatsView: View {
         .chartScrollPosition(x: $scrollPosition)
         .chartScrollTargetBehavior(
             .valueAligned(
-                matching: selectedDuration == .Day ?
+                matching: selectedDuration == .day ?
                     DateComponents(minute: 0) :
                     DateComponents(hour: 0),
                 majorAlignment: .matching(StatChartUtils.alignmentComponents(for: selectedDuration))
@@ -271,7 +271,7 @@ private struct MealSelectionPopover: View {
     let isFpuEnabled: Bool
 
     private var timeText: String {
-        if selectedDuration == .Day {
+        if selectedDuration == .day {
             let hour = Calendar.current.component(.hour, from: date)
             return "\(hour):00-\(hour + 1):00"
         } else {