polscm32 1 yıl önce
ebeveyn
işleme
b12ad9eeae

+ 40 - 0
Trio.xcodeproj/project.pbxproj

@@ -313,6 +313,10 @@
 		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
 		BD432CA12D2F4E3600D1EB79 /* WatchMessageKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD432CA02D2F4E3300D1EB79 /* WatchMessageKeys.swift */; };
 		BD432CA22D2F4E4000D1EB79 /* WatchMessageKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD432CA02D2F4E3300D1EB79 /* WatchMessageKeys.swift */; };
+		BD47FD132D88AA700043966B /* OnboardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD122D88AA6B0043966B /* OnboardingManager.swift */; };
+		BD47FD172D88AAF50043966B /* OnboardingStepViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD162D88AAEF0043966B /* OnboardingStepViews.swift */; };
+		BD47FD192D88AAFE0043966B /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD182D88AAF90043966B /* OnboardingView.swift */; };
+		BD47FD1B2D88AB4F0043966B /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD47FD1A2D88AB4A0043966B /* Model.swift */; };
 		BD4D738D2D15A4080052227B /* TDDStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4D738B2D15A4080052227B /* TDDStored+CoreDataClass.swift */; };
 		BD4D738E2D15A4080052227B /* TDDStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4D738C2D15A4080052227B /* TDDStored+CoreDataProperties.swift */; };
 		BD4D73A22D15A42A0052227B /* TDDStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4D73A12D15A4220052227B /* TDDStorage.swift */; };
@@ -1037,6 +1041,10 @@
 		BD3CC0712B0B89D50013189E /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
 		BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataObserver.swift; sourceTree = "<group>"; };
 		BD432CA02D2F4E3300D1EB79 /* WatchMessageKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchMessageKeys.swift; sourceTree = "<group>"; };
+		BD47FD122D88AA6B0043966B /* OnboardingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingManager.swift; sourceTree = "<group>"; };
+		BD47FD162D88AAEF0043966B /* OnboardingStepViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStepViews.swift; sourceTree = "<group>"; };
+		BD47FD182D88AAF90043966B /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
+		BD47FD1A2D88AB4A0043966B /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = "<group>"; };
 		BD4D738B2D15A4080052227B /* TDDStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TDDStored+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
 		BD4D738C2D15A4080052227B /* TDDStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TDDStored+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
 		BD4D73A12D15A4220052227B /* TDDStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDDStorage.swift; sourceTree = "<group>"; };
@@ -1648,6 +1656,7 @@
 		3811DE0325C9D31700A708ED /* Modules */ = {
 			isa = PBXGroup;
 			children = (
+				BD47FD142D88AACC0043966B /* Onboarding */,
 				DDD163032C4C67B400CD525A /* Adjustments */,
 				DD1745382C55BF8B00211FAC /* AlgorithmAdvancedSettings */,
 				DD1745422C55C5C400211FAC /* AutosensSettings */,
@@ -1797,6 +1806,7 @@
 		3811DE9125C9D88200A708ED /* Services */ = {
 			isa = PBXGroup;
 			children = (
+				BD47FD112D88AA630043966B /* OnboardingManager */,
 				DDA9AC072D67291600E6F1A9 /* AppVersionChecker */,
 				BD7DB88C2D2C49FF003D3155 /* BolusCalculator */,
 				3811DE9225C9D88200A708ED /* Appearance */,
@@ -2560,6 +2570,32 @@
 			path = "StatStateModel+Setup";
 			sourceTree = "<group>";
 		};
+		BD47FD112D88AA630043966B /* OnboardingManager */ = {
+			isa = PBXGroup;
+			children = (
+				BD47FD122D88AA6B0043966B /* OnboardingManager.swift */,
+			);
+			path = OnboardingManager;
+			sourceTree = "<group>";
+		};
+		BD47FD142D88AACC0043966B /* Onboarding */ = {
+			isa = PBXGroup;
+			children = (
+				BD47FD1A2D88AB4A0043966B /* Model.swift */,
+				BD47FD152D88AAD80043966B /* View */,
+			);
+			path = Onboarding;
+			sourceTree = "<group>";
+		};
+		BD47FD152D88AAD80043966B /* View */ = {
+			isa = PBXGroup;
+			children = (
+				BD47FD182D88AAF90043966B /* OnboardingView.swift */,
+				BD47FD162D88AAEF0043966B /* OnboardingStepViews.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
 		BD793CAD2CE7660C00D669AC /* Overrides */ = {
 			isa = PBXGroup;
 			children = (
@@ -3767,6 +3803,7 @@
 				5887527C2BD986E1008B081D /* OpenAPSBattery.swift in Sources */,
 				38569348270B5DFB0002C50D /* GlucoseSource.swift in Sources */,
 				CEE9A6582BBB418300EB5194 /* CalibrationsStateModel.swift in Sources */,
+				BD47FD192D88AAFE0043966B /* OnboardingView.swift in Sources */,
 				CEB434E328B8F9DB00B70274 /* BluetoothStateManager.swift in Sources */,
 				BDC2EA452C3043B000E5BBD0 /* OverrideStorage.swift in Sources */,
 				3811DE4225C9D4A100A708ED /* SettingsDataFlow.swift in Sources */,
@@ -3860,6 +3897,7 @@
 				582DF97B2C8CE209001F516D /* CarbView.swift in Sources */,
 				DD940BAA2CA7585D000830A5 /* GlucoseColorScheme.swift in Sources */,
 				3811DE2225C9D48300A708ED /* MainProvider.swift in Sources */,
+				BD47FD1B2D88AB4F0043966B /* Model.swift in Sources */,
 				3811DE0C25C9D32F00A708ED /* BaseProvider.swift in Sources */,
 				CE95BF5A2BA62E4A00DC3DE3 /* PluginSource.swift in Sources */,
 				DD21FCB52C6952AD00AF2C25 /* DecimalPickerSettings.swift in Sources */,
@@ -4035,6 +4073,7 @@
 				1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */,
 				DDB37CC72D05127500D99BF4 /* FontExtensions.swift in Sources */,
 				582DF9772C8CDBE7001F516D /* InsulinView.swift in Sources */,
+				BD47FD132D88AA700043966B /* OnboardingManager.swift in Sources */,
 				F816826028DB441800054060 /* BluetoothTransmitter.swift in Sources */,
 				DD68889D2C386E17006E3C44 /* NightscoutExercise.swift in Sources */,
 				5864E8592C42CFAE00294306 /* DeterminationStorage.swift in Sources */,
@@ -4062,6 +4101,7 @@
 				CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */,
 				58A3D5442C96DE11003F90FC /* TempTargetStored+Helper.swift in Sources */,
 				DD6B7CB42C7B71F700B75029 /* ForecastDisplayType.swift in Sources */,
+				BD47FD172D88AAF50043966B /* OnboardingStepViews.swift in Sources */,
 				DD5DC9F32CF3D9DD00AB8703 /* AdjustmentsStateModel+TempTargets.swift in Sources */,
 				F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */,
 				DD17453A2C55BFA600211FAC /* AlgorithmAdvancedSettingsDataFlow.swift in Sources */,

+ 28 - 2
Trio/Sources/Application/TrioApp.swift

@@ -1,4 +1,3 @@
-import ActivityKit
 import BackgroundTasks
 import CoreData
 import Foundation
@@ -34,6 +33,9 @@ extension Notification.Name {
     @State private var showLoadingView = true
     @State private var showLoadingError = false
 
+    // Onboarding manager to handle the onboarding flow
+    private let onboardingManager: OnboardingManager
+
     // Dependencies Assembler
     // contain all dependencies Assemblies
     // TODO: Remove static key after update "Use Dependencies" logic
@@ -69,6 +71,7 @@ extension Notification.Name {
         _ = resolver.resolve(ContactImageManager.self)!
         _ = resolver.resolve(HealthKitManager.self)!
         _ = resolver.resolve(WatchManager.self)!
+        _ = resolver.resolve(OnboardingManager.self)!
         _ = resolver.resolve(GarminManager.self)!
         _ = resolver.resolve(ContactImageManager.self)!
         _ = resolver.resolve(BluetoothStateManager.self)!
@@ -80,6 +83,25 @@ extension Notification.Name {
     }
 
     init() {
+        // Initialize onboardingManager in the initializer declaration
+        onboardingManager = OnboardingManager(resolver: TrioApp.assembler.resolver)
+
+        let notificationCenter = Foundation.NotificationCenter.default
+        notificationCenter.addObserver(
+            forName: .initializationCompleted,
+            object: nil,
+            queue: .main
+        ) { [self] _ in
+            showLoadingView = false
+        }
+        notificationCenter.addObserver(
+            forName: .initializationError,
+            object: nil,
+            queue: .main
+        ) { [self] _ in
+            showLoadingError = true
+        }
+
         let submodulesInfo = BuildDetails.shared.submodules.map { key, value in
             "\(key): \(value.branch) \(value.commitSHA)"
         }.joined(separator: ", ")
@@ -171,7 +193,11 @@ extension Notification.Name {
                     .onReceive(Foundation.NotificationCenter.default.publisher(for: .initializationError)) { _ in
                         self.showLoadingError = true
                     }
-
+            } else if onboardingManager.shouldShowOnboarding {
+                // Show onboarding if needed
+                OnboardingView(manager: onboardingManager)
+                    .preferredColorScheme(colorScheme(for: colorSchemePreference) ?? nil)
+                    .transition(.opacity)
             } else {
                 Main.RootView(resolver: resolver)
                     .preferredColorScheme(colorScheme(for: colorSchemePreference) ?? nil)

+ 1 - 0
Trio/Sources/Assemblies/ServiceAssembly.swift

@@ -23,6 +23,7 @@ final class ServiceAssembly: Assembly {
         container.register(GarminManager.self) { r in BaseGarminManager(resolver: r) }
         container.register(ContactImageManager.self) { r in BaseContactImageManager(resolver: r) }
         container.register(AlertPermissionsChecker.self) { r in AlertPermissionsChecker(resolver: r) }
+        container.register(OnboardingManager.self) { r in OnboardingManager(resolver: r) }
         if #available(iOS 16.2, *) {
             container.register(LiveActivityManager.self) { r in
                 LiveActivityManager(resolver: r)

+ 168 - 1
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -5898,6 +5898,16 @@
         }
       }
     },
+    "%@ ÷ %@ = %@ units of insulin" : {
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "%1$@ ÷ %2$@ = %3$@ units of insulin"
+          }
+        }
+      }
+    },
     "%@ g" : {
       "localizations" : {
         "bg" : {
@@ -5999,7 +6009,6 @@
       }
     },
     "%@ g/U" : {
-      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -6400,6 +6409,9 @@
         }
       }
     },
+    "%@ U/h" : {
+
+    },
     "%@ U/hr" : {
       "comment" : "Number of units per hour",
       "localizations" : {
@@ -6717,6 +6729,9 @@
         }
       }
     },
+    "%@g" : {
+
+    },
     "%1$@ for %2$@" : {
       "comment" : "Summary string for temporary basal rate configuration page",
       "extractionState" : "manual",
@@ -7425,9 +7440,33 @@
         }
       }
     },
+    "%lld:00" : {
+
+    },
     "%lld%%" : {
 
     },
+    "• A higher number means you need less insulin for the same amount of carbs" : {
+
+    },
+    "• A higher number means you're less sensitive to insulin" : {
+
+    },
+    "• A lower number means you need more insulin for the same amount of carbs" : {
+
+    },
+    "• A lower number means you're more sensitive to insulin" : {
+
+    },
+    "• A ratio of 10 g/U means 1 unit of insulin covers 10g of carbs" : {
+
+    },
+    "• An ISF of 2.8 mmol/L means 1 unit of insulin lowers your BG by 2.8 mmol/L" : {
+
+    },
+    "• An ISF of 50 mg/dL means 1 unit of insulin lowers your BG by 50 mg/dL" : {
+
+    },
     "• Basal Rate" : {
       "localizations" : {
         "bg" : {
@@ -13935,6 +13974,9 @@
         }
       }
     },
+    "=" : {
+
+    },
     ">  11  " : {
       "extractionState" : "stale",
       "localizations" : {
@@ -14542,6 +14584,9 @@
         }
       }
     },
+    "⟹" : {
+
+    },
     "💉: IOB" : {
       "localizations" : {
         "bg" : {
@@ -15250,6 +15295,9 @@
         }
       }
     },
+    "1U" : {
+
+    },
     "2 hours" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -16807,6 +16855,16 @@
         }
       }
     },
