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

Merge branch 'onboarding-fixes' of github.com:nightscout/Trio-dev into data-migration

Deniz Cengiz 1 год назад
Родитель
Сommit
6e7f8275aa
40 измененных файлов с 3144 добавлено и 434 удалено
  1. 0 5
      Model/Helper/Determination+helper.swift
  2. 52 8
      Trio.xcodeproj/project.pbxproj
  3. 1 1
      Trio.xcworkspace/xcshareddata/swiftpm/Package.resolved
  4. 12 0
      Trio/Resources/Assets.xcassets/logo.bluetooth.capsule.portrait.fill.symbolset/Contents.json
  5. 196 0
      Trio/Resources/Assets.xcassets/logo.bluetooth.capsule.portrait.fill.symbolset/logo.bluetooth.capsule.portrait.fill.svg
  6. 2 2
      Trio/Sources/APS/OpenAPS/OpenAPS.swift
  7. 47 28
      Trio/Sources/Application/TrioApp.swift
  8. 15 5
      Trio/Sources/Helpers/CheckboxToggleStyle.swift
  9. 53 0
      Trio/Sources/Helpers/ToggleStyles.swift
  10. 359 17
      Trio/Sources/Localizations/Main/Localizable.xcstrings
  11. 29 9
      Trio/Sources/Modules/AppDiagnostics/View/AppDiagnosticsRootView.swift
  12. 233 0
      Trio/Sources/Modules/AppDiagnostics/View/PrivacyPolicyView.swift
  13. 4 4
      Trio/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift
  14. 4 4
      Trio/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift
  15. 4 4
      Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift
  16. 5 2
      Trio/Sources/Modules/Onboarding/OnboardingStateModel+Nightscout.swift
  17. 71 5
      Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift
  18. 156 0
      Trio/Sources/Modules/Onboarding/View/Animations/LogoBurstSplash.swift
  19. 42 0
      Trio/Sources/Modules/Onboarding/View/Animations/PulsingLogoAnimation.swift
  20. 392 207
      Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift
  21. 70 0
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/AlgorithmSettings/AlgorithmSettingsStepView.swift
  22. 299 0
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/AlgorithmSettings/AlgorithmSettingsSubstepView.swift
  23. 142 0
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/BluetoothPermissionStepView.swift
  24. 89 8
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/CompletedStepView.swift
  25. 10 1
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/DeliveryLimitsStepView.swift
  26. 39 7
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/DiagnosticsStepView.swift
  27. 2 2
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/GlucoseTargetStepView.swift
  28. 3 8
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/InsulinSensitivityStepView.swift
  29. 1 0
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/Nightscout/NightscoutImportStepView.swift
  30. 68 0
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/NotificationPermissionStepView.swift
  31. 88 6
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/OverviewStepView.swift
  32. 32 8
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/StartupGuideStepView.swift
  33. 8 2
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/UnitSelectionStepView.swift
  34. 488 0
      Trio/Sources/Modules/Onboarding/View/OnboardingView+AlgorithmUtil.swift
  35. 74 66
      Trio/Sources/Modules/Onboarding/View/OnboardingView+Util.swift
  36. 22 4
      Trio/Sources/Modules/Onboarding/View/TherapySettingEditorView.swift
  37. 11 5
      Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift
  38. 2 2
      Trio/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift
  39. 3 3
      Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift
  40. 16 11
      Trio/Sources/Services/UserNotifications/UserNotificationsManager.swift

+ 0 - 5
Model/Helper/Determination+helper.swift

@@ -94,7 +94,6 @@ extension NSPredicate {
 
 /// Data Transfer Object for the enacted.json.
 struct DeterminationDTO: Decodable, ImportableDTO {
-    let tdd: Decimal?
     let threshold: Decimal?
     let timestamp: String?
     let insulinForManualBolus: Decimal?
@@ -113,14 +112,12 @@ struct DeterminationDTO: Decodable, ImportableDTO {
     let duration: Decimal?
     let temp: String?
     let insulinReq: Decimal?
-    let insulin: Insulin?
     let deliverAt: String?
     let reason: String?
     let iob: Decimal?
     let reservoir: Decimal?
 
     enum CodingKeys: String, CodingKey {
-        case tdd = "TDD"
         case threshold
         case timestamp
         case insulinForManualBolus
@@ -139,7 +136,6 @@ struct DeterminationDTO: Decodable, ImportableDTO {
         case duration
         case temp
         case insulinReq
-        case insulin
         case deliverAt
         case reason
         case iob = "IOB"
@@ -163,7 +159,6 @@ struct DeterminationDTO: Decodable, ImportableDTO {
         determinationEntity.expectedDelta = expectedDelta.map { NSDecimalNumber(decimal: $0) }
         determinationEntity.rate = rate.map { NSDecimalNumber(decimal: $0) }
         determinationEntity.reason = reason
-        determinationEntity.totalDailyDose = tdd.map { NSDecimalNumber(decimal: $0) }
         determinationEntity.reservoir = reservoir.map { NSDecimalNumber(decimal: $0) }
         determinationEntity.duration = duration.map { NSDecimalNumber(decimal: $0) }
         determinationEntity.currentTarget = currentTarget.map { NSDecimalNumber(decimal: $0) }

+ 52 - 8
Trio.xcodeproj/project.pbxproj

@@ -334,7 +334,7 @@
 		BA00D96F7B2FF169A06FB530 /* CGMSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C018D1680307A31C9ED7120 /* CGMSettingsStateModel.swift */; };
 		BD04ECCE2D29952A008C5FEB /* BolusProgressOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD04ECCD2D299522008C5FEB /* BolusProgressOverlay.swift */; };
 		BD0B2EF32C5998E600B3298F /* MealPresetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0B2EF22C5998E600B3298F /* MealPresetView.swift */; };
-		BD10516D2DA986E1007C6D89 /* LogoAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD10516C2DA986DC007C6D89 /* LogoAnimation.swift */; };
+		BD10516D2DA986E1007C6D89 /* PulsingLogoAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD10516C2DA986DC007C6D89 /* PulsingLogoAnimation.swift */; };
 		BD1661312B82ADAB00256551 /* CustomProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1661302B82ADAB00256551 /* CustomProgressView.swift */; };
 		BD249D862D42FBEC00412DEB /* GlucoseMetricsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD249D852D42FBE600412DEB /* GlucoseMetricsView.swift */; };
 		BD249D882D42FC0000412DEB /* BolusStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD249D872D42FBFB00412DEB /* BolusStatsView.swift */; };
@@ -352,7 +352,7 @@
 		BD249DA12D42FD1200412DEB /* TDDSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD249DA02D42FD1000412DEB /* TDDSetup.swift */; };
 		BD249DA72D42FE4600412DEB /* Calendar+GlucoseStatsChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD249DA62D42FE3800412DEB /* Calendar+GlucoseStatsChart.swift */; };
 		BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */; };
-		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
+		BD2FF1A02AE29D43005D1C5D /* ToggleStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* ToggleStyles.swift */; };
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
 		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
 		BD432CA12D2F4E3600D1EB79 /* WatchMessageKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD432CA02D2F4E3300D1EB79 /* WatchMessageKeys.swift */; };
@@ -563,6 +563,9 @@
 		DD498F2B2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2C2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		DD498F2D2D692BEA00AAEA30 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
+		DD4A00212DAEEED800AB7387 /* OnboardingView+AlgorithmUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A00202DAEEEC400AB7387 /* OnboardingView+AlgorithmUtil.swift */; };
+		DD4A00242DAEF5E400AB7387 /* AlgorithmSettingsSubstepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A00232DAEF5DC00AB7387 /* AlgorithmSettingsSubstepView.swift */; };
+		DD4AFFF12DADB59100AB7387 /* AlgorithmSettingsStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */; };
 		DD4C57A82D73ADEA001BFF2C /* RestartLiveActivityIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */; };
 		DD4C57AA2D73B3E2001BFF2C /* RestartLiveActivityIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */; };
 		DD4C581F2D73C43D001BFF2C /* LoopStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C581E2D73C43D001BFF2C /* LoopStatsView.swift */; };
@@ -597,6 +600,8 @@
 		DDA9AC092D672CF100E6F1A9 /* AppVersionChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9AC082D672CEB00E6F1A9 /* AppVersionChecker.swift */; };
 		DDAA29832D2D1D93006546A1 /* AdjustmentsRootView+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA29822D2D1D7B006546A1 /* AdjustmentsRootView+Overrides.swift */; };
 		DDAA29852D2D1D9E006546A1 /* AdjustmentsRootView+TempTargets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA29842D2D1D98006546A1 /* AdjustmentsRootView+TempTargets.swift */; };
+		DDB0E3712DB087B6004B826F /* PrivacyPolicyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB0E3702DB087B6004B826F /* PrivacyPolicyView.swift */; };
+		DDB0E3742DB1BAC1004B826F /* LogoBurstSplash.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB0E3732DB1BAC1004B826F /* LogoBurstSplash.swift */; };
 		DDB37CC52D05048F00D99BF4 /* ContactImageStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */; };
 		DDB37CC72D05127500D99BF4 /* FontExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC62D05127500D99BF4 /* FontExtensions.swift */; };
 		DDBD53FC2DAA903100F940A6 /* OverviewStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDBD53FB2DAA903100F940A6 /* OverviewStepView.swift */; };
@@ -656,6 +661,8 @@
 		DDF847E62C5D66490049BB3B /* AddMealPresetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF847E52C5D66490049BB3B /* AddMealPresetView.swift */; };
 		DDF847E82C5DABA30049BB3B /* WatchConfigAppleWatchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF847E72C5DABA30049BB3B /* WatchConfigAppleWatchView.swift */; };
 		DDF847EA2C5DABAC0049BB3B /* WatchConfigGarminView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF847E92C5DABAC0049BB3B /* WatchConfigGarminView.swift */; };
+		DDFF202F2DB1D14500AB8A96 /* NotificationPermissionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */; };
+		DDFF20312DB1D15500AB8A96 /* BluetoothPermissionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */; };
 		E00EEC0327368630002FF094 /* ServiceAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFD27368630002FF094 /* ServiceAssembly.swift */; };
 		E00EEC0427368630002FF094 /* SecurityAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFE27368630002FF094 /* SecurityAssembly.swift */; };
 		E00EEC0527368630002FF094 /* StorageAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFF27368630002FF094 /* StorageAssembly.swift */; };
@@ -1123,7 +1130,7 @@
 		BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorDataFlow.swift; sourceTree = "<group>"; };
 		BD04ECCD2D299522008C5FEB /* BolusProgressOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressOverlay.swift; sourceTree = "<group>"; };
 		BD0B2EF22C5998E600B3298F /* MealPresetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealPresetView.swift; sourceTree = "<group>"; };
-		BD10516C2DA986DC007C6D89 /* LogoAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoAnimation.swift; sourceTree = "<group>"; };
+		BD10516C2DA986DC007C6D89 /* PulsingLogoAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PulsingLogoAnimation.swift; sourceTree = "<group>"; };
 		BD1661302B82ADAB00256551 /* CustomProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomProgressView.swift; sourceTree = "<group>"; };
 		BD1CF8B72C1A4A8400CB930A /* ConfigOverride.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ConfigOverride.xcconfig; sourceTree = "<group>"; };
 		BD249D852D42FBE600412DEB /* GlucoseMetricsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseMetricsView.swift; sourceTree = "<group>"; };
@@ -1141,7 +1148,7 @@
 		BD249D9E2D42FD0200412DEB /* StackedChartSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackedChartSetup.swift; sourceTree = "<group>"; };
 		BD249DA02D42FD1000412DEB /* TDDSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDDSetup.swift; sourceTree = "<group>"; };
 		BD249DA62D42FE3800412DEB /* Calendar+GlucoseStatsChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+GlucoseStatsChart.swift"; sourceTree = "<group>"; };
-		BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleStyle.swift; sourceTree = "<group>"; };
+		BD2FF19F2AE29D43005D1C5D /* ToggleStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleStyles.swift; sourceTree = "<group>"; };
 		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>"; };
@@ -1353,6 +1360,9 @@
 		DD3F1F8A2D9E08B200DCE7B3 /* NightscoutLoginStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutLoginStepView.swift; sourceTree = "<group>"; };
 		DD3F1F8C2D9E0E0000DCE7B3 /* NightscoutSetupStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSetupStepView.swift; sourceTree = "<group>"; };
 		DD3F1F8F2D9E153A00DCE7B3 /* NightscoutImportStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutImportStepView.swift; sourceTree = "<group>"; };
+		DD4A00202DAEEEC400AB7387 /* OnboardingView+AlgorithmUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingView+AlgorithmUtil.swift"; sourceTree = "<group>"; };
+		DD4A00232DAEF5DC00AB7387 /* AlgorithmSettingsSubstepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlgorithmSettingsSubstepView.swift; sourceTree = "<group>"; };
+		DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlgorithmSettingsStepView.swift; sourceTree = "<group>"; };
 		DD4C57A72D73ADEA001BFF2C /* RestartLiveActivityIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntent.swift; sourceTree = "<group>"; };
 		DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntentRequest.swift; sourceTree = "<group>"; };
 		DD4C581E2D73C43D001BFF2C /* LoopStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatsView.swift; sourceTree = "<group>"; };
@@ -1388,6 +1398,8 @@
 		DDA9AC0A2D678DAD00E6F1A9 /* blacklisted-versions.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blacklisted-versions.json"; sourceTree = "<group>"; };
 		DDAA29822D2D1D7B006546A1 /* AdjustmentsRootView+Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsRootView+Overrides.swift"; sourceTree = "<group>"; };
 		DDAA29842D2D1D98006546A1 /* AdjustmentsRootView+TempTargets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsRootView+TempTargets.swift"; sourceTree = "<group>"; };
+		DDB0E3702DB087B6004B826F /* PrivacyPolicyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyView.swift; sourceTree = "<group>"; };
+		DDB0E3732DB1BAC1004B826F /* LogoBurstSplash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoBurstSplash.swift; sourceTree = "<group>"; };
 		DDB37CC22D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDB37CC32D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageStorage.swift; sourceTree = "<group>"; };
@@ -1449,6 +1461,8 @@
 		DDF847E52C5D66490049BB3B /* AddMealPresetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddMealPresetView.swift; sourceTree = "<group>"; };
 		DDF847E72C5DABA30049BB3B /* WatchConfigAppleWatchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConfigAppleWatchView.swift; sourceTree = "<group>"; };
 		DDF847E92C5DABAC0049BB3B /* WatchConfigGarminView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConfigGarminView.swift; sourceTree = "<group>"; };
+		DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionStepView.swift; sourceTree = "<group>"; };
+		DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothPermissionStepView.swift; sourceTree = "<group>"; };
 		E00EEBFD27368630002FF094 /* ServiceAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceAssembly.swift; sourceTree = "<group>"; };
 		E00EEBFE27368630002FF094 /* SecurityAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecurityAssembly.swift; sourceTree = "<group>"; };
 		E00EEBFF27368630002FF094 /* StorageAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageAssembly.swift; sourceTree = "<group>"; };
@@ -2366,7 +2380,7 @@
 				FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */,
 				FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */,
 				CEA4F62229BE10F70011ADF7 /* SavitzkyGolayFilter.swift */,
-				BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */,
+				BD2FF19F2AE29D43005D1C5D /* ToggleStyles.swift */,
 				BD1661302B82ADAB00256551 /* CustomProgressView.swift */,
 				581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */,
 				DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */,
