Ivan Valkou 4 лет назад
Родитель
Сommit
2edc9e1da0
69 измененных файлов с 579 добавлено и 724 удалено
  1. 1 1
      Dependencies/LoopKit/LoopKitUI/View Controllers/OverrideSelectionViewController.swift
  2. 3 3
      Dependencies/LoopKit/LoopKitUI/Views/OverrideSelectionHistory.swift
  3. 81 161
      FreeAPS.xcodeproj/project.pbxproj
  4. 2 11
      FreeAPS/Sources/Application/FreeAPSApp.swift
  5. 1 1
      FreeAPS/Sources/Assemblies/NetworkAssembly.swift
  6. 0 3
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsBuilder.swift
  7. 1 1
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsViewModel.swift
  8. 10 7
      FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift
  9. 0 3
      FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetBuilder.swift
  10. 1 1
      FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetViewModel.swift
  11. 21 18
      FreeAPS/Sources/Modules/AddTempTarget/View/AddTempTargetRootView.swift
  12. 0 3
      FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigBuilder.swift
  13. 11 5
      FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigViewModel.swift
  14. 11 8
      FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift
  15. 0 3
      FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorBuilder.swift
  16. 1 1
      FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorViewModel.swift
  17. 22 19
      FreeAPS/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift
  18. 0 26
      FreeAPS/Sources/Modules/Base/BaseModuleBuilder.swift
  19. 43 0
      FreeAPS/Sources/Modules/Base/BaseStateModel.swift
  20. 22 4
      FreeAPS/Sources/Modules/Base/BaseView.swift
  21. 0 56
      FreeAPS/Sources/Modules/Base/BaseViewModel.swift
  22. 0 15
      FreeAPS/Sources/Modules/Bolus/BolusBuilder.swift
  23. 6 15
      FreeAPS/Sources/Modules/Bolus/BolusViewModel.swift
  24. 25 16
      FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift
  25. 0 3
      FreeAPS/Sources/Modules/CGM/CGMBuilder.swift
  26. 1 1
      FreeAPS/Sources/Modules/CGM/CGMViewModel.swift
  27. 10 7
      FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift
  28. 0 3
      FreeAPS/Sources/Modules/CREditor/CREditorBuilder.swift
  29. 1 1
      FreeAPS/Sources/Modules/CREditor/CREditorViewModel.swift
  30. 21 18
      FreeAPS/Sources/Modules/CREditor/View/CREditorRootView.swift
  31. 0 16
      FreeAPS/Sources/Modules/ConfigEditor/ConfigEditorBuilder.swift
  32. 17 0
      FreeAPS/Sources/Modules/ConfigEditor/ConfigEditorStateModel.swift
  33. 0 26
      FreeAPS/Sources/Modules/ConfigEditor/ConfigEditorViewModel.swift
  34. 13 5
      FreeAPS/Sources/Modules/ConfigEditor/View/ConfigEditorRootView.swift
  35. 0 3
      FreeAPS/Sources/Modules/DataTable/DataTableBuilder.swift
  36. 2 2
      FreeAPS/Sources/Modules/DataTable/DataTableViewModel.swift
  37. 7 4
      FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift
  38. 0 3
      FreeAPS/Sources/Modules/Home/HomeBuilder.swift
  39. 3 3
      FreeAPS/Sources/Modules/Home/HomeViewModel.swift
  40. 62 58
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  41. 0 3
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorBuilder.swift
  42. 1 1
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorViewModel.swift
  43. 27 24
      FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift
  44. 0 5
      FreeAPS/Sources/Modules/Main/MainBuilder.swift
  45. 3 5
      FreeAPS/Sources/Modules/Main/MainViewModel.swift
  46. 13 11
      FreeAPS/Sources/Modules/Main/View/MainRootView.swift
  47. 0 3
      FreeAPS/Sources/Modules/ManualTempBasal/ManualTempBasalBuilder.swift
  48. 1 1
      FreeAPS/Sources/Modules/ManualTempBasal/ManualTempBasalViewModel.swift
  49. 12 9
      FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift
  50. 0 3
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigBuilder.swift
  51. 1 1
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigViewModel.swift
  52. 17 13
      FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift
  53. 0 3
      FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorBuilder.swift
  54. 1 3
      FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorViewModel.swift
  55. 13 10
      FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift
  56. 0 3
      FreeAPS/Sources/Modules/PumpConfig/PumpConfigBuilder.swift
  57. 3 3
      FreeAPS/Sources/Modules/PumpConfig/PumpConfigViewModel.swift
  58. 16 13
      FreeAPS/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift
  59. 0 3
      FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorBuilder.swift
  60. 1 1
      FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorViewModel.swift
  61. 11 8
      FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift
  62. 0 3
      FreeAPS/Sources/Modules/Settings/SettingsBuilder.swift
  63. 2 2
      FreeAPS/Sources/Modules/Settings/SettingsViewModel.swift
  64. 9 10
      FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift
  65. 0 3
      FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorBuilder.swift
  66. 1 1
      FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorViewModel.swift
  67. 25 22
      FreeAPS/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift
  68. 21 21
      FreeAPS/Sources/Router/Screen.swift
  69. 2 2
      FreeAPS/Sources/Views/ViewModifiers.swift

+ 1 - 1
Dependencies/LoopKit/LoopKitUI/View Controllers/OverrideSelectionViewController.swift

@@ -281,7 +281,7 @@ public final class OverrideSelectionViewController: UICollectionViewController,
                 customOverrideVC.delegate = self
                 show(customOverrideVC, sender: collectionView.cellForItem(at: indexPath))
             case .history:
-                let model = OverrideHistoryViewModel(
+                let model = OverrideHistorystate(
                     overrides: overrideHistory,
                     glucoseUnit: glucoseUnit
                 )

+ 3 - 3
Dependencies/LoopKit/LoopKitUI/Views/OverrideSelectionHistory.swift

@@ -10,7 +10,7 @@ import SwiftUI
 import LoopKit
 import HealthKit
 
-public class OverrideHistoryViewModel: ObservableObject {
+public class OverrideHistorystate: ObservableObject {
     var overrides: [TemporaryScheduleOverride]
     var glucoseUnit: HKUnit
     var didEditOverride: ((TemporaryScheduleOverride) -> Void)?
@@ -26,12 +26,12 @@ public class OverrideHistoryViewModel: ObservableObject {
 }
 
 public struct OverrideSelectionHistory: View {
-    @ObservedObject var model: OverrideHistoryViewModel
+    @ObservedObject var model: OverrideHistorystate
     private var quantityFormatter: QuantityFormatter
     private var glucoseNumberFormatter: NumberFormatter
     private var durationFormatter: DateComponentsFormatter
     
-    public init(model: OverrideHistoryViewModel) {
+    public init(model: OverrideHistorystate) {
         self.model = model
         self.quantityFormatter = {
             let quantityFormatter = QuantityFormatter()

+ 81 - 161
FreeAPS.xcodeproj/project.pbxproj

@@ -9,40 +9,29 @@
 /* Begin PBXBuildFile section */
 		041D1E995A6AE92E9289DC49 /* BolusDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */; };
 		0CEA2EA070AB041AF3E3745B /* BolusRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10A0C32B0DAB52726EF9B6D9 /* BolusRootView.swift */; };
-		0D9A5E34A899219C5C4CDFAF /* DataTableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9455FA2D92E77A6C4AFED8A3 /* DataTableViewModel.swift */; };
+		0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9455FA2D92E77A6C4AFED8A3 /* DataTableStateModel.swift */; };
 		17A9D0899046B45E87834820 /* CREditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C8D5F457B5AFF763F8CF3DF /* CREditorProvider.swift */; };
-		19434C14DF3F4816F4E4BF2E /* BolusBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FAEF7B34EEC71B3A7B800C /* BolusBuilder.swift */; };
 		198377D2266BFFF6004DE65E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 198377D4266BFFF6004DE65E /* Localizable.strings */; };
-		1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorViewModel.swift */; };
-		1D086541F369D339A74893AC /* BasalProfileEditorBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA56D2DCAB9E0A8AF24D984 /* BasalProfileEditorBuilder.swift */; };
+		1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */; };
 		1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60744C3E9BB3652895C908CC /* DataTableProvider.swift */; };
-		1FF95E8F785B28961EFDE5A9 /* ManualTempBasalBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A480F6EA37954BDE0DB4B64C /* ManualTempBasalBuilder.swift */; };
 		23888883D4EA091C88480FF2 /* BolusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19984D62EFC0035A9E9644D /* BolusProvider.swift */; };
-		25548F1F0AA8E42FF5F96DBA /* PumpSettingsEditorBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CAE3534904CDCA0F367017 /* PumpSettingsEditorBuilder.swift */; };
 		28089E07169488CF6DCC2A31 /* AddCarbsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86FC1CFD647CF34508AF9A3B /* AddCarbsRootView.swift */; };
 		2BE9A6FA20875F6F4F9CD461 /* PumpSettingsEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D97F14812C1AFED3621165A5 /* PumpSettingsEditorProvider.swift */; };
 		3083261C4B268E353F36CD0B /* AutotuneConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DCCCCE633F5E98E41B0CD3C /* AutotuneConfigDataFlow.swift */; };
-		3340E0D14D4701342D459C95 /* PumpConfigBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01C416A0792696C6911C1D7 /* PumpConfigBuilder.swift */; };
-		33E198D3039045D98C3DC5D4 /* AddCarbsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E7C997E56DAF8D28D09014 /* AddCarbsViewModel.swift */; };
-		3811DE0925C9D32F00A708ED /* BaseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0525C9D32E00A708ED /* BaseViewModel.swift */; };
-		3811DE0A25C9D32F00A708ED /* BaseModuleBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0625C9D32E00A708ED /* BaseModuleBuilder.swift */; };
+		33E198D3039045D98C3DC5D4 /* AddCarbsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E7C997E56DAF8D28D09014 /* AddCarbsStateModel.swift */; };
 		3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0725C9D32E00A708ED /* BaseView.swift */; };
 		3811DE0C25C9D32F00A708ED /* BaseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0825C9D32F00A708ED /* BaseProvider.swift */; };
 		3811DE1025C9D37700A708ED /* Swinject in Frameworks */ = {isa = PBXBuildFile; productRef = 3811DE0F25C9D37700A708ED /* Swinject */; };
 		3811DE1725C9D40400A708ED /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1525C9D40400A708ED /* Screen.swift */; };
 		3811DE1825C9D40400A708ED /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1625C9D40400A708ED /* Router.swift */; };
-		3811DE2125C9D48300A708ED /* MainBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1B25C9D48300A708ED /* MainBuilder.swift */; };
 		3811DE2225C9D48300A708ED /* MainProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1C25C9D48300A708ED /* MainProvider.swift */; };
 		3811DE2325C9D48300A708ED /* MainDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1D25C9D48300A708ED /* MainDataFlow.swift */; };
-		3811DE2425C9D48300A708ED /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1E25C9D48300A708ED /* MainViewModel.swift */; };
 		3811DE2525C9D48300A708ED /* MainRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2025C9D48300A708ED /* MainRootView.swift */; };
-		3811DE3025C9D49500A708ED /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2825C9D49500A708ED /* HomeViewModel.swift */; };
+		3811DE3025C9D49500A708ED /* HomeStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2825C9D49500A708ED /* HomeStateModel.swift */; };
 		3811DE3125C9D49500A708ED /* HomeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2925C9D49500A708ED /* HomeProvider.swift */; };
 		3811DE3225C9D49500A708ED /* HomeDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2A25C9D49500A708ED /* HomeDataFlow.swift */; };
-		3811DE3325C9D49500A708ED /* HomeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2B25C9D49500A708ED /* HomeBuilder.swift */; };
 		3811DE3525C9D49500A708ED /* HomeRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2E25C9D49500A708ED /* HomeRootView.swift */; };
-		3811DE3F25C9D4A100A708ED /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3925C9D4A100A708ED /* SettingsViewModel.swift */; };
-		3811DE4025C9D4A100A708ED /* SettingsBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3A25C9D4A100A708ED /* SettingsBuilder.swift */; };
+		3811DE3F25C9D4A100A708ED /* SettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3925C9D4A100A708ED /* SettingsStateModel.swift */; };
 		3811DE4125C9D4A100A708ED /* SettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3C25C9D4A100A708ED /* SettingsRootView.swift */; };
 		3811DE4225C9D4A100A708ED /* SettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3D25C9D4A100A708ED /* SettingsDataFlow.swift */; };
 		3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3E25C9D4A100A708ED /* SettingsProvider.swift */; };
@@ -85,7 +74,6 @@
 		38569348270B5DFB0002C50D /* GlucoseSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38569345270B5DFA0002C50D /* GlucoseSource.swift */; };
 		38569349270B5DFB0002C50D /* AppGroupSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38569346270B5DFB0002C50D /* AppGroupSource.swift */; };
 		3856934F270B5E1D0002C50D /* CGMProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3856934B270B5E1C0002C50D /* CGMProvider.swift */; };
-		38569351270B5E1D0002C50D /* CGMBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3856934D270B5E1D0002C50D /* CGMBuilder.swift */; };
 		38569353270B5E350002C50D /* CGMRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38569352270B5E350002C50D /* CGMRootView.swift */; };
 		385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385CEA8125F23DFD002D6D5B /* NightscoutStatus.swift */; };
 		385CEAC125F2EA52002D6D5B /* Announcement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385CEAC025F2EA52002D6D5B /* Announcement.swift */; };
@@ -190,38 +178,33 @@
 		38FCF3FD25E997A80078B0D1 /* PumpHistoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FCF3FC25E997A80078B0D1 /* PumpHistoryStorage.swift */; };
 		38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FE826925CC82DB001FF17A /* NetworkService.swift */; };
 		38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FE826C25CC8461001FF17A /* NightscoutAPI.swift */; };
-		3BD663A04B4CA5278B0260B4 /* CREditorBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42BA004367A6998F8AFC1A0F /* CREditorBuilder.swift */; };
+		38FEF3FA2737E42000574A46 /* BaseStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF3F92737E42000574A46 /* BaseStateModel.swift */; };
+		38FEF3FC2737E53800574A46 /* MainStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF3FB2737E53800574A46 /* MainStateModel.swift */; };
 		44190F0BBA464D74B857D1FB /* PreferencesEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A965332F237348B119FB858 /* PreferencesEditorRootView.swift */; };
 		448B6FCB252BD4796E2960C0 /* PumpSettingsEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0274EE6439B1C3ED70730D41 /* PumpSettingsEditorDataFlow.swift */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
 		45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */; };
-		460745235E45CA6311C98613 /* AddCarbsBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9EC17A17E1D371673E7F0 /* AddCarbsBuilder.swift */; };
-		46159F33C01CDBF822B4F1F8 /* AddTempTargetBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 073A69402BBB977D7E902997 /* AddTempTargetBuilder.swift */; };
 		5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */; };
 		53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8630D58BDAD6D9C650B9B39 /* PumpConfigProvider.swift */; };
 		5BFA1C2208114643B77F8CEB /* AddTempTargetProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE53A13D26F101B332EFFC8 /* AddTempTargetProvider.swift */; };
-		5D16287A969E64D18CE40E44 /* PumpConfigViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F60E97100041040446F44E7 /* PumpConfigViewModel.swift */; };
-		63E890B4D951EAA91C071D5C /* BasalProfileEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorViewModel.swift */; };
+		5D16287A969E64D18CE40E44 /* PumpConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F60E97100041040446F44E7 /* PumpConfigStateModel.swift */; };
+		63E890B4D951EAA91C071D5C /* BasalProfileEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorStateModel.swift */; };
 		642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */; };
 		6632A0DC746872439A858B44 /* ISFEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */; };
-		69A31254F2451C20361D172F /* BolusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223EC0494F55A91E3EA69EF4 /* BolusViewModel.swift */; };
-		69B9A368029F7EB39F525422 /* CREditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA5E04A2761F6EEA6568E1 /* CREditorViewModel.swift */; };
-		6B9625766B697D1C98E455A2 /* PumpSettingsEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72778B68C3004F71F6E79BDC /* PumpSettingsEditorViewModel.swift */; };
+		69A31254F2451C20361D172F /* BolusStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223EC0494F55A91E3EA69EF4 /* BolusStateModel.swift */; };
+		69B9A368029F7EB39F525422 /* CREditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA5E04A2761F6EEA6568E1 /* CREditorStateModel.swift */; };
+		6B9625766B697D1C98E455A2 /* PumpSettingsEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72778B68C3004F71F6E79BDC /* PumpSettingsEditorStateModel.swift */; };
 		711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */; };
 		72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */; };
 		7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */; };
-		7F7017AA5C69838FB7E6FECE /* TargetsEditorBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3409A5984BB4171EC484266B /* TargetsEditorBuilder.swift */; };
 		7F7B756BE8543965D9FDF1A2 /* DataTableDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A401509D21F7F35D4E109EDA /* DataTableDataFlow.swift */; };
 		88AB39B23C9552BD6E0C9461 /* ISFEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB3BAE7494CB771ABAC7B8B /* ISFEditorRootView.swift */; };
 		891DECF7BC20968D7F566161 /* AutotuneConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EF98E22A39CD656A230704 /* AutotuneConfigProvider.swift */; };
 		8B759CFCF47B392BB365C251 /* BasalProfileEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F94DD2853CF42BA4E30616 /* BasalProfileEditorDataFlow.swift */; };
 		8BC2F5A29AD1ED08AC0EE013 /* AddTempTargetRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AAB83FB6C3B41EFD1846A0 /* AddTempTargetRootView.swift */; };
-		91732A8060347C0E67024D80 /* AutotuneConfigBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B260840169E712C05ACC1F /* AutotuneConfigBuilder.swift */; };
-		919DBD08F13BAFB180DF6F47 /* AddTempTargetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C3B5FD881CA45DFDEE0EDA9 /* AddTempTargetViewModel.swift */; };
-		937407F04370AAB478C112D1 /* DataTableBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63869A4B6CAF91EB974D1581 /* DataTableBuilder.swift */; };
+		919DBD08F13BAFB180DF6F47 /* AddTempTargetStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C3B5FD881CA45DFDEE0EDA9 /* AddTempTargetStateModel.swift */; };
 		9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DD795BA46B193644D48138C /* TargetsEditorRootView.swift */; };