+    "45g ÷ %@ = %@ units of insulin" : {
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "45g ÷ %1$@ = %2$@ units of insulin"
+          }
+        }
+      }
+    },
     "50% is half effect:" : {
       "localizations" : {
         "bg" : {
@@ -17014,6 +17072,9 @@
     "75th Percentile" : {
 
     },
+    "80" : {
+
+    },
     "90%:" : {
 
     },
@@ -17220,6 +17281,9 @@
         }
       }
     },
+    "120" : {
+
+    },
     "123" : {
       "localizations" : {
         "bg" : {
@@ -20361,6 +20425,9 @@
         }
       }
     },
+    "Add Basal Rate" : {
+
+    },
     "Add calibration" : {
       "localizations" : {
         "bg" : {
@@ -36834,6 +36901,9 @@
         }
       }
     },
+    "Back" : {
+
+    },
     "Backfill Failed" : {
       "localizations" : {
         "bg" : {
@@ -39306,6 +39376,9 @@
         }
       }
     },
+    "Blood Glucose Units" : {
+
+    },
     "Bluetooth Power Off" : {
       "comment" : "Bluetooth Power Off",
       "extractionState" : "manual",
@@ -43975,6 +44048,9 @@
         }
       }
     },
+    "Carb Ratio" : {
+
+    },
     "Carb Ratio:" : {
       "localizations" : {
         "bg" : {
@@ -48573,6 +48649,9 @@
         }
       }
     },
+    "Choose when this basal rate should start" : {
+
+    },
     "Choose whether or not to display one or both X- and Y-Axis grid lines." : {
       "localizations" : {
         "bg" : {
@@ -79609,6 +79688,9 @@
         }
       }
     },
+    "Example Calculation" : {
+
+    },
     "exceeded" : {
       "comment" : "Limit Exceeded label",
       "extractionState" : "manual",
@@ -85584,6 +85666,9 @@
         }
       }
     },
+    "For 45g of carbs, you would need:" : {
+
+    },
     "For example, at a temp target of %@ %@, your basal is reduced to 50%%, but this scales depending on the target (e.g., 75%% at %@ %@, 60%% at %@ %@)." : {
       "localizations" : {
         "bg" : {
@@ -88638,6 +88723,9 @@
         }
       }
     },
+    "Get Started" : {
+
+    },
     "Getting everything ready for you..." : {
 
     },
@@ -94119,6 +94207,9 @@
         }
       }
     },
+    "High Target" : {
+
+    },
     "High Temp Target Raises Sensitivity" : {
       "comment" : "High Temp Target Raises Sensitivity",
       "localizations" : {
@@ -97083,6 +97174,16 @@
         }
       }
     },
+    "If you are %@ %@ above target:" : {
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "If you are %1$@ %2$@ above target:"
+          }
+        }
+      }
+    },
     "If you are not seeing calendars to choose here, please go to Settings -> Trio -> Calendars and change permissions to \"Full Access\"" : {
       "localizations" : {
         "bg" : {
@@ -101597,6 +101698,9 @@
         }
       }
     },
+    "Insulin Sensitivity Factor" : {
+
+    },
     "Insulin Suspended" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -105377,6 +105481,9 @@
         }
       }
     },
+    "Let's go through a few quick steps to ensure Trio works optimally for you." : {
+
+    },
     "Libre 2 Direct" : {
       "comment" : "Libre 2 Direct",
       "extractionState" : "manual",
@@ -110543,6 +110650,9 @@
         }
       }
     },