@@ -2760,6 +2774,8 @@
 		BD47FD152D88AAD80043966B /* View */ = {
 			isa = PBXGroup;
 			children = (
+				DDB0E3722DB1BABB004B826F /* Animations */,
+				DD4A00202DAEEEC400AB7387 /* OnboardingView+AlgorithmUtil.swift */,
 				DD3F1F882D9E078300DCE7B3 /* TherapySettingEditorView.swift */,
 				DDC38E0F2D9B376900ADCB46 /* OnboardingView+Util.swift */,
 				BD47FD182D88AAF90043966B /* OnboardingRootView.swift */,
@@ -2771,8 +2787,10 @@
 		BD47FDD52D8B64AE0043966B /* OnboardingSteps */ = {
 			isa = PBXGroup;
 			children = (
+				DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */,
+				DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */,
+				DD4A00222DAEF5CD00AB7387 /* AlgorithmSettings */,
 				DDBD53FB2DAA903100F940A6 /* OverviewStepView.swift */,
-				BD10516C2DA986DC007C6D89 /* LogoAnimation.swift */,
 				DDF691362DA30332008BF16C /* StartupGuideStepView.swift */,
 				DDF6905B2DA0AFC5008BF16C /* WelcomeStepView.swift */,
 				DDF6902B2DA028D3008BF16C /* DiagnosticsStepView.swift */,
@@ -3242,6 +3260,15 @@
 			path = Nightscout;
 			sourceTree = "<group>";
 		};
+		DD4A00222DAEF5CD00AB7387 /* AlgorithmSettings */ = {
+			isa = PBXGroup;
+			children = (
+				DD4A00232DAEF5DC00AB7387 /* AlgorithmSettingsSubstepView.swift */,
+				DD4AFFF02DADB59100AB7387 /* AlgorithmSettingsStepView.swift */,
+			);
+			path = AlgorithmSettings;
+			sourceTree = "<group>";
+		};
 		DD4C57A42D73ADDA001BFF2C /* LiveActivity */ = {
 			isa = PBXGroup;
 			children = (
@@ -3311,6 +3338,15 @@
 			path = AppVersionChecker;
 			sourceTree = "<group>";
 		};
+		DDB0E3722DB1BABB004B826F /* Animations */ = {
+			isa = PBXGroup;
+			children = (
+				BD10516C2DA986DC007C6D89 /* PulsingLogoAnimation.swift */,
+				DDB0E3732DB1BAC1004B826F /* LogoBurstSplash.swift */,
+			);
+			path = Animations;
+			sourceTree = "<group>";
+		};
 		DDC9B9962CFD2332003E7721 /* Nightscout */ = {
 			isa = PBXGroup;
 			children = (
@@ -3437,6 +3473,7 @@
 		DDF690FF2DA2CA03008BF16C /* View */ = {
 			isa = PBXGroup;
 			children = (
+				DDB0E3702DB087B6004B826F /* PrivacyPolicyView.swift */,
 				DDF691062DA2CA28008BF16C /* AppDiagnosticsRootView.swift */,
 			);
 			path = View;
@@ -3981,6 +4018,7 @@
 				38B4F3CD25E5031100E76A18 /* Broadcaster.swift in Sources */,
 				383420D925FFEB3F002D46C1 /* Popup.swift in Sources */,
 				DD4C57AA2D73B3E2001BFF2C /* RestartLiveActivityIntentRequest.swift in Sources */,
+				DDB0E3712DB087B6004B826F /* PrivacyPolicyView.swift in Sources */,
 				DD1745402C55BFC100211FAC /* AlgorithmAdvancedSettingsRootView.swift in Sources */,
 				58645BA52CA2D347008AFCE7 /* ForecastSetup.swift in Sources */,
 				110AEDEE2C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift in Sources */,
@@ -4024,6 +4062,8 @@
 				38192E04261B82FA0094D973 /* ReachabilityManager.swift in Sources */,
 				38E44539274E411700EC9A94 /* Disk+UIImage.swift in Sources */,
 				3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */,
+				DD4A00242DAEF5E400AB7387 /* AlgorithmSettingsSubstepView.swift in Sources */,
+				DDB0E3742DB1BAC1004B826F /* LogoBurstSplash.swift in Sources */,
 				388E595C25AD948C0019842D /* TrioApp.swift in Sources */,
 				38FEF3FC2737E53800574A46 /* MainStateModel.swift in Sources */,
 				DD1745352C55AE7E00211FAC /* TargetBehavoirRootView.swift in Sources */,
@@ -4160,6 +4200,7 @@
 				3811DEAB25C9D88300A708ED /* HTTPResponseStatus.swift in Sources */,
 				3811DE5F25C9D4D500A708ED /* ProgressBar.swift in Sources */,
 				BD249D8C2D42FC2C00412DEB /* GlucoseDistributionChart.swift in Sources */,
+				DDFF20312DB1D15500AB8A96 /* BluetoothPermissionStepView.swift in Sources */,
 				38E87408274F9AD000975559 /* UserNotificationsManager.swift in Sources */,
 				DD3F1F902D9E153F00DCE7B3 /* NightscoutImportStepView.swift in Sources */,
 				CE82E02528E867BA00473A9C /* AlertStorage.swift in Sources */,
@@ -4235,6 +4276,7 @@
 				9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */,
 				DDA6E3222D25901100C2988C /* TempTargetHelpView.swift in Sources */,
 				58645B9D2CA2D275008AFCE7 /* DeterminationSetup.swift in Sources */,
+				DDFF202F2DB1D14500AB8A96 /* NotificationPermissionStepView.swift in Sources */,
 				491D6FBD2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift in Sources */,
 				491D6FBE2D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift in Sources */,
 				491D6FBF2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift in Sources */,
@@ -4338,6 +4380,7 @@
 				58A3D5442C96DE11003F90FC /* TempTargetStored+Helper.swift in Sources */,
 				DD6B7CB42C7B71F700B75029 /* ForecastDisplayType.swift in Sources */,
 				BD47FD172D88AAF50043966B /* CompletedStepView.swift in Sources */,
+				DD4AFFF12DADB59100AB7387 /* AlgorithmSettingsStepView.swift in Sources */,
 				DDEBB05C2D89E9050032305D /* TimeInRangeType.swift in Sources */,
 				DD5DC9F32CF3D9DD00AB8703 /* AdjustmentsStateModel+TempTargets.swift in Sources */,
 				BD47FDDB2D8B659B0043966B /* BasalProfileStepView.swift in Sources */,
@@ -4369,7 +4412,7 @@
 				CE82E02728E869DF00473A9C /* AlertEntry.swift in Sources */,
 				DD30786A2D42F94000DE0490 /* GarminDevice.swift in Sources */,
 				38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */,
-				BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */,
+				BD2FF1A02AE29D43005D1C5D /* ToggleStyles.swift in Sources */,
 				DDD163162C4C690300CD525A /* AdjustmentsDataFlow.swift in Sources */,
 				BDF34F932C10D0E100D51995 /* LiveActivityAttributes+Helper.swift in Sources */,
 				E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */,
@@ -4453,7 +4496,7 @@
 				BDC530FF2D0F6BE300088832 /* ContactImageManager.swift in Sources */,
 				BDC531122D1060FA00088832 /* ContactImageDetailView.swift in Sources */,
 				DDE179552C910127003CDDB7 /* LoopStatRecord+CoreDataProperties.swift in Sources */,
-				BD10516D2DA986E1007C6D89 /* LogoAnimation.swift in Sources */,
+				BD10516D2DA986E1007C6D89 /* PulsingLogoAnimation.swift in Sources */,
 				DDE179562C910127003CDDB7 /* BolusStored+CoreDataClass.swift in Sources */,
 				DDE179572C910127003CDDB7 /* BolusStored+CoreDataProperties.swift in Sources */,
 				BD4D738D2D15A4080052227B /* TDDStored+CoreDataClass.swift in Sources */,
@@ -4484,6 +4527,7 @@
 				DDE1796F2C910127003CDDB7 /* OrefDetermination+CoreDataProperties.swift in Sources */,
 				DDE179702C910127003CDDB7 /* OverrideStored+CoreDataClass.swift in Sources */,
 				DD9ECB742CA9A0C300AA7C45 /* RemoteControlConfig.swift in Sources */,
+				DD4A00212DAEEED800AB7387 /* OnboardingView+AlgorithmUtil.swift in Sources */,
 				DDE179712C910127003CDDB7 /* OverrideStored+CoreDataProperties.swift in Sources */,
 				CD78BB94E43B249D60CC1A1B /* GlucoseNotificationSettingsRootView.swift in Sources */,
 				CE7CA3502A064973004BE681 /* CancelTempPresetIntent.swift in Sources */,

+ 1 - 1
Trio.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -1,5 +1,5 @@
 {
-  "originHash" : "89074a88ed67a58ecd7534519854c5a0928a4046d7c8a6123a7d70f27bf8b44d",
+  "originHash" : "b10fee57248e5d754951672d55dd1e425fadd3089d06858aed6f0f5206be7e5c",
   "pins" : [
     {
       "identity" : "abseil-cpp-binary",

+ 12 - 0
Trio/Resources/Assets.xcassets/logo.bluetooth.capsule.portrait.fill.symbolset/Contents.json

@@ -0,0 +1,12 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "symbols" : [
+    {
+      "filename" : "logo.bluetooth.capsule.portrait.fill.svg",
+      "idiom" : "universal"
+    }
+  ]
+}

Разница между файлами не показана из-за своего большого размера
+ 196 - 0
Trio/Resources/Assets.xcassets/logo.bluetooth.capsule.portrait.fill.symbolset/logo.bluetooth.capsule.portrait.fill.svg


+ 2 - 2
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -227,10 +227,10 @@ final class OpenAPS {
             if let tempBasalDTO = event.toTempBasalDTOEnum() {
                 eventDTOs.append(tempBasalDTO)
             }
-            if let pumpSuspendDTO = event.toPumpSuspendDTO() {
+            if let pumpSuspendDTO = event.toSuspendDTO() {
                 eventDTOs.append(pumpSuspendDTO)
             }
-            if let pumpResumeDTO = event.toPumpResumeDTO() {
+            if let pumpResumeDTO = event.toResumeDTO() {
                 eventDTOs.append(pumpResumeDTO)
             }
             if let rewindDTO = event.toRewindDTO() {

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

@@ -35,7 +35,7 @@ extension Notification.Name {
     @State private var appState = AppState()
     @State private var showLoadingView = true
     @State private var showLoadingError = false
-    @State private var showOnboardingView = false
+    @State private var showOnboardingCompletedSplash = false
 
     // Dependencies Assembler
     // contain all dependencies Assemblies
@@ -103,7 +103,7 @@ extension Notification.Name {
             object: nil,
             queue: .main
         ) { [self] _ in
-            showOnboardingView = false
+            showOnboardingCompletedSplash = true
         }
 
         let submodulesInfo = BuildDetails.shared.submodules.map { key, value in
@@ -205,40 +205,59 @@ extension Notification.Name {
 
     var body: some Scene {
         WindowGroup {
-            if self.showLoadingView {
-                Main.LoadingView(showError: $showLoadingError, retry: retryCoreDataInitialization)
-                    .onAppear {
-                        if self.initState.complete {
+            ZStack {
+                if self.showLoadingView {
+                    Main.LoadingView(showError: $showLoadingError, retry: retryCoreDataInitialization)
+                        .onAppear {
+                            if self.initState.complete {
+                                Task { @MainActor in
+                                    try? await Task.sleep(for: .seconds(1.8))
+                                    self.showLoadingView = false
+                                }
+                            }
+                            if self.initState.error {
+                                self.showLoadingError = true
+                            }
+                        }
+                        .onReceive(Foundation.NotificationCenter.default.publisher(for: .initializationCompleted)) { _ in
                             Task { @MainActor in
                                 try? await Task.sleep(for: .seconds(1.8))
                                 self.showLoadingView = false
                             }
                         }
-                        if self.initState.error {
+                        .onReceive(Foundation.NotificationCenter.default.publisher(for: .initializationError)) { _ in
                             self.showLoadingError = true
                         }
+                } else if showOnboardingCompletedSplash {
+                    LogoBurstSplash(isActive: $showOnboardingCompletedSplash) {
+                        Main.RootView(resolver: resolver)
+                            .preferredColorScheme(colorScheme(for: colorSchemePreference))
+                            .environment(
+                                \.managedObjectContext,
+                                coreDataStack.persistentContainer.viewContext
+                            )
+                            .environment(appState)
+                            .environmentObject(Icons())
+                            .onOpenURL(perform: handleURL)
                     }
-                    .onReceive(Foundation.NotificationCenter.default.publisher(for: .initializationCompleted)) { _ in
-                        Task { @MainActor in
-                            try? await Task.sleep(for: .seconds(1.8))
-                            self.showLoadingView = false
-                        }
-                    }
-                    .onReceive(Foundation.NotificationCenter.default.publisher(for: .initializationError)) { _ in
-                        self.showLoadingError = true
-                    }
-            } else if onboardingManager.shouldShowOnboarding {
-                // Show onboarding if needed
-                Onboarding.RootView(resolver: resolver, onboardingManager: onboardingManager)
-                    .preferredColorScheme(colorScheme(for: .dark) ?? nil)
-                    .transition(.opacity)
-            } else {
-                Main.RootView(resolver: resolver)
-                    .preferredColorScheme(colorScheme(for: colorSchemePreference) ?? nil)
-                    .environment(\.managedObjectContext, coreDataStack.persistentContainer.viewContext)
-                    .environment(appState)
-                    .environmentObject(Icons())
-                    .onOpenURL(perform: handleURL)
+                } else if onboardingManager.shouldShowOnboarding {
+                    // Show onboarding if needed
+                    Onboarding.RootView(resolver: resolver, onboardingManager: onboardingManager)
+                        .preferredColorScheme(colorScheme(for: .dark) ?? nil)
+                        .transition(.opacity)
+                } else {
+                    Main.RootView(resolver: resolver)
+                        .preferredColorScheme(colorScheme(for: colorSchemePreference) ?? nil)
+                        .environment(\.managedObjectContext, coreDataStack.persistentContainer.viewContext)
+                        .environment(appState)
+                        .environmentObject(Icons())
+                        .onOpenURL(perform: handleURL)
+                }
+            }
+            .onReceive(Foundation.NotificationCenter.default.publisher(for: .onboardingCompleted)) { _ in
+                Task { @MainActor in
+                    self.showOnboardingCompletedSplash = true
+                }
             }
         }
         .onChange(of: scenePhase) { _, newScenePhase in

+ 15 - 5
Trio/Sources/Helpers/CheckboxToggleStyle.swift

@@ -1,6 +1,6 @@
 import SwiftUI
 
-struct CheckboxToggleStyle: ToggleStyle {
+struct RadioButtonToggleStyle: ToggleStyle {
     func makeBody(configuration: Self.Configuration) -> some View {
         HStack {
             Circle()
@@ -22,22 +22,32 @@ struct CheckboxToggleStyle: ToggleStyle {
     }
 }
 
-struct Checkbox: ToggleStyle {
-    func makeBody(configuration: Self.Configuration) -> some View {
+struct CheckboxToggleStyle: ToggleStyle {
+    var tint = Color.primary
+
+    func makeBody(configuration: Configuration) -> some View {
         HStack {
             RoundedRectangle(cornerRadius: 5)
                 .stroke(lineWidth: 2)
-                .foregroundColor(.secondary)
+                .foregroundColor(Color.secondary)
                 .frame(width: 20, height: 20)
                 .overlay {
                     if configuration.isOn {
-                        Image(systemName: "checkmark").font(.body).fontWeight(.bold)
+                        Image(systemName: "checkmark")
+                            .font(.body)
+                            .fontWeight(.bold)
+                            .foregroundColor(tint)
                     }
                 }
                 .onTapGesture {
                     configuration.isOn.toggle()
                 }
+
             configuration.label
         }
+        .contentShape(Rectangle()) // make entire HStack tappable
+        .onTapGesture {
+            configuration.isOn.toggle()
+        }
     }
 }

+ 53 - 0
Trio/Sources/Helpers/ToggleStyles.swift

@@ -0,0 +1,53 @@
+import SwiftUI
+
+struct RadioButtonToggleStyle: ToggleStyle {
+    func makeBody(configuration: Self.Configuration) -> some View {
+        HStack {
+            Circle()
+                .stroke(lineWidth: 2)
+                .foregroundColor(.secondary)
+                .frame(width: 20, height: 20)
+                .overlay {
+                    if configuration.isOn {
+                        Image(systemName: "circle.fill")
+                    }
+                }
+                .onTapGesture {
+                    withAnimation {
+                        configuration.isOn.toggle()
+                    }
+                }
+            configuration.label
+        }
+    }
+}
+
+struct CheckboxToggleStyle: ToggleStyle {
+    var tint = Color.primary
+
+    func makeBody(configuration: Configuration) -> some View {
+        HStack {
+            RoundedRectangle(cornerRadius: 5)
+                .stroke(lineWidth: 2)
+                .foregroundColor(Color.secondary)
+                .frame(width: 20, height: 20)
+                .overlay {
+                    if configuration.isOn {
+                        Image(systemName: "checkmark")
+                            .font(.body)
+                            .fontWeight(.bold)
+                            .foregroundColor(tint)
+                    }
+                }
+                .onTapGesture {
+                    configuration.isOn.toggle()
+                }
+
+            configuration.label
+        }
+        .contentShape(Rectangle()) // make entire HStack tappable
+        .onTapGesture {
+            configuration.isOn.toggle()
+        }
+    }
+}

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


+ 29 - 9
Trio/Sources/Modules/AppDiagnostics/View/AppDiagnosticsRootView.swift

@@ -10,12 +10,14 @@ extension AppDiagnostics {
         @Environment(\.colorScheme) var colorScheme
         @Environment(AppState.self) var appState
 
+        @State private var shouldDisplayPrivacyPolicy: Bool = false
+
         var body: some View {
             List {
                 Section(
                     header: Text("Anonymized Data Sharing"),
                     content: {
-                        VStack {
+                        VStack(alignment: .leading) {
                             ForEach(DiagnosticsSharingOption.allCases, id: \.self) { option in
                                 Button(action: {
                                     state.diagnosticsSharingOption = option
@@ -50,17 +52,25 @@ extension AppDiagnostics {
                     VStack(alignment: .leading, spacing: 8) {
                         Text("Why does Trio collect this data?").bold()
                         VStack(alignment: .leading, spacing: 4) {
-                            Text(
-                                "•  App diagnostic insights help us enhance app stability, ensure safety for all users, and enable us to quickly identify and resolve critical issues."
+                            BulletPoint(
+                                String(
+                                    localized: "App diagnostic insights help us enhance app stability, ensure safety for all users, and enable us to quickly identify and resolve critical issues."
+                                )
                             )
-                            Text(
-                                "•  Trio collects the app's state on crash, device, iOS and general system info, and a stack trace."
+                            BulletPoint(
+                                String(
+                                    localized: "Trio collects the app's state on crash, device, iOS and general system info, and a stack trace."
+                                )
                             )
-                            Text(
-                                "•  Trio does not collect any health related data, e.g. glucose readings, insulin rates or doses, meal data, setting values, or similar."
+                            BulletPoint(
+                                String(
+                                    localized: "Trio does not collect any health related data, e.g. glucose readings, insulin rates or doses, meal data, setting values, or similar."
+                                )
                             )
-                            Text(
-                                "•  Trio does not track any usage metrics or any other personal data about users other than the used iPhone model and iOS version."
+                            BulletPoint(
+                                String(
+                                    localized: "Trio does not track any usage metrics or any other personal data about users other than the used iPhone model and iOS version."
+                                )
                             )
                         }
                         Text(
@@ -77,6 +87,16 @@ extension AppDiagnostics {
             .onAppear(perform: configureView)
             .navigationBarTitle("App Diagnostics")
             .navigationBarTitleDisplayMode(.automatic)
+            .toolbar {
+                ToolbarItem(placement: .topBarTrailing) {
+                    Button("Privacy Policy") {
+                        shouldDisplayPrivacyPolicy = true
+                    }
+                }
+            }
+            .sheet(isPresented: $shouldDisplayPrivacyPolicy) {
+                PrivacyPolicyView()
+            }
         }
     }
 }

+ 233 - 0
Trio/Sources/Modules/AppDiagnostics/View/PrivacyPolicyView.swift

@@ -0,0 +1,233 @@
+//
+//  PrivacyPolicyView.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 17.04.25.
+//
+import SwiftUI
+
+struct PrivacyPolicyView: View {
+    @Environment(\.openURL) var openURL
+    @Environment(\.dismiss) var dismiss
+
+    var body: some View {
+        NavigationStack {
+            List {
+                VStack(alignment: .leading, spacing: 20) {
+                    Text("Introduction").font(.headline).bold().foregroundStyle(Color.primary)
+                    Text(
+                        "This Privacy Policy explains how we collect, use, and share information when you use Trio. We respect your privacy and are committed to protecting your personal data. Please read this Privacy Policy carefully to understand our practices regarding your personal data."
+                    )
+
+                    Divider()
+
+                    Text("Information We Collect").font(.headline).bold().foregroundStyle(Color.primary)
+                    Text("What We Do NOT Collect").foregroundStyle(Color.primary)
+
+                    Text("For complete transparency, we want to clarify that Trio does not collect:")
+
+                    VStack(alignment: .leading, spacing: 10) {
+                        BulletPoint(String(localized: "Blood glucose (BG) readings"))
+                        BulletPoint(String(localized: "Treatment data"))
+                        BulletPoint(String(localized: "Total daily doses (TDD)"))
+                        BulletPoint(String(localized: "Any health-related statistics or personal medical information"))
+                        BulletPoint(String(localized: "Personal identifiable information such as name, address, or email"))
+                    }
+
+                    Text("Crash Reporting (Opt-In by default, with ability to Opt-Out)").foregroundStyle(Color.primary)
+                    Text(
+                        "Trio uses Google Firebase Crashlytics to collect crash reports. During the initial app setup (onboarding process), you will be asked to opt in to crash reporting. The onboarding process is the series of screens you see when first launching Trio that helps you set up the app."
+                    )
+
+                    Text("The following information may be sent to Crashlytics when Trio crashes:")
+
+                    VStack(alignment: .leading, spacing: 10) {
+                        BulletPoint(
+                            String(
+                                localized: "Time and date of the crash (example: \"Trio crashed on April 6, 2025 at 2:15 PM\")"
+                            )
+                        )
+                        BulletPoint(
+                            String(
+                                localized: "Device state at the time of the crash (example: \"Trio was in the foreground\" or \"Battery level was 42%\")"
+                            )
+                        )
+                        BulletPoint(
+                            String(localized: "Stack trace information (technical information showing which line of code failed)")
+                        )
+                        BulletPoint(
+                            String(localized: "Device model and OS version (example: \"iPhone 14 Pro running iOS 17.4.1\")")
+                        )
+                        BulletPoint(
+                            String(
+                                localized: "A generated unique identifier (a random code like \"A7B2C9D3\" that doesn't identify you personally)"
+                            )
+                        )
+                    }
+
+                    Text("Debug Symbols (dSYMs)").foregroundStyle(Color.primary)
+
+                    Text(
+                        "When we build the Trio app, we create special files called debug symbols (dSYMs) that help us read crash reports. Think of these like a decoder ring for crashes:"
+                    )
+
+                    Text(
+                        "Without dSYMs, a crash might look like: \"Error at memory address 0x1234ABCD\". With dSYMs, we can see: \"Error in function 'calculateInsulin' at line 157\""
+                    )
+
+                    Text(
+                        "These files only contain code-related information that helps us understand where crashes happen. They contain no personal information about you or how you use Trio."
+                    )
+
+                    Divider()
+
+                    Text("How We Use Your Information").font(.headline).bold().foregroundStyle(Color.primary)
+
+                    Text("We use anonymous crash report information exclusively to:")
+
+                    VStack(alignment: .leading, spacing: 10) {
+                        BulletPoint(String(localized: "Identify and fix bugs and crashes"))
+                        BulletPoint(String(localized: "Improve Trio's stability"))
+                    }
+
+                    Text("We do not use this information for any other purpose, such as analytics, marketing, or user profiling.")
+
+                    Divider()
+
+                    Text("Data Sharing and Third-Party Services").font(.headline).bold().foregroundStyle(Color.primary)
+
+                    Text("Crashlytics").foregroundStyle(Color.primary)
+
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text(
+                            "We use Google Firebase Crashlytics to collect and analyze crash reports. Crashlytics' privacy practices are governed by the Google Privacy Policy. For more information about how Crashlytics processes data, please visit their documentation."
+                        )
+
+                        Button {
+                            openURL(URL(string: "https://policies.google.com/privacy")!)
+                        } label: {
+                            Text("Google Privacy Policy")
+                                .padding(.horizontal, 12)
+                                .padding(.vertical, 8)
+                                .background(Color.blue.opacity(0.2))
+                                .cornerRadius(8)
+                        }
+                        .frame(maxWidth: .infinity, alignment: .center)
+                        .padding(.horizontal)
+                    }
+
+                    Text("Open Source Contributors").foregroundStyle(Color.primary)
+
+                    Text(
+                        "As an open source project, crash reports and debugging information may be visible to project contributors who help maintain and improve Trio. All contributors are expected to adhere to this privacy policy and handle any data responsibly."
+                    )
+
+                    Divider()
+
+                    Text("Opting Out and Data Retention")
+
+                    Text("You can opt out of crash reporting at any time through the Trio settings. If you opt out:")
+
+                    VStack(alignment: .leading, spacing: 10) {
+                        BulletPoint(String(localized: "No new crash data will be collected or sent to us"))
+                        BulletPoint(
+                            String(localized: "Previously collected crash data will still be retained for approximately 90 days")
+                        )
+                    }
+
+                    Text(
+                        "To avoid sending dSYMs to Crashlytics, you can delete the Trio target Build Phase script, titled \"Copy dSYMs to Crashlytics\"."
+                    )
+
+                    Divider()
+
+                    Text("Your Rights").font(.headline).bold().foregroundStyle(Color.primary)
+
+                    Text("You have certain rights regarding your information, including:")
+
+                    VStack(alignment: .leading, spacing: 10) {
+                        BulletPoint(String(localized: "The right to opt-out of crash reporting"))
+                        BulletPoint(String(localized: "The right to request deletion of your data"))
+                    }
+
+                    Text(
+                        "To opt-out of crash reporting, please see the section above for details about how to configure Trio to not record crash reports."
+                    )
+
+                    Text(
+                        "The information we store is anonymous, so we are unable to look up information for a particular individual. However, our general data retention policy ensures that data older than 90 days is deleted, enabling us to accommodate data deletion requests by design despite having anonymous data."
+                    )
+
+                    Divider()
+
+                    Text("Changes to This Privacy Policy").font(.headline).bold().foregroundStyle(Color.primary)
+
+                    Text(
+                        "We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the \"Last Updated\" date."
+                    )
+
+                    Divider()
+
+                    Text("Contact Us").font(.headline).bold().foregroundStyle(Color.primary)
+
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text(
+                            "If you have any questions about this Privacy Policy, please contact us on Discord, or send us an email."
+                        ).multilineTextAlignment(.leading)
+
+                        HStack(alignment: .center, spacing: 10) {
+                            Button {
+                                openURL(URL(string: "http://discord.diy-trio.org/")!)
+                            } label: {
+                                Text("Trio Discord")
+                                    .padding(.horizontal, 12)
+                                    .padding(.vertical, 8)
+                                    .background(Color.blue.opacity(0.2))
+                                    .cornerRadius(8)
+                            }
+                            .frame(maxWidth: .infinity, alignment: .center)
+                            .padding(.horizontal)
+
+                            Button {
+                                openURL(URL(string: "mailto:trio.diy.diabetes@gmail.com")!)
+                            } label: {
+                                Text("Email us")
+                                    .padding(.horizontal, 12)
+                                    .padding(.vertical, 8)
+                                    .background(Color.blue.opacity(0.2))
+                                    .cornerRadius(8)
+                            }
+                            .frame(maxWidth: .infinity, alignment: .center)
+                            .padding(.horizontal)
+                        }
+                    }
+
+                    Divider()
+
+                    HStack {
+                        Text("Last Updated:").bold()
+                        Text("April 15, 2025")
+                    }
+                    .font(.headline).foregroundStyle(Color.primary)
+                }
+                .font(.footnote)
+                .foregroundStyle(Color.secondary)
+                .listRowBackground(Color.clear)
+                .fixedSize(horizontal: false, vertical: true)
+                .multilineTextAlignment(.leading)
+            }
+            .scrollContentBackground(.hidden)
+            .navigationBarTitle("Privacy Policy", displayMode: .inline)
+
+            Spacer()
+
+            Button {
+                dismiss()
+            } label: {
+                Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center)
+            }
+            .buttonStyle(.bordered)
+            .padding([.top, .horizontal])
+        }.ignoresSafeArea(edges: .top)
+    }
+}

+ 4 - 4
Trio/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift

@@ -45,8 +45,8 @@ extension BasalProfileEditor {
                     ).foregroundStyle(
                         .linearGradient(
                             colors: [
-                                Color.insulin.opacity(0.6),
-                                Color.insulin.opacity(0.1)
+                                Color.purple.opacity(0.6),
+                                Color.purple.opacity(0.1)
                             ],
                             startPoint: .bottom,
                             endPoint: .top
@@ -54,10 +54,10 @@ extension BasalProfileEditor {
                     ).alignsMarkStylesWithPlotArea()
 
                     LineMark(x: .value("End Date", endDate), y: .value("Amount", profile.amount))
-                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.purple)
 
                     LineMark(x: .value("Start Date", startDate), y: .value("Amount", profile.amount))
-                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.purple)
                 }
             }
             .chartXAxis {

+ 4 - 4
Trio/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift

@@ -201,8 +201,8 @@ extension CarbRatioEditor {
                     ).foregroundStyle(
                         .linearGradient(
                             colors: [
-                                Color.insulin.opacity(0.6),
-                                Color.insulin.opacity(0.1)
+                                Color.orange.opacity(0.6),
+                                Color.orange.opacity(0.1)
                             ],
                             startPoint: .bottom,
                             endPoint: .top
@@ -210,10 +210,10 @@ extension CarbRatioEditor {
                     ).alignsMarkStylesWithPlotArea()
 
                     LineMark(x: .value("End Date", startDate), y: .value("Ratio", displayValue))
-                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.orange)
 
                     LineMark(x: .value("Start Date", endDate), y: .value("Ratio", displayValue))
-                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.orange)
                 }
             }
             .chartXAxis {

+ 4 - 4
Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -208,8 +208,8 @@ extension ISFEditor {
                     ).foregroundStyle(
                         .linearGradient(
                             colors: [
-                                Color.insulin.opacity(0.6),
-                                Color.insulin.opacity(0.1)
+                                Color.red.opacity(0.6),
+                                Color.red.opacity(0.1)
                             ],
                             startPoint: .bottom,
                             endPoint: .top
@@ -217,10 +217,10 @@ extension ISFEditor {
                     ).alignsMarkStylesWithPlotArea()
 
                     LineMark(x: .value("End Date", startDate), y: .value("ISF", displayValueFloat ?? 0))
-                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.red)
 
                     LineMark(x: .value("Start Date", endDate), y: .value("ISF", displayValueFloat ?? 0))
-                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.red)
                 }
             }
             .chartXAxis {

+ 5 - 2
Trio/Sources/Modules/Onboarding/OnboardingStateModel+Nightscout.swift

@@ -192,6 +192,11 @@ extension Onboarding.StateModel {
         userPreferredUnitsFromImport: String,
         currentStep: Binding<OnboardingStep>
     ) {
+        /// First, very important: assign `units` so that `xxxRateValues` contain the proper values
+        /// and array has the correct number of elements.
+        /// If not done here, this may lead to index-out-of-bound errors for users importing mmol/L settings.
+        units = userPreferredUnitsFromImport.contains("mmol") ? .mmolL : .mgdL
+
         // Parse: targetsProfile → targetItems
         targetItems = targetsProfile.targets.map { entry in
             let timeIndex = targetTimeValues.firstIndex(where: { Int($0) == entry.offset * 60 }) ?? 0
@@ -234,8 +239,6 @@ extension Onboarding.StateModel {
         }
         initialISFItems = isfItems
 
-        units = userPreferredUnitsFromImport.contains("mmol") ? .mmolL : .mgdL
-
         DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
             self.nightscoutImportStatus = .finished
             // navigate to the next onboarding step

+ 71 - 5
Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift

@@ -13,12 +13,19 @@ extension Onboarding {
         @ObservationIgnored @Injected() var broadcaster: Broadcaster!
         @ObservationIgnored @Injected() var keychain: Keychain!
         @ObservationIgnored @Injected() var nightscoutManager: NightscoutManager!
+        @ObservationIgnored @Injected() var notificationsManager: UserNotificationsManager!
+        @ObservationIgnored @Injected() var bluetoothManager: BluetoothStateManager!
 
         private let settingsProvider = PickerSettingsProvider.shared
 
         // MARK: - App Diagnostics
 
         var diagnosticsSharingOption: DiagnosticsSharingOption = .enabled
+        var hasAcceptedPrivacyPolicy: Bool = false
+
+        // MARK: - Important Startup Notes
+
+        var hasReadImportantStartupNotes: Bool = false
 
         // MARK: - Nightscout Setup
 
@@ -95,6 +102,37 @@ extension Onboarding {
         var maxCOB: Decimal = 120
         var minimumSafetyThreshold: Decimal = 60
 
+        // MARK: - Algorithm Settings Defaults & State
+
+        var hasReadAlgorithmSetupInformation: Bool = false
+
+        var autosensMin: Decimal = 0.7
+        var autosensMax: Decimal = 1.2
+        var rewindResetsAutosens: Bool = true
+        var enableSMBAlways: Bool = false
+        var enableSMBWithCOB: Bool = false
+        var enableSMBWithTempTarget: Bool = false
+        var enableSMBAfterCarbs: Bool = false
+        var enableSMBWithHighGlucoseTarget: Bool = false
+        var highGlucoseTarget: Decimal = 110
+        var allowSMBWithHighTempTarget: Bool = false
+        var enableUAM: Bool = false
+        var maxSMBMinutes: Decimal = 30
+        var maxUAMMinutes: Decimal = 30
+        var maxDeltaGlucoseThreshold: Decimal = 0.2
+        var highTempTargetRaisesSensitivity: Bool = false
+        var lowTempTargetLowersSensitivity: Bool = false
+        var sensitivityRaisesTarget: Bool = false
+        var resistanceLowersTarget: Bool = false
+        var halfBasalTarget: Decimal = 160
+
+        // MARK: - Permission Requests
+
+        var hasNotificationsGranted = false
+
+        var shouldDisplayBluetoothRequestAlert: Bool = false
+        var hasBluetoothGranted = false
+
         // MARK: - Subscribe
 
         override func subscribe() {
@@ -495,11 +533,39 @@ extension Onboarding {
 
         /// Applies the selected delivery preferences to the app's settings.
         func applyToPreferences() {
-            var preferencesCopy = settingsManager.preferences
-            preferencesCopy.maxIOB = maxIOB
-            preferencesCopy.maxCOB = maxCOB
-            preferencesCopy.threshold_setting = minimumSafetyThreshold
-            settingsManager.preferences = preferencesCopy
+            var preferences = Preferences()
+
+            // delivery limits (those that are preference-bound, not pump-settings-bound
+            preferences.maxIOB = maxIOB
+            preferences.maxCOB = maxCOB
+            preferences.threshold_setting = minimumSafetyThreshold
+
+            // autosens
+            preferences.autosensMin = autosensMin
+            preferences.autosensMax = autosensMax
+            preferences.rewindResetsAutosens = rewindResetsAutosens
+
+            // smb settings
+            preferences.enableSMBAlways = enableSMBAlways
+            preferences.enableSMBWithCOB = enableSMBWithCOB
+            preferences.enableSMBWithTemptarget = enableSMBWithTempTarget
+            preferences.enableSMBAfterCarbs = enableSMBAfterCarbs
+            preferences.enableSMB_high_bg = enableSMBWithHighGlucoseTarget
+            preferences.enableSMB_high_bg_target = highGlucoseTarget
+            preferences.allowSMBWithHighTemptarget = allowSMBWithHighTempTarget
+            preferences.enableUAM = enableUAM
+            preferences.maxSMBBasalMinutes = maxSMBMinutes
+            preferences.maxUAMSMBBasalMinutes = maxUAMMinutes
+            preferences.maxDeltaBGthreshold = maxDeltaGlucoseThreshold
+
+            // target behavior
+            preferences.highTemptargetRaisesSensitivity = highTempTargetRaisesSensitivity
+            preferences.lowTemptargetLowersSensitivity = lowTempTargetLowersSensitivity
+            preferences.sensitivityRaisesTarget = sensitivityRaisesTarget
+            preferences.resistanceLowersTarget = resistanceLowersTarget
+            preferences.halfBasalExerciseTarget = halfBasalTarget
+
+            settingsManager.preferences = preferences
         }
 
         /// Saves pump delivery limits to persistent storage and broadcasts changes.

+ 156 - 0
Trio/Sources/Modules/Onboarding/View/Animations/LogoBurstSplash.swift

@@ -0,0 +1,156 @@
+import SwiftUI
+
+struct LogoBurstSplash<Content: View>: View {
+    @Binding var isActive: Bool
+    private let content: Content
+
+    @State private var logoScale: CGFloat = 0.5
+    @State private var logoOpacity: Double = 0
+    @State private var logoRotation: Double = 0
+    @State private var isPulsing = false
+
+    @State private var exploded = false
+    @State private var shapes: [BurstShape] = []
+    @State private var shapesOpacity: Double = 1
+
+    @State private var viewOpacity: Double = 1.0
+    @State private var splashScale: CGFloat = 1.0
+
+    init(isActive: Binding<Bool>, @ViewBuilder content: () -> Content) {
+        _isActive = isActive
+        self.content = content()
+    }
+
+    var body: some View {
+        GeometryReader { geo in
+            ZStack {
+                content
+                    .opacity(isActive ? 0 : 1)
+
+                if isActive {
+                    LinearGradient(
+                        gradient: Gradient(colors: [Color.bgDarkBlue, Color.bgDarkerDarkBlue]),
+                        startPoint: .top, endPoint: .bottom
+                    )
+                    .ignoresSafeArea()
+
+                    ZStack {
+                        // shards
+                        ForEach(shapes) { shape in
+                            Circle()
+                                .fill(shape.color)
+                                .frame(width: 6, height: 6)
+                                .position(x: geo.size.width / 2, y: geo.size.height / 2)
+                                .offset(
+                                    x: exploded ? shape.xOffset : 0,
+                                    y: exploded ? shape.yOffset : 0
+                                )
+                                .opacity(exploded ? shapesOpacity : 0)
+                                .animation(.easeOut(duration: 0.8), value: exploded)
+                                .animation(.easeIn(duration: 0.5), value: shapesOpacity)
+                        }
+
+                        // logo
+                        Image("trioCircledNoBackground")
+                            .resizable()
+                            .scaledToFit()
+                            .frame(width: 100, height: 100)
+                            .scaleEffect(isPulsing ? 1.1 : logoScale)
+                            .opacity(logoOpacity)
+                            .rotationEffect(.degrees(logoRotation))
+                            .animation(.easeInOut(duration: 1.0), value: logoScale)
+                            .animation(.easeInOut(duration: 1.0), value: logoOpacity)
+                            .animation(.linear(duration: 2.0), value: logoRotation)
+                            .animation(
+                                .easeInOut(duration: 0.8).repeatForever(autoreverses: true),
+                                value: isPulsing
+                            )
+                    }
+                    .scaleEffect(splashScale)
+                    .opacity(viewOpacity)
+                    .onAppear {
+                        shapes = BurstShape.createBurst(count: 250, in: geo.frame(in: .local))
+
+                        withAnimation {
+                            isPulsing = true
+                            logoOpacity = 1
+                            logoScale = 1
+                            logoRotation = 360
+                        }
+
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
+                            isPulsing = false
+                        }
+
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
+                            exploded = true
+                        }
+
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
+                            withAnimation {
+                                logoOpacity = 0
+                                shapesOpacity = 0
+                            }
+                        }
+
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 2.8) {
+                            withAnimation(.easeIn(duration: 0.6)) {
+                                viewOpacity = 0
+                                splashScale = 0.1
+                            }
+                        }
+
+                        // 5) Hide splash at 3.0s
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 3.1) {
+                            isActive = false
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+private struct BurstShape: Identifiable {
+    let id = UUID()
+    let angle: Double
+    let distance: CGFloat
+    let color: Color
+
+    var xOffset: CGFloat { cos(angle) * distance }
+    var yOffset: CGFloat { sin(angle) * distance }
+
+    static func createBurst(count: Int, in rect: CGRect) -> [BurstShape] {
+        let gradientColors: [Color] = [
+            Color(red: 0.7216, green: 0.3412, blue: 1),
+            Color(red: 0.6235, green: 0.4235, blue: 0.9804),
+            Color(red: 0.4863, green: 0.5451, blue: 0.9529),
+            Color(red: 0.3412, green: 0.6667, blue: 0.9255),
+            Color(red: 0.2627, green: 0.7333, blue: 0.9137)
+        ]
+        return (0 ..< count).map { i in
+            let angle = Double.random(in: 0 ..< 360) * .pi / 180
+            let distance = CGFloat.random(
+                in: min(rect.width, rect.height) * 0.3 ... max(rect.width, rect.height) * 0.6
+            )
+            let color = gradientColors[i % gradientColors.count]
+            return BurstShape(angle: angle, distance: distance, color: color)
+        }
+    }
+}
+
+// MARK: Preview
+
+struct LogoBurstSplash_Previews: PreviewProvider {
+    static var previews: some View {
+        LogoBurstSplash(isActive: .constant(true)) {
+            ZStack {
+                Color.white.ignoresSafeArea()
+                Text("Main Content")
+                    .font(.largeTitle)
+                    .foregroundColor(.gray)
+            }
+        }
+        .previewDevice("iPhone 14 Pro")
+    }
+}

+ 42 - 0
Trio/Sources/Modules/Onboarding/View/Animations/PulsingLogoAnimation.swift

@@ -0,0 +1,42 @@
+//
+//  PulsingLogoAnimation.swift
+//  Trio
+//
+//  Created by Marvin Polscheit on 11.04.25.
+//
+import SwiftUI
+
+struct PulsingLogoAnimation: View {
+    @State private var scale = 0.5
+    @State private var opacity = 0.0
+    @State private var rotation = 0.0
+    @State private var isPulsing = false
+
+    var body: some View {
+        Image("trioCircledNoBackground")
+            .resizable()
+            .scaledToFit()
+            .frame(height: 100)
+            .scaleEffect(scale)
+            .opacity(opacity)
+            .rotationEffect(.degrees(rotation))
+            .scaleEffect(isPulsing ? 1.1 : 1.0)
+            .onAppear {
+                withAnimation(.easeInOut(duration: 1.0)) {
+                    scale = 1.0
+                    opacity = 1.0
+                    rotation = 360
+                }
+
+                withAnimation(.easeInOut(duration: 1.0).repeatForever()) {
+                    isPulsing.toggle()
+                }
+
+                DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
+                    withAnimation(.easeOut(duration: 1.0)) {
+                        isPulsing = false
+                    }
+                }
+            }
+    }
+}

+ 392 - 207
Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -8,9 +8,14 @@ extension Onboarding {
         @State var state = StateModel()
         @State private var navigationDirection: OnboardingNavigationDirection = .forward
         let onboardingManager: OnboardingManager
+
+        // Step management
         @State private var currentStep: OnboardingStep = .welcome
-        @State private var currentDeliverySubstep: DeliveryLimitSubstep = .maxIOB
         @State private var currentNightscoutSubstep: NightscoutSubstep = .setupSelection
+        @State private var currentDeliverySubstep: DeliveryLimitSubstep = .maxIOB
+        @State private var currentAutosensSubstep: AutosensSettingsSubstep = .autosensMin
+        @State private var currentSMBSubstep: SMBSettingsSubstep = .enableSMBAlways
+        @State private var currentTargetBehaviorSubstep: TargetBehaviorSubstep = .highTempTargetRaisesSensitivity
 
         // Animation states
         @State private var animationScale: CGFloat = 1.0
@@ -31,12 +36,19 @@ extension Onboarding {
             currentNightscoutSubstep == .importFromNightscout && state.nightscoutImportOption == .noSelection
         }
 
+        // Next button conditional
         private var shouldDisableNextButton: Bool {
-            (currentStep == .nightscout && didSelectNightscoutSetupOption)
+            (currentStep == .startupGuide && !state.hasReadImportantStartupNotes)
+                ||
+                (currentStep == .diagnostics && state.diagnosticsSharingOption == .enabled && !state.hasAcceptedPrivacyPolicy)
+                ||
+                (currentStep == .nightscout && didSelectNightscoutSetupOption)
                 ||
                 (currentStep == .nightscout && hasValidNightscoutConnection)
                 ||
                 (currentStep == .nightscout && didSelectNightscoutImportOption)
+                ||
+                (currentStep == .algorithmSettings && !state.hasReadAlgorithmSetupInformation)
         }
 
         var body: some View {
@@ -59,225 +71,52 @@ extension Onboarding {
                                     switch currentStep {
                                     case .deliveryLimits: return currentDeliverySubstep.rawValue
                                     case .nightscout: return currentNightscoutSubstep.rawValue
+                                    case .autosensSettings: return currentAutosensSubstep.rawValue
+                                    case .smbSettings: return currentSMBSubstep.rawValue
+                                    case .targetBehavior: return currentTargetBehaviorSubstep.rawValue
                                     default: return nil
                                     }
                                 }(),
                                 stepsWithSubsteps: [
                                     .nightscout: NightscoutSubstep.allCases.count,
-                                    .deliveryLimits: DeliveryLimitSubstep.allCases.count
+                                    .deliveryLimits: DeliveryLimitSubstep.allCases.count,
+                                    .autosensSettings: AutosensSettingsSubstep.allCases.count,
+                                    .smbSettings: SMBSettingsSubstep.allCases.count,
+                                    .targetBehavior: TargetBehaviorSubstep.allCases.count
                                 ],
                                 nightscoutSetupOption: state.nightscoutSetupOption
                             )
                             .padding(.top)
+                        } else {
+                            // avoid letting content scroll beneath the status bar / dynamic island for content views with no progress bar (which adds top spacing)
+                            Color.clear.frame(height: 1)
                         }
 
-                        // Step content
-                        ScrollViewReader { scrollProxy in
-                            ScrollView {
-                                VStack(alignment: .leading, spacing: 20) {
-                                    // Scroll position marker at top
-                                    Color.clear.frame(height: 0).id("top")
-
-                                    // Header
-                                    if currentStep != .welcome && currentStep != .completed {
-                                        HStack {
-                                            if currentStep == .nightscout {
-                                                Image(currentStep.iconName)
-                                                    .resizable()
-                                                    .scaledToFit()
-                                                    .frame(width: 60, height: 60)
-
-                                            } else {
-                                                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, .top])
-                                    }
-
-                                    // Step-specific content
-                                    Group {
-                                        switch currentStep {
-                                        case .welcome:
-                                            WelcomeStepView()
-                                        case .startupGuide:
-                                            StartupGuideStepView()
-                                        case .overview:
-                                            OverviewStepView()
-                                        case .diagnostics:
-                                            DiagnosticsStepView(state: state)
-                                        case .nightscout:
-                                            switch currentNightscoutSubstep {
-                                            case .setupSelection:
-                                                NightscoutSetupStepView(state: state)
-                                            case .connectToNightscout:
-                                                NightscoutLoginStepView(state: state)
-                                            case .importFromNightscout:
-                                                NightscoutImportStepView(state: state)
-                                            }
-                                        case .unitSelection:
-                                            UnitSelectionStepView(state: state)
-                                        case .glucoseTarget:
-                                            GlucoseTargetStepView(state: state)
-                                        case .basalRates:
-                                            BasalProfileStepView(state: state)
-                                        case .carbRatio:
-                                            CarbRatioStepView(state: state)
-                                        case .insulinSensitivity:
-                                            InsulinSensitivityStepView(state: state)
-                                        case .deliveryLimits:
-                                            DeliveryLimitsStepView(state: state, substep: currentDeliverySubstep)
-                                        case .completed:
-                                            CompletedStepView()
-                                        }
-                                    }
-                                    .transition(
-                                        navigationDirection == .forward
-                                            ? .asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))
-                                            : .asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing))
-                                    )
-                                    .padding(.horizontal)
-                                    .id(currentStep.id) // Force view recreation when step changes
-                                }
-                                .padding(.bottom, 80) // Make room for buttons at bottom
-                            }
-                            .onChange(of: currentStep) { _, _ in
-                                scrollProxy.scrollTo("top", anchor: .top)
-                            }
-                            .onChange(of: currentNightscoutSubstep) { _, _ in
-                                scrollProxy.scrollTo("top", anchor: .top)
-                            }
-                            .onChange(of: currentDeliverySubstep) { _, _ in
-                                scrollProxy.scrollTo("top", anchor: .top)
-                            }
-                        }
+                        OnboardingStepContent(
+                            currentStep: $currentStep,
+                            currentNightscoutSubstep: $currentNightscoutSubstep,
+                            currentDeliverySubstep: $currentDeliverySubstep,
+                            currentAutosensSubstep: $currentAutosensSubstep,
+                            currentSMBSubstep: $currentSMBSubstep,
+                            currentTargetBehaviorSubstep: $currentTargetBehaviorSubstep,
+                            state: state,
+                            navigationDirection: navigationDirection
+                        )
 
                         Spacer()
 
-                        // Navigation buttons
-                        HStack {
-                            // Back button
-                            if currentStep != .welcome {
-                                Button(action: {
-                                    navigationDirection = .backward
-                                    withAnimation {
-                                        if currentStep == .completed {
-                                            currentStep = .deliveryLimits
-                                            currentDeliverySubstep =
-                                                .minimumSafetyThreshold // ensure we land on the last substep visually
-                                        } else if currentStep == .nightscout {
-                                            if currentNightscoutSubstep == .setupSelection {
-                                                // First substep: go to previous main step
-                                                if let previousMainStep = currentStep.previous {
-                                                    currentStep = previousMainStep
-                                                    currentNightscoutSubstep = .setupSelection // reset substep
-                                                }
-                                            } else {
-                                                // Go back one substep
-                                                currentNightscoutSubstep = NightscoutSubstep(
-                                                    rawValue: currentNightscoutSubstep
-                                                        .rawValue - 1
-                                                )!
-                                            }
-                                        } else if currentStep == .deliveryLimits {
-                                            if let previousSub = DeliveryLimitSubstep(
-                                                rawValue: currentDeliverySubstep
-                                                    .rawValue - 1
-                                            ) {
-                                                currentDeliverySubstep = previousSub
-                                            } else if let previousMainStep = currentStep.previous {
-                                                currentStep = previousMainStep
-                                                currentDeliverySubstep = .maxIOB // reset to first substep for later return
-                                            }
-                                        } else if let previous = currentStep.previous {
-                                            currentStep = previous
-                                        }
-                                    }
-                                }) {
-                                    HStack {
-                                        Image(systemName: "chevron.left")
-                                        Text("Back")
-                                    }
-                                    .padding()
-                                    .foregroundColor(.primary)
-                                }
-                            }
-
-                            Spacer()
-
-                            // Next/Finish button
-                            Button(action: {
-                                navigationDirection = .forward
-                                withAnimation {
-                                    if currentStep == .completed {
-                                        state.saveOnboardingData()
-                                        onboardingManager.completeOnboarding()
-                                        Foundation.NotificationCenter.default.post(name: .onboardingCompleted, object: nil)
-                                    } else if currentStep == .nightscout {
-                                        if currentNightscoutSubstep != .importFromNightscout {
-                                            // Handle conditional skip
-                                            if currentNightscoutSubstep == .setupSelection,
-                                               state.nightscoutSetupOption == .skipNightscoutSetup,
-                                               let next = currentStep.next
-                                            {
-                                                currentStep = next
-                                            } else {
-                                                currentNightscoutSubstep = NightscoutSubstep(
-                                                    rawValue: currentNightscoutSubstep
-                                                        .rawValue + 1
-                                                )!
-                                            }
-                                        } else if currentNightscoutSubstep == .importFromNightscout,
-                                                  state.nightscoutImportOption == .useImport
-                                        {
-                                            // TODO: trigger import, show animation, then proceed to next step
-                                            Task {
-                                                await state.importSettingsFromNightscout(currentStep: $currentStep)
-                                            }
-                                        } else if let next = currentStep.next {
-                                            currentStep = next
-                                        }
-                                    } else if currentStep == .deliveryLimits {
-                                        if let nextSub = DeliveryLimitSubstep(rawValue: currentDeliverySubstep.rawValue + 1) {
-                                            currentDeliverySubstep = nextSub
-                                        } else if let next = currentStep.next {
-                                            currentStep = next
-                                            currentDeliverySubstep = .maxIOB
-                                        }
-                                    } 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(!shouldDisableNextButton ? Color.blue : Color(.systemGray)))
-                            }.disabled(shouldDisableNextButton)
-                        }
-                        .padding(.horizontal)
-                        .padding(.bottom)
+                        OnboardingNavigationButtons(
+                            currentStep: $currentStep,
+                            currentNightscoutSubstep: $currentNightscoutSubstep,
+                            currentDeliverySubstep: $currentDeliverySubstep,
+                            currentAutosensSubstep: $currentAutosensSubstep,
+                            currentSMBSubstep: $currentSMBSubstep,
+                            currentTargetBehaviorSubstep: $currentTargetBehaviorSubstep,
+                            onboardingManager: onboardingManager,
+                            state: state,
+                            shouldDisableNextButton: shouldDisableNextButton,
+                            navigationDirectionChanged: { navigationDirection = $0 }
+                        )
                     }
                 }
                 .navigationBarHidden(true)
@@ -373,6 +212,352 @@ struct OnboardingProgressBar: View {
     }
 }
 
+struct OnboardingStepContent: View {
+    @Binding var currentStep: OnboardingStep
+    @Binding var currentNightscoutSubstep: NightscoutSubstep
+    @Binding var currentDeliverySubstep: DeliveryLimitSubstep
+    @Binding var currentAutosensSubstep: AutosensSettingsSubstep
+    @Binding var currentSMBSubstep: SMBSettingsSubstep
+    @Binding var currentTargetBehaviorSubstep: TargetBehaviorSubstep
+    @Bindable var state: Onboarding.StateModel
+    var navigationDirection: OnboardingNavigationDirection
+
+    var body: some View {
+        ScrollViewReader { scrollProxy in
+            ScrollView(.vertical, showsIndicators: true) {
+                VStack(alignment: .leading, spacing: 20) {
+                    Color.clear.frame(height: 0).id("top")
+
+                    if currentStep != .welcome && currentStep != .completed {
+                        HStack {
+                            if currentStep == .nightscout {
+                                Image(currentStep.iconName)
+                                    .resizable()
+                                    .scaledToFit()
+                                    .frame(width: 60, height: 60)
+                            } else if currentStep == .bluetooth {
+                                Image(currentStep.iconName)
+                                    .font(.system(size: 40))
+                                    .foregroundColor(currentStep.accentColor)
+                                    .frame(width: 60, height: 60)
+                                    .background(
+                                        Circle()
+                                            .fill(currentStep.accentColor.opacity(0.2))
+                                    )
+                            } else {
+                                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(.title)
+                                    .fontWeight(.bold)
+                                    .foregroundColor(.primary)
+
+                                Text(currentStep.description)
+                                    .font(.subheadline)
+                                    .foregroundColor(.secondary)
+                                    .fixedSize(horizontal: false, vertical: true)
+                            }
+                        }
+                        .padding([.horizontal, .top])
+                    }
+
+                    Group {
+                        switch currentStep {
+                        case .welcome:
+                            WelcomeStepView()
+                        case .startupGuide:
+                            StartupGuideStepView(state: state)
+                        case .overview:
+                            OverviewStepView()
+                        case .diagnostics:
+                            DiagnosticsStepView(state: state)
+                        case .nightscout:
+                            switch currentNightscoutSubstep {
+                            case .setupSelection:
+                                NightscoutSetupStepView(state: state)
+                            case .connectToNightscout:
+                                NightscoutLoginStepView(state: state)
+                            case .importFromNightscout:
+                                NightscoutImportStepView(state: state)
+                            }
+                        case .unitSelection:
+                            UnitSelectionStepView(state: state)
+                        case .glucoseTarget:
+                            GlucoseTargetStepView(state: state)
+                        case .basalRates:
+                            BasalProfileStepView(state: state)
+                        case .carbRatio:
+                            CarbRatioStepView(state: state)
+                        case .insulinSensitivity:
+                            InsulinSensitivityStepView(state: state)
+                        case .deliveryLimits:
+                            DeliveryLimitsStepView(state: state, substep: currentDeliverySubstep)
+                        case .algorithmSettings:
+                            AlgorithmSettingsStepView(state: state)
+                        case .autosensSettings:
+                            AlgorithmSettingsSubstepView(state: state, substep: currentAutosensSubstep)
+                        case .smbSettings:
+                            AlgorithmSettingsSubstepView(state: state, substep: currentSMBSubstep)
+                        case .targetBehavior:
+                            AlgorithmSettingsSubstepView(state: state, substep: currentTargetBehaviorSubstep)
+                        case .notifications:
+                            NotificationPermissionStepView()
+                        case .bluetooth:
+                            BluetoothPermissionStepView(
+                                state: state,
+                                bluetoothManager: state.bluetoothManager,
+                                currentStep: $currentStep
+                            )
+                        case .completed:
+                            CompletedStepView()
+                        }
+                    }
+                    .transition(
+                        navigationDirection == .forward
+                            ? .asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))
+                            : .asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing))
+                    )
+                    .padding(.horizontal)
+                    .id(currentStep.id)
+                }
+                .padding(.bottom, 80)
+            }
+            .onChange(of: currentStep) { _, _ in scrollProxy.scrollTo("top", anchor: .top) }
+            .onChange(of: currentNightscoutSubstep) { _, _ in scrollProxy.scrollTo("top", anchor: .top) }
+            .onChange(of: currentDeliverySubstep) { _, _ in scrollProxy.scrollTo("top", anchor: .top) }
+            .safeAreaInset(edge: .top) {
+                // avoid letting content scroll beneath the status bar / dynamic island for content views with not progress bar (which adds top spacing)
+                if currentStep == .startupGuide || currentStep == .completed {
+                    Color.clear.frame(height: 0)
+                }
+            }
+        }
+    }
+}
+
+struct OnboardingNavigationButtons: View {
+    @Binding var currentStep: OnboardingStep
+    @Binding var currentNightscoutSubstep: NightscoutSubstep
+    @Binding var currentDeliverySubstep: DeliveryLimitSubstep
+    @Binding var currentAutosensSubstep: AutosensSettingsSubstep
+    @Binding var currentSMBSubstep: SMBSettingsSubstep
+    @Binding var currentTargetBehaviorSubstep: TargetBehaviorSubstep
+
+    let onboardingManager: OnboardingManager
+    @Bindable var state: Onboarding.StateModel
+    var shouldDisableNextButton: Bool
+    var navigationDirectionChanged: (OnboardingNavigationDirection) -> Void
+
+    var body: some View {
+        HStack {
+            if currentStep != .welcome {
+                Button(action: {
+                    navigationDirectionChanged(.backward)
+                    withAnimation {
+                        handleBackNavigation()
+                    }
+                }) {
+                    HStack {
+                        Image(systemName: "chevron.left")
+                        Text("Back")
+                    }
+                    .padding()
+                    .foregroundColor(.primary)
+                }
+            }
+
+            Spacer()
+
+            Button(action: {
+                navigationDirectionChanged(.forward)
+                withAnimation {
+                    handleNextNavigation()
+                }
+            }) {
+                HStack {
+                    Text(currentStep == .completed ? "Get Started" : "Next")
+                    Image(systemName: "chevron.right")
+                }
+                .padding()
+                .foregroundColor(.white)
+                .background(Capsule().fill(!shouldDisableNextButton ? Color.blue : Color(.systemGray)))
+            }
+            .disabled(shouldDisableNextButton)
+        }
+        .padding(.horizontal)
+        .padding(.bottom)
+    }
+
+    // MARK: - Navigation Logic
+
+    private func handleBackNavigation() {
+        switch currentStep {
+        case .completed:
+            currentStep = .targetBehavior
+            currentTargetBehaviorSubstep = .halfBasalTarget
+
+        case .nightscout:
+            if currentNightscoutSubstep == .setupSelection,
+               let previous = currentStep.previous
+            {
+                currentStep = previous
+                currentNightscoutSubstep = .setupSelection
+            } else {
+                currentNightscoutSubstep = NightscoutSubstep(rawValue: currentNightscoutSubstep.rawValue - 1)!
+            }
+
+        case .deliveryLimits:
+            if let previousSub = DeliveryLimitSubstep(rawValue: currentDeliverySubstep.rawValue - 1) {
+                currentDeliverySubstep = previousSub
+            } else if let previous = currentStep.previous {
+                currentStep = previous
+                currentDeliverySubstep = .maxIOB
+            }
+
+        case .algorithmSettings:
+            if let previous = currentStep.previous {
+                currentStep = previous
+                currentDeliverySubstep = .minimumSafetyThreshold
+                currentAutosensSubstep = .autosensMin
+            }
+
+        case .autosensSettings:
+            if let previous = AutosensSettingsSubstep(rawValue: currentAutosensSubstep.rawValue - 1) {
+                currentAutosensSubstep = previous
+            } else if let previousStep = currentStep.previous {
+                currentStep = previousStep
+                currentAutosensSubstep = .autosensMin
+            }
+
+        case .smbSettings:
+            if let previous = SMBSettingsSubstep(rawValue: currentSMBSubstep.rawValue - 1) {
+                /// If user has activated setting `.enableSMBAlways`, when navigating backwards
+                /// skip other redundant "Enable SMB"-settings and go straight to `enableSMBAlways`
+                /// from current substep `.allowSMBWithHighTempTarget`.
+                if state.enableSMBAlways, currentSMBSubstep == .allowSMBWithHighTempTarget {
+                    currentSMBSubstep = .enableSMBAlways
+                } else {
+                    currentSMBSubstep = previous
+                }
+            } else if let previousStep = currentStep.previous {
+                currentStep = previousStep
+                currentSMBSubstep = .enableSMBAlways
+                currentAutosensSubstep = .rewindResetsAutosens
+            }
+
+        case .targetBehavior:
+            if let previous = TargetBehaviorSubstep(rawValue: currentTargetBehaviorSubstep.rawValue - 1) {
+                currentTargetBehaviorSubstep = previous
+            } else if let previousStep = currentStep.previous {
+                currentStep = previousStep
+                currentTargetBehaviorSubstep = .highTempTargetRaisesSensitivity
+                currentSMBSubstep = .maxDeltaGlucoseThreshold
+            }
+
+        default:
+            if let previous = currentStep.previous {
+                currentStep = previous
+            }
+        }
+    }
+
+    private func handleNextNavigation() {
+        switch currentStep {
+        case .nightscout:
+            if currentNightscoutSubstep != .importFromNightscout {
+                if currentNightscoutSubstep == .setupSelection,
+                   state.nightscoutSetupOption == .skipNightscoutSetup,
+                   let next = currentStep.next
+                {
+                    currentStep = next
+                } else {
+                    currentNightscoutSubstep = NightscoutSubstep(rawValue: currentNightscoutSubstep.rawValue + 1)!
+                }
+            } else if currentNightscoutSubstep == .importFromNightscout,
+                      state.nightscoutImportOption == .useImport
+            {
+                Task {
+                    await state.importSettingsFromNightscout(currentStep: $currentStep)
+                }
+            } else if let next = currentStep.next {
+                currentStep = next
+            }
+
+        case .deliveryLimits:
+            if let next = DeliveryLimitSubstep(rawValue: currentDeliverySubstep.rawValue + 1) {
+                currentDeliverySubstep = next
+            } else if let nextStep = currentStep.next {
+                currentStep = nextStep
+                currentDeliverySubstep = .maxIOB
+            }
+
+        case .autosensSettings:
+            if let next = AutosensSettingsSubstep(rawValue: currentAutosensSubstep.rawValue + 1) {
+                currentAutosensSubstep = next
+            } else if let nextStep = currentStep.next {
+                currentStep = nextStep
+                currentAutosensSubstep = .autosensMin
+            }
+
+        case .smbSettings:
+            if let next = SMBSettingsSubstep(rawValue: currentSMBSubstep.rawValue + 1) {
+                /// If user has activated setting `.enableSMBAlways`, when navigating forward
+                /// skip other redundant "Enable SMB"-settings and go straight to `.allowSMBWithHighTempTarget`
+                /// from current substep `.enableSMBAlways`.
+                if state.enableSMBAlways, currentSMBSubstep == .enableSMBAlways {
+                    currentSMBSubstep = .allowSMBWithHighTempTarget
+                } else {
+                    currentSMBSubstep = next
+                }
+            } else if let nextStep = currentStep.next {
+                currentStep = nextStep
+                currentSMBSubstep = .enableSMBAlways
+            }
+
+        case .targetBehavior:
+            if let next = TargetBehaviorSubstep(rawValue: currentTargetBehaviorSubstep.rawValue + 1) {
+                currentTargetBehaviorSubstep = next
+            } else if let nextStep = currentStep.next {
+                currentStep = nextStep
+                currentTargetBehaviorSubstep = .highTempTargetRaisesSensitivity
+            }
+
+        case .notifications:
+            currentTargetBehaviorSubstep = .halfBasalTarget
+            if let next = currentStep.next {
+                DispatchQueue.main.async {
+                    state.notificationsManager.requestNotificationPermissions { granted in
+                        state.hasNotificationsGranted = granted
+                        currentStep = next
+                    }
+                }
+            }
+
+        case .bluetooth:
+            state.shouldDisplayBluetoothRequestAlert = true
+
+        case .completed:
+            state.saveOnboardingData()
+            onboardingManager.completeOnboarding()
+            Foundation.NotificationCenter.default.post(name: .onboardingCompleted, object: nil)
+
+        default:
+            if let next = currentStep.next {
+                currentStep = next
+            }
+        }
+    }
+}
+
 struct Onboarding_Preview: PreviewProvider {
     static var previews: some View {
         Group {

+ 70 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/AlgorithmSettings/AlgorithmSettingsStepView.swift

@@ -0,0 +1,70 @@
+//
+//  AlgorithmSettingsStepView.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 14.04.25
+//
+import SwiftUI
+
+struct AlgorithmSettingsStepView: View {
+    @Bindable var state: Onboarding.StateModel
+
+    @State private var shouldDisplayPicker: Bool = false
+    @State private var decimalPlaceholder: Decimal = 0.0
+    @State private var booleanPlaceholder: Bool = false
+
+    private let settingsProvider = PickerSettingsProvider.shared
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            Text("Configure the algorithm…")
+                .padding(.horizontal)
+                .font(.title3)
+                .bold()
+
+            VStack(alignment: .leading, spacing: 10) {
+                Text(
+                    "Trio can automatically adapt insulin delivery based on inputs and glucose forecasts. Your algorithm settings play a major part in accurate and effective dosing."
+                ).multilineTextAlignment(.leading)
+
+                Text("In the next few steps, you’ll configure your algorithm settings for")
+                BulletPoint(String(localized: "Autosens"))
+                BulletPoint(String(localized: "Super Micro Bolus (SMB)"))
+                BulletPoint(String(localized: "Target Behavior"))
+
+                Text("Our strong recommendation is to ")
+                    + Text("leave everything on default").bold()
+                    + Text(" as a beginner.")
+
+                Text("Only adjust these settings if you’re an advanced or returning user who knows what they’re doing.")
+                    .multilineTextAlignment(.leading)
+            }
+            .padding(.horizontal)
+
+            VStack(alignment: .leading, spacing: 10) {
+                Text("A few important notes:")
+                    .font(.headline)
+                    .padding(.bottom, 4)
+
+                BulletPoint(String(localized: "Dynamic ISF requires at least 7 days of usage data and is not yet configurable."))
+                BulletPoint(String(localized: "Even if you’re an updating user, you’ll be guided through this step-by-step."))
+                BulletPoint(String(localized: "All additional \"advanced settings\" have been reset."))
+                BulletPoint(
+                    String(localized: "The duration of insulin action (DIA) is now locked to Trio’s new default of 10 hours.")
+                )
+                BulletPoint(
+                    String(localized: "We strongly recommend not changing DIA — it’s essential to stable and safe operation.")
+                )
+            }
+            .padding(.horizontal)
+
+            Divider()
+
+            Toggle(isOn: $state.hasReadAlgorithmSetupInformation) {
+                Text("Got it! I'm ready to continue.").padding(.leading, 6).bold()
+            }
+            .toggleStyle(CheckboxToggleStyle(tint: Color.blue))
+            .padding(.horizontal)
+        }
+    }
+}

+ 299 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/AlgorithmSettings/AlgorithmSettingsSubstepView.swift

@@ -0,0 +1,299 @@
+//
+//  AlgorithmSettingsSubstepView.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 15.04.25.
+//
+import SwiftUI
+
+struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepresentable>: View where Substep.RawValue == Int {
+    @Bindable var state: Onboarding.StateModel
+    let substep: Substep
+
+    @State private var shouldDisplayPicker: Bool = false
+    @State private var decimalPlaceholder: Decimal = 0.0
+    @State private var booleanPlaceholder: Bool = false
+
+    private let settingsProvider = PickerSettingsProvider.shared
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 16) {
+            Text(substep.title)
+                .padding(.horizontal)
+                .font(.title3)
+                .bold()
+            Text(substep.hint(units: state.units))
+                .font(.subheadline)
+                .multilineTextAlignment(.leading)
+                .padding(.horizontal)
+                .foregroundStyle(Color.secondary)
+
+            if let step = substep.toAlgorithmSubstep() {
+                switch step {
+                case .autosensMin:
+                    algorithmSettingsInput(
+                        label: step.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: settingsProvider.settings.autosensMin,
+                        decimalValue: $state.autosensMin,
+                        booleanValue: $booleanPlaceholder,
+                        type: OnboardingInputSectionType.decimal
+                    )
+                case .autosensMax:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: settingsProvider.settings.autosensMax,
+                        decimalValue: $state.autosensMax,
+                        booleanValue: $booleanPlaceholder,
+                        type: OnboardingInputSectionType.decimal
+                    )
+                case .rewindResetsAutosens:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.rewindResetsAutosens,
+                        type: OnboardingInputSectionType.boolean
+                    )
+                case .enableSMBAlways:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.enableSMBAlways,
+                        type: OnboardingInputSectionType.boolean
+                    )
+                case .enableSMBWithCOB:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.enableSMBWithCOB,
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
+                    )
+                case .enableSMBWithTempTarget:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.enableSMBWithTempTarget,
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
+                    )
+                case .enableSMBAfterCarbs:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.enableSMBAfterCarbs,
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
+                    )
+                case .enableSMBWithHighGlucoseTarget:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.enableSMBWithHighGlucoseTarget,
+                        type: OnboardingInputSectionType.boolean,
+                        disabled: state.enableSMBAlways
+                    )
+                    if state.enableSMBWithHighGlucoseTarget {
+                        algorithmSettingsInput(
+                            label: String(localized: "High Glucose Target"),
+                            displayPicker: $shouldDisplayPicker,
+                            setting: settingsProvider.settings.enableSMB_high_bg_target,
+                            decimalValue: $state.highGlucoseTarget,
+                            booleanValue: $booleanPlaceholder,
+                            type: OnboardingInputSectionType.decimal,
+                            disabled: state.enableSMBAlways
+                        )
+                    }
+                case .allowSMBWithHighTempTarget:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.allowSMBWithHighTempTarget,
+                        type: OnboardingInputSectionType.boolean
+                    )
+                case .enableUAM:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.enableUAM,
+                        type: OnboardingInputSectionType.boolean
+                    )
+                case .maxSMBMinutes:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: settingsProvider.settings.maxSMBBasalMinutes,
+                        decimalValue: $state.maxSMBMinutes,
+                        booleanValue: $booleanPlaceholder,
+                        type: OnboardingInputSectionType.decimal
+                    )
+                case .maxUAMMinutes:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: settingsProvider.settings.maxUAMSMBBasalMinutes,
+                        decimalValue: $state.maxUAMMinutes,
+                        booleanValue: $booleanPlaceholder,
+                        type: OnboardingInputSectionType.decimal
+                    )
+                case .maxDeltaGlucoseThreshold:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: settingsProvider.settings.maxDeltaBGthreshold,
+                        decimalValue: $state.maxDeltaGlucoseThreshold,
+                        booleanValue: $booleanPlaceholder,
+                        type: OnboardingInputSectionType.decimal
+                    )
+                case .highTempTargetRaisesSensitivity:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.highTempTargetRaisesSensitivity,
+                        type: OnboardingInputSectionType.boolean
+                    )
+                case .lowTempTargetLowersSensitivity:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.lowTempTargetLowersSensitivity,
+                        type: OnboardingInputSectionType.boolean
+                    )
+                case .sensitivityRaisesTarget:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.sensitivityRaisesTarget,
+                        type: OnboardingInputSectionType.boolean
+                    )
+                case .resistanceLowersTarget:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: nil,
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.resistanceLowersTarget,
+                        type: OnboardingInputSectionType.boolean
+                    )
+                case .halfBasalTarget:
+                    algorithmSettingsInput(
+                        label: substep.title,
+                        displayPicker: $shouldDisplayPicker,
+                        setting: settingsProvider.settings.halfBasalExerciseTarget,
+                        decimalValue: $state.halfBasalTarget,
+                        booleanValue: $booleanPlaceholder,
+                        type: OnboardingInputSectionType.decimal
+                    )
+                }
+            }
+
+            substep.description(units: state.units).eraseToAnyView()
+                .font(.footnote)
+                .foregroundStyle(.secondary)
+                .padding(.horizontal)
+                .multilineTextAlignment(.leading)
+        }
+        .onAppear {
+            // Ensure picker view is closed, when switching between setting steps
+            shouldDisplayPicker = false
+        }
+    }
+
+    @ViewBuilder private func algorithmSettingsInput(
+        label: String,
+        displayPicker: Binding<Bool>,
+        setting: PickerSetting?,
+        decimalValue: Binding<Decimal>,
+        booleanValue: Binding<Bool>,
+        type: OnboardingInputSectionType,
+        disabled: Bool = false /// parameter only relevant for `Enable SMB Always` dependent settings
+    ) -> some View {
+        VStack {
+            VStack {
+                switch type {
+                case .boolean:
+                    Toggle(isOn: booleanValue) {
+                        Text(label)
+                    }.tint(Color.accentColor)
+                        .disabled(disabled)
+                case .decimal:
+                    Group {
+                        HStack {
+                            Text(label)
+                            Spacer()
+                            displayText(for: substep, decimalValue: decimalValue.wrappedValue, units: state.units)
+                                .foregroundColor(!displayPicker.wrappedValue ? .primary : .accentColor)
+                                .onTapGesture {
+                                    displayPicker.wrappedValue.toggle()
+                                }
+                        }.disabled(disabled)
+
+                        if displayPicker.wrappedValue {
+                            Picker(selection: decimalValue, label: Text(label)) {
+                                if let setting = setting {
+                                    ForEach(
+                                        settingsProvider.generatePickerValues(from: setting, units: state.units),
+                                        id: \.self
+                                    ) { value in
+                                        displayText(for: substep, decimalValue: value, units: state.units).tag(value)
+                                    }
+                                }
+                            }
+                            .disabled(disabled)
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+                        }
+                    }
+                }
+            }
+            .padding()
+            .background(Color.chart.opacity(0.65))
+            .cornerRadius(10)
+        }
+    }
+
+    private func displayText(for substep: Substep, decimalValue: Decimal, units: GlucoseUnits) -> Text {
+        guard let step = substep.toAlgorithmSubstep() else {
+            return Text(decimalValue.description)
+        }
+
+        switch step {
+        case .autosensMax,
+             .autosensMin,
+             .maxDeltaGlucoseThreshold:
+            return Text("\(decimalValue * 100) \(String(localized: "%"))")
+        case .enableSMBWithHighGlucoseTarget,
+             .halfBasalTarget:
+            let displayValue = units == .mmolL ? decimalValue.asMmolL : decimalValue
+            return Text("\(displayValue.description) \(units.rawValue)")
+        case .maxSMBMinutes,
+             .maxUAMMinutes:
+            return Text("\(decimalValue) \(String(localized: "min"))")
+        default:
+            return Text("") // not needed, because input type is boolean
+        }
+    }
+}

+ 142 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/BluetoothPermissionStepView.swift

@@ -0,0 +1,142 @@
+//
+//  BluetoothPermissionStepView.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 18.04.25.
+//
+import CoreBluetooth
+import SwiftUI
+import UIKit
+
+struct BluetoothPermissionStepView: View {
+    @Bindable var state: Onboarding.StateModel
+    var bluetoothManager: BluetoothStateManager
+    var currentStep: Binding<OnboardingStep>
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            Text("Enable device connectivity")
+                .font(.title3)
+                .bold()
+                .multilineTextAlignment(.leading)
+
+            Text("Trio requires Bluetooth to function as a (hybrid) closed‑loop system.")
+                .font(.body)
+                .multilineTextAlignment(.leading)
+                .foregroundColor(Color.secondary)
+                .padding(.bottom)
+
+            VStack(alignment: .leading, spacing: 20) {
+                HStack(spacing: 12) {
+                    Image(systemName: "keyboard.onehanded.left.fill")
+                        .font(.system(size: 24))
+                        .foregroundColor(Color.bgDarkBlue)
+                        .frame(width: 44, height: 44)
+                        .background(Circle().fill(Color.primary.opacity(0.8)))
+                    Text(
+                        "Connect to your insulin pump so Trio can send dosing commands and stay active in the background."
+                    )
+                    .font(.body)
+                    .foregroundColor(.primary)
+                }
+
+                HStack(spacing: 12) {
+                    Image(systemName: "sensor.tag.radiowaves.forward.fill")
+                        .font(.system(size: 24))
+                        .foregroundColor(Color.bgDarkBlue)
+                        .frame(width: 44, height: 44)
+                        .background(Circle().fill(Color.primary.opacity(0.8)))
+                    Text("Receive glucose readings every 5 minutes from your CGM to keep the loop running.")
+                        .font(.body)
+                        .foregroundColor(.primary)
+                }
+            }
+
+            Text("You can change these permissions any time in the iOS Settings app.")
+                .font(.footnote)
+                .multilineTextAlignment(.leading)
+                .foregroundColor(Color.secondary)
+                .padding(.top)
+        }
+        .padding(.horizontal)
+        .background(
+            SystemAlert(
+                isPresented: $state.shouldDisplayBluetoothRequestAlert,
+                title: String(localized: "“Trio” Would Like to Use Bluetooth"),
+                message: String(
+                    localized: "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices."
+                ),
+                allowTitle: String(localized: "Allow"),
+                denyTitle: String(localized: "Don’t Allow"),
+                onAllow: {
+                    bluetoothManager.authorizeBluetooth { auth in
+                        DispatchQueue.main.async {
+                            state.hasBluetoothGranted = (auth == .authorized)
+                            state.shouldDisplayBluetoothRequestAlert = false
+                            if let next = currentStep.wrappedValue.next {
+                                currentStep.wrappedValue = next
+                            }
+                        }
+                    }
+                },
+                onDeny: {
+                    state.hasBluetoothGranted = false
+                    state.shouldDisplayBluetoothRequestAlert = false
+                    if let next = currentStep.wrappedValue.next {
+                        currentStep.wrappedValue = next
+                    }
+                }
+            )
+        )
+    }
+}
+
+/// Presents a real UIAlertController, pinned to the system's own style
+///
+/// Why use this?
+/// SwiftUI’s built‑in .alert will always inherit the color scheme of its host view (in our case, we have forced .dark for the entire onboarding screen).
+/// There’s no way to tell SwiftUI “use the system setting here only for this one alert.”
+/// The workaround is to present a plain UIKit UIAlertController ourself, in its own representable, and explicitly tell it to use the system’s interface style instead of inheriting our forced dark mode.
+/// We enforce usage of the system's interface style by setting its overrideUserInterfaceStyle to whatever the device is actually using (.light or .dark).
+struct SystemAlert: UIViewControllerRepresentable {
+    @Binding var isPresented: Bool
+
+    let title: String
+    let message: String
+    let allowTitle: String
+    let denyTitle: String
+
+    /// called after Allow or Deny
+    let onAllow: () -> Void
+    let onDeny: () -> Void
+
+    func makeUIViewController(context _: Context) -> UIViewController {
+        // empty container
+        UIViewController()
+    }
+
+    func updateUIViewController(_ uiVC: UIViewController, context _: Context) {
+        guard isPresented, uiVC.presentedViewController == nil else { return }
+
+        let alert = UIAlertController(
+            title: title,
+            message: message,
+            preferredStyle: .alert
+        )
+
+        // force it back to the "real" system style
+        let systemStyle = UIScreen.main.traitCollection.userInterfaceStyle
+        alert.overrideUserInterfaceStyle = systemStyle
+
+        alert.addAction(.init(title: denyTitle, style: .cancel) { _ in
+            isPresented = false
+            onDeny()
+        })
+        alert.addAction(.init(title: allowTitle, style: .default) { _ in
+            isPresented = false
+            onAllow()
+        })
+
+        uiVC.present(alert, animated: true)
+    }
+}

+ 89 - 8
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/CompletedStepView.swift

@@ -5,7 +5,7 @@ struct CompletedStepView: View {
     var body: some View {
         VStack(alignment: .center, spacing: 20) {
             Image(systemName: "checkmark.circle.fill")
-                .font(.system(size: 80))
+                .font(.system(size: 60))
                 .foregroundColor(.green)
 
             Text("You're All Set!")
@@ -14,18 +14,55 @@ struct CompletedStepView: View {
                 .multilineTextAlignment(.center)
 
             Text(
-                "You've successfully completed the initial setup of Trio. Tap 'Get Started' to save your settings and get ready to start using Trio."
+                "You've successfully completed the initial setup of Trio. Tap 'Get Started' to save your settings and start using Trio."
             )
             .multilineTextAlignment(.center)
             .foregroundColor(.secondary)
 
             VStack(alignment: .leading, spacing: 12) {
-                ForEach(
-                    nonInfoOnboardingSteps,
-                    id: \.self
-                ) { step in
-                    SettingItemView(step: step, icon: step.iconName, title: step.title, type: .complete)
-                }
+                completedItemsView(
+                    stepIndex: 1,
+                    title: String(localized: "Prepare Trio"),
+                    description: String(
+                        localized: "App diagnostics sharing, Nightscout setup, and unit and pump model selection are all complete."
+                    )
+                )
+
+                Divider()
+
+                completedItemsView(
+                    stepIndex: 2,
+                    title: String(localized: "Therapy Settings"),
+                    description: String(
+                        localized: "Glucose target, basal rates, carb ratios, and insulin sensitivity match your needs."
+                    )
+                )
+
+                Divider()
+
+                completedItemsView(
+                    stepIndex: 3,
+                    title: String(localized: "Delivery Limits"),
+                    description: String(
+                        localized: "Safety boundaries for insulin delivery and carb entries are set to help Trio keep you safe."
+                    )
+                )
+
+                Divider()
+
+                completedItemsView(
+                    stepIndex: 4,
+                    title: String(localized: "Algorithm Settings"),
+                    description: String(localized: "Trio’s algorithm features are customized to fit your preferences and needs.")
+                )
+
+                Divider()
+
+                completedItemsView(
+                    stepIndex: 5,
+                    title: String(localized: "Permission Requests"),
+                    description: String(localized: "Notifications and Bluetooth permissions are handled to your liking.")
+                )
             }
             .padding()
             .background(Color.green.opacity(0.1))
@@ -39,4 +76,48 @@ struct CompletedStepView: View {
         .padding()
         .frame(maxWidth: .infinity)
     }
+
+    /// A reusable view for displaying setting items in the completed step.
+    @ViewBuilder private func completedItemsView(
+        stepIndex: Int,
+        title: String,
+        description: String
+    ) -> some View {
+        VStack(alignment: .leading, spacing: 10) {
+            HStack {
+                HStack(spacing: 14) {
+                    stepCount(stepIndex)
+                    Text(title)
+                        .font(.headline)
+                        .bold()
+                }
+
+                Spacer()
+
+                Image(systemName: "checkmark")
+                    .foregroundStyle(Color.green)
+                    .font(.headline)
+                    .bold()
+            }
+
+            Text(description)
+                .font(.footnote)
+                .foregroundStyle(Color.white.opacity(0.8))
+                .padding(.vertical, 8)
+                .multilineTextAlignment(.leading)
+        }
+    }
+
+    @ViewBuilder private func stepCount(_ count: Int) -> some View {
+        Text(count.description)
+            .font(.subheadline.bold())
+            .frame(width: 26, height: 26, alignment: .center)
+            .background(Color.green)
+            .foregroundStyle(Color.bgDarkerDarkBlue)
+            .clipShape(Capsule())
+    }
+}
+
+#Preview {
+    CompletedStepView()
 }

+ 10 - 1
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/DeliveryLimitsStepView.swift

@@ -10,9 +10,15 @@ struct DeliveryLimitsStepView: View {
 
     var body: some View {
         VStack(alignment: .leading, spacing: 16) {
+            Text(substep.title)
+                .padding(.horizontal)
+                .font(.title3)
+                .bold()
             Text(substep.hint)
+                .font(.subheadline)
+                .multilineTextAlignment(.leading)
                 .padding(.horizontal)
-                .font(.headline)
+                .foregroundStyle(Color.secondary)
 
             switch substep {
             case .maxIOB:
@@ -58,6 +64,9 @@ struct DeliveryLimitsStepView: View {
                 .padding(.horizontal)
                 .multilineTextAlignment(.leading)
         }
+        .onDisappear {
+            shouldDisplayPicker = false
+        }
     }
 
     @ViewBuilder private func deliveryLimitInputSection(

+ 39 - 7
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/DiagnosticsStepView.swift

@@ -3,6 +3,8 @@ import SwiftUI
 struct DiagnosticsStepView: View {
     @Bindable var state: Onboarding.StateModel
 
+    @State private var shouldDisplayPrivacyPolicy: Bool = false
+
     var body: some View {
         VStack(alignment: .leading, spacing: 20) {
             Text("If you prefer not to share this anonymized data, you can opt-out of data sharing.")
@@ -31,18 +33,45 @@ struct DiagnosticsStepView: View {
                 .buttonStyle(.plain)
             }
 
+            Toggle(isOn: $state.hasAcceptedPrivacyPolicy) {
+                HStack {
+                    Text("I have read and accept the")
+                    Button("Privacy Policy") {
+                        shouldDisplayPrivacyPolicy = true
+                    }
+                    .foregroundColor(.accentColor)
+                    .underline()
+                }
+                .font(.footnote)
+                .bold()
+            }
+            .toggleStyle(CheckboxToggleStyle(tint: Color.accentColor))
+            .padding(.horizontal)
+            .disabled(state.diagnosticsSharingOption == .disabled)
+            .opacity(state.diagnosticsSharingOption == .disabled ? 0.35 : 1)
+
             VStack(alignment: .leading, spacing: 8) {
                 Text("Why does Trio collect this data?").bold()
                 VStack(alignment: .leading, spacing: 4) {
-                    Text(
-                        "•  App diagnostic insights help us enhance app stability, ensure safety for all users, and enable us to quickly identify and resolve critical issues."
+                    BulletPoint(
+                        String(
+                            localized: "App diagnostic insights help us enhance app stability, ensure safety for all users, and enable us to quickly identify and resolve critical issues."
+                        )
                     )
-                    Text("•  Trio collects the app's state on crash, device, iOS and general system info, and a stack trace.")
-                    Text(
-                        "•  Trio does not collect any health related data, e.g. glucose readings, insulin rates or doses, meal data, setting values, or similar."
+                    BulletPoint(
+                        String(
+                            localized: "Trio collects the app's state on crash, device, iOS and general system info, and a stack trace."
+                        )
                     )
-                    Text(
-                        "•  Trio does not track any usage metrics or any other personal data about users other than the used iPhone model and iOS version."
+                    BulletPoint(
+                        String(
+                            localized: "Trio does not collect any health related data, e.g. glucose readings, insulin rates or doses, meal data, setting values, or similar."
+                        )
+                    )
+                    BulletPoint(
+                        String(
+                            localized: "Trio does not track any usage metrics or any other personal data about users other than the used iPhone model and iOS version."
+                        )
                     )
                 }
                 Text(
@@ -54,5 +83,8 @@ struct DiagnosticsStepView: View {
             .font(.footnote)
             .foregroundStyle(Color.secondary)
         }
+        .sheet(isPresented: $shouldDisplayPrivacyPolicy) {
+            PrivacyPolicyView()
+        }
     }
 }

+ 2 - 2
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/GlucoseTargetStepView.swift

@@ -97,10 +97,10 @@ struct GlucoseTargetStepView: View {
                 let endDate = Calendar.current.startOfDay(for: now).addingTimeInterval(offset)
 
                 LineMark(x: .value("End Date", startDate), y: .value("Ratio", displayValue))
-                    .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.green)
+                    .lineStyle(.init(lineWidth: 2.5)).foregroundStyle(Color.green)
 
                 LineMark(x: .value("Start Date", endDate), y: .value("Ratio", displayValue))
-                    .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.green)
+                    .lineStyle(.init(lineWidth: 2.5)).foregroundStyle(Color.green)
             }
         }
         .id(refreshUI) // Force chart update

+ 3 - 8
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/InsulinSensitivityStepView.swift

@@ -76,14 +76,9 @@ struct InsulinSensitivityStepView: View {
                             // Current glucose is 40 mg/dL or 2.2 mmol/L above target
                             let aboveTarget = state.units == .mgdL ? Decimal(40) : 40.asMmolL
 
-                            let isfValue = state.isfRateValues.isEmpty || state.isfItems.isEmpty ?
-                                Double(truncating: 50 as NSNumber) :
-                                Double(
-                                    truncating: state
-                                        .isfRateValues[state.isfItems.first!.rateIndex] as NSNumber
-                                )
-
-                            let insulinNeeded = aboveTarget / Decimal(isfValue)
+                            let isfValue = state.units == .mgdL ? Decimal(50) : 50.asMmolL
+
+                            let insulinNeeded = aboveTarget / isfValue
 
                             Text(
                                 "If you are \(numberFormatter.string(from: aboveTarget as NSNumber) ?? "--") \(state.units.rawValue) above target:"

+ 1 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/Nightscout/NightscoutImportStepView.swift

@@ -52,6 +52,7 @@ struct NightscoutImportStepView: View {
 
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Trio will import the following therapy settings from your Nightscout instance:")
+                            .multilineTextAlignment(.leading)
                         VStack(alignment: .leading) {
                             Text("• Glucose Targets")
                             Text("• Basal Rates")

+ 68 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/NotificationPermissionStepView.swift

@@ -0,0 +1,68 @@
+//
+//  NotificationPermissionStepView.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 18.04.25.
+//
+import SwiftUI
+import UserNotifications
+
+struct NotificationPermissionStepView: View {
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            Text("Allow Notifications")
+                .font(.title3)
+                .bold()
+                .multilineTextAlignment(.leading)
+
+            Text(
+                "Trio can notify of different events when you use it. You must allow Trio to send you notifications to work properly."
+            )
+            .font(.body)
+            .multilineTextAlignment(.leading)
+            .foregroundColor(Color.secondary)
+            .padding(.bottom)
+
+            VStack(alignment: .leading, spacing: 20) {
+                HStack(spacing: 12) {
+                    Image(systemName: "ellipsis.message.fill")
+                        .font(.system(size: 24))
+                        .foregroundColor(Color.bgDarkBlue)
+                        .frame(width: 44, height: 44)
+                        .background(Circle().fill(Color.primary.opacity(0.8)))
+                    Text("Receive optional real‑time low/high glucose alerts.")
+                        .font(.body)
+                        .foregroundColor(.primary)
+                }
+
+                HStack(spacing: 12) {
+                    Image(systemName: "exclamationmark.triangle.fill")
+                        .font(.system(size: 24))
+                        .foregroundColor(Color.bgDarkBlue)
+                        .frame(width: 44, height: 44)
+                        .background(Circle().fill(Color.primary.opacity(0.8)))
+                    Text("Be warned of connectivity or looping issues.")
+                        .font(.body)
+                        .foregroundColor(.primary)
+                }
+
+                HStack(spacing: 12) {
+                    Image(systemName: "app.badge.fill")
+                        .font(.system(size: 24))
+                        .foregroundColor(Color.bgDarkBlue)
+                        .frame(width: 44, height: 44)
+                        .background(Circle().fill(Color.primary.opacity(0.8)))
+                    Text("See a badge count when you need a carb correction.")
+                        .font(.body)
+                        .foregroundColor(.primary)
+                }
+            }
+
+            Text("You can change these permissions any time in the iOS Settings app.")
+                .font(.footnote)
+                .multilineTextAlignment(.leading)
+                .foregroundColor(Color.secondary)
+                .padding(.top)
+        }.padding(.horizontal)
+    }
+}

+ 88 - 6
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/OverviewStepView.swift

@@ -14,16 +14,98 @@ struct OverviewStepView: View {
                 .padding(.horizontal)
 
             VStack(alignment: .center, spacing: 12) {
-                ForEach(
-                    nonInfoOnboardingSteps,
-                    id: \.self
-                ) { step in
-                    SettingItemView(step: step, icon: step.iconName, title: step.title, type: .overview)
-                }
+                overviewItem(
+                    stepIndex: 1,
+                    title: String(localized: "Prepare Trio"),
+                    duration: "3-5",
+                    description: String(
+                        localized: "Configure diagnostics sharing, optionally sync with Nightscout, and enter essentials."
+                    )
+                )
+
+                Divider()
+
+                overviewItem(
+                    stepIndex: 2,
+                    title: String(localized: "Therapy Settings"),
+                    duration: "5-10",
+                    description: String(
+                        localized: "Define your glucose targets, basal rates, carb ratios, and insulin sensitivities."
+                    )
+                )
+
+                Divider()
+
+                overviewItem(
+                    stepIndex: 3,
+                    title: String(localized: "Delivery Limits"),
+                    duration: "3-5",
+                    description: String(
+                        localized: "Set boundaries for insulin delivery and carb entries to help Trio keep you safe."
+                    )
+                )
+
+                Divider()
+
+                overviewItem(
+                    stepIndex: 4,
+                    title: String(localized: "Algorithm Settings"),
+                    duration: "5-10",
+                    description: String(
+                        localized: "Customize Trio’s algorithm features. Most users start with the recommended settings."
+                    )
+                )
+
+                Divider()
+
+                overviewItem(
+                    stepIndex: 5,
+                    title: String(localized: "Permission Requests"),
+                    duration: "1",
+                    description: String(
+                        localized: "Authorize Trio to send notifications and use Bluetooth. You must allow both for Trio to work properly."
+                    )
+                )
             }
             .padding()
             .background(Color.chart.opacity(0.65))
             .cornerRadius(10)
         }
     }
+
+    @ViewBuilder private func overviewItem(
+        stepIndex: Int,
+        title: String,
+        duration: String,
+        description: String
+    ) -> some View {
+        VStack(alignment: .leading) {
+            HStack {
+                HStack(spacing: 14) {
+                    stepCount(stepIndex)
+                    Text(title).font(.headline)
+                }
+
+                Spacer()
+
+                Text("\(duration) \(String(localized: "min"))")
+                    .font(.subheadline)
+            }
+
+            Text(description)
+                .font(.footnote)
+                .foregroundStyle(Color.secondary)
+                .padding(.vertical, 8)
+                .multilineTextAlignment(.leading)
+        }
+    }
+
+    @ViewBuilder private func stepCount(_ count: Int) -> some View {
+        Text(count.description)
+            .font(.subheadline.bold())
+            .frame(width: 26, height: 26, alignment: .center)
+            .background(Color.blue)
+            .foregroundStyle(.white)
+            .clipShape(Capsule())
+    }
 }

+ 32 - 8
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/StartupGuideStepView.swift

@@ -7,13 +7,16 @@
 import SwiftUI
 
 struct StartupGuideStepView: View {
+    @Bindable var state: Onboarding.StateModel
+
     @Environment(\.openURL) var openURL
 
     var body: some View {
         VStack(alignment: .leading, spacing: 20) {
             Text("Before you begin…")
-                .font(.title.bold())
                 .padding(.horizontal)
+                .font(.title3)
+                .bold()
 
             VStack(alignment: .leading, spacing: 10) {
                 BulletPoint(String(localized: "Take a deep breath — you've got this."))
@@ -31,16 +34,37 @@ struct StartupGuideStepView: View {
                         .cornerRadius(8)
                 }
                 .frame(maxWidth: .infinity, alignment: .center)
-                .padding([.top, .horizontal])
+                .padding(.horizontal)
+            }.padding(.horizontal)
+
+            VStack(alignment: .leading, spacing: 10) {
+                Text("Already using Trio and updating from an older version?").bold()
+                BulletPoint(String(localized: "Your therapy settings, pump, and CGM configurations will be carried over."))
+                BulletPoint(
+                    String(
+                        localized: "Your algorithm settings (previously called \"OpenAPS settings\") will be reset to defaults."
+                    )
+                )
+                BulletPoint(String(localized: "We recommend reviewing them carefully — Trio will guide you step-by-step."))
             }.padding(.horizontal)
 
-            HStack {
-                Text("You can pause at any time. Just be aware: if you ")
-                    + Text("force quit").bold()
-                    + Text(" the app before finishing onboarding, ")
-                    + Text("your progress will not be saved.").bold()
+            VStack(alignment: .leading, spacing: 10) {
+                Text("One last thing, before you begin...").bold()
+                HStack {
+                    Text("You can pause at any time. Just be aware: if you ")
+                        + Text("force quit").bold()
+                        + Text(" the app before finishing onboarding, ")
+                        + Text("your progress will not be saved.").bold()
+                }
+            }.multilineTextAlignment(.leading)
+                .padding(.horizontal)
+
+            Divider()
+
+            Toggle(isOn: $state.hasReadImportantStartupNotes) {
+                Text("Got it! I'm ready to continue.").padding(.leading, 6).bold()
             }
-            .multilineTextAlignment(.leading)
+            .toggleStyle(CheckboxToggleStyle(tint: Color.blue))
             .padding(.horizontal)
         }
     }

+ 8 - 2
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/UnitSelectionStepView.swift

@@ -30,9 +30,15 @@ struct UnitSelectionStepView: View {
                         Text(pumpModel.displayName).tag(pumpModel)
                     }
                 }
-                .onChange(of: state.pumpOptionForOnboardingUnits, { _, _ in
-                    // Reset basal profile and related values when pump model changes
+                .onChange(of: state.pumpOptionForOnboardingUnits, { _, newValue in
+                    // Reset therapy settings and related values when pump model changes
+                    state.targetItems = []
                     state.basalProfileItems = []
+                    state.carbRatioItems = []
+                    state.isfItems = []
+
+                    // Conditionally set rewind setting, if pump model is MDT
+                    state.rewindResetsAutosens = newValue == .minimed
                 })
             }
             .padding()

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

@@ -0,0 +1,488 @@
+//
+//  OnboardingView+AlgorithmUtil.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 15.04.25.
+//
+import SwiftUI
+
+protocol AlgorithmSubstepProtocol: Identifiable, RawRepresentable {
+    var title: String { get }
+    func hint(units: GlucoseUnits) -> String
+    func description(units: GlucoseUnits) -> any View
+}
+
+extension AlgorithmSubstepProtocol {
+    var title: String {
+        AlgorithmSettingMeta(rawValue: String(describing: self))?.title ?? ""
+    }
+
+    func hint(units: GlucoseUnits) -> String {
+        AlgorithmSettingMeta(rawValue: String(describing: self))?.hint(units: units) ?? ""
+    }
+
+    func description(units: GlucoseUnits) -> any View {
+        AlgorithmSettingMeta(rawValue: String(describing: self))?.description(units: units) ?? AnyView(EmptyView())
+    }
+}
+
+extension AlgorithmSubstepProtocol where Self: RawRepresentable, Self.RawValue == Int {
+    func toAlgorithmSubstep() -> AlgorithmSettingsSubstep? {
+        switch self {
+        case let step as AutosensSettingsSubstep:
+            return [
+                .autosensMin,
+                .autosensMax,
+                .rewindResetsAutosens
+            ][step.rawValue]
+        case let step as SMBSettingsSubstep:
+            return [
+                .enableSMBAlways,
+                .enableSMBWithCOB,
+                .enableSMBWithTempTarget,
+                .enableSMBAfterCarbs,
+                .enableSMBWithHighGlucoseTarget,
+                .allowSMBWithHighTempTarget,
+                .enableUAM,
+                .maxSMBMinutes,
+                .maxUAMMinutes,
+                .maxDeltaGlucoseThreshold
+            ][step.rawValue]
+        case let step as TargetBehaviorSubstep:
+            return [
+                .highTempTargetRaisesSensitivity,
+                .lowTempTargetLowersSensitivity,
+                .sensitivityRaisesTarget,
+                .resistanceLowersTarget,
+                .halfBasalTarget
+            ][step.rawValue]
+        default:
+            return nil
+        }
+    }
+}
+
+extension View {
+    func eraseToAnyView() -> AnyView {
+        AnyView(self)
+    }
+}
+
+enum AlgorithmSettingsSubstep: Int, CaseIterable, Identifiable {
+    case autosensMin
+    case autosensMax
+    case rewindResetsAutosens
+    case enableSMBAlways
+    case enableSMBWithCOB
+    case enableSMBWithTempTarget
+    case enableSMBAfterCarbs
+    case enableSMBWithHighGlucoseTarget
+    case allowSMBWithHighTempTarget
+    case enableUAM
+    case maxSMBMinutes
+    case maxUAMMinutes
+    case maxDeltaGlucoseThreshold
+    case highTempTargetRaisesSensitivity
+    case lowTempTargetLowersSensitivity
+    case sensitivityRaisesTarget
+    case resistanceLowersTarget
+    case halfBasalTarget
+
+    var id: Int { rawValue }
+
+    var title: String {
+        switch self {
+        case .autosensMin: return String(localized: "Autosens Min", comment: "Autosens Min")
+        case .autosensMax: return String(localized: "Autosens Max", comment: "Autosens Max")
+        case .rewindResetsAutosens: return String(localized: "Rewind Resets Autosens", comment: "Rewind Resets Autosens")
+        case .enableSMBAlways: return String(localized: "Enable SMB Always", comment: "Enable SMB Always")
+        case .enableSMBWithCOB: return String(localized: "Enable SMB With COB", comment: "Enable SMB With COB")
+        case .enableSMBWithTempTarget: return String(
+                localized: "Enable SMB With Temptarget",
+                comment: "Enable SMB With Temptarget"
+            )
+        case .enableSMBAfterCarbs: return String(localized: "Enable SMB After Carbs", comment: "Enable SMB After Carbs")
+        case .enableSMBWithHighGlucoseTarget: return String(
+                localized: "Enable SMB With High BG",
+                comment: "Enable SMB With High BG"
+            )
+        case .allowSMBWithHighTempTarget: return String(
+                localized: "Allow SMB With High Temptarget",
+                comment: "Allow SMB With High Temptarget"
+            )
+        case .enableUAM: return String(localized: "Enable UAM", comment: "Enable UAM")
+        case .maxSMBMinutes: return String(localized: "Max SMB Basal Minutes", comment: "Max SMB Basal Minutes")
+        case .maxUAMMinutes: return String(localized: "Max UAM Basal Minutes", comment: "Max UAM Basal Minutes")
+        case .maxDeltaGlucoseThreshold: return String(localized: "Max Delta-BG Threshold SMB", comment: "Max Delta-BG Threshold")
+        case .highTempTargetRaisesSensitivity: return String(
+                localized: "High Temp Target Raises Sensitivity",
+                comment: "High Temp Target Raises Sensitivity"
+            )
+        case .lowTempTargetLowersSensitivity: return String(
+                localized:
+                "Low Temp Target Lowers Sensitivity",
+                comment: "Low Temp Target Lowers Sensitivity"
+            )
+        case .sensitivityRaisesTarget: return String(localized: "Sensitivity Raises Target", comment: "Sensitivity Raises Target")
+        case .resistanceLowersTarget: return String(localized: "Resistance Lowers Target", comment: "Resistance Lowers Target")
+        case .halfBasalTarget: return String(localized: "Half Basal Exercise Target", comment: "Half Basal Exercise Target")
+        }
+    }
+
+    func hint(units: GlucoseUnits) -> String {
+        switch self {
+        case .autosensMin: return String(localized: "Lower limit of the Autosens Ratio.")
+        case .autosensMax: return String(localized: "Upper limit of the Autosens Ratio.")
+        case .rewindResetsAutosens: return String(localized: "Pump rewind initiates a reset in Autosens Ratio.")
+        case .enableSMBAlways: return String(localized: "Allow SMBs at all times except when a high Temp Target is set.")
+        case .enableSMBWithCOB: return String(localized: "Allow SMB when carbs are on board.")
+        case .enableSMBWithTempTarget: return String(
+                localized: "Allow SMB when a manual Temporary Target is set under \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue)."
+            )
+        case .enableSMBAfterCarbs: return String(localized: "Allow SMB for 6 hrs after a carb entry.")
+        case .enableSMBWithHighGlucoseTarget: return String(localized: "Allow SMB when glucose is above the High BG Target value.")
+        case .allowSMBWithHighTempTarget: return String(
+                localized: "Allow SMB when a manual Temporary Target is set greater than \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue)."
+            )
+        case .enableUAM: return String(localized: "Enable Unannounced Meals SMB.")
+        case .maxSMBMinutes: return String(localized: "Limits the size of a single Super Micro Bolus (SMB) dose.")
+        case .maxUAMMinutes: return String(localized: "Limits the size of a single Unannounced Meal (UAM) SMB dose.")
+        case .maxDeltaGlucoseThreshold: return String(localized: "Disables SMBs if last two glucose values differ by more than this percent.")
+        case .highTempTargetRaisesSensitivity: return String(
+                localized: "Increase sensitivity when glucose is above target if a manual Temp Target > \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) is set."
+            )
+        case .lowTempTargetLowersSensitivity: return String(
+                localized: "Decrease sensitivity when glucose is below target if a manual Temp Target < \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) is set."
+            )
+        case .sensitivityRaisesTarget: return String(localized: "Raise target glucose if when Autosens Ratio is >1.")
+        case .resistanceLowersTarget: return String(localized: "Lower target glucose when Autosens Ratio is <1.")
+        case .halfBasalTarget: return String(localized: "Scales down your basal rate to 50% at this value.")
+        }
+    }
+
+    func description(units: GlucoseUnits) -> any View {
+        switch self {
+        case .autosensMin:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: 70%").bold().foregroundStyle(Color.primary)
+                Text(
+                    "Autosens Min sets the minimum Autosens Ratio used by Autosens, Dynamic ISF, and Sigmoid Formula."
+                )
+                Text(
+                    "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
+                )
+                Text(
+                    "Tip: Decreasing this value allows automatic adjustments of basal rates to be lower, ISF to be higher, and CR to be higher."
+                )
+            }
+        case .autosensMax:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: 120%").bold().foregroundStyle(Color.primary)
+                Text(
+                    "Autosens Max sets the maximum Autosens Ratio used by Autosens, Dynamic ISF, and Sigmoid Formula."
+                )
+                Text(
+                    "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
+                )
+                Text(
+                    "Tip: Increasing this value allows automatic adjustments of basal rates to be higher, ISF to be lower, and CR to be lower."
+                )
+            }
+        case .rewindResetsAutosens:
+            return VStack(alignment: .leading, spacing: 5) {
+                Text("Default: ON").bold().foregroundStyle(Color.primary)
+                Text("Medtronic Users Only").bold()
+                VStack(alignment: .leading, spacing: 8) {
+                    Text(
+                        "This feature resets the Autosens Ratio to neutral when you rewind your pump on the assumption that this corresponds to a site change."
+                    )
+                    Text(
+                        "Autosens will begin learning sensitivity anew from the time of the rewind, which may take up to 6 hours."
+                    )
+                    Text(
+                        "Tip: If you usually rewind your pump independently of site changes, you may want to consider disabling this feature."
+                    )
+                }
+            }
+        case .enableSMBAlways:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text(
+                    "Enabling SMB Always will disable some of the subsequent \"Enable SMB\" options during Onboarding. These redundant options will be skipped."
+                )
+                .padding(.bottom)
+                .foregroundStyle(Color.orange)
+
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "When enabled, Super Micro Boluses (SMBs) will always be allowed if dosing calculations determine insulin is needed via the SMB delivery method, except when a high Temp Target is set."
+                )
+                Text(
+                    "Note: If you would like to allow SMBs when a high Temp Target is set, enable the \"Allow SMBs with High Temptarget\" setting."
+                )
+            }
+        case .enableSMBWithCOB:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "When the carb on board (COB) forecast line is active, enabling this feature allows Trio to use Super Micro Boluses (SMB) to deliver the insulin required."
+                )
+                Text(
+                    "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
+                )
+            }
+        case .enableSMBWithTempTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) at times when a manual Temporary Target under \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) is set."
+                )
+                Text(
+                    "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
+                )
+            }
+        case .enableSMBAfterCarbs:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) for 6 hours after a carb entry, regardless of whether there are active carbs on board (COB)."
+                )
+                Text(
+                    "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
+                )
+            }
+        case .enableSMBWithHighGlucoseTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when glucose reading is above the value set as High BG Target."
+                )
+                Text(
+                    "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
+                )
+            }
+        case .allowSMBWithHighTempTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when a manual Temporary Target above \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) is set."
+                )
+                Text(
+                    "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
+                )
+                Text(
+                    "Warning: High Temp Targets are often set when recovering from lows. If you use High Temp Targets for that purpose, this feature should remain disabled."
+                ).bold()
+            }
+        case .enableUAM:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "Enabling the UAM (Unannounced Meals) feature allows the system to detect and respond to unexpected rises in glucose readings caused by unannounced or miscalculated carbs, meals high in fat or protein, or other factors like adrenaline."
+                )
+                Text(
+                    "It uses the SMB (Super Micro Bolus) algorithm to deliver insulin in small amounts to correct glucose spikes. UAM also works in reverse, reducing or stopping SMBs if glucose levels drop unexpectedly."
+                )
+                Text(
+                    "This feature ensures more accurate insulin adjustments when carb entries are missing or incorrect."
+                )
+            }
+        case .maxSMBMinutes:
+            return VStack(spacing: 8) {
+                VStack(alignment: .leading, spacing: 8) {
+                    Text("Default: 30 minutes").bold().foregroundStyle(Color.primary)
+                    Text("(50% current basal rate)")
+                    VStack(alignment: .leading, spacing: 8) {
+                        Text(
+                            "This is a limit on the size of a single SMB. One SMB can only be as large as this many minutes of your current profile basal rate."
+                        )
+                        Text(
+                            "To calculate the maximum SMB allowed based on this setting, use the following formula:"
+                        )
+                    }
+                }
+                VStack(alignment: .center, spacing: 5) {
+                    Text(
+                        "𝒳 = Max SMB Basal Minutes"
+                    )
+                    Text("(𝒳 / 60) × current basal rate")
+                }
+
+                VStack(alignment: .leading, spacing: 8) {
+                    Text(
+                        "Warning: Increasing this value above 90 minutes may impact Trio's ability to effectively zero temp and prevent lows."
+                    ).bold()
+                    Text("Note: SMBs must be enabled to use this limit.")
+                }
+            }
+        case .maxUAMMinutes:
+            return VStack(spacing: 8) {
+                VStack(alignment: .leading, spacing: 8) {
+                    Text("Default: 30 minutes").bold().foregroundStyle(Color.primary)
+                    Text("(50% current basal rate)")
+
+                    VStack(alignment: .leading, spacing: 8) {
+                        Text(
+                            "This is a limit on the size of a single UAM SMB. One UAM SMB can only be as large as this many minutes of your current profile basal rate."
+                        )
+                        Text(
+                            "To calculate the maximum UAM SMB allowed based on this setting, use the following formula:"
+                        )
+                    }
+                }
+                VStack(alignment: .center, spacing: 5) {
+                    Text(
+                        "𝒳 = Max UAM SMB Basal Minutes"
+                    )
+                    Text("(𝒳 / 60) × current basal rate")
+                }
+                VStack(alignment: .leading, spacing: 8) {
+                    Text(
+                        "Warning: Increasing this value above 60 minutes may impact Trio's ability to effectively zero temp and prevent lows."
+                    ).bold()
+                    Text("Note: UAM SMBs must be enabled to use this limit.")
+                }
+            }
+        case .maxDeltaGlucoseThreshold:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: 20% increase").bold().foregroundStyle(Color.primary)
+                Text(
+                    "Maximum allowed positive percent change in glucose level to permit SMBs. If the difference in glucose is greater than this, Trio will disable SMBs."
+                )
+                Text(
+                    "This is a safety limitation to avoid high SMB doses when glucose is rising abnormally fast, such as after a meal or with a very jumpy CGM sensor."
+                )
+                Text("Note: This setting has a hard-coded cap of 40%")
+            }
+        case .highTempTargetRaisesSensitivity:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "When this feature is enabled, manually setting a temporary target above \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) will decrease the Autosens Ratio used for ISF and basal adjustments, resulting in less insulin delivered overall. This scales with the temporary target set; the higher the temp target, the lower the Autosens Ratio used."
+                )
+                Text(
+                    "If Half Basal Exercise Target is set to \(units == .mgdL ? "160" : 160.formattedAsMmolL) \(units.rawValue), a temp target of \(units == .mgdL ? "120" : 120.formattedAsMmolL) \(units.rawValue) uses an Autosens Ratio of 0.75. A temp target of \(units == .mgdL ? "140" : 140.formattedAsMmolL) \(units.rawValue) uses an Autosens Ratio of 0.6."
+                )
+                Text("Note: The effect of this can be adjusted with the Half Basal Exercise Target")
+            }
+        case .lowTempTargetLowersSensitivity:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "When this feature is enabled, setting a temporary target below \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) will increase the Autosens Ratio used for ISF and basal adjustments, resulting in more insulin delivered overall. This scales with the temporary target set; the lower the Temp Target, the higher the Autosens Ratio used. It requires Algorithm Settings > Autosens > Autosens Max to be set to > 100% to work."
+                )
+                Text(
+                    "If Half Basal Exercise Target is \(units == .mgdL ? "160" : 160.formattedAsMmolL) \(units.rawValue), a Temp Target of \(units == .mgdL ? "95" : 95.formattedAsMmolL) \(units.rawValue) uses an Autosens Ratio of 1.09. A Temp Target of \(units == .mgdL ? "85" : 85.formattedAsMmolL) \(units.rawValue) uses an Autosens Ratio of 1.33."
+                )
+                Text("Note: The effect of this can be adjusted with the Half Basal Exercise Target")
+            }
+        case .sensitivityRaisesTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "Enabling this feature causes Trio to automatically raise the targeted glucose if it detects an increase in insulin sensitivity from your baseline."
+                )
+            }
+        case .resistanceLowersTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text("Default: OFF").bold().foregroundStyle(Color.primary)
+                Text(
+                    "Enabling this feature causes Trio to automatically reduce the targeted glucose if it detects a decrease in sensitivity (resistance) from your baseline."
+                )
+            }
+        case .halfBasalTarget:
+            return VStack(alignment: .leading, spacing: 8) {
+                Text(
+                    "Default: \(units == .mgdL ? "160" : 160.formattedAsMmolL) \(units.rawValue)"
+                )
+                .bold().foregroundStyle(Color.primary)
+                Text(
+                    "The Half Basal Exercise Target allows you to scale down your basal insulin during exercise or scale up your basal insulin when eating soon when a temporary glucose target is set."
+                )
+                Text(
+                    "For example, at a temp target of \(units == .mgdL ? "160" : 160.formattedAsMmolL) \(units.rawValue), your basal is reduced to 50%, but this scales depending on the target (e.g., 75% at \(units == .mgdL ? "120" : 120.formattedAsMmolL) \(units.rawValue), 60% at \(units == .mgdL ? "140" : 140.formattedAsMmolL) \(units.rawValue))."
+                )
+                Text(
+                    "Note: This setting is only utilized if the settings \"Low Temp Target Lowers Sensitivity\" OR \"High Temp Target Raises Sensitivity\" are enabled."
+                )
+            }
+        }
+    }
+
+    init?(rawValue: String) {
+        self.init(rawValue: AlgorithmSettingsSubstep.allCases.first { rawValue == "\($0)" }?.rawValue ?? -1)
+    }
+}
+
+// MARK: - Algorithm Settings Substep Groups
+
+enum AutosensSettingsSubstep: Int, CaseIterable, Identifiable {
+    case autosensMin
+    case autosensMax
+    case rewindResetsAutosens
+
+    var id: Int { rawValue }
+}
+
+enum SMBSettingsSubstep: Int, CaseIterable, Identifiable {
+    case enableSMBAlways
+    case enableSMBWithCOB
+    case enableSMBWithTempTarget
+    case enableSMBAfterCarbs
+    case enableSMBWithHighGlucoseTarget
+    case allowSMBWithHighTempTarget
+    case enableUAM
+    case maxSMBMinutes
+    case maxUAMMinutes
+    case maxDeltaGlucoseThreshold
+
+    var id: Int { rawValue }
+}
+
+enum TargetBehaviorSubstep: Int, CaseIterable, Identifiable {
+    case highTempTargetRaisesSensitivity
+    case lowTempTargetLowersSensitivity
+    case sensitivityRaisesTarget
+    case resistanceLowersTarget
+    case halfBasalTarget
+
+    var id: Int { rawValue }
+}
+
+extension AutosensSettingsSubstep: AlgorithmSubstepProtocol {}
+extension SMBSettingsSubstep: AlgorithmSubstepProtocol {}
+extension TargetBehaviorSubstep: AlgorithmSubstepProtocol {}
+
+// MARK: - Shared Metadata Helper
+
+enum AlgorithmSettingMeta: String {
+    case autosensMin
+    case autosensMax
+    case rewindResetsAutosens
+    case enableSMBAlways
+    case enableSMBWithCOB
+    case enableSMBWithTempTarget
+    case enableSMBAfterCarbs
+    case enableSMBWithHighGlucoseTarget
+    case allowSMBWithHighTempTarget
+    case enableUAM
+    case maxSMBMinutes
+    case maxUAMMinutes
+    case maxDeltaGlucoseThreshold
+    case highTempTargetRaisesSensitivity
+    case lowTempTargetLowersSensitivity
+    case sensitivityRaisesTarget
+    case resistanceLowersTarget
+    case halfBasalTarget
+
+    var title: String {
+        AlgorithmSettingsSubstep(rawValue: rawValue)?.title ?? ""
+    }
+
+    func hint(units: GlucoseUnits) -> String {
+        AlgorithmSettingsSubstep(rawValue: rawValue)?.hint(units: units) ?? ""
+    }
+
+    func description(units: GlucoseUnits) -> any View {
+        AlgorithmSettingsSubstep(rawValue: rawValue)?.description(units: units) ?? AnyView(EmptyView())
+    }
+}