-		97C1388354C7133C1D5ED72A /* PreferencesEditorBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08D9D69E5B052E5C9E8BD32 /* PreferencesEditorBuilder.swift */; };
-		9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A48AE3AC813A49A517846A /* NightscoutConfigViewModel.swift */; };
+		9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A48AE3AC813A49A517846A /* NightscoutConfigStateModel.swift */; };
 		98641AF4F92123DA668AB931 /* CREditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BDC6993C1087310EDFC428 /* CREditorRootView.swift */; };
 		A05235B9112E677ED03B6E8E /* AutotuneConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CF5ACEE1F0859670E71B2C0 /* AutotuneConfigRootView.swift */; };
 		A0B8EC8CC5CD1DD237D1BCD2 /* PumpSettingsEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C7F882606FF83A21BE00D8 /* PumpSettingsEditorRootView.swift */; };
@@ -229,31 +212,28 @@
 		A33352ED40476125EBAC6EE0 /* CREditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E22146D3DF4853786C78132 /* CREditorDataFlow.swift */; };
 		A6F097A14CAAE0CE0D11BE1B /* AddCarbsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E62C9757B2F95431B5DC0 /* AddCarbsProvider.swift */; };
 		AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */; };
-		BA00D96F7B2FF169A06FB530 /* CGMViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C018D1680307A31C9ED7120 /* CGMViewModel.swift */; };
+		BA00D96F7B2FF169A06FB530 /* CGMStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C018D1680307A31C9ED7120 /* CGMStateModel.swift */; };
 		BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */; };
 		BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */; };
-		C967DACD3B1E638F8B43BE06 /* ManualTempBasalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalViewModel.swift */; };
+		C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */; };
 		CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; };
-		CDB87FA71A93F3739D3D338E /* NightscoutConfigBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111579A6E3AC6BFA79C4DD43 /* NightscoutConfigBuilder.swift */; };
 		D2165E9D78EFF692C1DED1C6 /* AddTempTargetDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8A42073A2D03A278914448 /* AddTempTargetDataFlow.swift */; };
 		D6D02515BBFBE64FEBE89856 /* DataTableRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881E04BA5E0A003DE8E0A9C6 /* DataTableRootView.swift */; };
 		D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */; };
-		D76333C9256787610B3B4875 /* AutotuneConfigViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D295A3F870E826BE371C0BB5 /* AutotuneConfigViewModel.swift */; };
+		D76333C9256787610B3B4875 /* AutotuneConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D295A3F870E826BE371C0BB5 /* AutotuneConfigStateModel.swift */; };
 		DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9F137F126D9F8DEB799F26 /* ISFEditorProvider.swift */; };
-		DD399FB31EACB9343C944C4C /* PreferencesEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3E609094E064C99A4752C /* PreferencesEditorViewModel.swift */; };
+		DD399FB31EACB9343C944C4C /* PreferencesEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3E609094E064C99A4752C /* PreferencesEditorStateModel.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 */; };
 		E00EEC0627368630002FF094 /* UIAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEC0027368630002FF094 /* UIAssembly.swift */; };
 		E00EEC0727368630002FF094 /* APSAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEC0127368630002FF094 /* APSAssembly.swift */; };
 		E00EEC0827368630002FF094 /* NetworkAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEC0227368630002FF094 /* NetworkAssembly.swift */; };
-		E102DE9C3E9C8AEDCB3C61BB /* ConfigEditorBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E492D5B2EEF2119977EA2CE4 /* ConfigEditorBuilder.swift */; };
-		E13B7DAB2A435F57066AF02E /* TargetsEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F58DDD71F0E795464FA3F0 /* TargetsEditorViewModel.swift */; };
-		E39E418C56A5A46B61D960EE /* ConfigEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B4F8B4194BB7E260EF251 /* ConfigEditorViewModel.swift */; };
+		E13B7DAB2A435F57066AF02E /* TargetsEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F58DDD71F0E795464FA3F0 /* TargetsEditorStateModel.swift */; };
+		E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B4F8B4194BB7E260EF251 /* ConfigEditorStateModel.swift */; };
 		E4984C5262A90469788754BB /* PreferencesEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8BA8533F56BC55748CA877 /* PreferencesEditorProvider.swift */; };
 		E97285ED9B814CD5253C6658 /* AddCarbsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F48C3AC770D4CCD0EA2B0C2 /* AddCarbsDataFlow.swift */; };
 		E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */; };
-		F215CAB49BA4B5A01C3BC6B6 /* ISFEditorBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3239D795F77DEB5676F9427A /* ISFEditorBuilder.swift */; };
 		F5CA3DB1F9DC8B05792BBFAA /* CGMDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B5C0607505A38F256BF99A /* CGMDataFlow.swift */; };
 		F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */; };
 		FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */; };