+    "Low Target" : {
+
+    },
     "Low Temp Target Lowers Sensitivity" : {
       "comment" : "Low Temp Target Lowers Sensitivity",
       "localizations" : {
@@ -120910,6 +121020,9 @@
         }
       }
     },
+    "Next" : {
+
+    },
     "Nightscout" : {
       "localizations" : {
         "bg" : {
@@ -141366,6 +141479,9 @@
         }
       }
     },
+    "Remember, you can adjust these settings at any time in the app settings if needed." : {
+
+    },
     "Remote Control" : {
       "comment" : "Allow remote control from NS",
       "localizations" : {
@@ -146798,6 +146914,9 @@
         }
       }
     },
+    "Select Blood Glucose Units" : {
+
+    },
     "Select CGM Model" : {
       "localizations" : {
         "bg" : {
@@ -147201,6 +147320,9 @@
         }
       }
     },
+    "Select Start Time" : {
+
+    },
     "Select Temporary Target" : {
       "localizations" : {
         "bg" : {
@@ -163773,6 +163895,9 @@
     "Target glucose is out of range (%@)." : {
 
     },
+    "Target Glucose Range" : {
+
+    },
     "Target presets" : {
       "comment" : "Debug option view Target presets",
       "extractionState" : "manual",
@@ -170183,6 +170308,9 @@
         }
       }
     },
+    "These values reflect your personal target range and can be adjusted at any time in the Settings." : {
+
+    },
     "This adjusted ISF is temporary, will change with the next loop cycle, and should not be directly used as your profile ISF value." : {
       "localizations" : {
         "bg" : {
@@ -170590,6 +170718,9 @@
         }
       }
     },
+    "This chart shows your basal insulin delivery throughout a 24-hour day." : {
+
+    },
     "This could be useful for fast absorbing meals like sugary cereal." : {
       "localizations" : {
         "bg" : {
@@ -174597,6 +174728,9 @@
         }
       }
     },
+    "This range defines your ideal blood glucose values. Trio uses this to calculate insulin doses." : {
+
+    },
     "This range is for display and statistical purposes only and does not influence insulin dosing." : {
       "localizations" : {
         "bg" : {
@@ -182516,6 +182650,9 @@
         }
       }
     },
+    "Trio is designed to help manage your diabetes efficiently. To get the most out of the app, we'll guide you through setting up some essential parameters." : {
+
+    },
     "Trio lets you create automations using iOS Shortcuts. Go to the Shortcuts app to create new automations." : {
       "localizations" : {
         "bg" : {
@@ -189586,6 +189723,9 @@
     "View" : {
 
     },
+    "Visual Reference" : {
+
+    },
     "W" : {
       "comment" : "Abbreviation for week"
     },
@@ -191527,6 +191667,9 @@
         }
       }
     },
+    "Welcome to Trio!" : {
+
+    },
     "What is the numeric value of the carb to add" : {
       "localizations" : {
         "bg" : {
@@ -191727,6 +191870,9 @@
         }
       }
     },
+    "What This Means" : {
+
+    },
     "When \"Fatty Meal\" is selected in the bolus calculator, the recommended bolus will be multiplied by the \"Fatty Meal Bolus Percentage\" as well as the \"Recommended Bolus Percentage\"." : {
       "localizations" : {
         "bg" : {
@@ -195597,6 +195743,21 @@
         }
       }
     },
+    "You're All Set!" : {
+
+    },
+    "You've successfully completed the initial setup of Trio. Your settings have been saved and you're ready to start using the app." : {
+
+    },
+    "Your basal insulin profile determines how much background insulin you receive throughout the day." : {
+
+    },
+    "Your Basal Profile" : {
+
+    },
+    "Your carb ratio tells how many grams of carbohydrates one unit of insulin will cover." : {
+
+    },
     "Your entered amount was limited by your max Bolus setting of %d%@" : {
       "comment" : "For the  Bolus View pop-up",
       "extractionState" : "manual",
@@ -195705,6 +195866,9 @@
         }
       }
     },
+    "Your insulin sensitivity factor (ISF) indicates how much one unit of insulin will lower your blood glucose." : {
+
+    },
     "Your phone or app is not enabled for NFC communications, which is needed to pair to libre2 sensors" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -196020,6 +196184,9 @@
         }
       }
     },