+ 74 - 66
Trio/Sources/Modules/Onboarding/View/OnboardingView+Util.swift

@@ -19,6 +19,12 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
     case carbRatio
     case insulinSensitivity
     case deliveryLimits
+    case algorithmSettings
+    case autosensSettings
+    case smbSettings
+    case targetBehavior
+    case notifications
+    case bluetooth
     case completed
 
     var id: Int { rawValue }
@@ -57,6 +63,18 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return String(localized: "Insulin Sensitivities")
         case .deliveryLimits:
             return String(localized: "Delivery Limits")
+        case .algorithmSettings:
+            return String(localized: "Algorithm Settings")
+        case .autosensSettings:
+            return String(localized: "Autosens")
+        case .smbSettings:
+            return String(localized: "Super Micro Bolus")
+        case .targetBehavior:
+            return String(localized: "Target Behavior")
+        case .notifications:
+            return String(localized: "Notifications")
+        case .bluetooth:
+            return String(localized: "Bluetooth")
         case .completed:
             return String(localized: "All Set!")
         }
@@ -75,7 +93,7 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             )
         case .overview:
             return String(
-                localized: "Trio's Onboarding consists of several steps. It takes about 5-10 minutes to complete. We'll guide you through each step."
+                localized: "Trio's Onboarding takes about 15-30 minutes to complete. We'll guide you through each step."
             )
         case .diagnostics:
             return String(
@@ -109,6 +127,26 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return String(
                 localized: "Trio includes several safety limits for insulin delivery and carbohydrate entry, helping ensure a safe and effective experience."
             )
+        case .algorithmSettings:
+            return String(
+                localized: "Trio includes several algorithm settings that allow you to customize the oref algorithm behavior to suit your specific needs."
+            )
+        case .autosensSettings:
+            return String(
+                localized: "Auto-sensitivity (Autosens) adjusts insulin delivery based on observed sensitivity or resistance."
+            )
+        case .smbSettings:
+            return String(
+                localized: "SMB (Super Micro Bolus) is an oref algorithm feature that delivers small frequent boluses instead of temporary basals for faster glucose control."
+            )
+        case .targetBehavior:
+            return String(
+                localized: "Target Behavior allows you to adjust how temporary targets influence ISF, basal, and auto-targeting based on sensitivity or resistance."
+            )
+        case .notifications:
+            return String(localized: " Allow Trio to send you Notifications. These may include alerts, sounds, and icon badges.")
+        case .bluetooth:
+            return String(localized: "Allow Trio to use Bluetooth to communicate with your insulin pump and CGM.")
         case .completed:
             return String(
                 localized: "Great job! You've completed the initial setup of Trio. You can always adjust these settings later in the app."
@@ -141,6 +179,18 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return "drop.fill"
         case .deliveryLimits:
             return "slider.horizontal.3"
+        case .algorithmSettings:
+            return "gearshape.2.fill"
+        case .autosensSettings:
+            return "dial.low.fill"
+        case .smbSettings:
+            return "bolt.fill"
+        case .targetBehavior:
+            return "gyroscope"
+        case .notifications:
+            return "bell.badge.fill"
+        case .bluetooth:
+            return "logo.bluetooth.capsule.portrait.fill"
         case .completed:
             return "checkmark.circle.fill"
         }
@@ -165,12 +215,18 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
     /// The accent color to use for this step.
     var accentColor: Color {
         switch self {
-        case .completed,
+        case .algorithmSettings,
+             .autosensSettings,
+             .bluetooth,
+             .completed,
              .deliveryLimits,
              .diagnostics,
              .nightscout,
+             .notifications,
              .overview,
+             .smbSettings,
              .startupGuide,
+             .targetBehavior,
              .unitSelection,
              .welcome:
             return Color.blue
@@ -307,9 +363,9 @@ enum DiagnosticsSharingOption: String, Equatable, CaseIterable, Identifiable {
     var displayName: String {
         switch self {
         case .enabled:
-            return "Enable Sharing"
+            return String(localized: "Enable Sharing")
         case .disabled:
-            return "Disable Sharing"
+            return String(localized: "Disable Sharing")
         }
     }
 }
@@ -393,72 +449,24 @@ struct BulletPoint: View {
         HStack(alignment: .top) {
             Text("•")
             Text(text)
+                .fixedSize(horizontal: false, vertical: true)
+                .multilineTextAlignment(.leading)
         }
     }
 }
 
-enum OnboardingSettingItemType: Equatable, CaseIterable, Identifiable {
-    case overview
-    case complete
-
-    var id: UUID {
-        UUID()
-    }
-}
-
-/// A reusable view for displaying setting items in the completed step.
-struct SettingItemView: View {
-    let step: OnboardingStep
-    let icon: String
-    let title: String
-    let type: OnboardingSettingItemType
-
-    private var accentColor: Color {
-        switch type {
-        case .overview:
-            Color.blue
-        case .complete:
-            Color.green
-        }
-    }
-
-    var body: some View {
-        HStack(spacing: 10) {
-            if step == .nightscout {
-                Image(icon)
-                    .resizable()
-                    .scaledToFit()
-                    .frame(width: 40, height: 24)
-                    .colorMultiply(accentColor)
-            } else {
-                Image(systemName: icon)
-                    .font(.system(size: 24))
-                    .foregroundStyle(accentColor)
-                    .frame(width: 40)
-            }
-
-            VStack(alignment: .leading, spacing: 2) {
-                Text(title)
-                    .font(.headline)
-            }
-
-            Spacer()
-
-            switch type {
-            case .overview:
-                let index = nonInfoOnboardingSteps.firstIndex(of: step) ?? 0
-                let stepNumber = index + 1
-                Text(stepNumber.description)
-                    .bold()
-                    .frame(width: 32, height: 32, alignment: .center)
-                    .background(accentColor)
-                    .foregroundStyle(.white)
-                    .clipShape(Capsule())
-            case .complete:
-                Image(systemName: "checkmark")
-                    .foregroundStyle(accentColor)
-            }
+enum OnboardingInputSectionType: Equatable {
+    case decimal
+    case boolean
+
+    static func == (lhs: OnboardingInputSectionType, rhs: OnboardingInputSectionType) -> Bool {
+        switch (lhs, rhs) {
+        case (.boolean, .boolean):
+            return true
+        case (.decimal, .decimal):
+            return true
+        default:
+            return false
         }
-        .padding(.vertical, 8)
     }
 }

+ 22 - 4
Trio/Sources/Modules/Onboarding/View/TherapySettingEditorView.swift

@@ -116,6 +116,9 @@ struct TherapySettingEditorView: View {
             selectedItemID = nil
             validateTherapySettingItems()
         }
+        .onChange(of: items, { _, _ in
+            validateTherapySettingItems()
+        })
     }
 
     @ViewBuilder private func timeValuePickerRow(
@@ -185,12 +188,18 @@ struct TherapySettingEditorView: View {
 
     private func validateTherapySettingItems() {
         // validates therapy items (i.e. parsed therapy settings into wrapper class)
-        let newItems = Array(Set(items)).sorted { $0.time < $1.time }
-        if var first = newItems.first, first.time != 0 {
-            first.time = 0
-            items = newItems
+        var newItems = Array(Set(items)).sorted { $0.time < $1.time }
+        if !newItems.isEmpty {
+            var first = newItems[0]
+            if first.time != 0 {
+                first.time = 0
+            }
+            newItems[0] = first
         }
 
+        // force ALL items to have new UUIDs (to enforce binding update)
+        items = newItems.map { TherapySettingItem(copying: $0, newID: true) }
+
         // validates underlying "raw" therapy setting (i.e. item of type basal, target, isf, carb ratio)
         validateOnDelete?()
     }
@@ -236,6 +245,15 @@ struct TherapySettingItem: Identifiable, Equatable, Hashable {
     }
 }
 
+/// Convenience extension to ease copying of existing `TherapySettingItem`s
+extension TherapySettingItem {
+    init(copying item: TherapySettingItem, newID: Bool = false) {
+        id = newID ? UUID() : item.id
+        time = item.time
+        value = item.value
+    }
+}
+
 enum TherapySettingUnit: String, CaseIterable {
     case mmolLPerUnit
     case mgdLPerUnit

+ 11 - 5
Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -137,19 +137,22 @@ extension SMBSettings {
                             get: { selectedVerboseHint },
                             set: {
                                 selectedVerboseHint = $0.map { AnyView($0) }
-                                hintLabel = String(localized: "Enable SMB With High BG", comment: "Enable SMB With High BG")
+                                hintLabel = String(
+                                    localized: "Enable SMB With High Glucose",
+                                    comment: "Enable SMB With High Glucose"
+                                )
                             }
                         ),
                         units: state.units,
                         type: .conditionalDecimal("enableSMB_high_bg_target"),
-                        label: String(localized: "Enable SMB With High BG", comment: "Enable SMB With High BG"),
-                        conditionalLabel: "High BG Target",
-                        miniHint: String(localized: "Allow SMB when glucose is above the High BG Target value."),
+                        label: String(localized: "Enable SMB With High Glucose", comment: "Enable SMB With High Glucose"),
+                        conditionalLabel: String(localized: "High Glucose Target"),
+                        miniHint: String(localized: "Allow SMB when glucose is above the High Glucose Target value."),
                         verboseHint:
                         VStack(alignment: .leading, spacing: 10) {
                             Text("Default: OFF").bold()
                             Text(
-                                "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when glucose reading is above the value set as High BG Target."
+                                "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when glucose reading is above the value set as High Glucose Target."
                             )
                             Text(
                                 "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."
@@ -340,6 +343,9 @@ extension SMBSettings {
                         Text(
                             "Maximum allowed positive percent change in glucose level to permit SMBs. If the difference in glucose is greater than this, Trio will disable SMBs."
                         )
+                        Text(
+                            "This is a safety limitation to avoid high SMB doses when glucose is rising abnormally fast, such as after a meal or with a very jumpy CGM sensor."
+                        )
                         Text("Note: This setting has a hard-coded cap of 40%")
                     }
                 )

+ 2 - 2
Trio/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift

@@ -193,10 +193,10 @@ extension TargetsEditor {
                         .addingTimeInterval(state.timeValues.last! + 30 * 60)
 
                     LineMark(x: .value("End Date", startDate), y: .value("Target", displayValueFloat ?? 0.0))
-                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.green.gradient)
+                        .lineStyle(.init(lineWidth: 2.5)).foregroundStyle(Color.green.gradient)
 
                     LineMark(x: .value("Start Date", endDate), y: .value("Target", displayValueFloat ?? 0.0))
-                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.green.gradient)
+                        .lineStyle(.init(lineWidth: 2.5)).foregroundStyle(Color.green.gradient)
                 }
             }
             .chartXAxis {

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

@@ -249,7 +249,7 @@ extension Treatments {
                                         Toggle(isOn: $state.useFattyMealCorrectionFactor) {
                                             Text("Fatty Meal")
                                         }
-                                        .toggleStyle(CheckboxToggleStyle())
+                                        .toggleStyle(RadioButtonToggleStyle())
                                         .font(.footnote)
                                         .onChange(of: state.useFattyMealCorrectionFactor) {
                                             Task {
@@ -264,7 +264,7 @@ extension Treatments {
                                         Toggle(isOn: $state.useSuperBolus) {
                                             Text("Super Bolus")
                                         }
-                                        .toggleStyle(CheckboxToggleStyle())
+                                        .toggleStyle(RadioButtonToggleStyle())
                                         .font(.footnote)
                                         .onChange(of: state.useSuperBolus) {
                                             Task {
@@ -336,7 +336,7 @@ extension Treatments {
                             HStack {
                                 Text("External Insulin")
                                 Spacer()
-                                Toggle("", isOn: $state.externalInsulin).toggleStyle(Checkbox())
+                                Toggle("", isOn: $state.externalInsulin).toggleStyle(CheckboxToggleStyle())
                             }
                         }.listRowBackground(Color.chart)
 

+ 16 - 11
Trio/Sources/Services/UserNotifications/UserNotificationsManager.swift

@@ -8,7 +8,9 @@ import Swinject
 import UIKit
 import UserNotifications
 
-protocol UserNotificationsManager {}
+protocol UserNotificationsManager {
+    func requestNotificationPermissions(completion: @escaping (Bool) -> Void)
+}
 
 enum GlucoseSourceKey: String {
     case transmitterBattery
@@ -88,7 +90,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         broadcaster.register(BolusFailureObserver.self, observer: self)
         broadcaster.register(pumpNotificationObserver.self, observer: self)
         broadcaster.register(alertMessageNotificationObserver.self, observer: self)
-        requestNotificationPermissionsIfNeeded()
+//        requestNotificationPermissionsIfNeeded()
         Task {
             await sendGlucoseNotification()
         }
@@ -384,20 +386,23 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         return body
     }
 
-    private func requestNotificationPermissionsIfNeeded() {
-        center.getNotificationSettings { settings in
-            debug(.service, "UNUserNotificationCenter.authorizationStatus: \(String(describing: settings.authorizationStatus))")
-            if ![.authorized, .provisional].contains(settings.authorizationStatus) {
-                self.requestNotificationPermissions()
-            }
-        }
-    }
+//    private func requestNotificationPermissionsIfNeeded() {
+//        center.getNotificationSettings { settings in
+//            debug(.service, "UNUserNotificationCenter.authorizationStatus: \(String(describing: settings.authorizationStatus))")
+//            if ![.authorized, .provisional].contains(settings.authorizationStatus) {
+//                self.requestNotificationPermissions()
+//            }
+//        }
+//    }
 
-    private func requestNotificationPermissions() {
+    func requestNotificationPermissions(completion: @escaping (Bool) -> Void) {
         debug(.service, "requestNotificationPermissions")
         center.requestAuthorization(options: [.badge, .sound, .alert]) { granted, error in
             if granted {
                 debug(.service, "requestNotificationPermissions was granted")
+                DispatchQueue.main.async {
+                    completion(granted)
+                }
             } else {
                 warning(.service, "requestNotificationPermissions failed", error: error)
             }