@@ -299,12 +279,9 @@
 
 /* Begin PBXFileReference section */
 		0274EE6439B1C3ED70730D41 /* PumpSettingsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorDataFlow.swift; sourceTree = "<group>"; };
-		073A69402BBB977D7E902997 /* AddTempTargetBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddTempTargetBuilder.swift; sourceTree = "<group>"; };
-		0CA3E609094E064C99A4752C /* PreferencesEditorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorViewModel.swift; sourceTree = "<group>"; };
+		0CA3E609094E064C99A4752C /* PreferencesEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorStateModel.swift; sourceTree = "<group>"; };
 		0E0D51F68E37921622962DB4 /* CGMProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CGMProvider.swift; sourceTree = "<group>"; };
 		10A0C32B0DAB52726EF9B6D9 /* BolusRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusRootView.swift; sourceTree = "<group>"; };
-		10CAE3534904CDCA0F367017 /* PumpSettingsEditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorBuilder.swift; sourceTree = "<group>"; };
-		111579A6E3AC6BFA79C4DD43 /* NightscoutConfigBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigBuilder.swift; sourceTree = "<group>"; };
 		12204445D7632AF09264A979 /* PreferencesEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorDataFlow.swift; sourceTree = "<group>"; };
 		1918333A26ADA46800F45722 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
 		198377D3266BFFF6004DE65E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -326,30 +303,22 @@
 		198377E4266C13D2004DE65E /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
 		199732B4271B72DD00129A3F /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
 		199732B5271B9EE900129A3F /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
-		223EC0494F55A91E3EA69EF4 /* BolusViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusViewModel.swift; sourceTree = "<group>"; };
+		223EC0494F55A91E3EA69EF4 /* BolusStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusStateModel.swift; sourceTree = "<group>"; };
 		2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigRootView.swift; sourceTree = "<group>"; };
 		2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigDataFlow.swift; sourceTree = "<group>"; };
-		3239D795F77DEB5676F9427A /* ISFEditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorBuilder.swift; sourceTree = "<group>"; };
-		3409A5984BB4171EC484266B /* TargetsEditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorBuilder.swift; sourceTree = "<group>"; };
-		36F58DDD71F0E795464FA3F0 /* TargetsEditorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorViewModel.swift; sourceTree = "<group>"; };
-		3811DE0525C9D32E00A708ED /* BaseViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseViewModel.swift; sourceTree = "<group>"; };
-		3811DE0625C9D32E00A708ED /* BaseModuleBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseModuleBuilder.swift; sourceTree = "<group>"; };
+		36F58DDD71F0E795464FA3F0 /* TargetsEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorStateModel.swift; sourceTree = "<group>"; };
 		3811DE0725C9D32E00A708ED /* BaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseView.swift; sourceTree = "<group>"; };
 		3811DE0825C9D32F00A708ED /* BaseProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseProvider.swift; sourceTree = "<group>"; };
 		3811DE1525C9D40400A708ED /* Screen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
 		3811DE1625C9D40400A708ED /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
-		3811DE1B25C9D48300A708ED /* MainBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainBuilder.swift; sourceTree = "<group>"; };
 		3811DE1C25C9D48300A708ED /* MainProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainProvider.swift; sourceTree = "<group>"; };
 		3811DE1D25C9D48300A708ED /* MainDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainDataFlow.swift; sourceTree = "<group>"; };
-		3811DE1E25C9D48300A708ED /* MainViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = "<group>"; };
 		3811DE2025C9D48300A708ED /* MainRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainRootView.swift; sourceTree = "<group>"; };
-		3811DE2825C9D49500A708ED /* HomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
+		3811DE2825C9D49500A708ED /* HomeStateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStateModel.swift; sourceTree = "<group>"; };
 		3811DE2925C9D49500A708ED /* HomeProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeProvider.swift; sourceTree = "<group>"; };
 		3811DE2A25C9D49500A708ED /* HomeDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeDataFlow.swift; sourceTree = "<group>"; };
-		3811DE2B25C9D49500A708ED /* HomeBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeBuilder.swift; sourceTree = "<group>"; };
 		3811DE2E25C9D49500A708ED /* HomeRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeRootView.swift; sourceTree = "<group>"; };
-		3811DE3925C9D4A100A708ED /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
-		3811DE3A25C9D4A100A708ED /* SettingsBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsBuilder.swift; sourceTree = "<group>"; };
+		3811DE3925C9D4A100A708ED /* SettingsStateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsStateModel.swift; sourceTree = "<group>"; };
 		3811DE3C25C9D4A100A708ED /* SettingsRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsRootView.swift; sourceTree = "<group>"; };
 		3811DE3D25C9D4A100A708ED /* SettingsDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDataFlow.swift; sourceTree = "<group>"; };
 		3811DE3E25C9D4A100A708ED /* SettingsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsProvider.swift; sourceTree = "<group>"; };
@@ -390,7 +359,6 @@
 		38569345270B5DFA0002C50D /* GlucoseSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseSource.swift; sourceTree = "<group>"; };
 		38569346270B5DFB0002C50D /* AppGroupSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppGroupSource.swift; sourceTree = "<group>"; };
 		3856934B270B5E1C0002C50D /* CGMProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMProvider.swift; sourceTree = "<group>"; };
-		3856934D270B5E1D0002C50D /* CGMBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMBuilder.swift; sourceTree = "<group>"; };
 		38569352270B5E350002C50D /* CGMRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMRootView.swift; sourceTree = "<group>"; };
 		385CEA8125F23DFD002D6D5B /* NightscoutStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutStatus.swift; sourceTree = "<group>"; };
 		385CEAC025F2EA52002D6D5B /* Announcement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Announcement.swift; sourceTree = "<group>"; };
@@ -487,50 +455,46 @@
 		38FCF3FC25E997A80078B0D1 /* PumpHistoryStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpHistoryStorage.swift; sourceTree = "<group>"; };
 		38FE826925CC82DB001FF17A /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = "<group>"; };
 		38FE826C25CC8461001FF17A /* NightscoutAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutAPI.swift; sourceTree = "<group>"; };
-		39E7C997E56DAF8D28D09014 /* AddCarbsViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddCarbsViewModel.swift; sourceTree = "<group>"; };
+		38FEF3F92737E42000574A46 /* BaseStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStateModel.swift; sourceTree = "<group>"; };
+		38FEF3FB2737E53800574A46 /* MainStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainStateModel.swift; sourceTree = "<group>"; };
+		39E7C997E56DAF8D28D09014 /* AddCarbsStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddCarbsStateModel.swift; sourceTree = "<group>"; };
 		3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorProvider.swift; sourceTree = "<group>"; };
 		3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigProvider.swift; sourceTree = "<group>"; };
-		3F60E97100041040446F44E7 /* PumpConfigViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigViewModel.swift; sourceTree = "<group>"; };
+		3F60E97100041040446F44E7 /* PumpConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigStateModel.swift; sourceTree = "<group>"; };
 		3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorDataFlow.swift; sourceTree = "<group>"; };
 		42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorProvider.swift; sourceTree = "<group>"; };
-		42BA004367A6998F8AFC1A0F /* CREditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CREditorBuilder.swift; sourceTree = "<group>"; };
 		44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorProvider.swift; sourceTree = "<group>"; };
 		4DD795BA46B193644D48138C /* TargetsEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorRootView.swift; sourceTree = "<group>"; };
-		505E09DC17A0C3D0AF4B66FE /* ISFEditorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorViewModel.swift; sourceTree = "<group>"; };
+		505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorStateModel.swift; sourceTree = "<group>"; };
 		5B8A42073A2D03A278914448 /* AddTempTargetDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddTempTargetDataFlow.swift; sourceTree = "<group>"; };
-		5C018D1680307A31C9ED7120 /* CGMViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CGMViewModel.swift; sourceTree = "<group>"; };
-		5D5B4F8B4194BB7E260EF251 /* ConfigEditorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorViewModel.swift; sourceTree = "<group>"; };
+		5C018D1680307A31C9ED7120 /* CGMStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CGMStateModel.swift; sourceTree = "<group>"; };
+		5D5B4F8B4194BB7E260EF251 /* ConfigEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorStateModel.swift; sourceTree = "<group>"; };
 		5F48C3AC770D4CCD0EA2B0C2 /* AddCarbsDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddCarbsDataFlow.swift; sourceTree = "<group>"; };
 		60744C3E9BB3652895C908CC /* DataTableProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataTableProvider.swift; sourceTree = "<group>"; };
 		618E62C9757B2F95431B5DC0 /* AddCarbsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddCarbsProvider.swift; sourceTree = "<group>"; };
-		63869A4B6CAF91EB974D1581 /* DataTableBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataTableBuilder.swift; sourceTree = "<group>"; };
-		64AA5E04A2761F6EEA6568E1 /* CREditorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CREditorViewModel.swift; sourceTree = "<group>"; };
+		64AA5E04A2761F6EEA6568E1 /* CREditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CREditorStateModel.swift; sourceTree = "<group>"; };
 		67F94DD2853CF42BA4E30616 /* BasalProfileEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorDataFlow.swift; sourceTree = "<group>"; };
 		680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalProvider.swift; sourceTree = "<group>"; };
-		6BA56D2DCAB9E0A8AF24D984 /* BasalProfileEditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorBuilder.swift; sourceTree = "<group>"; };
 		6F8BA8533F56BC55748CA877 /* PreferencesEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorProvider.swift; sourceTree = "<group>"; };
-		72778B68C3004F71F6E79BDC /* PumpSettingsEditorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorViewModel.swift; sourceTree = "<group>"; };
-		77FAEF7B34EEC71B3A7B800C /* BolusBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusBuilder.swift; sourceTree = "<group>"; };
+		72778B68C3004F71F6E79BDC /* PumpSettingsEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorStateModel.swift; sourceTree = "<group>"; };
 		79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorDataFlow.swift; sourceTree = "<group>"; };
 		7E22146D3DF4853786C78132 /* CREditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CREditorDataFlow.swift; sourceTree = "<group>"; };
 		86FC1CFD647CF34508AF9A3B /* AddCarbsRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddCarbsRootView.swift; sourceTree = "<group>"; };
 		8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigRootView.swift; sourceTree = "<group>"; };
 		881E04BA5E0A003DE8E0A9C6 /* DataTableRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataTableRootView.swift; sourceTree = "<group>"; };
 		8A965332F237348B119FB858 /* PreferencesEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorRootView.swift; sourceTree = "<group>"; };
-		8C3B5FD881CA45DFDEE0EDA9 /* AddTempTargetViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddTempTargetViewModel.swift; sourceTree = "<group>"; };
+		8C3B5FD881CA45DFDEE0EDA9 /* AddTempTargetStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddTempTargetStateModel.swift; sourceTree = "<group>"; };
 		8CF5ACEE1F0859670E71B2C0 /* AutotuneConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigRootView.swift; sourceTree = "<group>"; };
 		8DCCCCE633F5E98E41B0CD3C /* AutotuneConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigDataFlow.swift; sourceTree = "<group>"; };
-		91B260840169E712C05ACC1F /* AutotuneConfigBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigBuilder.swift; sourceTree = "<group>"; };
 		920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorRootView.swift; sourceTree = "<group>"; };
-		9455FA2D92E77A6C4AFED8A3 /* DataTableViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataTableViewModel.swift; sourceTree = "<group>"; };
+		9455FA2D92E77A6C4AFED8A3 /* DataTableStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataTableStateModel.swift; sourceTree = "<group>"; };
 		96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalDataFlow.swift; sourceTree = "<group>"; };
 		9C8D5F457B5AFF763F8CF3DF /* CREditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CREditorProvider.swift; sourceTree = "<group>"; };
 		9F9F137F126D9F8DEB799F26 /* ISFEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorProvider.swift; sourceTree = "<group>"; };
-		A0A48AE3AC813A49A517846A /* NightscoutConfigViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigViewModel.swift; sourceTree = "<group>"; };
+		A0A48AE3AC813A49A517846A /* NightscoutConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigStateModel.swift; sourceTree = "<group>"; };
 		A401509D21F7F35D4E109EDA /* DataTableDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataTableDataFlow.swift; sourceTree = "<group>"; };
-		A480F6EA37954BDE0DB4B64C /* ManualTempBasalBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalBuilder.swift; sourceTree = "<group>"; };
 		A8630D58BDAD6D9C650B9B39 /* PumpConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigProvider.swift; sourceTree = "<group>"; };
-		AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorViewModel.swift; sourceTree = "<group>"; };
+		AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorStateModel.swift; sourceTree = "<group>"; };
 		AEE53A13D26F101B332EFFC8 /* AddTempTargetProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddTempTargetProvider.swift; sourceTree = "<group>"; };
 		AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigDataFlow.swift; sourceTree = "<group>"; };
 		B5EF98E22A39CD656A230704 /* AutotuneConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigProvider.swift; sourceTree = "<group>"; };
@@ -541,9 +505,9 @@
 		C19984D62EFC0035A9E9644D /* BolusProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusProvider.swift; sourceTree = "<group>"; };
 		C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = "<group>"; };
 		C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusDataFlow.swift; sourceTree = "<group>"; };
-		CFCFE0781F9074C2917890E8 /* ManualTempBasalViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalViewModel.swift; sourceTree = "<group>"; };
+		CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalStateModel.swift; sourceTree = "<group>"; };
 		D0BDC6993C1087310EDFC428 /* CREditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CREditorRootView.swift; sourceTree = "<group>"; };
-		D295A3F870E826BE371C0BB5 /* AutotuneConfigViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigViewModel.swift; sourceTree = "<group>"; };
+		D295A3F870E826BE371C0BB5 /* AutotuneConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigStateModel.swift; sourceTree = "<group>"; };
 		D97F14812C1AFED3621165A5 /* PumpSettingsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorProvider.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>"; };
@@ -551,12 +515,8 @@
 		E00EEC0027368630002FF094 /* UIAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIAssembly.swift; sourceTree = "<group>"; };
 		E00EEC0127368630002FF094 /* APSAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APSAssembly.swift; sourceTree = "<group>"; };
 		E00EEC0227368630002FF094 /* NetworkAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkAssembly.swift; sourceTree = "<group>"; };
-		E01C416A0792696C6911C1D7 /* PumpConfigBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigBuilder.swift; sourceTree = "<group>"; };
-		E08D9D69E5B052E5C9E8BD32 /* PreferencesEditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorBuilder.swift; sourceTree = "<group>"; };
-		E492D5B2EEF2119977EA2CE4 /* ConfigEditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorBuilder.swift; sourceTree = "<group>"; };
 		E9AAB83FB6C3B41EFD1846A0 /* AddTempTargetRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddTempTargetRootView.swift; sourceTree = "<group>"; };
 		FBB3BAE7494CB771ABAC7B8B /* ISFEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorRootView.swift; sourceTree = "<group>"; };
-		FCB9EC17A17E1D371673E7F0 /* AddCarbsBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddCarbsBuilder.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -600,10 +560,9 @@
 		0610F7D6D2EC00E3BA1569F0 /* ConfigEditor */ = {
 			isa = PBXGroup;
 			children = (
-				E492D5B2EEF2119977EA2CE4 /* ConfigEditorBuilder.swift */,
 				3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */,
 				44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */,
-				5D5B4F8B4194BB7E260EF251 /* ConfigEditorViewModel.swift */,
+				5D5B4F8B4194BB7E260EF251 /* ConfigEditorStateModel.swift */,
 				4E8C7B59F8065047ECE20965 /* View */,
 			);
 			path = ConfigEditor;
@@ -687,10 +646,9 @@
 		3811DE0425C9D32E00A708ED /* Base */ = {
 			isa = PBXGroup;
 			children = (
-				3811DE0525C9D32E00A708ED /* BaseViewModel.swift */,
-				3811DE0625C9D32E00A708ED /* BaseModuleBuilder.swift */,
 				3811DE0725C9D32E00A708ED /* BaseView.swift */,
 				3811DE0825C9D32F00A708ED /* BaseProvider.swift */,
+				38FEF3F92737E42000574A46 /* BaseStateModel.swift */,
 			);
 			path = Base;
 			sourceTree = "<group>";
@@ -726,10 +684,9 @@
 		3811DE1A25C9D48300A708ED /* Main */ = {
 			isa = PBXGroup;
 			children = (
-				3811DE1B25C9D48300A708ED /* MainBuilder.swift */,
-				3811DE1C25C9D48300A708ED /* MainProvider.swift */,
 				3811DE1D25C9D48300A708ED /* MainDataFlow.swift */,
-				3811DE1E25C9D48300A708ED /* MainViewModel.swift */,
+				3811DE1C25C9D48300A708ED /* MainProvider.swift */,
+				38FEF3FB2737E53800574A46 /* MainStateModel.swift */,
 				3811DE1F25C9D48300A708ED /* View */,
 			);
 			path = Main;
@@ -746,10 +703,9 @@
 		3811DE2725C9D49500A708ED /* Home */ = {
 			isa = PBXGroup;
 			children = (
-				3811DE2B25C9D49500A708ED /* HomeBuilder.swift */,
 				3811DE2A25C9D49500A708ED /* HomeDataFlow.swift */,
 				3811DE2925C9D49500A708ED /* HomeProvider.swift */,
-				3811DE2825C9D49500A708ED /* HomeViewModel.swift */,
+				3811DE2825C9D49500A708ED /* HomeStateModel.swift */,
 				3811DE2C25C9D49500A708ED /* View */,
 			);
 			path = Home;
@@ -768,10 +724,9 @@
 		3811DE3825C9D4A100A708ED /* Settings */ = {
 			isa = PBXGroup;
 			children = (
-				3811DE3A25C9D4A100A708ED /* SettingsBuilder.swift */,
 				3811DE3D25C9D4A100A708ED /* SettingsDataFlow.swift */,
 				3811DE3E25C9D4A100A708ED /* SettingsProvider.swift */,
-				3811DE3925C9D4A100A708ED /* SettingsViewModel.swift */,
+				3811DE3925C9D4A100A708ED /* SettingsStateModel.swift */,
 				3811DE3B25C9D4A100A708ED /* View */,
 			);
 			path = Settings;
@@ -1154,10 +1109,9 @@
 		3E1C41D9301B7058AA7BF5EA /* PreferencesEditor */ = {
 			isa = PBXGroup;
 			children = (
-				E08D9D69E5B052E5C9E8BD32 /* PreferencesEditorBuilder.swift */,
 				12204445D7632AF09264A979 /* PreferencesEditorDataFlow.swift */,
 				6F8BA8533F56BC55748CA877 /* PreferencesEditorProvider.swift */,
-				0CA3E609094E064C99A4752C /* PreferencesEditorViewModel.swift */,
+				0CA3E609094E064C99A4752C /* PreferencesEditorStateModel.swift */,
 				833DA2F9E47E64E305F92F9D /* View */,
 			);
 			path = PreferencesEditor;
@@ -1190,10 +1144,9 @@
 		5031FE61F63C2A8A8B7674DD /* ManualTempBasal */ = {
 			isa = PBXGroup;
 			children = (
-				A480F6EA37954BDE0DB4B64C /* ManualTempBasalBuilder.swift */,
 				96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */,
 				680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */,
-				CFCFE0781F9074C2917890E8 /* ManualTempBasalViewModel.swift */,
+				CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */,
 				84BDC840A57C65A1E6F9F780 /* View */,
 			);
 			path = ManualTempBasal;
@@ -1244,10 +1197,9 @@
 		6517011F19F244F64E1FF14B /* TargetsEditor */ = {
 			isa = PBXGroup;
 			children = (
-				3409A5984BB4171EC484266B /* TargetsEditorBuilder.swift */,
 				BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */,
 				3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */,
-				36F58DDD71F0E795464FA3F0 /* TargetsEditorViewModel.swift */,
+				36F58DDD71F0E795464FA3F0 /* TargetsEditorStateModel.swift */,
 				34CA4DF169B53D67EF18ED8A /* View */,
 			);
 			path = TargetsEditor;
@@ -1256,10 +1208,9 @@
 		672F63EEAE27400625E14BAD /* AutotuneConfig */ = {
 			isa = PBXGroup;
 			children = (
-				91B260840169E712C05ACC1F /* AutotuneConfigBuilder.swift */,
 				8DCCCCE633F5E98E41B0CD3C /* AutotuneConfigDataFlow.swift */,
 				B5EF98E22A39CD656A230704 /* AutotuneConfigProvider.swift */,
-				D295A3F870E826BE371C0BB5 /* AutotuneConfigViewModel.swift */,
+				D295A3F870E826BE371C0BB5 /* AutotuneConfigStateModel.swift */,
 				55DE731ACE8289FAF3819077 /* View */,
 			);
 			path = AutotuneConfig;
@@ -1268,10 +1219,9 @@
 		6DC5D590658EF8B8DF94F9F5 /* AddCarbs */ = {
 			isa = PBXGroup;
 			children = (
-				FCB9EC17A17E1D371673E7F0 /* AddCarbsBuilder.swift */,
 				5F48C3AC770D4CCD0EA2B0C2 /* AddCarbsDataFlow.swift */,
 				618E62C9757B2F95431B5DC0 /* AddCarbsProvider.swift */,
-				39E7C997E56DAF8D28D09014 /* AddCarbsViewModel.swift */,
+				39E7C997E56DAF8D28D09014 /* AddCarbsStateModel.swift */,
 				50E85421406582CF9D321A20 /* View */,
 			);
 			path = AddCarbs;
@@ -1296,10 +1246,9 @@
 		99C01B871ACAB3F32CE755C7 /* PumpConfig */ = {
 			isa = PBXGroup;
 			children = (
-				E01C416A0792696C6911C1D7 /* PumpConfigBuilder.swift */,
 				AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */,
 				A8630D58BDAD6D9C650B9B39 /* PumpConfigProvider.swift */,
-				3F60E97100041040446F44E7 /* PumpConfigViewModel.swift */,
+				3F60E97100041040446F44E7 /* PumpConfigStateModel.swift */,
 				510CCF29FD3216C5BBC49A15 /* View */,
 			);
 			path = PumpConfig;
@@ -1308,10 +1257,9 @@
 		9E56E3626FAD933385101B76 /* DataTable */ = {
 			isa = PBXGroup;
 			children = (
-				63869A4B6CAF91EB974D1581 /* DataTableBuilder.swift */,
 				A401509D21F7F35D4E109EDA /* DataTableDataFlow.swift */,
 				60744C3E9BB3652895C908CC /* DataTableProvider.swift */,
-				9455FA2D92E77A6C4AFED8A3 /* DataTableViewModel.swift */,
+				9455FA2D92E77A6C4AFED8A3 /* DataTableStateModel.swift */,
 				0EE66DD474AFFD4FD787D5B9 /* View */,
 			);
 			path = DataTable;
@@ -1320,10 +1268,9 @@
 		A42F1FEDFFD0DDE00AAD54D3 /* BasalProfileEditor */ = {
 			isa = PBXGroup;
 			children = (
-				6BA56D2DCAB9E0A8AF24D984 /* BasalProfileEditorBuilder.swift */,
 				67F94DD2853CF42BA4E30616 /* BasalProfileEditorDataFlow.swift */,
 				42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */,
-				AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorViewModel.swift */,
+				AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorStateModel.swift */,
 				18B49BC9587A59E3A347C1CD /* View */,
 			);
 			path = BasalProfileEditor;
@@ -1332,10 +1279,9 @@
 		A9A4C88374496B3C89058A89 /* AddTempTarget */ = {
 			isa = PBXGroup;
 			children = (
-				073A69402BBB977D7E902997 /* AddTempTargetBuilder.swift */,
 				5B8A42073A2D03A278914448 /* AddTempTargetDataFlow.swift */,
 				AEE53A13D26F101B332EFFC8 /* AddTempTargetProvider.swift */,
-				8C3B5FD881CA45DFDEE0EDA9 /* AddTempTargetViewModel.swift */,
+				8C3B5FD881CA45DFDEE0EDA9 /* AddTempTargetStateModel.swift */,
 				3F8670D63672AF88E2E9B09E /* View */,
 			);
 			path = AddTempTarget;
@@ -1352,10 +1298,9 @@
 		C2C98283C436DB934D7E7994 /* Bolus */ = {
 			isa = PBXGroup;
 			children = (
-				77FAEF7B34EEC71B3A7B800C /* BolusBuilder.swift */,
 				C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */,
 				C19984D62EFC0035A9E9644D /* BolusProvider.swift */,
-				223EC0494F55A91E3EA69EF4 /* BolusViewModel.swift */,
+				223EC0494F55A91E3EA69EF4 /* BolusStateModel.swift */,
 				B9488883C59C31550E0B4CEC /* View */,
 			);
 			path = Bolus;
@@ -1364,10 +1309,9 @@
 		D533BF261CDC1C3F871E7BFD /* NightscoutConfig */ = {
 			isa = PBXGroup;
 			children = (
-				111579A6E3AC6BFA79C4DD43 /* NightscoutConfigBuilder.swift */,
 				2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */,
 				3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */,
-				A0A48AE3AC813A49A517846A /* NightscoutConfigViewModel.swift */,
+				A0A48AE3AC813A49A517846A /* NightscoutConfigStateModel.swift */,
 				4F4AE4D901E8BA872B207D7F /* View */,
 			);
 			path = NightscoutConfig;
@@ -1376,10 +1320,9 @@
 		D8F047E14D567F2B5DBEFD96 /* ISFEditor */ = {
 			isa = PBXGroup;
 			children = (
-				3239D795F77DEB5676F9427A /* ISFEditorBuilder.swift */,
 				79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */,
 				9F9F137F126D9F8DEB799F26 /* ISFEditorProvider.swift */,
-				505E09DC17A0C3D0AF4B66FE /* ISFEditorViewModel.swift */,
+				505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */,
 				EEC747824D6593B5CD87E195 /* View */,
 			);
 			path = ISFEditor;
@@ -1401,10 +1344,9 @@
 		E42231DBF0DBE2B4B92D1B15 /* CREditor */ = {
 			isa = PBXGroup;
 			children = (
-				42BA004367A6998F8AFC1A0F /* CREditorBuilder.swift */,
 				7E22146D3DF4853786C78132 /* CREditorDataFlow.swift */,
 				9C8D5F457B5AFF763F8CF3DF /* CREditorProvider.swift */,
-				64AA5E04A2761F6EEA6568E1 /* CREditorViewModel.swift */,
+				64AA5E04A2761F6EEA6568E1 /* CREditorStateModel.swift */,
 				54946647FDCFE43028F60511 /* View */,
 			);
 			path = CREditor;
@@ -1413,10 +1355,9 @@
 		E493126EA71765130F64CCE5 /* PumpSettingsEditor */ = {
 			isa = PBXGroup;
 			children = (
-				10CAE3534904CDCA0F367017 /* PumpSettingsEditorBuilder.swift */,
 				0274EE6439B1C3ED70730D41 /* PumpSettingsEditorDataFlow.swift */,
 				D97F14812C1AFED3621165A5 /* PumpSettingsEditorProvider.swift */,
-				72778B68C3004F71F6E79BDC /* PumpSettingsEditorViewModel.swift */,
+				72778B68C3004F71F6E79BDC /* PumpSettingsEditorStateModel.swift */,
 				64271A287C92581EADCB47FA /* View */,
 			);
 			path = PumpSettingsEditor;
@@ -1433,11 +1374,10 @@
 		F75CB57ED6971B46F8756083 /* CGM */ = {
 			isa = PBXGroup;
 			children = (
-				3856934D270B5E1D0002C50D /* CGMBuilder.swift */,
 				B9B5C0607505A38F256BF99A /* CGMDataFlow.swift */,
 				0E0D51F68E37921622962DB4 /* CGMProvider.swift */,
 				3856934B270B5E1C0002C50D /* CGMProvider.swift */,
-				5C018D1680307A31C9ED7120 /* CGMViewModel.swift */,
+				5C018D1680307A31C9ED7120 /* CGMStateModel.swift */,
 				0D76BBC81CEDC1A0050F45EF /* View */,
 			);
 			path = CGM;
@@ -1603,7 +1543,6 @@
 				3811DE2325C9D48300A708ED /* MainDataFlow.swift in Sources */,
 				3811DEEB25CA063400A708ED /* PersistedProperty.swift in Sources */,
 				388E5A6025B6F2310019842D /* Autosens.swift in Sources */,
-				38569351270B5E1D0002C50D /* CGMBuilder.swift in Sources */,
 				3811DE8F25C9D80400A708ED /* User.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
 				385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */,
@@ -1621,19 +1560,17 @@
 				386A124F271707F000DDC61C /* DexcomSource.swift in Sources */,
 				38B4F3CD25E5031100E76A18 /* Broadcaster.swift in Sources */,
 				383420D925FFEB3F002D46C1 /* Popup.swift in Sources */,
-				3811DE3025C9D49500A708ED /* HomeViewModel.swift in Sources */,
+				3811DE3025C9D49500A708ED /* HomeStateModel.swift in Sources */,
 				38BF021725E7CBBC00579895 /* PumpManagerExtensions.swift in Sources */,
 				38F3B2EF25ED8E2A005C48AA /* TempTargetsStorage.swift in Sources */,
 				3811DF1025CAAAE200A708ED /* APSManager.swift in Sources */,
 				3870FF4725EC187A0088248F /* BloodGlucose.swift in Sources */,
-				3811DE0A25C9D32F00A708ED /* BaseModuleBuilder.swift in Sources */,
 				38A0364225ED069400FCBB52 /* TempBasal.swift in Sources */,
 				3811DE1725C9D40400A708ED /* Screen.swift in Sources */,
 				383948DA25CD64D500E91849 /* Glucose.swift in Sources */,
 				388E596C25AD95110019842D /* OpenAPS.swift in Sources */,
 				E00EEC0527368630002FF094 /* StorageAssembly.swift in Sources */,
 				384E803825C388640086DB71 /* Script.swift in Sources */,
-				3811DE0925C9D32F00A708ED /* BaseViewModel.swift in Sources */,
 				3883583425EEB38000E024B2 /* PumpSettings.swift in Sources */,
 				38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */,
 				3811DEB125C9D88300A708ED /* Keychain.swift in Sources */,
@@ -1642,6 +1579,7 @@
 				3811DE4125C9D4A100A708ED /* SettingsRootView.swift in Sources */,
 				38192E04261B82FA0094D973 /* ReachabilityManager.swift in Sources */,
 				388E595C25AD948C0019842D /* FreeAPSApp.swift in Sources */,
+				38FEF3FC2737E53800574A46 /* MainStateModel.swift in Sources */,
 				38569348270B5DFB0002C50D /* GlucoseSource.swift in Sources */,
 				3811DE4225C9D4A100A708ED /* SettingsDataFlow.swift in Sources */,
 				3811DE2525C9D48300A708ED /* MainRootView.swift in Sources */,
@@ -1661,7 +1599,6 @@
 				3811DEE825CA063400A708ED /* Injected.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
 				38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */,
-				3811DE4025C9D4A100A708ED /* SettingsBuilder.swift in Sources */,
 				388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */,
 				3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */,
 				3811DE3225C9D49500A708ED /* HomeDataFlow.swift in Sources */,
@@ -1683,11 +1620,9 @@
 				38EA05DA261F6E7C0064E39B /* SimpleLogReporter.swift in Sources */,
 				3811DE6125C9D4D500A708ED /* ViewModifiers.swift in Sources */,
 				3811DEAC25C9D88300A708ED /* NightscoutManager.swift in Sources */,
-				3811DE3325C9D49500A708ED /* HomeBuilder.swift in Sources */,
 				3811DEA925C9D88300A708ED /* AppearanceManager.swift in Sources */,
 				38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */,
 				38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */,
-				3811DE2125C9D48300A708ED /* MainBuilder.swift in Sources */,
 				38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */,
 				3871F39C25ED892B0013ECB5 /* TempTarget.swift in Sources */,
 				3811DEAB25C9D88300A708ED /* HTTPResponseStatus.swift in Sources */,
@@ -1700,116 +1635,101 @@
 				38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */,
 				3883581C25EE79BB00E024B2 /* DecimalTextField.swift in Sources */,
 				38DAB28A260D349500F74C1A /* FetchGlucoseManager.swift in Sources */,
-				3811DE2425C9D48300A708ED /* MainViewModel.swift in Sources */,
 				38F37828261260DC009DB701 /* Color+Extensions.swift in Sources */,
-				3811DE3F25C9D4A100A708ED /* SettingsViewModel.swift in Sources */,
+				3811DE3F25C9D4A100A708ED /* SettingsStateModel.swift in Sources */,
 				38B4F3CB25E502E200E76A18 /* WeakObjectSet.swift in Sources */,
 				38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */,
 				38E98A2525F52C9300C0CED0 /* IssueReporter.swift in Sources */,
 				3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */,
 				3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */,
-				E102DE9C3E9C8AEDCB3C61BB /* ConfigEditorBuilder.swift in Sources */,
 				45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */,
 				3871F38725ED661C0013ECB5 /* Suggestion.swift in Sources */,
 				3856934F270B5E1D0002C50D /* CGMProvider.swift in Sources */,
 				38C4D33A25E9A1ED00D30B77 /* NSObject+AssociatedValues.swift in Sources */,
 				38AAF8712600C1B0004AF583 /* MainChartView.swift in Sources */,
 				72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */,
-				E39E418C56A5A46B61D960EE /* ConfigEditorViewModel.swift in Sources */,
+				E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */,
 				45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */,
-				CDB87FA71A93F3739D3D338E /* NightscoutConfigBuilder.swift in Sources */,
 				D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */,
 				38E98A3025F52FF700C0CED0 /* Config.swift in Sources */,
 				BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */,
-				9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigViewModel.swift in Sources */,
+				9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */,
 				38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */,
 				642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */,
-				3340E0D14D4701342D459C95 /* PumpConfigBuilder.swift in Sources */,
 				AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */,
 				53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */,
-				5D16287A969E64D18CE40E44 /* PumpConfigViewModel.swift in Sources */,
+				5D16287A969E64D18CE40E44 /* PumpConfigStateModel.swift in Sources */,
 				E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */,
-				25548F1F0AA8E42FF5F96DBA /* PumpSettingsEditorBuilder.swift in Sources */,
 				448B6FCB252BD4796E2960C0 /* PumpSettingsEditorDataFlow.swift in Sources */,
 				2BE9A6FA20875F6F4F9CD461 /* PumpSettingsEditorProvider.swift in Sources */,
-				6B9625766B697D1C98E455A2 /* PumpSettingsEditorViewModel.swift in Sources */,
+				6B9625766B697D1C98E455A2 /* PumpSettingsEditorStateModel.swift in Sources */,
 				A0B8EC8CC5CD1DD237D1BCD2 /* PumpSettingsEditorRootView.swift in Sources */,
 				38EA0600262091870064E39B /* BolusProgressViewStyle.swift in Sources */,
 				389ECDFE2601061500D86C4F /* View+Snapshot.swift in Sources */,
 				38E98A3725F5509500C0CED0 /* String+Extensions.swift in Sources */,
-				1D086541F369D339A74893AC /* BasalProfileEditorBuilder.swift in Sources */,
 				385CEAC125F2EA52002D6D5B /* Announcement.swift in Sources */,
 				8B759CFCF47B392BB365C251 /* BasalProfileEditorDataFlow.swift in Sources */,
 				389442CB25F65F7100FA1F27 /* NightscoutTreatment.swift in Sources */,
 				FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */,
-				63E890B4D951EAA91C071D5C /* BasalProfileEditorViewModel.swift in Sources */,
+				63E890B4D951EAA91C071D5C /* BasalProfileEditorStateModel.swift in Sources */,
+				38FEF3FA2737E42000574A46 /* BaseStateModel.swift in Sources */,
 				385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */,
 				38887CCE25F5725200944304 /* IOBEntry.swift in Sources */,
 				38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */,
 				CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */,
 				E00EEC0327368630002FF094 /* ServiceAssembly.swift in Sources */,
-				F215CAB49BA4B5A01C3BC6B6 /* ISFEditorBuilder.swift in Sources */,
 				38192E07261BA9960094D973 /* FetchTreatmentsManager.swift in Sources */,
 				6632A0DC746872439A858B44 /* ISFEditorDataFlow.swift in Sources */,
 				DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */,
-				1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorViewModel.swift in Sources */,
+				1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */,
 				38192E0D261BAF980094D973 /* ConvenienceExtensions.swift in Sources */,
 				88AB39B23C9552BD6E0C9461 /* ISFEditorRootView.swift in Sources */,
-				3BD663A04B4CA5278B0260B4 /* CREditorBuilder.swift in Sources */,
 				A33352ED40476125EBAC6EE0 /* CREditorDataFlow.swift in Sources */,
 				17A9D0899046B45E87834820 /* CREditorProvider.swift in Sources */,
-				69B9A368029F7EB39F525422 /* CREditorViewModel.swift in Sources */,
+				69B9A368029F7EB39F525422 /* CREditorStateModel.swift in Sources */,
 				98641AF4F92123DA668AB931 /* CREditorRootView.swift in Sources */,
-				7F7017AA5C69838FB7E6FECE /* TargetsEditorBuilder.swift in Sources */,
 				38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */,
 				F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */,
 				5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */,
-				E13B7DAB2A435F57066AF02E /* TargetsEditorViewModel.swift in Sources */,
+				E13B7DAB2A435F57066AF02E /* TargetsEditorStateModel.swift in Sources */,
 				9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */,
-				97C1388354C7133C1D5ED72A /* PreferencesEditorBuilder.swift in Sources */,
 				A228DF96647338139F152B15 /* PreferencesEditorDataFlow.swift in Sources */,
 				389ECE052601144100D86C4F /* ConcurrentMap.swift in Sources */,
 				E4984C5262A90469788754BB /* PreferencesEditorProvider.swift in Sources */,
-				DD399FB31EACB9343C944C4C /* PreferencesEditorViewModel.swift in Sources */,
+				DD399FB31EACB9343C944C4C /* PreferencesEditorStateModel.swift in Sources */,
 				44190F0BBA464D74B857D1FB /* PreferencesEditorRootView.swift in Sources */,
-				460745235E45CA6311C98613 /* AddCarbsBuilder.swift in Sources */,
 				E97285ED9B814CD5253C6658 /* AddCarbsDataFlow.swift in Sources */,
 				A6F097A14CAAE0CE0D11BE1B /* AddCarbsProvider.swift in Sources */,
-				33E198D3039045D98C3DC5D4 /* AddCarbsViewModel.swift in Sources */,
+				33E198D3039045D98C3DC5D4 /* AddCarbsStateModel.swift in Sources */,
 				28089E07169488CF6DCC2A31 /* AddCarbsRootView.swift in Sources */,
-				46159F33C01CDBF822B4F1F8 /* AddTempTargetBuilder.swift in Sources */,
 				D2165E9D78EFF692C1DED1C6 /* AddTempTargetDataFlow.swift in Sources */,
 				5BFA1C2208114643B77F8CEB /* AddTempTargetProvider.swift in Sources */,
-				919DBD08F13BAFB180DF6F47 /* AddTempTargetViewModel.swift in Sources */,
+				919DBD08F13BAFB180DF6F47 /* AddTempTargetStateModel.swift in Sources */,
 				8BC2F5A29AD1ED08AC0EE013 /* AddTempTargetRootView.swift in Sources */,
 				38A00B1F25FC00F7006BC0B0 /* Autotune.swift in Sources */,
-				19434C14DF3F4816F4E4BF2E /* BolusBuilder.swift in Sources */,
 				38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */,
 				041D1E995A6AE92E9289DC49 /* BolusDataFlow.swift in Sources */,
 				23888883D4EA091C88480FF2 /* BolusProvider.swift in Sources */,
 				38E98A2D25F52DC400C0CED0 /* NSLocking+Extensions.swift in Sources */,
 				38569353270B5E350002C50D /* CGMRootView.swift in Sources */,
-				69A31254F2451C20361D172F /* BolusViewModel.swift in Sources */,
+				69A31254F2451C20361D172F /* BolusStateModel.swift in Sources */,
 				0CEA2EA070AB041AF3E3745B /* BolusRootView.swift in Sources */,
-				1FF95E8F785B28961EFDE5A9 /* ManualTempBasalBuilder.swift in Sources */,
 				711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */,
 				BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */,
-				C967DACD3B1E638F8B43BE06 /* ManualTempBasalViewModel.swift in Sources */,
+				C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */,
 				7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */,
 				38A00B2325FC2B55006BC0B0 /* LRUCache.swift in Sources */,
-				91732A8060347C0E67024D80 /* AutotuneConfigBuilder.swift in Sources */,
 				3083261C4B268E353F36CD0B /* AutotuneConfigDataFlow.swift in Sources */,
 				891DECF7BC20968D7F566161 /* AutotuneConfigProvider.swift in Sources */,
-				D76333C9256787610B3B4875 /* AutotuneConfigViewModel.swift in Sources */,
+				D76333C9256787610B3B4875 /* AutotuneConfigStateModel.swift in Sources */,
 				A05235B9112E677ED03B6E8E /* AutotuneConfigRootView.swift in Sources */,
-				937407F04370AAB478C112D1 /* DataTableBuilder.swift in Sources */,
 				7F7B756BE8543965D9FDF1A2 /* DataTableDataFlow.swift in Sources */,
 				1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */,
-				0D9A5E34A899219C5C4CDFAF /* DataTableViewModel.swift in Sources */,
+				0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */,
 				D6D02515BBFBE64FEBE89856 /* DataTableRootView.swift in Sources */,
 				38569349270B5DFB0002C50D /* AppGroupSource.swift in Sources */,
 				F5CA3DB1F9DC8B05792BBFAA /* CGMDataFlow.swift in Sources */,
-				BA00D96F7B2FF169A06FB530 /* CGMViewModel.swift in Sources */,
+				BA00D96F7B2FF169A06FB530 /* CGMStateModel.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 2 - 11
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -42,19 +42,10 @@ import Swinject
 
     var body: some Scene {
         WindowGroup {
-            Main.Builder(resolver: resolver).buildView()
+            Main.RootView(resolver: resolver)
         }
         .onChange(of: scenePhase) { newScenePhase in
-            switch newScenePhase {
-            case .active:
-                debug(.default, "APPLICATION is active")
-            case .inactive:
-                debug(.default, "APPLICATION is inactive")
-            case .background:
-                debug(.default, "APPLICATION is in background")
-            @unknown default:
-                debug(.default, "APPLICATION: Received an unexpected scenePhase.")
-            }
+            debug(.default, "APPLICATION PHASE: \(newScenePhase)")
         }
     }
 }

+ 1 - 1
FreeAPS/Sources/Assemblies/NetworkAssembly.swift

@@ -6,7 +6,7 @@ final class NetworkAssembly: Assembly {
     func assemble(container: Container) {
         container.register(ReachabilityManager.self) { _ in
             NetworkReachabilityManager()!
-        }.inObjectScope(.transient)
+        }
 
         container.register(NightscoutManager.self) { r in BaseNightscoutManager(resolver: r) }
     }

+ 0 - 3
FreeAPS/Sources/Modules/AddCarbs/AddCarbsBuilder.swift

@@ -1,3 +0,0 @@
-extension AddCarbs {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 1
FreeAPS/Sources/Modules/AddCarbs/AddCarbsViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension AddCarbs {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: AddCarbsProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() var carbsStorage: CarbsStorage!
         @Injected() var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!

+ 10 - 7
FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension AddCarbs {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
         private var formatter: NumberFormatter {
             let formatter = NumberFormatter()
@@ -13,7 +15,7 @@ extension AddCarbs {
 
         var body: some View {
             Form {
-                if let carbsReq = viewModel.carbsRequired {
+                if let carbsReq = state.carbsRequired {
                     Section {
                         HStack {
                             Text("Carbs required")
@@ -26,21 +28,22 @@ extension AddCarbs {
                     HStack {
                         Text("Amount")
                         Spacer()
-                        DecimalTextField("0", value: $viewModel.carbs, formatter: formatter, autofocus: true, cleanInput: true)
+                        DecimalTextField("0", value: $state.carbs, formatter: formatter, autofocus: true, cleanInput: true)
                         Text("grams").foregroundColor(.secondary)
                     }
-                    DatePicker("Date", selection: $viewModel.date)
+                    DatePicker("Date", selection: $state.date)
                 }
 
                 Section {
-                    Button { viewModel.add() }
+                    Button { state.add() }
                     label: { Text("Add") }
-                        .disabled(viewModel.carbs <= 0)
+                        .disabled(state.carbs <= 0)
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Add Carbs")
             .navigationBarTitleDisplayMode(.automatic)
-            .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
+            .navigationBarItems(leading: Button("Close", action: state.hideModal))
         }
     }
 }

+ 0 - 3
FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetBuilder.swift

@@ -1,3 +0,0 @@
-extension AddTempTarget {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 1
FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension AddTempTarget {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: AddTempTargetProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() private var storage: TempTargetsStorage!
         @Injected() private var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!

+ 21 - 18
FreeAPS/Sources/Modules/AddTempTarget/View/AddTempTargetRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension AddTempTarget {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
         @State private var isPromtPresented = false
         @State private var isRemoveAlertPresented = false
         @State private var removeAlert: Alert?
@@ -16,9 +18,9 @@ extension AddTempTarget {
 
         var body: some View {
             Form {
-                if !viewModel.presets.isEmpty {
+                if !state.presets.isEmpty {
                     Section(header: Text("Presets")) {
-                        ForEach(viewModel.presets) { preset in
+                        ForEach(state.presets) { preset in
                             presetView(for: preset)
                         }
                     }
@@ -28,39 +30,39 @@ extension AddTempTarget {
                     HStack {
                         Text("Bottom target")
                         Spacer()
-                        DecimalTextField("0", value: $viewModel.low, formatter: formatter, cleanInput: true)
-                        Text(viewModel.units.rawValue).foregroundColor(.secondary)
+                        DecimalTextField("0", value: $state.low, formatter: formatter, cleanInput: true)
+                        Text(state.units.rawValue).foregroundColor(.secondary)
                     }
                     HStack {
                         Text("Top target")
                         Spacer()
-                        DecimalTextField("0", value: $viewModel.high, formatter: formatter, cleanInput: true)
-                        Text(viewModel.units.rawValue).foregroundColor(.secondary)
+                        DecimalTextField("0", value: $state.high, formatter: formatter, cleanInput: true)
+                        Text(state.units.rawValue).foregroundColor(.secondary)
                     }
                     HStack {
                         Text("Duration")
                         Spacer()
-                        DecimalTextField("0", value: $viewModel.duration, formatter: formatter, cleanInput: true)
+                        DecimalTextField("0", value: $state.duration, formatter: formatter, cleanInput: true)
                         Text("minutes").foregroundColor(.secondary)
                     }
-                    DatePicker("Date", selection: $viewModel.date)
+                    DatePicker("Date", selection: $state.date)
                     Button { isPromtPresented = true }
                     label: { Text("Save as preset") }
                 }
 
                 Section {
-                    Button { viewModel.enact() }
+                    Button { state.enact() }
                     label: { Text("Enact") }
-                    Button { viewModel.cancel() }
+                    Button { state.cancel() }
                     label: { Text("Cancel Temp Target") }
                 }
             }
             .popover(isPresented: $isPromtPresented) {
                 Form {
                     Section(header: Text("Enter preset name")) {
-                        TextField("Name", text: $viewModel.newPresetName)
+                        TextField("Name", text: $state.newPresetName)
                         Button {
-                            viewModel.save()
+                            state.save()
                             isPromtPresented = false
                         }
                         label: { Text("Save") }
@@ -69,15 +71,16 @@ extension AddTempTarget {
                     }
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Enact Temp Target")
             .navigationBarTitleDisplayMode(.automatic)
-            .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
+            .navigationBarItems(leading: Button("Close", action: state.hideModal))
         }
 
         private func presetView(for preset: TempTarget) -> some View {
             var low = preset.targetBottom
             var high = preset.targetTop
-            if viewModel.units == .mmolL {
+            if state.units == .mmolL {
                 low = low?.asMmolL
                 high = high?.asMmolL
             }
@@ -94,7 +97,7 @@ extension AddTempTarget {
                         .foregroundColor(.secondary)
                         .font(.caption)
 
-                        Text(viewModel.units.rawValue)
+                        Text(state.units.rawValue)
                             .foregroundColor(.secondary)
                             .font(.caption)
                         Text("for \(formatter.string(from: preset.duration as NSNumber)!) min")
@@ -105,7 +108,7 @@ extension AddTempTarget {
                 }
                 .contentShape(Rectangle())
                 .onTapGesture {
-                    viewModel.enactPreset(id: preset.id)
+                    state.enactPreset(id: preset.id)
                 }
 
                 Image(systemName: "xmark.circle").foregroundColor(.secondary)
@@ -115,7 +118,7 @@ extension AddTempTarget {
                         removeAlert = Alert(
                             title: Text("Are you sure?"),
                             message: Text("Delete preset \"\(preset.displayName)\""),
-                            primaryButton: .destructive(Text("Delete"), action: { viewModel.removePreset(id: preset.id) }),
+                            primaryButton: .destructive(Text("Delete"), action: { state.removePreset(id: preset.id) }),
                             secondaryButton: .cancel()
                         )
                         isRemoveAlertPresented = true

+ 0 - 3
FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigBuilder.swift

@@ -1,3 +0,0 @@
-extension AutotuneConfig {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 11 - 5
FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigViewModel.swift

@@ -2,7 +2,7 @@ import Combine
 import SwiftUI
 
 extension AutotuneConfig {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: AutotuneConfigProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!
         @Published var useAutotune = false
@@ -25,7 +25,10 @@ extension AutotuneConfig {
 
             $useAutotune
                 .removeDuplicates()
-                .flatMap { use -> AnyPublisher<Bool, Never> in
+                .flatMap { [weak self] use -> AnyPublisher<Bool, Never> in
+                    guard let self = self else {
+                        return Just(false).eraseToAnyPublisher()
+                    }
                     self.settingsManager.settings.useAutotune = use
                     return self.apsManager.makeProfiles()
                 }
@@ -36,12 +39,15 @@ extension AutotuneConfig {
         func run() {
             provider.runAutotune()
                 .receive(on: DispatchQueue.main)
-                .flatMap { result -> AnyPublisher<Bool, Never> in
+                .flatMap { [weak self] result -> AnyPublisher<Bool, Never> in
+                    guard let self = self else {
+                        return Just(false).eraseToAnyPublisher()
+                    }
                     self.autotune = result
                     return self.apsManager.makeProfiles()
                 }
-                .sink { _ in
-                    self.lastAutotuneDate = Date()
+                .sink { [weak self] _ in
+                    self?.lastAutotuneDate = Date()
                 }.store(in: &lifetime)
         }
 

+ 11 - 8
FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension AutotuneConfig {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
         private var isfFormatter: NumberFormatter {
             let formatter = NumberFormatter()
@@ -28,20 +30,20 @@ extension AutotuneConfig {
         var body: some View {
             Form {
                 Section {
-                    Toggle("Use Autotune", isOn: $viewModel.useAutotune)
+                    Toggle("Use Autotune", isOn: $state.useAutotune)
                 }
 
                 Section {
                     HStack {
                         Text("Last run")
                         Spacer()
-                        Text(dateFormatter.string(from: viewModel.publishedDate))
+                        Text(dateFormatter.string(from: state.publishedDate))
                     }
-                    Button { viewModel.run() }
+                    Button { state.run() }
                     label: { Text("Run now") }
                 }
 
-                if let autotune = viewModel.autotune {
+                if let autotune = state.autotune {
                     Section {
                         HStack {
                             Text("Carb ratio")
@@ -52,12 +54,12 @@ extension AutotuneConfig {
                         HStack {
                             Text("Sensitivity")
                             Spacer()
-                            if viewModel.units == .mmolL {
+                            if state.units == .mmolL {
                                 Text(isfFormatter.string(from: autotune.sensitivity.asMmolL as NSNumber) ?? "0")
                             } else {
                                 Text(isfFormatter.string(from: autotune.sensitivity as NSNumber) ?? "0")
                             }
-                            Text(viewModel.units.rawValue + "/U").foregroundColor(.secondary)
+                            Text(state.units.rawValue + "/U").foregroundColor(.secondary)
                         }
                     }
 
@@ -73,12 +75,13 @@ extension AutotuneConfig {
                     }
 
                     Section {
-                        Button { viewModel.delete() }
+                        Button { state.delete() }
                         label: { Text("Delete autotune data") }
                             .foregroundColor(.red)
                     }
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Autotune")
             .navigationBarTitleDisplayMode(.automatic)
         }

+ 0 - 3
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorBuilder.swift

@@ -1,3 +0,0 @@
-extension BasalProfileEditor {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 1
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension BasalProfileEditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: BasalProfileEditorProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Published var syncInProgress = false
         @Published var items: [Item] = []
 

+ 22 - 19
FreeAPS/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension BasalProfileEditor {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
         @State private var editMode = EditMode.inactive
 
         private var dateFormatter: DateFormatter {
@@ -26,17 +28,18 @@ extension BasalProfileEditor {
                 }
                 Section {
                     HStack {
-                        if viewModel.syncInProgress {
+                        if state.syncInProgress {
                             ProgressView().padding(.trailing, 10)
                         }
-                        Button { viewModel.save() }
+                        Button { state.save() }
                         label: {
-                            Text(viewModel.syncInProgress ? "Saving..." : "Save on Pump")
+                            Text(state.syncInProgress ? "Saving..." : "Save on Pump")
                         }
-                        .disabled(viewModel.syncInProgress || viewModel.items.isEmpty)
+                        .disabled(state.syncInProgress || state.items.isEmpty)
                     }
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Basal Profile")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(
@@ -44,7 +47,7 @@ extension BasalProfileEditor {
             )
             .environment(\.editMode, $editMode)
             .onAppear {
-                viewModel.validate()
+                state.validate()
             }
         }
 
@@ -56,12 +59,12 @@ extension BasalProfileEditor {
                         Text("Time").frame(width: geometry.size.width / 2)
                     }
                     HStack(spacing: 0) {
-                        Picker(selection: $viewModel.items[index].rateIndex, label: EmptyView()) {
-                            ForEach(0 ..< viewModel.rateValues.count, id: \.self) { i in
+                        Picker(selection: $state.items[index].rateIndex, label: EmptyView()) {
+                            ForEach(0 ..< state.rateValues.count, id: \.self) { i in
                                 Text(
                                     (
                                         self.rateFormatter
-                                            .string(from: viewModel.rateValues[i] as NSNumber) ?? ""
+                                            .string(from: state.rateValues[i] as NSNumber) ?? ""
                                     ) + " U/hr"
                                 ).tag(i)
                             }
@@ -69,12 +72,12 @@ extension BasalProfileEditor {
                         .frame(maxWidth: geometry.size.width / 2)
                         .clipped()
 
-                        Picker(selection: $viewModel.items[index].timeIndex, label: EmptyView()) {
-                            ForEach(0 ..< viewModel.timeValues.count, id: \.self) { i in
+                        Picker(selection: $state.items[index].timeIndex, label: EmptyView()) {
+                            ForEach(0 ..< state.timeValues.count, id: \.self) { i in
                                 Text(
                                     self.dateFormatter
                                         .string(from: Date(
-                                            timeIntervalSince1970: viewModel
+                                            timeIntervalSince1970: state
                                                 .timeValues[i]
                                         ))
                                 ).tag(i)
@@ -89,17 +92,17 @@ extension BasalProfileEditor {
 
         private var list: some View {
             List {
-                ForEach(viewModel.items.indexed(), id: \.1.id) { index, item in
+                ForEach(state.items.indexed(), id: \.1.id) { index, item in
                     NavigationLink(destination: pickers(for: index)) {
                         HStack {
                             Text("Rate").foregroundColor(.secondary)
                             Text(
-                                "\(rateFormatter.string(from: viewModel.rateValues[item.rateIndex] as NSNumber) ?? "0") U/hr"
+                                "\(rateFormatter.string(from: state.rateValues[item.rateIndex] as NSNumber) ?? "0") U/hr"
                             )
                             Spacer()
                             Text("starts at").foregroundColor(.secondary)
                             Text(
-                                "\(dateFormatter.string(from: Date(timeIntervalSince1970: viewModel.timeValues[item.timeIndex])))"
+                                "\(dateFormatter.string(from: Date(timeIntervalSince1970: state.timeValues[item.timeIndex])))"
                             )
                         }
                     }
@@ -110,7 +113,7 @@ extension BasalProfileEditor {
         }
 
         private var addButton: some View {
-            guard viewModel.canAdd else {
+            guard state.canAdd else {
                 return AnyView(EmptyView())
             }
 
@@ -123,12 +126,12 @@ extension BasalProfileEditor {
         }
 
         func onAdd() {
-            viewModel.add()
+            state.add()
         }
 
         private func onDelete(offsets: IndexSet) {
-            viewModel.items.remove(atOffsets: offsets)
-            viewModel.validate()
+            state.items.remove(atOffsets: offsets)
+            state.validate()
         }
     }
 }

+ 0 - 26
FreeAPS/Sources/Modules/Base/BaseModuleBuilder.swift

@@ -1,26 +0,0 @@
-import SwiftUI
-import Swinject
-
-protocol ModuleBuilder {
-    associatedtype View: SwiftUI.View
-    func buildView() -> AnyView
-}
-
-class BaseModuleBuilder<View: BaseView, ViewModel: ObservableObject, Provider: FreeAPS.Provider>: ModuleBuilder
-    where ViewModel: BaseViewModel<Provider>, View.ViewModel == ViewModel
-{
-    let resolver: Resolver
-    lazy var viewModel: ViewModel = { buildViewModel() }()
-
-    init(resolver: Resolver) {
-        self.resolver = resolver
-    }
-
-    func buildViewModel() -> ViewModel {
-        ViewModel(provider: Provider(resolver: resolver), resolver: resolver)
-    }
-
-    func buildView() -> AnyView {
-        View().environmentObject(viewModel).asAny()
-    }
-}

+ 43 - 0
FreeAPS/Sources/Modules/Base/BaseStateModel.swift

@@ -0,0 +1,43 @@
+import SwiftUI
+import Swinject
+
+protocol StateModel: ObservableObject {
+    var resolver: Resolver? { get set }
+    var isInitial: Bool { get set }
+    func subscribe()
+    func showModal(for screen: Screen?)
+    func hideModal()
+    func view(for screen: Screen) -> AnyView
+}
+
+class BaseStateModel<Provider>: StateModel, Injectable where Provider: FreeAPS.Provider {
+    @Injected() var router: Router!
+    var isInitial: Bool = true
+    private(set) var provider: Provider!
+
+    var resolver: Resolver? {
+        didSet {
+            if let resolver = resolver {
+                injectServices(resolver)
+                provider = Provider(resolver: resolver)
+                subscribe()
+            }
+        }
+    }
+
+    var lifetime = Lifetime()
+
+    func subscribe() {}
+
+    func showModal(for screen: Screen?) {
+        router.mainModalScreen.send(screen)
+    }
+
+    func hideModal() {
+        router.mainModalScreen.send(nil)
+    }
+
+    func view(for screen: Screen) -> AnyView {
+        router.view(for: screen)
+    }
+}

+ 22 - 4
FreeAPS/Sources/Modules/Base/BaseView.swift

@@ -1,11 +1,29 @@
 import SwiftUI
+import Swinject
 
 protocol BaseView: View {
-    associatedtype ViewModel: FreeAPS.ViewModel
-    var viewModel: ViewModel { get }
-    init()
+    associatedtype StateModelType: StateModel
+    var resolver: Resolver { get }
+    var state: StateModelType { get }
+    var router: Router { get }
+    func configureView()
+    func configureView(_ configure: (() -> Void)?)
 }
 
 extension BaseView {
-    init() { self.init() }
+    var router: Router { resolver.resolve(Router.self)! }
+}
+
+extension BaseView {
+    func configureView() {
+        configureView(nil)
+    }
+
+    func configureView(_ configure: (() -> Void)?) {
+        if state.isInitial {
+            configure?()
+            state.resolver = resolver
+            state.isInitial = false
+        }
+    }
 }

+ 0 - 56
FreeAPS/Sources/Modules/Base/BaseViewModel.swift

@@ -1,56 +0,0 @@
-import Combine
-import SwiftUI
-import Swinject
-
-protocol ViewModel {
-    func subscribe()
-    func view(for screen: Screen) -> AnyView
-    func cachedView(for screen: Screen) -> AnyView
-    func showModal(for screen: Screen?)
-    func hideModal()
-    func cleanViewCache()
-}
-
-class BaseViewModel<Provider>: ViewModel, Injectable where Provider: FreeAPS.Provider {
-    let resolver: Resolver
-    let provider: Provider
-    var lifetime = Lifetime()
-    @Injected() var router: Router!
-
-    private var viewCache: [Screen: AnyView] = [:]
-
-    required init(provider: Provider, resolver: Resolver) {
-        self.provider = provider
-        self.resolver = resolver
-        injectServices(resolver)
-        subscribe()
-    }
-
-    func subscribe() {}
-
-    func view(for screen: Screen) -> AnyView {
-        router.view(for: screen)
-    }
-
-    func cachedView(for screen: Screen) -> AnyView {
-        if let view = viewCache[screen] {
-            return view
-        }
-
-        let view = view(for: screen)
-        viewCache[screen] = view
-        return view
-    }
-
-    func cleanViewCache() {
-        viewCache.removeAll()
-    }
-
-    func showModal(for screen: Screen?) {
-        router.mainModalScreen.send(screen)
-    }
-
-    func hideModal() {
-        router.mainModalScreen.send(nil)
-    }
-}

+ 0 - 15
FreeAPS/Sources/Modules/Bolus/BolusBuilder.swift

@@ -1,15 +0,0 @@
-import Swinject
-
-extension Bolus {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {
-        private let waitForSuggestion: Bool
-        init(resolver: Resolver, waitForSuggestion: Bool) {
-            self.waitForSuggestion = waitForSuggestion
-            super.init(resolver: resolver)
-        }
-
-        override func buildViewModel() -> Bolus.ViewModel<Bolus.Provider> {
-            ViewModel(provider: Provider(resolver: resolver), resolver: resolver, waitForSuggestion: waitForSuggestion)
-        }
-    }
-}

+ 6 - 15
FreeAPS/Sources/Modules/Bolus/BolusViewModel.swift

@@ -2,7 +2,7 @@ import SwiftUI
 import Swinject
 
 extension Bolus {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: BolusProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() var unlockmanager: UnlockManager!
         @Injected() var apsManager: APSManager!
         @Injected() var broadcaster: Broadcaster!
@@ -11,18 +11,8 @@ extension Bolus {
         @Published var amount: Decimal = 0
         @Published var inslinRecommended: Decimal = 0
         @Published var inslinRequired: Decimal = 0
-        @Published var waitForSuggestion: Bool
-        let waitForSuggestionInitial: Bool
-
-        init(provider: Provider, resolver: Resolver, waitForSuggestion: Bool) {
-            self.waitForSuggestion = waitForSuggestion
-            waitForSuggestionInitial = waitForSuggestion
-            super.init(provider: provider, resolver: resolver)
-        }
-
-        required init(provider _: Provider, resolver _: Resolver) {
-            error(.default, "init(provider:resolver:) has not been implemented")
-        }
+        @Published var waitForSuggestion: Bool = false
+        var waitForSuggestionInitial: Bool = false
 
         override func subscribe() {
             setupInsulinRequired()
@@ -51,7 +41,8 @@ extension Bolus {
             let maxAmount = Double(min(amount, provider.pumpSettings().maxBolus))
 
             unlockmanager.unlock()
-                .sink { _ in } receiveValue: {
+                .sink { _ in } receiveValue: { [weak self] _ in
+                    guard let self = self else { return }
                     self.apsManager.enactBolus(amount: maxAmount, isSMB: false)
                     self.showModal(for: nil)
                 }
@@ -92,7 +83,7 @@ extension Bolus {
     }
 }
 
-extension Bolus.ViewModel: SuggestionObserver {
+extension Bolus.StateModel: SuggestionObserver {
     func suggestionDidUpdate(_: Suggestion) {
         DispatchQueue.main.async {
             self.waitForSuggestion = false

+ 25 - 16
FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift

@@ -1,8 +1,11 @@
 import SwiftUI
+import Swinject
 
 extension Bolus {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        let waitForSuggestion: Bool
+        @StateObject var state = StateModel()
         @State private var isAddInsulinAlertPresented = false
 
         private var formatter: NumberFormatter {
@@ -15,7 +18,7 @@ extension Bolus {
         var body: some View {
             Form {
                 Section(header: Text("Recommendation")) {
-                    if viewModel.waitForSuggestion {
+                    if state.waitForSuggestion {
                         HStack {
                             Text("Wait please").foregroundColor(.secondary)
                             Spacer()
@@ -27,36 +30,36 @@ extension Bolus {
                             Spacer()
                             Text(
                                 formatter
-                                    .string(from: viewModel.inslinRequired as NSNumber)! +
+                                    .string(from: state.inslinRequired as NSNumber)! +
                                     NSLocalizedString(" U", comment: "Insulin unit")
                             ).foregroundColor(.secondary)
                         }.contentShape(Rectangle())
                             .onTapGesture {
-                                viewModel.amount = viewModel.inslinRecommended
+                                state.amount = state.inslinRecommended
                             }
                         HStack {
                             Text("Insulin recommended")
                             Spacer()
                             Text(
                                 formatter
-                                    .string(from: viewModel.inslinRecommended as NSNumber)! +
+                                    .string(from: state.inslinRecommended as NSNumber)! +
                                     NSLocalizedString(" U", comment: "Insulin unit")
                             ).foregroundColor(.secondary)
                         }.contentShape(Rectangle())
                             .onTapGesture {
-                                viewModel.amount = viewModel.inslinRecommended
+                                state.amount = state.inslinRecommended
                             }
                     }
                 }
 
-                if !viewModel.waitForSuggestion {
+                if !state.waitForSuggestion {
                     Section(header: Text("Bolus")) {
                         HStack {
                             Text("Amount")
                             Spacer()
                             DecimalTextField(
                                 "0",
-                                value: $viewModel.amount,
+                                value: $state.amount,
                                 formatter: formatter,
                                 autofocus: true,
                                 cleanInput: true
@@ -66,39 +69,45 @@ extension Bolus {
                     }
 
                     Section {
-                        Button { viewModel.add() }
+                        Button { state.add() }
                         label: { Text("Enact bolus") }
-                            .disabled(viewModel.amount <= 0)
+                            .disabled(state.amount <= 0)
                     }
 
                     Section {
-                        if viewModel.waitForSuggestionInitial {
-                            Button { viewModel.showModal(for: nil) }
+                        if waitForSuggestion {
+                            Button { state.showModal(for: nil) }
                             label: { Text("Continue without bolus") }
                         } else {
                             Button { isAddInsulinAlertPresented = true }
                             label: { Text("Add insulin without actually bolusing") }
-                                .disabled(viewModel.amount <= 0)
+                                .disabled(state.amount <= 0)
                         }
                     }
                 }
             }
             .alert(isPresented: $isAddInsulinAlertPresented) {
                 let amount = formatter
-                    .string(from: viewModel.amount as NSNumber)! + NSLocalizedString(" U", comment: "Insulin unit")
+                    .string(from: state.amount as NSNumber)! + NSLocalizedString(" U", comment: "Insulin unit")
                 return Alert(
                     title: Text("Are you sure?"),
                     message: Text("Add \(amount) without bolusing"),
                     primaryButton: .destructive(
                         Text("Add"),
-                        action: { viewModel.addWithoutBolus() }
+                        action: { state.addWithoutBolus() }
                     ),
                     secondaryButton: .cancel()
                 )
             }
+            .onAppear {
+                configureView {
+                    state.waitForSuggestionInitial = waitForSuggestion
+                    state.waitForSuggestion = waitForSuggestion
+                }
+            }
             .navigationTitle("Enact Bolus")
             .navigationBarTitleDisplayMode(.automatic)
-            .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
+            .navigationBarItems(leading: Button("Close", action: state.hideModal))
         }
     }
 }

+ 0 - 3
FreeAPS/Sources/Modules/CGM/CGMBuilder.swift

@@ -1,3 +0,0 @@
-extension CGM {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 1
FreeAPS/Sources/Modules/CGM/CGMViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension CGM {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: CGMProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() var settingsManager: SettingsManager!
 
         @Published var cgm: CGMType = .nightscout

+ 10 - 7
FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift

@@ -1,37 +1,40 @@
 import SwiftUI
+import Swinject
 
 extension CGM {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
         var body: some View {
             Form {
                 Section {
-                    Picker("Type", selection: $viewModel.cgm) {
+                    Picker("Type", selection: $state.cgm) {
                         ForEach(CGMType.allCases) {
                             Text($0.displayName).tag($0)
                         }
                     }
                 }
-                if [.dexcomG5, .dexcomG6].contains(viewModel.cgm) {
+                if [.dexcomG5, .dexcomG6].contains(state.cgm) {
                     Section(header: Text("Transmitter ID")) {
-                        TextField("XXXXXX", text: $viewModel.transmitterID, onCommit: {
+                        TextField("XXXXXX", text: $state.transmitterID, onCommit: {
                             UIApplication.shared.endEditing()
-                            viewModel.onChangeID()
+                            state.onChangeID()
                         })
                             .disableAutocorrection(true)
                             .autocapitalization(.allCharacters)
                             .keyboardType(.asciiCapable)
                     }
                     .onDisappear {
-                        viewModel.onChangeID()
+                        state.onChangeID()
                     }
                 }
 
                 Section(header: Text("Other")) {
-                    Toggle("Upload glucose to Nightscout", isOn: $viewModel.uploadGlucose)
+                    Toggle("Upload glucose to Nightscout", isOn: $state.uploadGlucose)
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("CGM")
             .navigationBarTitleDisplayMode(.automatic)
         }

+ 0 - 3
FreeAPS/Sources/Modules/CREditor/CREditorBuilder.swift

@@ -1,3 +0,0 @@
-extension CREditor {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 1
FreeAPS/Sources/Modules/CREditor/CREditorViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension CREditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: CREditorProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Published var items: [Item] = []
         @Published var autotune: Autotune?
 

+ 21 - 18
FreeAPS/Sources/Modules/CREditor/View/CREditorRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension CREditor {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
         @State private var editMode = EditMode.inactive
 
         private var dateFormatter: DateFormatter {
@@ -20,7 +22,7 @@ extension CREditor {
 
         var body: some View {
             Form {
-                if let autotune = viewModel.autotune {
+                if let autotune = state.autotune {
                     Section(header: Text("Autotune")) {
                         HStack {
                             Text("Calculated Ratio")
@@ -35,13 +37,14 @@ extension CREditor {
                     addButton
                 }
                 Section {
-                    Button { viewModel.save() }
+                    Button { state.save() }
                     label: {
                         Text("Save")
                     }
-                    .disabled(viewModel.items.isEmpty)
+                    .disabled(state.items.isEmpty)
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Carb Ratios")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(
@@ -49,7 +52,7 @@ extension CREditor {
             )
             .environment(\.editMode, $editMode)
             .onAppear {
-                viewModel.validate()
+                state.validate()
             }
         }
 
@@ -61,12 +64,12 @@ extension CREditor {
                         Text("Time").frame(width: geometry.size.width / 2)
                     }
                     HStack(spacing: 0) {
-                        Picker(selection: $viewModel.items[index].rateIndex, label: EmptyView()) {
-                            ForEach(0 ..< viewModel.rateValues.count, id: \.self) { i in
+                        Picker(selection: $state.items[index].rateIndex, label: EmptyView()) {
+                            ForEach(0 ..< state.rateValues.count, id: \.self) { i in
                                 Text(
                                     (
                                         self.rateFormatter
-                                            .string(from: viewModel.rateValues[i] as NSNumber) ?? ""
+                                            .string(from: state.rateValues[i] as NSNumber) ?? ""
                                     ) + " g/U"
                                 ).tag(i)
                             }
@@ -74,12 +77,12 @@ extension CREditor {
                         .frame(maxWidth: geometry.size.width / 2)
                         .clipped()
 
-                        Picker(selection: $viewModel.items[index].timeIndex, label: EmptyView()) {
-                            ForEach(0 ..< viewModel.timeValues.count, id: \.self) { i in
+                        Picker(selection: $state.items[index].timeIndex, label: EmptyView()) {
+                            ForEach(0 ..< state.timeValues.count, id: \.self) { i in
                                 Text(
                                     self.dateFormatter
                                         .string(from: Date(
-                                            timeIntervalSince1970: viewModel
+                                            timeIntervalSince1970: state
                                                 .timeValues[i]
                                         ))
                                 ).tag(i)
@@ -94,17 +97,17 @@ extension CREditor {
 
         private var list: some View {
             List {
-                ForEach(viewModel.items.indexed(), id: \.1.id) { index, item in
+                ForEach(state.items.indexed(), id: \.1.id) { index, item in
                     NavigationLink(destination: pickers(for: index)) {
                         HStack {
                             Text("Ratio").foregroundColor(.secondary)
                             Text(
-                                "\(rateFormatter.string(from: viewModel.rateValues[item.rateIndex] as NSNumber) ?? "0") g/U"
+                                "\(rateFormatter.string(from: state.rateValues[item.rateIndex] as NSNumber) ?? "0") g/U"
                             )
                             Spacer()
                             Text("starts at").foregroundColor(.secondary)
                             Text(
-                                "\(dateFormatter.string(from: Date(timeIntervalSince1970: viewModel.timeValues[item.timeIndex])))"
+                                "\(dateFormatter.string(from: Date(timeIntervalSince1970: state.timeValues[item.timeIndex])))"
                             )
                         }
                     }
@@ -115,7 +118,7 @@ extension CREditor {
         }
 
         private var addButton: some View {
-            guard viewModel.canAdd else {
+            guard state.canAdd else {
                 return AnyView(EmptyView())
             }
 
@@ -128,12 +131,12 @@ extension CREditor {
         }
 
         func onAdd() {
-            viewModel.add()
+            state.add()
         }
 
         private func onDelete(offsets: IndexSet) {
-            viewModel.items.remove(atOffsets: offsets)
-            viewModel.validate()
+            state.items.remove(atOffsets: offsets)
+            state.validate()
         }
     }
 }

+ 0 - 16
FreeAPS/Sources/Modules/ConfigEditor/ConfigEditorBuilder.swift

@@ -1,16 +0,0 @@
-import Swinject
-
-extension ConfigEditor {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {
-        private let file: String
-
-        init(resolver: Resolver, file: String) {
-            self.file = file
-            super.init(resolver: resolver)
-        }
-
-        override func buildViewModel() -> ConfigEditor.ViewModel<ConfigEditor.Provider> {
-            ViewModel(provider: Provider(resolver: resolver), resolver: resolver, file: file)
-        }
-    }
-}

+ 17 - 0
FreeAPS/Sources/Modules/ConfigEditor/ConfigEditorStateModel.swift

@@ -0,0 +1,17 @@
+import SwiftUI
+import Swinject
+
+extension ConfigEditor {
+    class StateModel: BaseStateModel<Provider> {
+        var file: String = ""
+        @Published var configText = ""
+
+        override func subscribe() {
+            configText = provider.load(file: file)
+        }
+
+        func save() {
+            provider.save(configText, as: file)
+        }
+    }
+}

+ 0 - 26
FreeAPS/Sources/Modules/ConfigEditor/ConfigEditorViewModel.swift

@@ -1,26 +0,0 @@
-import SwiftUI
-import Swinject
-
-extension ConfigEditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: ConfigEditorProvider {
-        let file: String
-        @Published var configText = ""
-
-        init(provider: Provider, resolver: Resolver, file: String) {
-            self.file = file
-            super.init(provider: provider, resolver: resolver)
-        }
-
-        required init(provider _: Provider, resolver _: Resolver) {
-            error(.default, "init(provider:resolver:) has not been implemented")
-        }
-
-        override func subscribe() {
-            configText = provider.load(file: file)
-        }
-
-        func save() {
-            provider.save(configText, as: file)
-        }
-    }
-}

+ 13 - 5
FreeAPS/Sources/Modules/ConfigEditor/View/ConfigEditorRootView.swift

@@ -1,12 +1,15 @@
 import SwiftUI
+import Swinject
 
 extension ConfigEditor {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        let file: String
+        @StateObject var state = StateModel()
         @State private var showShareSheet = false
 
         var body: some View {
-            TextEditor(text: $viewModel.configText)
+            TextEditor(text: $state.configText)
                 .keyboardType(.asciiCapable)
                 .font(.system(.subheadline, design: .monospaced))
                 .allowsTightening(true)
@@ -22,12 +25,17 @@ extension ConfigEditor {
                     }
                 }
                 .navigationBarItems(
-                    trailing: Button("Save", action: viewModel.save)
+                    trailing: Button("Save", action: state.save)
                 )
                 .sheet(isPresented: $showShareSheet) {
-                    ShareSheet(activityItems: [viewModel.provider.urlFor(file: viewModel.file)!])
+                    ShareSheet(activityItems: [state.provider.urlFor(file: state.file)!])
                 }
-                .navigationTitle(viewModel.file)
+                .onAppear {
+                    configureView {
+                        state.file = file
+                    }
+                }
+                .navigationTitle(file)
                 .navigationBarTitleDisplayMode(.inline)
                 .padding()
         }

+ 0 - 3
FreeAPS/Sources/Modules/DataTable/DataTableBuilder.swift

@@ -1,3 +0,0 @@
-extension DataTable {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 2 - 2
FreeAPS/Sources/Modules/DataTable/DataTableViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension DataTable {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: DataTableProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() var broadcaster: Broadcaster!
         @Injected() var settingsManager: SettingsManager!
         @Published var items: [Item] = []
@@ -83,7 +83,7 @@ extension DataTable {
     }
 }
 
-extension DataTable.ViewModel:
+extension DataTable.StateModel:
     SettingsObserver,
     PumpHistoryObserver,
     TempTargetsObserver,

+ 7 - 4
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension DataTable {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
         @State private var isRemoveCarbsAlertPresented = false
         @State private var removeCarbsAlert: Alert?
 
@@ -16,16 +18,17 @@ extension DataTable {
             Form {
                 list
             }
+            .onAppear(perform: configureView)
             .navigationTitle("History")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(
-                leading: Button("Close", action: viewModel.hideModal)
+                leading: Button("Close", action: state.hideModal)
             )
         }
 
         private var list: some View {
             List {
-                ForEach(viewModel.items.indexed(), id: \.1.id) { _, item in
+                ForEach(state.items.indexed(), id: \.1.id) { _, item in
                     HStack {
                         Image(systemName: "circle.fill").foregroundColor(item.color)
                         Text(dateFormatter.string(from: item.date))
@@ -47,7 +50,7 @@ extension DataTable {
                                         message: Text(item.amountText),
                                         primaryButton: .destructive(
                                             Text("Delete"),
-                                            action: { viewModel.deleteCarbs(at: item.date) }
+                                            action: { state.deleteCarbs(at: item.date) }
                                         ),
                                         secondaryButton: .cancel()
                                     )

+ 0 - 3
FreeAPS/Sources/Modules/Home/HomeBuilder.swift

@@ -1,3 +0,0 @@
-extension Home {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 3 - 3
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -3,7 +3,7 @@ import SwiftDate
 import SwiftUI
 
 extension Home {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: HomeProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() var broadcaster: Broadcaster!
         @Injected() var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!
@@ -281,7 +281,7 @@ extension Home {
     }
 }
 
-extension Home.ViewModel:
+extension Home.StateModel:
     GlucoseObserver,
     SuggestionObserver,
     SettingsObserver,
@@ -346,7 +346,7 @@ extension Home.ViewModel:
     }
 }
 
-extension Home.ViewModel: CompletionDelegate {
+extension Home.StateModel: CompletionDelegate {
     func completionNotifyingDidComplete(_: CompletionNotifying) {
         setupPump = false
     }

+ 62 - 58
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -1,9 +1,12 @@
 import SwiftDate
 import SwiftUI
+import Swinject
 
 extension Home {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+
+        @StateObject var state = StateModel()
         @State var isStatusPopupPresented = false
 
         private var numberFormatter: NumberFormatter {
@@ -33,7 +36,7 @@ extension Home {
                     HStack {
                         Text("IOB").font(.caption2).foregroundColor(.secondary)
                         Text(
-                            (numberFormatter.string(from: (viewModel.suggestion?.iob ?? 0) as NSNumber) ?? "0") +
+                            (numberFormatter.string(from: (state.suggestion?.iob ?? 0) as NSNumber) ?? "0") +
                                 NSLocalizedString(" U", comment: "Insulin unit")
                         )
                         .font(.system(size: 12, weight: .bold))
@@ -41,7 +44,7 @@ extension Home {
                     HStack {
                         Text("COB").font(.caption2).foregroundColor(.secondary)
                         Text(
-                            (numberFormatter.string(from: (viewModel.suggestion?.cob ?? 0) as NSNumber) ?? "0") +
+                            (numberFormatter.string(from: (state.suggestion?.cob ?? 0) as NSNumber) ?? "0") +
                                 NSLocalizedString(" g", comment: "gram of carbs")
                         )
                         .font(.system(size: 12, weight: .bold))
@@ -50,41 +53,41 @@ extension Home {
                 Spacer()
 
                 CurrentGlucoseView(
-                    recentGlucose: $viewModel.recentGlucose,
-                    delta: $viewModel.glucoseDelta,
-                    units: $viewModel.units
+                    recentGlucose: $state.recentGlucose,
+                    delta: $state.glucoseDelta,
+                    units: $state.units
                 )
                 .onTapGesture {
-                    viewModel.openCGM()
+                    state.openCGM()
                 }
                 Spacer()
                 PumpView(
-                    reservoir: $viewModel.reservoir,
-                    battery: $viewModel.battery,
-                    name: $viewModel.pumpName,
-                    expiresAtDate: $viewModel.pumpExpiresAtDate,
-                    timerDate: $viewModel.timerDate
+                    reservoir: $state.reservoir,
+                    battery: $state.battery,
+                    name: $state.pumpName,
+                    expiresAtDate: $state.pumpExpiresAtDate,
+                    timerDate: $state.timerDate
                 )
                 .onTapGesture {
-                    viewModel.setupPump = true
+                    state.setupPump = true
                 }
-                .popover(isPresented: $viewModel.setupPump) {
-                    if let pumpManager = viewModel.provider.apsManager.pumpManager {
-                        PumpConfig.PumpSettingsView(pumpManager: pumpManager, completionDelegate: viewModel)
+                .popover(isPresented: $state.setupPump) {
+                    if let pumpManager = state.provider.apsManager.pumpManager {
+                        PumpConfig.PumpSettingsView(pumpManager: pumpManager, completionDelegate: state)
                     }
                 }
                 Spacer()
                 LoopView(
-                    suggestion: $viewModel.suggestion,
-                    enactedSuggestion: $viewModel.enactedSuggestion,
-                    closedLoop: $viewModel.closedLoop,
-                    timerDate: $viewModel.timerDate,
-                    isLooping: $viewModel.isLooping,
-                    lastLoopDate: $viewModel.lastLoopDate
+                    suggestion: $state.suggestion,
+                    enactedSuggestion: $state.enactedSuggestion,
+                    closedLoop: $state.closedLoop,
+                    timerDate: $state.timerDate,
+                    isLooping: $state.isLooping,
+                    lastLoopDate: $state.lastLoopDate
                 ).onTapGesture {
                     isStatusPopupPresented = true
                 }.onLongPressGesture {
-                    viewModel.runLoop()
+                    state.runLoop()
                 }
                 Spacer()
             }.frame(maxWidth: .infinity)
@@ -92,11 +95,11 @@ extension Home {
 
         var infoPanal: some View {
             HStack(alignment: .center) {
-                if viewModel.pumpSuspended {
+                if state.pumpSuspended {
                     Text("Pump suspended")
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.loopGray)
                         .padding(.leading, 8)
-                } else if let tempRate = viewModel.tempRate {
+                } else if let tempRate = state.tempRate {
                     Text(
                         (numberFormatter.string(from: tempRate as NSNumber) ?? "0") +
                             NSLocalizedString(" U/hr", comment: "Unit per hour with space")
@@ -105,9 +108,9 @@ extension Home {
                     .padding(.leading, 8)
                 }
 
-                if let tempTarget = viewModel.tempTarget {
+                if let tempTarget = state.tempTarget {
                     Text(tempTarget.displayName).font(.caption).foregroundColor(.secondary)
-                    if viewModel.units == .mmolL {
+                    if state.units == .mmolL {
                         Text(
                             targetFormatter
                                 .string(from: (tempTarget.targetBottom?.asMmolL ?? 0) as NSNumber)!
@@ -120,12 +123,12 @@ extension Home {
                             Text(
                                 targetFormatter
                                     .string(from: (tempTarget.targetTop?.asMmolL ?? 0) as NSNumber)! +
-                                    " \(viewModel.units.rawValue)"
+                                    " \(state.units.rawValue)"
                             )
                             .font(.caption)
                             .foregroundColor(.secondary)
                         } else {
-                            Text(viewModel.units.rawValue).font(.caption)
+                            Text(state.units.rawValue).font(.caption)
                                 .foregroundColor(.secondary)
                         }
 
@@ -138,25 +141,25 @@ extension Home {
                                 .foregroundColor(.secondary)
                             Text(
                                 targetFormatter
-                                    .string(from: (tempTarget.targetTop ?? 0) as NSNumber)! + " \(viewModel.units.rawValue)"
+                                    .string(from: (tempTarget.targetTop ?? 0) as NSNumber)! + " \(state.units.rawValue)"
                             )
                             .font(.caption)
                             .foregroundColor(.secondary)
                         } else {
-                            Text(viewModel.units.rawValue).font(.caption)
+                            Text(state.units.rawValue).font(.caption)
                                 .foregroundColor(.secondary)
                         }
                     }
                 }
                 Spacer()
-                if let progress = viewModel.bolusProgress {
+                if let progress = state.bolusProgress {
                     Text("Bolusing")
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
                     ProgressView(value: Double(progress))
                         .progressViewStyle(BolusProgressViewStyle())
                         .padding(.trailing, 8)
                         .onTapGesture {
-                            viewModel.cancelBolus()
+                            state.cancelBolus()
                         }
                 }
             }
@@ -195,10 +198,10 @@ extension Home {
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.uam)
                 }
 
-                if let eventualBG = viewModel.eventualBG {
+                if let eventualBG = state.eventualBG {
                     Text(
                         "⇢ " + numberFormatter.string(
-                            from: (viewModel.units == .mmolL ? eventualBG.asMmolL : Decimal(eventualBG)) as NSNumber
+                            from: (state.units == .mmolL ? eventualBG.asMmolL : Decimal(eventualBG)) as NSNumber
                         )!
                     )
                     .font(.system(size: 12, weight: .bold)).foregroundColor(.secondary)
@@ -217,19 +220,19 @@ extension Home {
 
                     infoPanal
                     MainChartView(
-                        glucose: $viewModel.glucose,
-                        suggestion: $viewModel.suggestion,
-                        tempBasals: $viewModel.tempBasals,
-                        boluses: $viewModel.boluses,
-                        suspensions: $viewModel.suspensions,
-                        hours: .constant(viewModel.filteredHours),
-                        maxBasal: $viewModel.maxBasal,
-                        autotunedBasalProfile: $viewModel.autotunedBasalProfile,
-                        basalProfile: $viewModel.basalProfile,
-                        tempTargets: $viewModel.tempTargets,
-                        carbs: $viewModel.carbs,
-                        timerDate: $viewModel.timerDate,
-                        units: $viewModel.units
+                        glucose: $state.glucose,
+                        suggestion: $state.suggestion,
+                        tempBasals: $state.tempBasals,
+                        boluses: $state.boluses,
+                        suspensions: $state.suspensions,
+                        hours: .constant(state.filteredHours),
+                        maxBasal: $state.maxBasal,
+                        autotunedBasalProfile: $state.autotunedBasalProfile,
+                        basalProfile: $state.basalProfile,
+                        tempTargets: $state.tempTargets,
+                        carbs: $state.carbs,
+                        timerDate: $state.timerDate,
+                        units: $state.units
                     )
                     .padding(.bottom)
                     .modal(for: .dataTable, from: self)
@@ -240,7 +243,7 @@ extension Home {
                         Rectangle().fill(Color.gray.opacity(0.2)).frame(height: 50 + geo.safeAreaInsets.bottom)
 
                         HStack {
-                            Button { viewModel.showModal(for: .addCarbs) }
+                            Button { state.showModal(for: .addCarbs) }
                             label: {
                                 ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
                                     Image("carbs")
@@ -249,7 +252,7 @@ extension Home {
                                         .frame(width: 24, height: 24)
                                         .foregroundColor(.loopGreen)
                                         .padding(8)
-                                    if let carbsReq = viewModel.carbsRequired {
+                                    if let carbsReq = state.carbsRequired {
                                         Text(numberFormatter.string(from: carbsReq as NSNumber)!)
                                             .font(.caption)
                                             .foregroundColor(.white)
@@ -259,7 +262,7 @@ extension Home {
                                 }
                             }
                             Spacer()
-                            Button { viewModel.showModal(for: .addTempTarget) }
+                            Button { state.showModal(for: .addTempTarget) }
                             label: {
                                 Image("target")
                                     .renderingMode(.template)
@@ -268,7 +271,7 @@ extension Home {
                                     .padding(8)
                             }.foregroundColor(.loopYellow)
                             Spacer()
-                            Button { viewModel.showModal(for: .bolus(waitForDuggestion: false)) }
+                            Button { state.showModal(for: .bolus(waitForDuggestion: false)) }
                             label: {
                                 Image("bolus")
                                     .renderingMode(.template)
@@ -277,8 +280,8 @@ extension Home {
                                     .padding(8)
                             }.foregroundColor(.insulin)
                             Spacer()
-                            if viewModel.allowManualTemp {
-                                Button { viewModel.showModal(for: .manualTempBasal) }
+                            if state.allowManualTemp {
+                                Button { state.showModal(for: .manualTempBasal) }
                                 label: {
                                     Image("bolus1")
                                         .renderingMode(.template)
@@ -288,7 +291,7 @@ extension Home {
                                 }.foregroundColor(.insulin)
                                 Spacer()
                             }
-                            Button { viewModel.showModal(for: .settings) }
+                            Button { state.showModal(for: .settings) }
                             label: {
                                 Image("settings1")
                                     .renderingMode(.template)
@@ -303,16 +306,17 @@ extension Home {
                 }
                 .edgesIgnoringSafeArea(.vertical)
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Home")
             .navigationBarHidden(true)
             .ignoresSafeArea(.keyboard)
             .popup(isPresented: isStatusPopupPresented, alignment: .top, direction: .top) {
                 VStack(alignment: .leading) {
-                    Text(viewModel.statusTitle).foregroundColor(.white)
+                    Text(state.statusTitle).foregroundColor(.white)
                         .padding(.bottom, 4)
-                    Text(viewModel.suggestion?.reason ?? "No sugestion found").font(.caption).foregroundColor(.white)
+                    Text(state.suggestion?.reason ?? "No sugestion found").font(.caption).foregroundColor(.white)
 
-                    if let errorMessage = viewModel.errorMessage, let date = viewModel.errorDate {
+                    if let errorMessage = state.errorMessage, let date = state.errorDate {
                         Text("Error at \(dateFormatter.string(from: date))").foregroundColor(.white)
                             .padding(.bottom, 4)
                             .padding(.top, 8)

+ 0 - 3
FreeAPS/Sources/Modules/ISFEditor/ISFEditorBuilder.swift

@@ -1,3 +0,0 @@
-extension ISFEditor {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 1
FreeAPS/Sources/Modules/ISFEditor/ISFEditorViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension ISFEditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: ISFEditorProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() var settingsManager: SettingsManager!
         @Published var items: [Item] = []
         private(set) var autosensISF: Decimal?

+ 27 - 24
FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension ISFEditor {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
         @State private var editMode = EditMode.inactive
 
         private var dateFormatter: DateFormatter {
@@ -21,32 +23,32 @@ extension ISFEditor {
 
         var body: some View {
             Form {
-                if let autotune = viewModel.autotune {
+                if let autotune = state.autotune {
                     Section(header: Text("Autotune")) {
                         HStack {
                             Text("Calculated Sensitivity")
                             Spacer()
-                            if viewModel.units == .mmolL {
+                            if state.units == .mmolL {
                                 Text(rateFormatter.string(from: autotune.sensitivity.asMmolL as NSNumber) ?? "0")
                             } else {
                                 Text(rateFormatter.string(from: autotune.sensitivity as NSNumber) ?? "0")
                             }
-                            Text(viewModel.units.rawValue + "/U").foregroundColor(.secondary)
+                            Text(state.units.rawValue + "/U").foregroundColor(.secondary)
                         }
                     }
                 }
-                if let newISF = viewModel.autosensISF {
+                if let newISF = state.autosensISF {
                     Section(header: Text("Autosens")) {
                         HStack {
                             Text("Sensitivity Ratio")
                             Spacer()
-                            Text(rateFormatter.string(from: viewModel.autosensRatio as NSNumber) ?? "1")
+                            Text(rateFormatter.string(from: state.autosensRatio as NSNumber) ?? "1")
                         }
                         HStack {
                             Text("Calculated Sensitivity")
                             Spacer()
                             Text(rateFormatter.string(from: newISF as NSNumber) ?? "0")
-                            Text(viewModel.units.rawValue + "/U").foregroundColor(.secondary)
+                            Text(state.units.rawValue + "/U").foregroundColor(.secondary)
                         }
                     }
                 }
@@ -55,13 +57,14 @@ extension ISFEditor {
                     addButton
                 }
                 Section {
-                    Button { viewModel.save() }
+                    Button { state.save() }
                     label: {
                         Text("Save")
                     }
-                    .disabled(viewModel.items.isEmpty)
+                    .disabled(state.items.isEmpty)
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Insulin Sensitivities")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(
@@ -69,7 +72,7 @@ extension ISFEditor {
             )
             .environment(\.editMode, $editMode)
             .onAppear {
-                viewModel.validate()
+                state.validate()
             }
         }
 
@@ -81,25 +84,25 @@ extension ISFEditor {
                         Text("Time").frame(width: geometry.size.width / 2)
                     }
                     HStack(spacing: 0) {
-                        Picker(selection: $viewModel.items[index].rateIndex, label: EmptyView()) {
-                            ForEach(0 ..< viewModel.rateValues.count, id: \.self) { i in
+                        Picker(selection: $state.items[index].rateIndex, label: EmptyView()) {
+                            ForEach(0 ..< state.rateValues.count, id: \.self) { i in
                                 Text(
                                     (
                                         self.rateFormatter
-                                            .string(from: viewModel.rateValues[i] as NSNumber) ?? ""
-                                    ) + " \(viewModel.units.rawValue)/U"
+                                            .string(from: state.rateValues[i] as NSNumber) ?? ""
+                                    ) + " \(state.units.rawValue)/U"
                                 ).tag(i)
                             }
                         }
                         .frame(maxWidth: geometry.size.width / 2)
                         .clipped()
 
-                        Picker(selection: $viewModel.items[index].timeIndex, label: EmptyView()) {
-                            ForEach(0 ..< viewModel.timeValues.count, id: \.self) { i in
+                        Picker(selection: $state.items[index].timeIndex, label: EmptyView()) {
+                            ForEach(0 ..< state.timeValues.count, id: \.self) { i in
                                 Text(
                                     self.dateFormatter
                                         .string(from: Date(
-                                            timeIntervalSince1970: viewModel
+                                            timeIntervalSince1970: state
                                                 .timeValues[i]
                                         ))
                                 ).tag(i)
@@ -114,17 +117,17 @@ extension ISFEditor {
 
         private var list: some View {
             List {
-                ForEach(viewModel.items.indexed(), id: \.1.id) { index, item in
+                ForEach(state.items.indexed(), id: \.1.id) { index, item in
                     NavigationLink(destination: pickers(for: index)) {
                         HStack {
                             Text("Rate").foregroundColor(.secondary)
                             Text(
-                                "\(rateFormatter.string(from: viewModel.rateValues[item.rateIndex] as NSNumber) ?? "0") \(viewModel.units.rawValue)/U"
+                                "\(rateFormatter.string(from: state.rateValues[item.rateIndex] as NSNumber) ?? "0") \(state.units.rawValue)/U"
                             )
                             Spacer()
                             Text("starts at").foregroundColor(.secondary)
                             Text(
-                                "\(dateFormatter.string(from: Date(timeIntervalSince1970: viewModel.timeValues[item.timeIndex])))"
+                                "\(dateFormatter.string(from: Date(timeIntervalSince1970: state.timeValues[item.timeIndex])))"
                             )
                         }
                     }
@@ -135,7 +138,7 @@ extension ISFEditor {
         }
 
         private var addButton: some View {
-            guard viewModel.canAdd else {
+            guard state.canAdd else {
                 return AnyView(EmptyView())
             }
 
@@ -148,12 +151,12 @@ extension ISFEditor {
         }
 
         func onAdd() {
-            viewModel.add()
+            state.add()
         }
 
         private func onDelete(offsets: IndexSet) {
-            viewModel.items.remove(atOffsets: offsets)
-            viewModel.validate()
+            state.items.remove(atOffsets: offsets)
+            state.validate()
         }
     }
 }

+ 0 - 5
FreeAPS/Sources/Modules/Main/MainBuilder.swift

@@ -1,5 +0,0 @@
-import Swinject
-
-extension Main {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 3 - 5
FreeAPS/Sources/Modules/Main/MainViewModel.swift

@@ -1,10 +1,8 @@
-import Combine
 import SwiftUI
 import Swinject
 
 extension Main {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: MainProvider {
-        @Published private(set) var isAuthotized = false
+    class StateModel: BaseStateModel<Provider> {
         private(set) var modal: Modal?
         @Published var isModalPresented = false
         @Published var isAlertPresented = false
@@ -12,9 +10,9 @@ extension Main {
 
         override func subscribe() {
             router.mainModalScreen
-                .map { $0?.modal(resolver: self.resolver) }
+                .map { $0?.modal(resolver: self.resolver!) }
                 .removeDuplicates { $0?.id == $1?.id }
-                .receive(on: RunLoop.main)
+                .receive(on: DispatchQueue.main)
                 .sink { modal in
                     self.modal = modal
                     self.isModalPresented = modal != nil

+ 13 - 11
FreeAPS/Sources/Modules/Main/View/MainRootView.swift

@@ -1,26 +1,28 @@
 import SwiftUI
+import Swinject
 
 extension Main {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
-
-        @ViewBuilder func presentedView() -> some View {
-            viewModel.cachedView(for: .home)
-        }
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
         var body: some View {
-            presentedView()
-                .sheet(isPresented: $viewModel.isModalPresented) {
-                    NavigationView { self.viewModel.modal!.view }
+            router.view(for: .home)
+                .sheet(isPresented: $state.isModalPresented) {
+                    NavigationView { self.state.modal!.view }
                         .navigationViewStyle(StackNavigationViewStyle())
                 }
-                .alert(isPresented: $viewModel.isAlertPresented) {
+                .alert(isPresented: $state.isAlertPresented) {
                     Alert(
                         title: Text("Important message"),
-                        message: Text(viewModel.alertMessage),
-                        dismissButton: .default(Text("Dismiss"))
+                        message: Text(state.alertMessage),
+                        dismissButton: .default(Text("Dismiss")) {
+                            state.isAlertPresented = false
+                            state.alertMessage = ""
+                        }
                     )
                 }
+                .onAppear(perform: configureView)
         }
     }
 }

+ 0 - 3
FreeAPS/Sources/Modules/ManualTempBasal/ManualTempBasalBuilder.swift

@@ -1,3 +0,0 @@
-extension ManualTempBasal {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 1
FreeAPS/Sources/Modules/ManualTempBasal/ManualTempBasalViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension ManualTempBasal {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: ManualTempBasalProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() var apsManager: APSManager!
         @Published var rate: Decimal = 0
         @Published var durationIndex = 0

+ 12 - 9
FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension ManualTempBasal {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
         private var formatter: NumberFormatter {
             let formatter = NumberFormatter()
@@ -17,16 +19,16 @@ extension ManualTempBasal {
                     HStack {
                         Text("Amount")
                         Spacer()
-                        DecimalTextField("0", value: $viewModel.rate, formatter: formatter, autofocus: true, cleanInput: true)
+                        DecimalTextField("0", value: $state.rate, formatter: formatter, autofocus: true, cleanInput: true)
                         Text("U/hr").foregroundColor(.secondary)
                     }
-                    Picker(selection: $viewModel.durationIndex, label: Text("Duration")) {
-                        ForEach(0 ..< viewModel.durationValues.count) { index in
+                    Picker(selection: $state.durationIndex, label: Text("Duration")) {
+                        ForEach(0 ..< state.durationValues.count) { index in
                             Text(
                                 String(
                                     format: "%.0f h %02.0f min",
-                                    viewModel.durationValues[index] / 60 - 0.1,
-                                    viewModel.durationValues[index].truncatingRemainder(dividingBy: 60)
+                                    state.durationValues[index] / 60 - 0.1,
+                                    state.durationValues[index].truncatingRemainder(dividingBy: 60)
                                 )
                             ).tag(index)
                         }
@@ -34,15 +36,16 @@ extension ManualTempBasal {
                 }
 
                 Section {
-                    Button { viewModel.enact() }
+                    Button { state.enact() }
                     label: { Text("Enact") }
-                    Button { viewModel.cancel() }
+                    Button { state.cancel() }
                     label: { Text("Cancel Temp Basal") }
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Manual Temp Basal")
             .navigationBarTitleDisplayMode(.automatic)
-            .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
+            .navigationBarItems(leading: Button("Close", action: state.hideModal))
         }
     }
 }

+ 0 - 3
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigBuilder.swift

@@ -1,3 +0,0 @@
-extension NightscoutConfig {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 1
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigViewModel.swift

@@ -2,7 +2,7 @@ import Combine
 import SwiftUI
 
 extension NightscoutConfig {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: NightscoutConfigProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() var keychain: Keychain!
         @Injected() var settingsManager: SettingsManager!
 

+ 17 - 13
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension NightscoutConfig {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
         private var portFormater: NumberFormatter {
             let formatter = NumberFormatter()
@@ -13,20 +15,20 @@ extension NightscoutConfig {
         var body: some View {
             Form {
                 Section {
-                    TextField("URL", text: $viewModel.url)
+                    TextField("URL", text: $state.url)
                         .disableAutocorrection(true)
                         .textContentType(.URL)
                         .autocapitalization(.none)
                         .keyboardType(.URL)
-                    SecureField("API secret", text: $viewModel.secret)
+                    SecureField("API secret", text: $state.secret)
                         .disableAutocorrection(true)
                         .autocapitalization(.none)
                         .textContentType(.password)
                         .keyboardType(.asciiCapable)
-                    if !viewModel.message.isEmpty {
-                        Text(viewModel.message)
+                    if !state.message.isEmpty {
+                        Text(state.message)
                     }
-                    if viewModel.connecting {
+                    if state.connecting {
                         HStack {
                             Text("Connecting...")
                             Spacer()
@@ -36,24 +38,26 @@ extension NightscoutConfig {
                 }
 
                 Section {
-                    Button("Connect") { viewModel.connect() }
-                        .disabled(viewModel.url.isEmpty || viewModel.connecting)
-                    Button("Delete") { viewModel.delete() }.foregroundColor(.red).disabled(viewModel.connecting)
+                    Button("Connect") { state.connect() }
+                        .disabled(state.url.isEmpty || state.connecting)
+                    Button("Delete") { state.delete() }.foregroundColor(.red).disabled(state.connecting)
                 }
 
                 Section {
-                    Toggle("Allow uploads", isOn: $viewModel.isUploadEnabled)
+                    Toggle("Allow uploads", isOn: $state.isUploadEnabled)
                 }
 
                 Section(header: Text("Local glucose source")) {
-                    Toggle("Use local glucose server", isOn: $viewModel.useLocalSource)
+                    Toggle("Use local glucose server", isOn: $state.useLocalSource)
                     HStack {
                         Text("Port")
-                        DecimalTextField("", value: $viewModel.localPort, formatter: portFormater)
+                        DecimalTextField("", value: $state.localPort, formatter: portFormater)
                     }
                 }
             }
-            .navigationBarTitle("Nightscout Config", displayMode: .automatic)
+            .onAppear(perform: configureView)
+            .navigationBarTitle("Nightscout Config")
+            .navigationBarTitleDisplayMode(.automatic)
         }
     }
 }

+ 0 - 3
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorBuilder.swift

@@ -1,3 +0,0 @@
-extension PreferencesEditor {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 3
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorViewModel.swift

@@ -2,9 +2,7 @@ import Foundation
 import SwiftUI
 
 extension PreferencesEditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject,
-        PreferencesSettable where Provider: PreferencesEditorProvider
-    {
+    class StateModel: BaseStateModel<Provider>, PreferencesSettable {
         @Injected() var settingsManager: SettingsManager!
         private(set) var preferences = Preferences()
         @Published var unitsIndex = 1

+ 13 - 10
FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift

@@ -1,4 +1,5 @@
 import SwiftUI
+import Swinject
 
 struct InfoText: Identifiable {
     var id: String { description }
@@ -8,7 +9,8 @@ struct InfoText: Identifiable {
 
 extension PreferencesEditor {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
         private var formatter: NumberFormatter {
             let formatter = NumberFormatter()
@@ -21,44 +23,44 @@ extension PreferencesEditor {
         var body: some View {
             Form {
                 Section(header: Text("FreeAPS X")) {
-                    Picker("Glucose units", selection: $viewModel.unitsIndex) {
+                    Picker("Glucose units", selection: $state.unitsIndex) {
                         Text("mg/dL").tag(0)
                         Text("mmol/L").tag(1)
                     }
 
-                    Toggle("Remote control", isOn: $viewModel.allowAnnouncements)
+                    Toggle("Remote control", isOn: $state.allowAnnouncements)
 
                     HStack {
                         Text("Recommended Insulin Fraction")
-                        DecimalTextField("", value: $viewModel.insulinReqFraction, formatter: formatter)
+                        DecimalTextField("", value: $state.insulinReqFraction, formatter: formatter)
                     }
 
-                    Toggle("Skip Bolus screen after carbs", isOn: $viewModel.skipBolusScreenAfterCarbs)
+                    Toggle("Skip Bolus screen after carbs", isOn: $state.skipBolusScreenAfterCarbs)
                 }
 
                 Section(header: Text("OpenAPS")) {
-                    Picker(selection: $viewModel.insulinCurveField.value, label: Text(viewModel.insulinCurveField.displayName)) {
+                    Picker(selection: $state.insulinCurveField.value, label: Text(state.insulinCurveField.displayName)) {
                         ForEach(InsulinCurve.allCases) { v in
                             Text(v.rawValue).tag(v)
                         }
                     }
 
-                    ForEach(viewModel.boolFields.indexed(), id: \.1.id) { index, field in
+                    ForEach(state.boolFields.indexed(), id: \.1.id) { index, field in
                         HStack {
                             Button("", action: {
                                 infoButtonPressed = InfoText(description: field.infoText, oref0Variable: field.displayName)
                             })
-                            Toggle(field.displayName, isOn: self.$viewModel.boolFields[index].value)
+                            Toggle(field.displayName, isOn: self.$state.boolFields[index].value)
                         }
                     }
 
-                    ForEach(viewModel.decimalFields.indexed(), id: \.1.id) { index, field in
+                    ForEach(state.decimalFields.indexed(), id: \.1.id) { index, field in
                         HStack {
                             Button("", action: {
                                 infoButtonPressed = InfoText(description: field.infoText, oref0Variable: field.displayName)
                             })
                             Text(field.displayName)
-                            DecimalTextField("0", value: self.$viewModel.decimalFields[index].value, formatter: formatter)
+                            DecimalTextField("0", value: self.$state.decimalFields[index].value, formatter: formatter)
                         }
                     }
                 }
@@ -67,6 +69,7 @@ extension PreferencesEditor {
                         .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Preferences")
             .navigationBarTitleDisplayMode(.automatic)
             .alert(item: $infoButtonPressed) { infoButton in

+ 0 - 3
FreeAPS/Sources/Modules/PumpConfig/PumpConfigBuilder.swift

@@ -1,3 +0,0 @@
-extension PumpConfig {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 3 - 3
FreeAPS/Sources/Modules/PumpConfig/PumpConfigViewModel.swift

@@ -4,7 +4,7 @@ import SwiftDate
 import SwiftUI
 
 extension PumpConfig {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: PumpConfigProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Published var setupPump = false
         private(set) var setupPumpType: PumpType = .minimed
         @Published var pumpState: PumpDisplayState?
@@ -38,13 +38,13 @@ extension PumpConfig {
     }
 }
 
-extension PumpConfig.ViewModel: CompletionDelegate {
+extension PumpConfig.StateModel: CompletionDelegate {
     func completionNotifyingDidComplete(_: CompletionNotifying) {
         setupPump = false
     }
 }
 
-extension PumpConfig.ViewModel: PumpManagerSetupViewControllerDelegate {
+extension PumpConfig.StateModel: PumpManagerSetupViewControllerDelegate {
     func pumpManagerSetupViewController(_: PumpManagerSetupViewController, didSetUpPumpManager pumpManager: PumpManagerUI) {
         provider.setPumpManager(pumpManager)
         setupPump = false

+ 16 - 13
FreeAPS/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift

@@ -1,15 +1,17 @@
 import SwiftUI
+import Swinject
 
 extension PumpConfig {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
         var body: some View {
             Form {
                 Section(header: Text("Model")) {
-                    if let pumpState = viewModel.pumpState {
+                    if let pumpState = state.pumpState {
                         Button {
-                            viewModel.setupPump = true
+                            state.setupPump = true
                         } label: {
                             HStack {
                                 Image(uiImage: pumpState.image ?? UIImage()).padding()
@@ -17,23 +19,24 @@ extension PumpConfig {
                             }
                         }
                     } else {
-                        Button("Add Medtronic") { viewModel.addPump(.minimed) }
-                        Button("Add Omnipod") { viewModel.addPump(.omnipod) }
-                        Button("Add Simulator") { viewModel.addPump(.simulator) }
+                        Button("Add Medtronic") { state.addPump(.minimed) }
+                        Button("Add Omnipod") { state.addPump(.omnipod) }
+                        Button("Add Simulator") { state.addPump(.simulator) }
                     }
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Pump config")
             .navigationBarTitleDisplayMode(.automatic)
-            .popover(isPresented: $viewModel.setupPump) {
-                if let pumpManager = viewModel.provider.apsManager.pumpManager {
-                    PumpSettingsView(pumpManager: pumpManager, completionDelegate: viewModel)
+            .popover(isPresented: $state.setupPump) {
+                if let pumpManager = state.provider.apsManager.pumpManager {
+                    PumpSettingsView(pumpManager: pumpManager, completionDelegate: state)
                 } else {
                     PumpSetupView(
-                        pumpType: viewModel.setupPumpType,
-                        pumpInitialSettings: viewModel.initialSettings,
-                        completionDelegate: viewModel,
-                        setupDelegate: viewModel
+                        pumpType: state.setupPumpType,
+                        pumpInitialSettings: state.initialSettings,
+                        completionDelegate: state,
+                        setupDelegate: state
                     )
                 }
             }

+ 0 - 3
FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorBuilder.swift

@@ -1,3 +0,0 @@
-extension PumpSettingsEditor {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 1
FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension PumpSettingsEditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: PumpSettingsEditorProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Published var maxBasal: Decimal = 0.0
         @Published var maxBolus: Decimal = 0.0
         @Published var dia: Decimal = 0.0

+ 11 - 8
FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension PumpSettingsEditor {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
         private var formatter: NumberFormatter {
             let formatter = NumberFormatter()
@@ -15,34 +17,35 @@ extension PumpSettingsEditor {
                 Section(header: Text("Delivery limits")) {
                     HStack {
                         Text("Max Basal")
-                        DecimalTextField("U/hr", value: $viewModel.maxBasal, formatter: formatter)
+                        DecimalTextField("U/hr", value: $state.maxBasal, formatter: formatter)
                     }
                     HStack {
                         Text("Max Bolus")
-                        DecimalTextField("U", value: $viewModel.maxBolus, formatter: formatter)
+                        DecimalTextField("U", value: $state.maxBolus, formatter: formatter)
                     }
                 }
 
                 Section(header: Text("Duration of Insulin Action")) {
                     HStack {
                         Text("DIA")
-                        DecimalTextField("hours", value: $viewModel.dia, formatter: formatter)
+                        DecimalTextField("hours", value: $state.dia, formatter: formatter)
                     }
                 }
 
                 Section {
                     HStack {
-                        if viewModel.syncInProgress {
+                        if state.syncInProgress {
                             ProgressView().padding(.trailing, 10)
                         }
-                        Button { viewModel.save() }
+                        Button { state.save() }
                         label: {
-                            Text(viewModel.syncInProgress ? "Saving..." : "Save on Pump")
+                            Text(state.syncInProgress ? "Saving..." : "Save on Pump")
                         }
-                        .disabled(viewModel.syncInProgress)
+                        .disabled(state.syncInProgress)
                     }
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Pump Settings")
             .navigationBarTitleDisplayMode(.automatic)
         }

+ 0 - 3
FreeAPS/Sources/Modules/Settings/SettingsBuilder.swift

@@ -1,3 +0,0 @@
-extension Settings {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 2 - 2
FreeAPS/Sources/Modules/Settings/SettingsViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension Settings {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: SettingsProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() private var settingsManager: SettingsManager!
         @Injected() private var broadcaster: Broadcaster!
         @Injected() private var fileManager: FileManager!
@@ -42,7 +42,7 @@ extension Settings {
     }
 }
 
-extension Settings.ViewModel: SettingsObserver {
+extension Settings.StateModel: SettingsObserver {
     func settingsDidChange(_ settings: FreeAPSSettings) {
         closedLoop = settings.closedLoop
         debugOptions = settings.debugOptions ?? false

+ 9 - 10
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -1,14 +1,16 @@
 import SwiftUI
+import Swinject
 
 extension Settings {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
         @State private var showShareSheet = false
 
         var body: some View {
             Form {
-                Section(header: Text("FreeAPS X v\(viewModel.buildNumber)")) {
-                    Toggle("Closed loop", isOn: $viewModel.closedLoop)
+                Section(header: Text("FreeAPS X v\(state.buildNumber)")) {
+                    Toggle("Closed loop", isOn: $state.closedLoop)
                 }
 
                 Section(header: Text("Devices")) {
@@ -30,7 +32,7 @@ extension Settings {
                     Text("Autotune").navigationLink(to: .autotuneConfig, from: self)
                 }
 
-                if viewModel.debugOptions {
+                if state.debugOptions {
                     Section(header: Text("Config files")) {
                         Group {
                             Text("Preferences")
@@ -92,17 +94,14 @@ extension Settings {
                         .onTapGesture {
                             showShareSheet = true
                         }
-//                    Text("Read disclaimer")
-//                        .onTapGesture {
-//                            viewModel.logout()
-//                        }
                 }
             }
             .sheet(isPresented: $showShareSheet) {
-                ShareSheet(activityItems: viewModel.logItems())
+                ShareSheet(activityItems: state.logItems())
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Settings")
-            .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
+            .navigationBarItems(leading: Button("Close", action: state.hideModal))
             .navigationBarTitleDisplayMode(.automatic)
         }
     }

+ 0 - 3
FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorBuilder.swift

@@ -1,3 +0,0 @@
-extension TargetsEditor {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
-}

+ 1 - 1
FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorViewModel.swift

@@ -1,7 +1,7 @@
 import SwiftUI
 
 extension TargetsEditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: TargetsEditorProvider {
+    class StateModel: BaseStateModel<Provider> {
         @Injected() var settingsManager: SettingsManager!
         @Published var items: [Item] = []
 

+ 25 - 22
FreeAPS/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift

@@ -1,8 +1,10 @@
 import SwiftUI
+import Swinject
 
 extension TargetsEditor {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
         @State private var editMode = EditMode.inactive
 
         private var dateFormatter: DateFormatter {
@@ -25,13 +27,14 @@ extension TargetsEditor {
                     addButton
                 }
                 Section {
-                    Button { viewModel.save() }
+                    Button { state.save() }
                     label: {
                         Text("Save")
                     }
-                    .disabled(viewModel.items.isEmpty)
+                    .disabled(state.items.isEmpty)
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Target Ranges")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(
@@ -39,7 +42,7 @@ extension TargetsEditor {
             )
             .environment(\.editMode, $editMode)
             .onAppear {
-                viewModel.validate()
+                state.validate()
             }
         }
 
@@ -52,33 +55,33 @@ extension TargetsEditor {
                         Text("Time").frame(width: geometry.size.width / 3)
                     }
                     HStack(spacing: 0) {
-                        Picker(selection: $viewModel.items[index].lowIndex, label: EmptyView()) {
-                            ForEach(0 ..< viewModel.rateValues.count, id: \.self) { i in
+                        Picker(selection: $state.items[index].lowIndex, label: EmptyView()) {
+                            ForEach(0 ..< state.rateValues.count, id: \.self) { i in
                                 Text(
                                     self.rateFormatter
-                                        .string(from: viewModel.rateValues[i] as NSNumber) ?? ""
+                                        .string(from: state.rateValues[i] as NSNumber) ?? ""
                                 ).tag(i)
                             }
                         }
                         .frame(maxWidth: geometry.size.width / 3)
                         .clipped()
-                        Picker(selection: $viewModel.items[index].highIndex, label: EmptyView()) {
-                            ForEach(0 ..< viewModel.rateValues.count, id: \.self) { i in
+                        Picker(selection: $state.items[index].highIndex, label: EmptyView()) {
+                            ForEach(0 ..< state.rateValues.count, id: \.self) { i in
                                 Text(
                                     self.rateFormatter
-                                        .string(from: viewModel.rateValues[i] as NSNumber) ?? ""
+                                        .string(from: state.rateValues[i] as NSNumber) ?? ""
                                 ).tag(i)
                             }
                         }
                         .frame(maxWidth: geometry.size.width / 3)
                         .clipped()
 
-                        Picker(selection: $viewModel.items[index].timeIndex, label: EmptyView()) {
-                            ForEach(0 ..< viewModel.timeValues.count, id: \.self) { i in
+                        Picker(selection: $state.items[index].timeIndex, label: EmptyView()) {
+                            ForEach(0 ..< state.timeValues.count, id: \.self) { i in
                                 Text(
                                     self.dateFormatter
                                         .string(from: Date(
-                                            timeIntervalSince1970: viewModel
+                                            timeIntervalSince1970: state
                                                 .timeValues[i]
                                         ))
                                 ).tag(i)
@@ -93,21 +96,21 @@ extension TargetsEditor {
 
         private var list: some View {
             List {
-                ForEach(viewModel.items.indexed(), id: \.1.id) { index, item in
+                ForEach(state.items.indexed(), id: \.1.id) { index, item in
                     NavigationLink(destination: pickers(for: index)) {
                         HStack {
                             Text(
-                                "\(rateFormatter.string(from: viewModel.rateValues[item.lowIndex] as NSNumber) ?? "0")"
+                                "\(rateFormatter.string(from: state.rateValues[item.lowIndex] as NSNumber) ?? "0")"
                             )
                             Text("–").foregroundColor(.secondary)
                             Text(
-                                "\(rateFormatter.string(from: viewModel.rateValues[item.highIndex] as NSNumber) ?? "0")"
+                                "\(rateFormatter.string(from: state.rateValues[item.highIndex] as NSNumber) ?? "0")"
                             )
-                            Text("\(viewModel.units.rawValue)").foregroundColor(.secondary)
+                            Text("\(state.units.rawValue)").foregroundColor(.secondary)
                             Spacer()
                             Text("starts at").foregroundColor(.secondary)
                             Text(
-                                "\(dateFormatter.string(from: Date(timeIntervalSince1970: viewModel.timeValues[item.timeIndex])))"
+                                "\(dateFormatter.string(from: Date(timeIntervalSince1970: state.timeValues[item.timeIndex])))"
                             )
                         }
                     }
@@ -118,7 +121,7 @@ extension TargetsEditor {
         }
 
         private var addButton: some View {
-            guard viewModel.canAdd else {
+            guard state.canAdd else {
                 return AnyView(EmptyView())
             }
 
@@ -131,12 +134,12 @@ extension TargetsEditor {
         }
 
         func onAdd() {
-            viewModel.add()
+            state.add()
         }
 
         private func onDelete(offsets: IndexSet) {
-            viewModel.items.remove(atOffsets: offsets)
-            viewModel.validate()
+            state.items.remove(atOffsets: offsets)
+            state.validate()
         }
     }
 }

+ 21 - 21
FreeAPS/Sources/Router/Screen.swift

@@ -26,50 +26,50 @@ enum Screen: Identifiable, Hashable {
 }
 
 extension Screen {
-    func view(resolver: Resolver) -> AnyView {
+    @ViewBuilder func view(resolver: Resolver) -> some View {
         switch self {
         case .loading:
-            return ProgressView().asAny()
+            ProgressView()
         case .home:
-            return Home.Builder(resolver: resolver).buildView()
+            Home.RootView(resolver: resolver)
         case .settings:
-            return Settings.Builder(resolver: resolver).buildView()
+            Settings.RootView(resolver: resolver)
         case let .configEditor(file):
-            return ConfigEditor.Builder(resolver: resolver, file: file).buildView()
+            ConfigEditor.RootView(resolver: resolver, file: file)
         case .nighscoutConfig:
-            return NightscoutConfig.Builder(resolver: resolver).buildView()
+            NightscoutConfig.RootView(resolver: resolver)
         case .pumpConfig:
-            return PumpConfig.Builder(resolver: resolver).buildView()
+            PumpConfig.RootView(resolver: resolver)
         case .pumpSettingsEditor:
-            return PumpSettingsEditor.Builder(resolver: resolver).buildView()
+            PumpSettingsEditor.RootView(resolver: resolver)
         case .basalProfileEditor:
-            return BasalProfileEditor.Builder(resolver: resolver).buildView()
+            BasalProfileEditor.RootView(resolver: resolver)
         case .isfEditor:
-            return ISFEditor.Builder(resolver: resolver).buildView()
+            ISFEditor.RootView(resolver: resolver)
         case .crEditor:
-            return CREditor.Builder(resolver: resolver).buildView()
+            CREditor.RootView(resolver: resolver)
         case .targetsEditor:
-            return TargetsEditor.Builder(resolver: resolver).buildView()
+            TargetsEditor.RootView(resolver: resolver)
         case .preferencesEditor:
-            return PreferencesEditor.Builder(resolver: resolver).buildView()
+            PreferencesEditor.RootView(resolver: resolver)
         case .addCarbs:
-            return AddCarbs.Builder(resolver: resolver).buildView()
+            AddCarbs.RootView(resolver: resolver)
         case .addTempTarget:
-            return AddTempTarget.Builder(resolver: resolver).buildView()
+            AddTempTarget.RootView(resolver: resolver)
         case let .bolus(waitForSuggestion):
-            return Bolus.Builder(resolver: resolver, waitForSuggestion: waitForSuggestion).buildView()
+            Bolus.RootView(resolver: resolver, waitForSuggestion: waitForSuggestion)
         case .manualTempBasal:
-            return ManualTempBasal.Builder(resolver: resolver).buildView()
+            ManualTempBasal.RootView(resolver: resolver)
         case .autotuneConfig:
-            return AutotuneConfig.Builder(resolver: resolver).buildView()
+            AutotuneConfig.RootView(resolver: resolver)
         case .dataTable:
-            return DataTable.Builder(resolver: resolver).buildView()
+            DataTable.RootView(resolver: resolver)
         case .cgm:
-            return CGM.Builder(resolver: resolver).buildView()
+            CGM.RootView(resolver: resolver)
         }
     }
 
     func modal(resolver: Resolver) -> Main.Modal {
-        .init(screen: self, view: view(resolver: resolver))
+        .init(screen: self, view: view(resolver: resolver).asAny())
     }
 }

+ 2 - 2
FreeAPS/Sources/Views/ViewModifiers.swift

@@ -131,7 +131,7 @@ extension View {
     }
 
     func navigationLink<V: BaseView>(to screen: Screen, from view: V) -> some View {
-        modifier(Link(destination: view.viewModel.view(for: screen), screen: screen))
+        modifier(Link(destination: view.state.view(for: screen), screen: screen))
     }
 
     func adaptsToSoftwareKeyboard() -> some View {
@@ -140,7 +140,7 @@ extension View {
 
     func modal<V: BaseView>(for screen: Screen?, from view: V) -> some View {
         onTapGesture {
-            view.viewModel.showModal(for: screen)
+            view.state.showModal(for: screen)
         }
     }