+    "Your Target Range" : {
+
+    },
     "ZT" : {
       "localizations" : {
         "bg" : {

+ 164 - 0
Trio/Sources/Modules/Onboarding/Model.swift

@@ -0,0 +1,164 @@
+import Foundation
+import SwiftUI
+
+/// Represents the different steps in the onboarding process.
+enum OnboardingStep: Int, CaseIterable, Identifiable {
+    case welcome
+    case glucoseTarget
+    case basalProfile
+    case carbRatio
+    case insulinSensitivity
+    case completed
+
+    var id: Int { rawValue }
+
+    /// The title to display for this onboarding step.
+    var title: String {
+        switch self {
+        case .welcome:
+            return "Welcome to Trio"
+        case .glucoseTarget:
+            return "Glucose Target"
+        case .basalProfile:
+            return "Basal Profile"
+        case .carbRatio:
+            return "Carbohydrate Ratio"
+        case .insulinSensitivity:
+            return "Insulin Sensitivity"
+        case .completed:
+            return "All Set!"
+        }
+    }
+
+    /// A detailed description of what this onboarding step is about.
+    var description: String {
+        switch self {
+        case .welcome:
+            return "Trio is a powerful app that helps you manage your diabetes. Let's get started by setting up a few important parameters that will help Trio work effectively for you."
+        case .glucoseTarget:
+            return "Your glucose target is the blood glucose level you aim to maintain. Trio will use this to calculate insulin doses and provide recommendations."
+        case .basalProfile:
+            return "Your basal profile represents the amount of background insulin you need throughout the day. This helps Trio calculate your insulin needs."
+        case .carbRatio:
+            return "Your carb ratio tells how many grams of carbohydrates one unit of insulin will cover. This is essential for accurate meal bolus calculations."
+        case .insulinSensitivity:
+            return "Your insulin sensitivity factor (ISF) indicates how much one unit of insulin will lower your blood glucose. This helps calculate correction boluses."
+        case .completed:
+            return "Great job! You've completed the initial setup of Trio. You can always adjust these settings later in the app."
+        }
+    }
+
+    /// The system icon name associated with this step.
+    var iconName: String {
+        switch self {
+        case .welcome:
+            return "hand.wave.fill"
+        case .glucoseTarget:
+            return "target"
+        case .basalProfile:
+            return "chart.xyaxis.line"
+        case .carbRatio:
+            return "fork.knife"
+        case .insulinSensitivity:
+            return "drop.fill"
+        case .completed:
+            return "checkmark.circle.fill"
+        }
+    }
+
+    /// Returns the next step in the onboarding process, or nil if this is the last step.
+    var next: OnboardingStep? {
+        let allCases = OnboardingStep.allCases
+        let currentIndex = allCases.firstIndex(of: self) ?? 0
+        let nextIndex = currentIndex + 1
+        return nextIndex < allCases.count ? allCases[nextIndex] : nil
+    }
+
+    /// Returns the previous step in the onboarding process, or nil if this is the first step.
+    var previous: OnboardingStep? {
+        let allCases = OnboardingStep.allCases
+        let currentIndex = allCases.firstIndex(of: self) ?? 0
+        let previousIndex = currentIndex - 1
+        return previousIndex >= 0 ? allCases[previousIndex] : nil
+    }
+
+    /// The accent color to use for this step.
+    var accentColor: Color {
+        switch self {
+        case .welcome:
+            return Color.blue
+        case .glucoseTarget:
+            return Color.green
+        case .basalProfile:
+            return Color.purple
+        case .carbRatio:
+            return Color.orange
+        case .insulinSensitivity:
+            return Color.red
+        case .completed:
+            return Color.blue
+        }
+    }
+}
+
+/// Model that holds the data collected during onboarding.
+@Observable class OnboardingData {
+    // Glucose Target
+    var targetLow: Decimal = 80
+    var targetHigh: Decimal = 120
+
+    // Basal Profile
+    var basalRates: [BasalRateEntry] = [BasalRateEntry(startTime: 0, rate: 1.0)]
+
+    // Carb Ratio
+    var carbRatio: Decimal = 10
+
+    // Insulin Sensitivity Factor
+    var isf: Decimal = 40
+
+    // Blood Glucose Units
+    var units: GlucoseUnits = .mgdL
+
+    struct BasalRateEntry: Identifiable {
+        var id = UUID()
+        var startTime: Int // Minutes from midnight
+        var rate: Decimal
+
+        var timeFormatted: String {
+            let hours = startTime / 60
+            let minutes = startTime % 60
+            return String(format: "%02d:%02d", hours, minutes)
+        }
+    }
+
+    /// Applies the onboarding data to the app's settings.
+    func applyToSettings(settingsManager: SettingsManager) {
+        // Make a copy of the current settings that we can mutate
+        var settingsCopy = settingsManager.settings
+
+        // Apply glucose target - We'll use lowGlucose and highGlucose properties
+        settingsCopy.lowGlucose = targetLow
+        settingsCopy.highGlucose = targetHigh
+
+        // Apply glucose units
+        settingsCopy.units = units
+
+        // Apply carb ratio (if the property exists in TrioSettings)
+        if let carbRatioValue = Double(exactly: NSDecimalNumber(decimal: carbRatio)) {
+            // Assuming there is a related property for carb ratio in TrioSettings
+            // This might need to be adjusted based on the actual property name
+            // settingsCopy.carbRatio = carbRatioValue
+        }
+
+        // Apply ISF (if the property exists in TrioSettings)
+        if let isfValue = Double(exactly: NSDecimalNumber(decimal: isf)) {
+            // Assuming there is a related property for insulin sensitivity factor in TrioSettings
+            // This might need to be adjusted based on the actual property name
+            // settingsCopy.insulinSensitivityFactor = isfValue
+        }
+
+        // Instead of using updateSettings which doesn't exist,
+        // we'll directly set the settings property which will trigger the didSet observer
+        settingsManager.settings = settingsCopy
+    }
+}

+ 700 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingStepViews.swift

@@ -0,0 +1,700 @@
+import SwiftUI
+
+/// Welcome step view shown at the beginning of onboarding.
+struct WelcomeStepView: View {
+    var body: some View {
+        VStack(alignment: .center, spacing: 20) {
+            Text("Welcome to Trio!")
+                .font(.title2)
+                .fontWeight(.bold)
+                .multilineTextAlignment(.center)
+
+            Text(
+                "Trio is designed to help manage your diabetes efficiently. To get the most out of the app, we'll guide you through setting up some essential parameters."
+            )
+            .multilineTextAlignment(.center)
+            .foregroundColor(.secondary)
+
+            Text("Let's go through a few quick steps to ensure Trio works optimally for you.")
+                .multilineTextAlignment(.center)
+                .foregroundColor(.secondary)
+
+            Image("trio-logo")
+                .resizable()
+                .scaledToFit()
+                .frame(height: 100)
+                .padding()
+        }
+        .padding()
+        .frame(maxWidth: .infinity)
+    }
+}
+
+/// Glucose target step view for setting target glucose range.
+struct GlucoseTargetStepView: View {
+    @State var onboardingData: OnboardingData
+    @State private var showUnitPicker = false
+
+    // Formatter for glucose values
+    private var numberFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = onboardingData.units == .mmolL ? 1 : 0
+        return formatter
+    }
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            // Unit selector
+            HStack {
+                Text("Blood Glucose Units")
+                    .font(.headline)
+
+                Spacer()
+
+                Button(action: {
+                    showUnitPicker.toggle()
+                }) {
+                    HStack {
+                        Text(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")
+                        Image(systemName: "chevron.down")
+                    }
+                    .padding(.horizontal, 12)
+                    .padding(.vertical, 8)
+                    .background(Color.blue.opacity(0.1))
+                    .cornerRadius(8)
+                }
+                .actionSheet(isPresented: $showUnitPicker) {
+                    ActionSheet(
+                        title: Text("Select Blood Glucose Units"),
+                        buttons: [
+                            .default(Text("mg/dL")) {
+                                onboardingData.units = .mgdL
+                                // Adjust values for unit change
+                                if onboardingData.units == .mgdL {
+                                    onboardingData.targetLow = max(70, onboardingData.targetLow * 18)
+                                    onboardingData.targetHigh = max(120, onboardingData.targetHigh * 18)
+                                    onboardingData.isf = max(30, onboardingData.isf * 18)
+                                }
+                            },
+                            .default(Text("mmol/L")) {
+                                onboardingData.units = .mmolL
+                                // Adjust values for unit change
+                                if onboardingData.units == .mmolL {
+                                    onboardingData.targetLow = max(3.9, onboardingData.targetLow / 18)
+                                    onboardingData.targetHigh = max(6.7, onboardingData.targetHigh / 18)
+                                    onboardingData.isf = max(1.7, onboardingData.isf / 18)
+                                }
+                            },
+                            .cancel()
+                        ]
+                    )
+                }
+            }
+
+            Divider()
+
+            // Target glucose range
+            VStack(alignment: .leading, spacing: 12) {
+                Text("Target Glucose Range")
+                    .font(.headline)
+
+                Text("This range defines your ideal blood glucose values. Trio uses this to calculate insulin doses.")
+                    .font(.subheadline)
+                    .foregroundColor(.secondary)
+
+                // Low target
+                VStack(alignment: .leading) {
+                    Text("Low Target")
+                        .font(.subheadline)
+
+                    HStack {
+                        Slider(
+                            value: Binding(
+                                get: { Double(truncating: onboardingData.targetLow as NSNumber) },
+                                set: { onboardingData.targetLow = Decimal($0) }
+                            ),
+                            in: onboardingData.units == .mgdL ? 70 ... 120 : 3.9 ... 6.7,
+                            step: onboardingData.units == .mgdL ? 1 : 0.1
+                        )
+                        .accentColor(.green)
+
+                        Text(
+                            "\(numberFormatter.string(from: onboardingData.targetLow as NSNumber) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")"
+                        )
+                        .frame(width: 80, alignment: .trailing)
+                    }
+                }
+                .padding(.vertical, 4)
+
+                // High target
+                VStack(alignment: .leading) {
+                    Text("High Target")
+                        .font(.subheadline)
+
+                    HStack {
+                        Slider(
+                            value: Binding(
+                                get: { Double(truncating: onboardingData.targetHigh as NSNumber) },
+                                set: { onboardingData.targetHigh = Decimal($0) }
+                            ),
+                            in: onboardingData.units == .mgdL ? onboardingData
+                                .targetLow as! Double + 10 ... 200 : (onboardingData.targetLow as! Double) + 0.6 ... 11.1,
+                            step: onboardingData.units == .mgdL ? 1 : 0.1
+                        )
+                        .accentColor(.green)
+
+                        Text(
+                            "\(numberFormatter.string(from: onboardingData.targetHigh as NSNumber) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")"
+                        )
+                        .frame(width: 80, alignment: .trailing)
+                    }
+                }
+                .padding(.vertical, 4)
+            }
+
+            Divider()
+
+            // Target range visualization
+            VStack(alignment: .leading, spacing: 8) {
+                Text("Your Target Range")
+                    .font(.headline)
+
+                HStack(spacing: 0) {
+                    // Below range
+                    Rectangle()
+                        .fill(Color.red.opacity(0.3))
+                        .frame(width: 50, height: 30)
+                        .overlay(
+                            Text("Low")
+                                .font(.caption)
+                                .foregroundColor(.red)
+                        )
+
+                    // Target range
+                    Rectangle()
+                        .fill(Color.green.opacity(0.3))
+                        .frame(width: 100, height: 30)
+                        .overlay(
+                            Text("Target")
+                                .font(.caption)
+                                .foregroundColor(.green)
+                        )
+
+                    // Above range
+                    Rectangle()
+                        .fill(Color.yellow.opacity(0.3))
+                        .frame(width: 50, height: 30)
+                        .overlay(
+                            Text("High")
+                                .font(.caption)
+                                .foregroundColor(.orange)
+                        )
+                }
+                .cornerRadius(8)
+
+                // Range values
+                HStack(spacing: 0) {
+                    Text("\(numberFormatter.string(from: onboardingData.targetLow as NSNumber) ?? "--")")
+                        .font(.caption)
+                        .frame(width: 50, alignment: .center)
+
+                    Spacer()
+                        .frame(width: 100)
+
+                    Text("\(numberFormatter.string(from: onboardingData.targetHigh as NSNumber) ?? "--")")
+                        .font(.caption)
+                        .frame(width: 50, alignment: .center)
+                }
+
+                Text("These values reflect your personal target range and can be adjusted at any time in the Settings.")
+                    .font(.caption)
+                    .foregroundColor(.secondary)
+                    .padding(.top, 8)
+            }
+        }
+        .padding()
+    }
+}
+
+/// Basal profile step view for setting basal insulin rates.
+struct BasalProfileStepView: View {
+    @State var onboardingData: OnboardingData
+    @State private var showTimeSelector = false
+    @State private var selectedBasalIndex: Int?
+    @State private var newStartTime: Int = 0
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            Text("Your basal insulin profile determines how much background insulin you receive throughout the day.")
+                .font(.subheadline)
+                .foregroundColor(.secondary)
+
+            // Basal rates list
+            VStack(alignment: .leading, spacing: 10) {
+                Text("Basal Rates")
+                    .font(.headline)
+
+                ForEach(Array(onboardingData.basalRates.enumerated()), id: \.element.id) { index, basalRate in
+                    HStack {
+                        Text(basalRate.timeFormatted)
+                            .frame(width: 80, alignment: .leading)
+
+                        Slider(
+                            value: Binding(
+                                get: { Double(truncating: onboardingData.basalRates[index].rate as NSNumber) },
+                                set: { onboardingData.basalRates[index].rate = Decimal($0) }
+                            ),
+                            in: 0 ... 5,
+                            step: 0.05
+                        )
+                        .accentColor(.purple)
+
+                        Text("\(String(format: "%.2f", Double(truncating: basalRate.rate as NSNumber))) U/h")
+                            .frame(width: 70, alignment: .trailing)
+
+                        // Delete button (not for the first entry at 00:00)
+                        if index > 0 {
+                            Button(action: {
+                                onboardingData.basalRates.remove(at: index)
+                            }) {
+                                Image(systemName: "trash")
+                                    .foregroundColor(.red)
+                            }
+                        }
+                    }
+                    .padding(.vertical, 8)
+                    .background(Color.purple.opacity(0.05))
+                    .cornerRadius(8)
+                }
+            }
+
+            // Add new basal rate button
+            if onboardingData.basalRates.count < 24 {
+                Button(action: {
+                    showTimeSelector = true
+                }) {
+                    HStack {
+                        Image(systemName: "plus.circle.fill")
+                        Text("Add Basal Rate")
+                    }
+                    .foregroundColor(.purple)
+                    .padding(.vertical, 8)
+                }
+            }
+
+            Divider()
+
+            // Basal profile visualization
+            VStack(alignment: .leading, spacing: 8) {
+                Text("Your Basal Profile")
+                    .font(.headline)
+
+                // Simple chart representation
+                HStack(alignment: .bottom, spacing: 2) {
+                    ForEach(0 ..< 24) { hour in
+                        let rate = basalRateAt(hour: hour)
+                        let height = min(120, CGFloat(Double(rate) * 30))
+
+                        VStack {
+                            Rectangle()
+                                .fill(Color.purple.opacity(0.7))
+                                .frame(width: 10, height: height)
+
+                            if hour % 6 == 0 {
+                                Text("\(hour):00")
+                                    .font(.system(size: 8))
+                                    .frame(width: 20)
+                                    .rotationEffect(.degrees(-45))
+                                    .offset(y: 10)
+                            }
+                        }
+                    }
+                }
+                .frame(height: 150)
+                .padding(.top)
+
+                Text("This chart shows your basal insulin delivery throughout a 24-hour day.")
+                    .font(.caption)
+                    .foregroundColor(.secondary)
+            }
+        }
+        .padding()
+        .actionSheet(isPresented: $showTimeSelector) {
+            var buttons: [ActionSheet.Button] = []
+
+            // Find available time slots in 1-hour increments
+            for hour in 1 ..< 24 {
+                let hourInMinutes = hour * 60
+                // Check if this hour is already in the profile
+                if !onboardingData.basalRates.contains(where: { $0.startTime == hourInMinutes }) {
+                    buttons.append(.default(Text("\(String(format: "%02d:00", hour))")) {
+                        // Get the current basal rate active at this time
+                        let rate = basalRateAt(hour: hour)
+                        // Add new basal rate with the same value
+                        onboardingData.basalRates.append(
+                            OnboardingData.BasalRateEntry(startTime: hourInMinutes, rate: rate)
+                        )
+                        // Sort basal rates by time
+                        onboardingData.basalRates.sort(by: { $0.startTime < $1.startTime })
+                    })
+                }
+            }
+
+            buttons.append(.cancel())
+
+            return ActionSheet(
+                title: Text("Select Start Time"),
+                message: Text("Choose when this basal rate should start"),
+                buttons: buttons
+            )
+        }
+    }
+
+    /// Calculates the basal rate at a specific hour based on the profile.
+    private func basalRateAt(hour: Int) -> Decimal {
+        let minutes = hour * 60
+        // Find the most recent basal rate entry that starts before or at the given hour
+        let applicableRate = onboardingData.basalRates
+            .filter { $0.startTime <= minutes }
+            .sorted(by: { $0.startTime > $1.startTime })
+            .first
+
+        return applicableRate?.rate ?? Decimal(1.0)
+    }
+}
+
+/// Carb ratio step view for setting insulin-to-carb ratio.
+struct CarbRatioStepView: View {
+    @State var onboardingData: OnboardingData
+
+    private var formatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        return formatter
+    }
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            Text("Your carb ratio tells how many grams of carbohydrates one unit of insulin will cover.")
+                .font(.subheadline)
+                .foregroundColor(.secondary)
+
+            VStack(alignment: .leading, spacing: 12) {
+                Text("Carb Ratio")
+                    .font(.headline)
+
+                HStack {
+                    Slider(
+                        value: Binding(
+                            get: { Double(truncating: onboardingData.carbRatio as NSNumber) },
+                            set: { onboardingData.carbRatio = Decimal($0) }
+                        ),
+                        in: 2 ... 30,
+                        step: 0.5
+                    )
+                    .accentColor(.orange)
+
+                    // Display the current value
+                    Text("\(formatter.string(from: onboardingData.carbRatio as NSNumber) ?? "--") g/U")
+                        .frame(width: 80, alignment: .trailing)
+                }
+
+                // Example calculation
+                VStack(alignment: .leading, spacing: 8) {
+                    Text("Example Calculation")
+                        .font(.headline)
+                        .padding(.top)
+
+                    VStack(alignment: .leading, spacing: 4) {
+                        Text("For 45g of carbs, you would need:")
+                            .font(.subheadline)
+
+                        let insulinNeeded = 45 / Double(truncating: onboardingData.carbRatio as NSNumber)
+                        Text(
+                            "45g ÷ \(formatter.string(from: onboardingData.carbRatio as NSNumber) ?? "--") = \(String(format: "%.1f", insulinNeeded)) units of insulin"
+                        )
+                        .font(.system(.body, design: .monospaced))
+                        .foregroundColor(.orange)
+                        .padding(.vertical, 8)
+                        .padding(.horizontal, 12)
+                        .background(Color.orange.opacity(0.1))
+                        .cornerRadius(8)
+                    }
+                    .padding(.vertical, 4)
+
+                    // Information about the carb ratio
+                    VStack(alignment: .leading, spacing: 8) {
+                        Text("What This Means")
+                            .font(.headline)
+                            .padding(.top, 8)
+
+                        VStack(alignment: .leading, spacing: 4) {
+                            Text("• A ratio of 10 g/U means 1 unit of insulin covers 10g of carbs")
+                            Text("• A lower number means you need more insulin for the same amount of carbs")
+                            Text("• A higher number means you need less insulin for the same amount of carbs")
+                        }
+                        .font(.caption)
+                        .foregroundColor(.secondary)
+                    }
+                }
+            }
+
+            // Visualization of carb ratio
+            VStack(alignment: .leading, spacing: 8) {
+                Text("Visual Reference")
+                    .font(.headline)
+                    .padding(.top)
+
+                HStack(spacing: 20) {
+                    VStack {
+                        Image(systemName: "fork.knife")
+                            .font(.system(size: 40))
+                            .foregroundColor(.orange)
+                        Text("\(formatter.string(from: onboardingData.carbRatio as NSNumber) ?? "--")g")
+                            .font(.headline)
+                        Text("Carbs")
+                            .font(.caption)
+                    }
+
+                    Text("=")
+                        .font(.title)
+
+                    VStack {
+                        Image(systemName: "drop.fill")
+                            .font(.system(size: 40))
+                            .foregroundColor(.blue)
+                        Text("1U")
+                            .font(.headline)
+                        Text("Insulin")
+                            .font(.caption)
+                    }
+                }
+                .frame(maxWidth: .infinity)
+                .padding()
+                .background(Color.orange.opacity(0.1))
+                .cornerRadius(12)
+            }
+        }
+        .padding()
+    }
+}
+
+/// Insulin sensitivity step view for setting insulin sensitivity factor.
+struct InsulinSensitivityStepView: View {
+    @State var onboardingData: OnboardingData
+
+    private var numberFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = onboardingData.units == .mmolL ? 1 : 0
+        return formatter
+    }
+
+    private var ispRange: ClosedRange<Double> {
+        if onboardingData.units == .mgdL {
+            return 10 ... 100
+        } else {
+            return 0.5 ... 5.5
+        }
+    }
+
+    private var ispStep: Double {
+        onboardingData.units == .mgdL ? 1 : 0.1
+    }
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            Text("Your insulin sensitivity factor (ISF) indicates how much one unit of insulin will lower your blood glucose.")
+                .font(.subheadline)
+                .foregroundColor(.secondary)
+
+            VStack(alignment: .leading, spacing: 12) {
+                Text("Insulin Sensitivity Factor")
+                    .font(.headline)
+
+                HStack {
+                    Slider(
+                        value: Binding(
+                            get: { Double(truncating: onboardingData.isf as NSNumber) },
+                            set: { onboardingData.isf = Decimal($0) }
+                        ),
+                        in: ispRange,
+                        step: ispStep
+                    )
+                    .accentColor(.red)
+
+                    // Display the current value
+                    Text(
+                        "\(numberFormatter.string(from: onboardingData.isf as NSNumber) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")"
+                    )
+                    .frame(width: 80, alignment: .trailing)
+                }
+
+                // Example calculation
+                VStack(alignment: .leading, spacing: 8) {
+                    Text("Example Calculation")
+                        .font(.headline)
+                        .padding(.top)
+
+                    VStack(alignment: .leading, spacing: 4) {
+                        // Current glucose is 40 mg/dL or 2.2 mmol/L above target
+                        let aboveTarget = onboardingData.units == .mgdL ? 40.0 : 2.2
+                        let insulinNeeded = aboveTarget / Double(truncating: onboardingData.isf as NSNumber)
+
+                        Text(
+                            "If you are \(numberFormatter.string(from: NSNumber(value: aboveTarget)) ?? "--") \(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L") above target:"
+                        )
+                        .font(.subheadline)
+
+                        Text(
+                            "\(numberFormatter.string(from: NSNumber(value: aboveTarget)) ?? "--") ÷ \(numberFormatter.string(from: onboardingData.isf as NSNumber) ?? "--") = \(String(format: "%.1f", insulinNeeded)) units of insulin"
+                        )
+                        .font(.system(.body, design: .monospaced))
+                        .foregroundColor(.red)
+                        .padding(.vertical, 8)
+                        .padding(.horizontal, 12)
+                        .background(Color.red.opacity(0.1))
+                        .cornerRadius(8)
+                    }
+                    .padding(.vertical, 4)
+
+                    // Information about ISF
+                    VStack(alignment: .leading, spacing: 8) {
+                        Text("What This Means")
+                            .font(.headline)
+                            .padding(.top, 8)
+
+                        VStack(alignment: .leading, spacing: 4) {
+                            if onboardingData.units == .mgdL {
+                                Text("• An ISF of 50 mg/dL means 1 unit of insulin lowers your BG by 50 mg/dL")
+                                Text("• A lower number means you're more sensitive to insulin")
+                                Text("• A higher number means you're less sensitive to insulin")
+                            } else {
+                                Text("• An ISF of 2.8 mmol/L means 1 unit of insulin lowers your BG by 2.8 mmol/L")
+                                Text("• A lower number means you're more sensitive to insulin")
+                                Text("• A higher number means you're less sensitive to insulin")
+                            }
+                        }
+                        .font(.caption)
+                        .foregroundColor(.secondary)
+                    }
+                }
+            }
+
+            // Visualization of ISF
+            VStack(alignment: .leading, spacing: 8) {
+                Text("Visual Reference")
+                    .font(.headline)
+                    .padding(.top)
+
+                HStack(spacing: 20) {
+                    VStack {
+                        Image(systemName: "drop.fill")
+                            .font(.system(size: 40))
+                            .foregroundColor(.blue)
+                        Text("1U")
+                            .font(.headline)
+                        Text("Insulin")
+                            .font(.caption)
+                    }
+
+                    Text("⟹")
+                        .font(.title)
+
+                    VStack {
+                        Image(systemName: "arrow.down.circle.fill")
+                            .font(.system(size: 40))
+                            .foregroundColor(.red)
+                        Text("\(numberFormatter.string(from: onboardingData.isf as NSNumber) ?? "--")")
+                            .font(.headline)
+                        Text(onboardingData.units == .mgdL ? "mg/dL" : "mmol/L")
+                            .font(.caption)
+                    }
+                }
+                .frame(maxWidth: .infinity)
+                .padding()
+                .background(Color.red.opacity(0.1))
+                .cornerRadius(12)
+            }
+        }
+        .padding()
+    }
+}
+
+/// Completed step view shown at the end of onboarding.
+struct CompletedStepView: View {
+    var body: some View {
+        VStack(alignment: .center, spacing: 20) {
+            Image(systemName: "checkmark.circle.fill")
+                .font(.system(size: 80))
+                .foregroundColor(.green)
+                .padding()
+
+            Text("You're All Set!")
+                .font(.title)
+                .fontWeight(.bold)
+                .multilineTextAlignment(.center)
+
+            Text(
+                "You've successfully completed the initial setup of Trio. Your settings have been saved and you're ready to start using the app."
+            )
+            .multilineTextAlignment(.center)
+            .foregroundColor(.secondary)
+
+            VStack(alignment: .leading, spacing: 12) {
+                SettingItemView(icon: "target", title: "Glucose Target", description: "Your target range is set")
+                SettingItemView(
+                    icon: "chart.xyaxis.line",
+                    title: "Basal Profile",
+                    description: "Your basal profile is configured"
+                )
+                SettingItemView(icon: "fork.knife", title: "Carb Ratio", description: "Your carb ratio is defined")
+                SettingItemView(icon: "drop.fill", title: "Insulin Sensitivity", description: "Your ISF is established")
+            }
+            .padding()
+            .background(Color.green.opacity(0.1))
+            .cornerRadius(12)
+
+            Text("Remember, you can adjust these settings at any time in the app settings if needed.")
+                .font(.caption)
+                .foregroundColor(.secondary)
+                .multilineTextAlignment(.center)
+                .padding(.top)
+        }
+        .padding()
+        .frame(maxWidth: .infinity)
+    }
+}
+
+/// A reusable view for displaying setting items in the completed step.
+struct SettingItemView: View {
+    let icon: String
+    let title: String
+    let description: String
+
+    var body: some View {
+        HStack(spacing: 15) {
+            Image(systemName: icon)
+                .font(.system(size: 24))
+                .foregroundColor(.green)
+                .frame(width: 40)
+
+            VStack(alignment: .leading, spacing: 2) {
+                Text(title)
+                    .font(.headline)
+
+                Text(description)
+                    .font(.subheadline)
+                    .foregroundColor(.secondary)
+            }
+
+            Spacer()
+
+            Image(systemName: "checkmark")
+                .foregroundColor(.green)
+        }
+        .padding(.vertical, 8)
+    }
+}

+ 421 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingView.swift

@@ -0,0 +1,421 @@
+import SwiftUI
+
+/// The main onboarding view that manages navigation between onboarding steps.
+struct OnboardingView: View {
+    @ObservedObject var manager: OnboardingManager
+    @State private var onboardingData = OnboardingData()
+    @State private var currentStep: OnboardingStep = .welcome
+
+    // Animation states
+    @State private var animationScale: CGFloat = 1.0
+    @State private var animationOpacity: Double = 0
+    @State private var isAnimating = false
+
+    var body: some View {
+        NavigationView {
+            ZStack {
+                // Background gradient
+                LinearGradient(
+                    gradient: Gradient(colors: [Color(UIColor.systemBackground), currentStep.accentColor.opacity(0.1)]),
+                    startPoint: .top,
+                    endPoint: .bottom
+                )
+                .ignoresSafeArea()
+
+                VStack(spacing: 0) {
+                    // Progress bar
+                    OnboardingProgressBar(
+                        currentStep: OnboardingStep.allCases.firstIndex(of: currentStep) ?? 0,
+                        totalSteps: OnboardingStep.allCases.count - 1
+                    )
+                    .padding(.top)
+
+                    // Step content
+                    ScrollView {
+                        VStack(alignment: .leading, spacing: 20) {
+                            // Header
+                            HStack {
+                                Image(systemName: currentStep.iconName)
+                                    .font(.system(size: 40))
+                                    .foregroundColor(currentStep.accentColor)
+                                    .frame(width: 60, height: 60)
+                                    .background(
+                                        Circle()
+                                            .fill(currentStep.accentColor.opacity(0.2))
+                                    )
+
+                                VStack(alignment: .leading) {
+                                    Text(currentStep.title)
+                                        .font(.largeTitle)
+                                        .fontWeight(.bold)
+                                        .foregroundColor(.primary)
+
+                                    Text(currentStep.description)
+                                        .font(.subheadline)
+                                        .foregroundColor(.secondary)
+                                        .fixedSize(horizontal: false, vertical: true)
+                                }
+                            }
+                            .padding(.horizontal)
+                            .padding(.top)
+
+                            // Animation container (for steps that include animations)
+                            AnimationPlaceholder(for: currentStep)
+                                .padding()
+                                .scaleEffect(animationScale)
+                                .opacity(animationOpacity)
+                                .onAppear {
+                                    withAnimation(.easeInOut(duration: 0.7)) {
+                                        animationOpacity = 1
+                                        animationScale = 1.0
+                                    }
+                                    // Start pulse animation
+                                    isAnimating = true
+                                }
+
+                            // Step-specific content
+                            Group {
+                                switch currentStep {
+                                case .welcome:
+                                    WelcomeStepView()
+                                case .glucoseTarget:
+                                    GlucoseTargetStepView(onboardingData: onboardingData)
+                                case .basalProfile:
+                                    BasalProfileStepView(onboardingData: onboardingData)
+                                case .carbRatio:
+                                    CarbRatioStepView(onboardingData: onboardingData)
+                                case .insulinSensitivity:
+                                    InsulinSensitivityStepView(onboardingData: onboardingData)
+                                case .completed:
+                                    CompletedStepView()
+                                }
+                            }
+                            .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
+                            .padding(.horizontal)
+                            .id(currentStep.id) // Force view recreation when step changes
+                        }
+                        .padding(.bottom, 80) // Make room for buttons at bottom
+                    }
+
+                    Spacer()
+
+                    // Navigation buttons
+                    HStack {
+                        // Back button
+                        if currentStep != .welcome {
+                            Button(action: {
+                                withAnimation {
+                                    if let previous = currentStep.previous {
+                                        currentStep = previous
+                                    }
+                                }
+                            }) {
+                                HStack {
+                                    Image(systemName: "chevron.left")
+                                    Text("Back")
+                                }
+                                .padding()
+                                .foregroundColor(.primary)
+                            }
+                        }
+
+                        Spacer()
+
+                        // Next/Finish button
+                        Button(action: {
+                            withAnimation {
+                                if currentStep == .completed {
+                                    // Apply settings and complete onboarding
+                                    onboardingData.applyToSettings(settingsManager: manager.settingsManager)
+                                    manager.completeOnboarding()
+                                } else if let next = currentStep.next {
+                                    currentStep = next
+                                }
+                            }
+                        }) {
+                            HStack {
+                                Text(currentStep == .completed ? "Get Started" : "Next")
+                                Image(systemName: "chevron.right")
+                            }
+                            .padding()
+                            .foregroundColor(.white)
+                            .background(
+                                Capsule()
+                                    .fill(currentStep.accentColor)
+                            )
+                        }
+                    }
+                    .padding(.horizontal)
+                    .padding(.bottom)
+                }
+            }
+            .navigationBarHidden(true)
+        }
+        .onChange(of: currentStep) { _, _ in
+            // Reset animation when step changes
+            animationScale = 0.9
+            animationOpacity = 0
+            isAnimating = false
+
+            // Start new animation
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+                withAnimation(.easeInOut(duration: 0.7)) {
+                    animationOpacity = 1
+                    animationScale = 1.0
+                }
+                isAnimating = true
+            }
+        }
+    }
+}
+
+/// A progress bar that shows the user's progress through the onboarding process.
+struct OnboardingProgressBar: View {
+    let currentStep: Int
+    let totalSteps: Int
+
+    var body: some View {
+        HStack(spacing: 4) {
+            ForEach(0 ..< totalSteps, id: \.self) { step in
+                Rectangle()
+                    .fill(step <= currentStep ? Color.blue : Color.gray.opacity(0.3))
+                    .frame(height: 4)
+                    .cornerRadius(2)
+            }
+        }
+        .padding(.horizontal)
+    }
+}
+
+/// A simple animated placeholder for each step
+struct AnimationPlaceholder: View {
+    let step: OnboardingStep
+    @State private var animationValue: Double = 0
+
+    init(for step: OnboardingStep) {
+        self.step = step
+    }
+
+    var body: some View {
+        VStack {
+            Group {
+                switch step {
+                case .welcome:
+                    welcomeAnimation
+                case .glucoseTarget:
+                    glucoseTargetAnimation
+                case .basalProfile:
+                    basalProfileAnimation
+                case .carbRatio:
+                    carbRatioAnimation
+                case .insulinSensitivity:
+                    insulinSensitivityAnimation
+                case .completed:
+                    completedAnimation
+                }
+            }
+            .frame(height: 180)
+        }
+        .onAppear {
+            withAnimation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true)) {
+                animationValue = 1.0
+            }
+        }
+    }
+
+    // Custom animated views for each step
+    var welcomeAnimation: some View {
+        ZStack {
+            ForEach(0 ..< 5) { index in
+                Image(systemName: "heart.fill")
+                    .font(.system(size: 40))
+                    .foregroundColor(step.accentColor.opacity(0.8 - Double(index) * 0.15))
+                    .offset(x: CGFloat.random(in: -100 ... 100), y: CGFloat.random(in: -60 ... 60))
+                    .scaleEffect(1.0 + animationValue * 0.3)
+                    .rotationEffect(.degrees(animationValue * Double.random(in: -30 ... 30)))
+            }
+
+            Image(systemName: "waveform.path.ecg.rectangle.fill")
+                .font(.system(size: 80))
+                .foregroundColor(step.accentColor)
+                .scaleEffect(1.0 + animationValue * 0.2)
+                .shadow(color: step.accentColor.opacity(0.5), radius: 10 * animationValue, x: 0, y: 0)
+        }
+    }
+
+    var glucoseTargetAnimation: some View {
+        ZStack {
+            // Target rings
+            ForEach(0 ..< 3) { index in
+                Circle()
+                    .stroke(step.accentColor.opacity(Double(3 - index) * 0.3), lineWidth: 8)
+                    .frame(width: 120 + CGFloat(index * 40))
+                    .scaleEffect(1.0 + animationValue * 0.05)
+            }
+
+            // Arrow
+            Image(systemName: "arrow.down.to.line")
+                .font(.system(size: 50))
+                .foregroundColor(step.accentColor)
+                .offset(y: -10 + animationValue * 20)
+                .rotationEffect(.degrees(animationValue * 360))
+        }
+    }
+
+    var basalProfileAnimation: some View {
+        ZStack {
+            // Line graph representation
+            Path { path in
+                let width: CGFloat = 300
+                let height: CGFloat = 100
+
+                path.move(to: CGPoint(x: 0, y: height * 0.5))
+
+                for i in 0 ..< 8 {
+                    let x = width * CGFloat(i) / 7
+                    let y = height * (0.5 + (sin(Double(i) * .pi / 3) * 0.4))
+
+                    if i == 0 {
+                        path.move(to: CGPoint(x: x, y: y))
+                    } else {
+                        path.addLine(to: CGPoint(x: x, y: y))
+                    }
+                }
+            }
+            .trim(from: 0, to: animationValue)
+            .stroke(step.accentColor, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
+            .frame(width: 300, height: 100)
+
+            // Clock symbols to represent time
+            HStack(spacing: 50) {
+                Image(systemName: "clock")
+                    .font(.system(size: 20))
+                    .foregroundColor(step.accentColor)
+                    .opacity(animationValue)
+
+                Image(systemName: "clock.fill")
+                    .font(.system(size: 20))
+                    .foregroundColor(step.accentColor)
+                    .opacity(animationValue)
+
+                Image(systemName: "clock")
+                    .font(.system(size: 20))
+                    .foregroundColor(step.accentColor)
+                    .opacity(animationValue)
+            }
+            .offset(y: 70)
+        }
+    }
+
+    var carbRatioAnimation: some View {
+        ZStack {
+            // Plate
+            Circle()
+                .fill(Color.gray.opacity(0.1))
+                .frame(width: 150)
+
+            // Food items
+            ForEach(0 ..< 5) { index in
+                Image(systemName: [
+                    "carrot.fill",
+                    "fork.knife",
+                    "takeoutbag.and.cup.and.straw.fill",
+                    "wallet.pass.fill",
+                    "cup.and.saucer.fill"
+                ][index % 5])
+                    .font(.system(size: 25))
+                    .foregroundColor(step.accentColor)
+                    .offset(
+                        x: cos(Double(index) * .pi * 2 / 5) * 50 * animationValue,
+                        y: sin(Double(index) * .pi * 2 / 5) * 50 * animationValue
+                    )
+                    .rotationEffect(.degrees(animationValue * 360))
+            }
+
+            // Insulin
+            Image(systemName: "drop.fill")
+                .font(.system(size: 40))
+                .foregroundColor(.blue)
+                .scaleEffect(0.8 + animationValue * 0.3)
+                .shadow(color: .blue.opacity(0.5), radius: 5, x: 0, y: 0)
+        }
+    }
+
+    var insulinSensitivityAnimation: some View {
+        ZStack {
+            // Glucose meter
+            RoundedRectangle(cornerRadius: 20)
+                .fill(Color.gray.opacity(0.1))
+                .frame(width: 120, height: 200)
+
+            // Display screen
+            RoundedRectangle(cornerRadius: 10)
+                .fill(Color.black.opacity(0.1))
+                .frame(width: 100, height: 60)
+                .offset(y: -60)
+
+            // Value on screen
+            Text("120")
+                .font(.system(size: 24, weight: .bold, design: .monospaced))
+                .foregroundColor(step.accentColor)
+                .offset(y: -60)
+                .opacity(animationValue)
+
+            // Insulin drop
+            Image(systemName: "drop.fill")
+                .font(.system(size: 30))
+                .foregroundColor(.blue)
+                .offset(y: 20)
+                .opacity(1)
+
+            // Arrow showing decrease
+            Image(systemName: "arrow.down")
+                .font(.system(size: 30))
+                .foregroundColor(step.accentColor)
+                .offset(y: 60)
+                .opacity(animationValue)
+                .scaleEffect(1.0 + animationValue * 0.5)
+
+            // Lower value
+            Text("80")
+                .font(.system(size: 24, weight: .bold, design: .monospaced))
+                .foregroundColor(step.accentColor)
+                .offset(y: 100)
+                .opacity(animationValue)
+        }
+    }
+
+    var completedAnimation: some View {
+        ZStack {
+            // Success checkmark
+            Circle()
+                .fill(step.accentColor.opacity(0.2))
+                .frame(width: 150)
+                .scaleEffect(animationValue)
+
+            Circle()
+                .stroke(step.accentColor, lineWidth: 5)
+                .frame(width: 150)
+                .scaleEffect(animationValue)
+
+            Image(systemName: "checkmark")
+                .font(.system(size: 80, weight: .bold))
+                .foregroundColor(step.accentColor)
+                .offset(y: animationValue * 5)
+                .scaleEffect(animationValue)
+
+            // Celebrate particles
+            ForEach(0 ..< 8) { index in
+                Image(systemName: "star.fill")
+                    .font(.system(size: 20))
+                    .foregroundColor(step.accentColor)
+                    .offset(
+                        x: cos(Double(index) * .pi / 4) * 100 * animationValue,
+                        y: sin(Double(index) * .pi / 4) * 100 * animationValue
+                    )
+                    .opacity(animationValue)
+                    .scaleEffect(animationValue)
+            }
+        }
+    }
+}

+ 47 - 0
Trio/Sources/Services/OnboardingManager/OnboardingManager.swift

@@ -0,0 +1,47 @@
+import Foundation
+import SwiftUI
+import Swinject
+
+extension UserDefaults {
+    /// Flag that indicates if onboarding has been completed.
+    var onboardingCompleted: Bool {
+        get {
+            bool(forKey: "onboardingCompleted")
+        }
+        set {
+            set(newValue, forKey: "onboardingCompleted")
+        }
+    }
+}
+
+/// Manages the app's onboarding experience, ensuring it's only shown to new users.
+/// Coordinates the display of onboarding screens when the app is launched for the first time.
+final class OnboardingManager: Injectable, ObservableObject {
+    @Injected() var settingsManager: SettingsManager!
+
+    /// Indicates whether the onboarding flow should be presented.
+    @Published var shouldShowOnboarding: Bool = false
+
+    /// Initialize the OnboardingManager with the required dependencies.
+    init(resolver: Resolver) {
+        injectServices(resolver)
+        checkOnboardingStatus()
+    }
+
+    /// Checks if onboarding has been completed and updates the shouldShowOnboarding flag accordingly.
+    private func checkOnboardingStatus() {
+        shouldShowOnboarding = !UserDefaults.standard.onboardingCompleted
+    }
+
+    /// Marks onboarding as completed and updates the shouldShowOnboarding flag.
+    func completeOnboarding() {
+        UserDefaults.standard.onboardingCompleted = true
+        shouldShowOnboarding = false
+    }
+
+    /// Resets the onboarding status for testing purposes.
+    func resetOnboarding() {
+        UserDefaults.standard.onboardingCompleted = false
+        shouldShowOnboarding = true
+    }
+}