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

Merge branch 'dev' into Crowdin

Jon B.M 4 лет назад
Родитель
Сommit
2efe7ff9a0
100 измененных файлов с 1004 добавлено и 1399 удалено
  1. 5 0
      Dependencies/LoopKit/LoopKit/Insulin/ExponentialInsulinModelPreset.swift
  2. 6 0
      Dependencies/LoopKit/LoopKit/Insulin/InsulinModelSettings.swift
  3. 5 0
      Dependencies/LoopKit/LoopKit/InsulinKit/HKQuantitySample+InsulinKit.swift
  4. 7 0
      Dependencies/LoopKit/LoopKit/InsulinKit/InsulinType.swift
  5. 1 0
      Dependencies/LoopKit/LoopKit/StoredInsulinModel.swift
  6. 38 0
      Dependencies/LoopKit/LoopKitUI/Assets.xcassets/Lyumjev.colorset/Contents.json
  7. 4 0
      Dependencies/LoopKit/LoopKitUI/InsulinModelSettings+LoopKitUI.swift
  8. 1 1
      Dependencies/LoopKit/LoopKitUI/View Controllers/OverrideSelectionViewController.swift
  9. 3 3
      Dependencies/LoopKit/LoopKitUI/Views/OverrideSelectionHistory.swift
  10. 117 359
      FreeAPS.xcodeproj/project.pbxproj
  11. 1 1
      FreeAPS/Resources/Config.xcconfig
  12. 2 2
      FreeAPS/Sources/APS/Extensions/PumpManagerExtensions.swift
  13. 26 38
      FreeAPS/Sources/Application/FreeAPSApp.swift
  14. 12 0
      FreeAPS/Sources/Assemblies/APSAssembly.swift
  15. 13 0
      FreeAPS/Sources/Assemblies/NetworkAssembly.swift
  16. 8 0
      FreeAPS/Sources/Assemblies/SecurityAssembly.swift
  17. 2 4
      FreeAPS/Sources/Containers/ServiceContainer.swift
  18. 18 0
      FreeAPS/Sources/Assemblies/StorageAssembly.swift
  19. 9 0
      FreeAPS/Sources/Assemblies/UIAssembly.swift
  20. 0 14
      FreeAPS/Sources/Containers/APSContainer.swift
  21. 0 10
      FreeAPS/Sources/Containers/DependeciesContainer.swift
  22. 0 16
      FreeAPS/Sources/Containers/NetworkContainer.swift
  23. 0 9
      FreeAPS/Sources/Containers/SecurityContainer.swift
  24. 0 21
      FreeAPS/Sources/Containers/StorageContainer.swift
  25. 0 10
      FreeAPS/Sources/Containers/UIContainer.swift
  26. 1 1
      FreeAPS/Sources/Models/Preferences.swift
  27. 0 3
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsBuilder.swift
  28. 1 1
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsViewModel.swift
  29. 10 7
      FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift
  30. 0 3
      FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetBuilder.swift
  31. 1 1
      FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetViewModel.swift
  32. 21 18
      FreeAPS/Sources/Modules/AddTempTarget/View/AddTempTargetRootView.swift
  33. 0 3
      FreeAPS/Sources/Modules/AuthorizedRoot/AuthorizedRootBuilder.swift
  34. 0 7
      FreeAPS/Sources/Modules/AuthorizedRoot/AuthorizedRootDataFlow.swift
  35. 0 3
      FreeAPS/Sources/Modules/AuthorizedRoot/AuthorizedRootProvider.swift
  36. 0 10
      FreeAPS/Sources/Modules/AuthorizedRoot/AuthorizedRootViewModel.swift
  37. 0 13
      FreeAPS/Sources/Modules/AuthorizedRoot/View/AuthotizedRootRootView.swift
  38. 0 3
      FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigBuilder.swift
  39. 11 5
      FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigViewModel.swift
  40. 11 8
      FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift
  41. 0 3
      FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorBuilder.swift
  42. 1 1
      FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorViewModel.swift
  43. 22 19
      FreeAPS/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift
  44. 0 26
      FreeAPS/Sources/Modules/Base/BaseModuleBuilder.swift
  45. 0 1
      FreeAPS/Sources/Modules/Base/BaseProvider.swift
  46. 43 0
      FreeAPS/Sources/Modules/Base/BaseStateModel.swift
  47. 22 4
      FreeAPS/Sources/Modules/Base/BaseView.swift
  48. 0 56
      FreeAPS/Sources/Modules/Base/BaseViewModel.swift
  49. 0 15
      FreeAPS/Sources/Modules/Bolus/BolusBuilder.swift
  50. 6 15
      FreeAPS/Sources/Modules/Bolus/BolusViewModel.swift
  51. 25 16
      FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift
  52. 0 3
      FreeAPS/Sources/Modules/CGM/CGMBuilder.swift
  53. 1 1
      FreeAPS/Sources/Modules/CGM/CGMViewModel.swift
  54. 10 7
      FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift
  55. 0 3
      FreeAPS/Sources/Modules/CREditor/CREditorBuilder.swift
  56. 1 1
      FreeAPS/Sources/Modules/CREditor/CREditorViewModel.swift
  57. 21 18
      FreeAPS/Sources/Modules/CREditor/View/CREditorRootView.swift
  58. 0 16
      FreeAPS/Sources/Modules/ConfigEditor/ConfigEditorBuilder.swift
  59. 17 0
      FreeAPS/Sources/Modules/ConfigEditor/ConfigEditorStateModel.swift
  60. 0 26
      FreeAPS/Sources/Modules/ConfigEditor/ConfigEditorViewModel.swift
  61. 13 5
      FreeAPS/Sources/Modules/ConfigEditor/View/ConfigEditorRootView.swift
  62. 0 3
      FreeAPS/Sources/Modules/DataTable/DataTableBuilder.swift
  63. 2 2
      FreeAPS/Sources/Modules/DataTable/DataTableViewModel.swift
  64. 7 4
      FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift
  65. 0 3
      FreeAPS/Sources/Modules/Home/HomeBuilder.swift
  66. 3 3
      FreeAPS/Sources/Modules/Home/HomeViewModel.swift
  67. 62 58
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  68. 0 3
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorBuilder.swift
  69. 1 1
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorViewModel.swift
  70. 27 24
      FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift
  71. 0 3
      FreeAPS/Sources/Modules/Login/LoginBuilder.swift
  72. 0 11
      FreeAPS/Sources/Modules/Login/LoginDataFlow.swift
  73. 0 19
      FreeAPS/Sources/Modules/Login/LoginProvider.swift
  74. 0 23
      FreeAPS/Sources/Modules/Login/LoginViewModel.swift
  75. 0 24
      FreeAPS/Sources/Modules/Login/View/LoginRootView.swift
  76. 0 5
      FreeAPS/Sources/Modules/Main/MainBuilder.swift
  77. 1 20
      FreeAPS/Sources/Modules/Main/MainDataFlow.swift
  78. 3 18
      FreeAPS/Sources/Modules/Main/MainViewModel.swift
  79. 13 11
      FreeAPS/Sources/Modules/Main/View/MainRootView.swift
  80. 0 3
      FreeAPS/Sources/Modules/ManualTempBasal/ManualTempBasalBuilder.swift
  81. 1 1
      FreeAPS/Sources/Modules/ManualTempBasal/ManualTempBasalViewModel.swift
  82. 12 9
      FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift
  83. 0 3
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigBuilder.swift
  84. 1 1
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigViewModel.swift
  85. 17 13
      FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift
  86. 0 3
      FreeAPS/Sources/Modules/Onboarding/OnboardingBuilder.swift
  87. 0 19
      FreeAPS/Sources/Modules/Onboarding/OnboardingDataFlow.swift
  88. 0 3
      FreeAPS/Sources/Modules/Onboarding/OnboardingProvider.swift
  89. 0 13
      FreeAPS/Sources/Modules/Onboarding/OnboardingViewModel.swift
  90. 0 11
      FreeAPS/Sources/Modules/Onboarding/View/OnboardingRootView.swift
  91. 0 3
      FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorBuilder.swift
  92. 67 10
      FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorDataFlow.swift
  93. 199 211
      FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorViewModel.swift
  94. 52 28
      FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift
  95. 0 3
      FreeAPS/Sources/Modules/PumpConfig/PumpConfigBuilder.swift
  96. 3 3
      FreeAPS/Sources/Modules/PumpConfig/PumpConfigViewModel.swift
  97. 16 13
      FreeAPS/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift
  98. 0 3
      FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorBuilder.swift
  99. 1 1
      FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorViewModel.swift
  100. 0 0
      FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift

+ 5 - 0
Dependencies/LoopKit/LoopKit/Insulin/ExponentialInsulinModelPreset.swift

@@ -9,6 +9,7 @@ public enum ExponentialInsulinModelPreset: String, Codable {
     case rapidActingAdult
     case rapidActingAdult
     case rapidActingChild
     case rapidActingChild
     case fiasp
     case fiasp
+    case lyumjev
 }
 }
 
 
 
 
@@ -22,6 +23,8 @@ extension ExponentialInsulinModelPreset {
             return .minutes(360)
             return .minutes(360)
         case .fiasp:
         case .fiasp:
             return .minutes(360)
             return .minutes(360)
+        case .lyumjev:
+            return .minutes(330)
         }
         }
     }
     }
 
 
@@ -33,6 +36,8 @@ extension ExponentialInsulinModelPreset {
             return .minutes(65)
             return .minutes(65)
         case .fiasp:
         case .fiasp:
             return .minutes(55)
             return .minutes(55)
+        case .lyumjev:
+            return .minutes(45)
         }
         }
     }
     }
 
 

+ 6 - 0
Dependencies/LoopKit/LoopKit/Insulin/InsulinModelSettings.swift

@@ -28,6 +28,8 @@ public enum InsulinModelSettings: Equatable {
         switch type {
         switch type {
         case .fiasp:
         case .fiasp:
             return ExponentialInsulinModelPreset.fiasp
             return ExponentialInsulinModelPreset.fiasp
+        case .lyumjev:
+            return ExponentialInsulinModelPreset.lyumjev
         default:
         default:
             switch self {
             switch self {
             case .exponentialPreset(let model):
             case .exponentialPreset(let model):
@@ -150,6 +152,8 @@ public extension InsulinModelSettings {
         switch storedSettingsInsulinModel.modelType {
         switch storedSettingsInsulinModel.modelType {
         case .fiasp:
         case .fiasp:
             self = .exponentialPreset(.fiasp)
             self = .exponentialPreset(.fiasp)
+        case .lyumjev:
+            self = .exponentialPreset(.lyumjev)
         case .rapidAdult:
         case .rapidAdult:
             self = .exponentialPreset(.rapidActingAdult)
             self = .exponentialPreset(.rapidActingAdult)
         case .rapidChild:
         case .rapidChild:
@@ -175,6 +179,8 @@ public extension StoredInsulinModel {
                 modelType = .rapidChild
                 modelType = .rapidChild
             case .fiasp:
             case .fiasp:
                 modelType = .fiasp
                 modelType = .fiasp
+            case .lyumjev:
+                modelType = .lyumjev
             }
             }
             actionDuration = preset.actionDuration
             actionDuration = preset.actionDuration
             peakActivity = preset.peakActivity
             peakActivity = preset.peakActivity

+ 5 - 0
Dependencies/LoopKit/LoopKit/InsulinKit/HKQuantitySample+InsulinKit.swift

@@ -189,6 +189,7 @@ enum InsulinTypeHealthKitRepresentation: String {
     case humalog = "Humalog"
     case humalog = "Humalog"
     case apidra = "Apidra"
     case apidra = "Apidra"
     case fiasp = "Fiasp"
     case fiasp = "Fiasp"
+    case lyumjev = "Lyumjev"
 }
 }
 
 
 extension InsulinType {
 extension InsulinType {
@@ -202,6 +203,8 @@ extension InsulinType {
             return InsulinTypeHealthKitRepresentation.apidra.rawValue
             return InsulinTypeHealthKitRepresentation.apidra.rawValue
         case .fiasp:
         case .fiasp:
             return InsulinTypeHealthKitRepresentation.fiasp.rawValue
             return InsulinTypeHealthKitRepresentation.fiasp.rawValue
+        case .lyumjev:
+            return InsulinTypeHealthKitRepresentation.lyumjev.rawValue
         }
         }
     }
     }
     
     
@@ -215,6 +218,8 @@ extension InsulinType {
             self = .apidra
             self = .apidra
         case InsulinTypeHealthKitRepresentation.fiasp.rawValue:
         case InsulinTypeHealthKitRepresentation.fiasp.rawValue:
             self = .fiasp
             self = .fiasp
+        case InsulinTypeHealthKitRepresentation.lyumjev.rawValue:
+            self = .lyumjev
         default:
         default:
             return nil
             return nil
         }
         }

+ 7 - 0
Dependencies/LoopKit/LoopKit/InsulinKit/InsulinType.swift

@@ -13,6 +13,7 @@ public enum InsulinType: Int, Codable, CaseIterable {
     case humalog
     case humalog
     case apidra
     case apidra
     case fiasp
     case fiasp
+    case lyumjev
     
     
     public var title: String {
     public var title: String {
         switch self {
         switch self {
@@ -24,6 +25,8 @@ public enum InsulinType: Int, Codable, CaseIterable {
             return LocalizedString("Apidra (insulin glulisine)", comment: "Title for Apidra insulin type")
             return LocalizedString("Apidra (insulin glulisine)", comment: "Title for Apidra insulin type")
         case .fiasp:
         case .fiasp:
             return LocalizedString("Fiasp", comment: "Title for Fiasp insulin type")
             return LocalizedString("Fiasp", comment: "Title for Fiasp insulin type")
+        case .lyumjev:
+            return LocalizedString("Lyumjev", comment: "Title for Lyumjev insulin type")
         }
         }
     }
     }
     
     
@@ -37,6 +40,8 @@ public enum InsulinType: Int, Codable, CaseIterable {
             return LocalizedString("Apidra", comment: "Brand name for apidra insulin type")
             return LocalizedString("Apidra", comment: "Brand name for apidra insulin type")
         case .fiasp:
         case .fiasp:
             return LocalizedString("Fiasp", comment: "Brand name for fiasp insulin type")
             return LocalizedString("Fiasp", comment: "Brand name for fiasp insulin type")
+        case .lyumjev:
+            return LocalizedString("Lyumjev", comment: "Brand name for lyumjev insulin type")
         }
         }
     }
     }
     
     
@@ -50,6 +55,8 @@ public enum InsulinType: Int, Codable, CaseIterable {
             return LocalizedString("Apidra (insulin glulisine) is a fast-acting insulin made by Sanofi-aventis ", comment: "Description for apidra insulin type")
             return LocalizedString("Apidra (insulin glulisine) is a fast-acting insulin made by Sanofi-aventis ", comment: "Description for apidra insulin type")
         case .fiasp:
         case .fiasp:
             return LocalizedString("Fiasp is a mealtime insulin aspart formulation with the addition of nicotinamide (vitamin B3) made by Novo Nordisk", comment: "Description for fiasp insulin type")
             return LocalizedString("Fiasp is a mealtime insulin aspart formulation with the addition of nicotinamide (vitamin B3) made by Novo Nordisk", comment: "Description for fiasp insulin type")
+        case .lyumjev:
+            return LocalizedString("Lyumjev (ultra rapid lispro) is a mealtime insulin lispro formulation with the addition of Citrat and Treprostinil made by Eli Lilly", comment: "Description for lyumjev insulin type")
         }
         }
     }
     }
 }
 }

+ 1 - 0
Dependencies/LoopKit/LoopKit/StoredInsulinModel.swift

@@ -11,6 +11,7 @@ import Foundation
 public struct StoredInsulinModel: Codable, Equatable {
 public struct StoredInsulinModel: Codable, Equatable {
     public enum ModelType: String, Codable {
     public enum ModelType: String, Codable {
         case fiasp
         case fiasp
+        case lyumjev
         case rapidAdult
         case rapidAdult
         case rapidChild
         case rapidChild
         case walsh
         case walsh

+ 38 - 0
Dependencies/LoopKit/LoopKitUI/Assets.xcassets/Lyumjev.colorset/Contents.json

@@ -0,0 +1,38 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "display-p3",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.385",
+          "green" : "0.179",
+          "red" : "0.866"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "display-p3",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.314",
+          "green" : "0.149",
+          "red" : "0.693"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 4 - 0
Dependencies/LoopKit/LoopKitUI/InsulinModelSettings+LoopKitUI.swift

@@ -37,6 +37,8 @@ public extension ExponentialInsulinModelPreset {
             return LocalizedString("Rapid-Acting – Children", comment: "Title of insulin model preset - rapid acting children")
             return LocalizedString("Rapid-Acting – Children", comment: "Title of insulin model preset - rapid acting children")
         case .fiasp:
         case .fiasp:
             return LocalizedString("Fiasp", comment: "Title of insulin model preset - fiasp")
             return LocalizedString("Fiasp", comment: "Title of insulin model preset - fiasp")
+        case .lyumjev:
+            return LocalizedString("Lyumjev", comment: "Title of insulin model preset - lyumjev")
         }
         }
     }
     }
 
 
@@ -48,6 +50,8 @@ public extension ExponentialInsulinModelPreset {
             return LocalizedString("This model assumes peak insulin activity at 65 minutes.", comment: "Subtitle of Rapid-Acting – Children preset")
             return LocalizedString("This model assumes peak insulin activity at 65 minutes.", comment: "Subtitle of Rapid-Acting – Children preset")
         case .fiasp:
         case .fiasp:
             return LocalizedString("This model assumes peak insulin activity at 55 minutes.", comment: "Subtitle of Fiasp preset")
             return LocalizedString("This model assumes peak insulin activity at 55 minutes.", comment: "Subtitle of Fiasp preset")
+        case .lyumjev:
+            return LocalizedString("This model assumes peak insulin activity at 45 minutes.", comment: "Subtitle of Lyumjev preset")
         }
         }
     }
     }
 }
 }

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

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

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

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

Разница между файлами не показана из-за своего большого размера
+ 117 - 359
FreeAPS.xcodeproj/project.pbxproj


+ 1 - 1
FreeAPS/Resources/Config.xcconfig

@@ -1 +1 @@
-BUILD_VERSION = 0.2.2
+BUILD_VERSION = 0.2.3

+ 2 - 2
FreeAPS/Sources/APS/Extensions/PumpManagerExtensions.swift

@@ -15,7 +15,7 @@ extension PumpManagerUI {
         setupViewController(
         setupViewController(
             insulinTintColor: .accentColor,
             insulinTintColor: .accentColor,
             guidanceColors: GuidanceColors(acceptable: .green, warning: .orange, critical: .red),
             guidanceColors: GuidanceColors(acceptable: .green, warning: .orange, critical: .red),
-            allowedInsulinTypes: [.apidra, .humalog, .novolog, .fiasp]
+            allowedInsulinTypes: [.apidra, .humalog, .novolog, .fiasp, .lyumjev]
         )
         )
     }
     }
 
 
@@ -23,7 +23,7 @@ extension PumpManagerUI {
         settingsViewController(
         settingsViewController(
             insulinTintColor: .accentColor,
             insulinTintColor: .accentColor,
             guidanceColors: GuidanceColors(acceptable: .green, warning: .orange, critical: .red),
             guidanceColors: GuidanceColors(acceptable: .green, warning: .orange, critical: .red),
-            allowedInsulinTypes: [.apidra, .humalog, .novolog, .fiasp]
+            allowedInsulinTypes: [.apidra, .humalog, .novolog, .fiasp, .lyumjev]
         )
         )
     }
     }
 }
 }

+ 26 - 38
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -1,33 +1,33 @@
 import SwiftUI
 import SwiftUI
 import Swinject
 import Swinject
 
 
-private let dependencies: [DependeciesContainer.Type] = [
-    StorageContainer.self,
-    ServiceContainer.self,
-    APSContainer.self,
-    UIContainer.self,
-    NetworkContainer.self,
-    SecurityContainer.self
-]
-
-private extension Swinject.Resolver {
-    func setup() {
-        for dep in dependencies {
-            dep.setup()
-        }
-    }
-}
-
 @main struct FreeAPSApp: App {
 @main struct FreeAPSApp: App {
     @Environment(\.scenePhase) var scenePhase
     @Environment(\.scenePhase) var scenePhase
 
 
-    static let resolver = Container(defaultObjectScope: .container) { container in
-        for dep in dependencies {
-            dep.register(container: container)
-        }
-    }.synchronize()
+    // Dependencies Assembler
+    // contain all dependencies Assemblies
+    // TODO: Remove static key after update "Use Dependencies" logic
+    private static var assembler = Assembler([
+        StorageAssembly(),
+        ServiceAssembly(),
+        APSAssembly(),
+        NetworkAssembly(),
+        UIAssembly(),
+        SecurityAssembly()
+    ], parent: nil, defaultObjectScope: .container)
+
+    var resolver: Resolver {
+        FreeAPSApp.assembler.resolver
+    }
 
 
-    private static func loadServices() {
+    // Temp static var
+    // Use to backward compatibility with old Dependencies logic on Logger
+    // TODO: Remove var after update "Use Dependencies" logic in Logger
+    static var resolver: Resolver {
+        FreeAPSApp.assembler.resolver
+    }
+
+    private func loadServices() {
         resolver.resolve(AppearanceManager.self)!.setupGlobalAppearance()
         resolver.resolve(AppearanceManager.self)!.setupGlobalAppearance()
         _ = resolver.resolve(DeviceDataManager.self)!
         _ = resolver.resolve(DeviceDataManager.self)!
         _ = resolver.resolve(APSManager.self)!
         _ = resolver.resolve(APSManager.self)!
@@ -37,27 +37,15 @@ private extension Swinject.Resolver {
     }
     }
 
 
     init() {
     init() {
-        FreeAPSApp.resolver.setup()
-        FreeAPSApp.loadServices()
+        loadServices()
     }
     }
 
 
-    private let mainView = Main.Builder(resolver: FreeAPSApp.resolver).buildView()
-
     var body: some Scene {
     var body: some Scene {
         WindowGroup {
         WindowGroup {
-            mainView
+            Main.RootView(resolver: resolver)
         }
         }
         .onChange(of: scenePhase) { newScenePhase in
         .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)")
         }
         }
     }
     }
 }
 }

+ 12 - 0
FreeAPS/Sources/Assemblies/APSAssembly.swift

@@ -0,0 +1,12 @@
+import Foundation
+import Swinject
+
+final class APSAssembly: Assembly {
+    func assemble(container: Container) {
+        container.register(DeviceDataManager.self) { r in BaseDeviceDataManager(resolver: r) }
+        container.register(APSManager.self) { r in BaseAPSManager(resolver: r) }
+        container.register(FetchGlucoseManager.self) { r in BaseFetchGlucoseManager(resolver: r) }
+        container.register(FetchTreatmentsManager.self) { r in BaseFetchTreatmentsManager(resolver: r) }
+        container.register(FetchAnnouncementsManager.self) { r in BaseFetchAnnouncementsManager(resolver: r) }
+    }
+}

+ 13 - 0
FreeAPS/Sources/Assemblies/NetworkAssembly.swift

@@ -0,0 +1,13 @@
+import Alamofire
+import Foundation
+import Swinject
+
+final class NetworkAssembly: Assembly {
+    func assemble(container: Container) {
+        container.register(ReachabilityManager.self) { _ in
+            NetworkReachabilityManager()!
+        }
+
+        container.register(NightscoutManager.self) { r in BaseNightscoutManager(resolver: r) }
+    }
+}

+ 8 - 0
FreeAPS/Sources/Assemblies/SecurityAssembly.swift

@@ -0,0 +1,8 @@
+import Foundation
+import Swinject
+
+final class SecurityAssembly: Assembly {
+    func assemble(container: Container) {
+        container.register(UnlockManager.self) { _ in BaseUnlockManager() }
+    }
+}

+ 2 - 4
FreeAPS/Sources/Containers/ServiceContainer.swift

@@ -1,10 +1,8 @@
 import Foundation
 import Foundation
 import Swinject
 import Swinject
 
 
-private let resolver = FreeAPSApp.resolver
-
-enum ServiceContainer: DependeciesContainer {
-    static func register(container: Container) {
+final class ServiceAssembly: Assembly {
+    func assemble(container: Container) {
         container.register(NotificationCenter.self) { _ in Foundation.NotificationCenter.default }
         container.register(NotificationCenter.self) { _ in Foundation.NotificationCenter.default }
         container.register(Broadcaster.self) { _ in BaseBroadcaster() }
         container.register(Broadcaster.self) { _ in BaseBroadcaster() }
         container.register(GroupedIssueReporter.self) { _ in
         container.register(GroupedIssueReporter.self) { _ in

+ 18 - 0
FreeAPS/Sources/Assemblies/StorageAssembly.swift

@@ -0,0 +1,18 @@
+import Foundation
+import Swinject
+
+final class StorageAssembly: Assembly {
+    func assemble(container: Container) {
+        container.register(FileManager.self) { _ in
+            Foundation.FileManager.default
+        }
+        container.register(FileStorage.self) { _ in BaseFileStorage() }
+        container.register(PumpHistoryStorage.self) { r in BasePumpHistoryStorage(resolver: r) }
+        container.register(GlucoseStorage.self) { r in BaseGlucoseStorage(resolver: r) }
+        container.register(TempTargetsStorage.self) { r in BaseTempTargetsStorage(resolver: r) }
+        container.register(CarbsStorage.self) { r in BaseCarbsStorage(resolver: r) }
+        container.register(AnnouncementsStorage.self) { r in BaseAnnouncementsStorage(resolver: r) }
+        container.register(SettingsManager.self) { r in BaseSettingsManager(resolver: r) }
+        container.register(Keychain.self) { _ in BaseKeychain() }
+    }
+}

+ 9 - 0
FreeAPS/Sources/Assemblies/UIAssembly.swift

@@ -0,0 +1,9 @@
+import Foundation
+import Swinject
+
+final class UIAssembly: Assembly {
+    func assemble(container: Container) {
+        container.register(AppearanceManager.self) { _ in BaseAppearanceManager() }
+        container.register(Router.self) { r in BaseRouter(resolver: r) }
+    }
+}

+ 0 - 14
FreeAPS/Sources/Containers/APSContainer.swift

@@ -1,14 +0,0 @@
-import Foundation
-import Swinject
-
-private let resolver = FreeAPSApp.resolver
-
-enum APSContainer: DependeciesContainer {
-    static func register(container: Container) {
-        container.register(DeviceDataManager.self) { _ in BaseDeviceDataManager(resolver: resolver) }
-        container.register(APSManager.self) { _ in BaseAPSManager(resolver: resolver) }
-        container.register(FetchGlucoseManager.self) { _ in BaseFetchGlucoseManager(resolver: resolver) }
-        container.register(FetchTreatmentsManager.self) { _ in BaseFetchTreatmentsManager(resolver: resolver) }
-        container.register(FetchAnnouncementsManager.self) { _ in BaseFetchAnnouncementsManager(resolver: resolver) }
-    }
-}

+ 0 - 10
FreeAPS/Sources/Containers/DependeciesContainer.swift

@@ -1,10 +0,0 @@
-import Swinject
-
-protocol DependeciesContainer {
-    static func register(container: Container)
-    static func setup()
-}
-
-extension DependeciesContainer {
-    static func setup() {}
-}

+ 0 - 16
FreeAPS/Sources/Containers/NetworkContainer.swift

@@ -1,16 +0,0 @@
-import Alamofire
-import Swinject
-import UIKit
-
-private let resolver = FreeAPSApp.resolver
-
-enum NetworkContainer: DependeciesContainer {
-    static func register(container: Container) {
-        container.register(ReachabilityManager.self) { _ in
-            NetworkReachabilityManager()!
-        }.inObjectScope(.transient)
-
-        container.register(NightscoutManager.self) { _ in BaseNightscoutManager(resolver: resolver) }
-        container.register(AuthorizationManager.self) { _ in BaseAuthorizationManager(resolver: resolver) }
-    }
-}

+ 0 - 9
FreeAPS/Sources/Containers/SecurityContainer.swift

@@ -1,9 +0,0 @@
-import Swinject
-
-private let resolver = FreeAPSApp.resolver
-
-enum SecurityContainer: DependeciesContainer {
-    static func register(container: Container) {
-        container.register(UnlockManager.self) { _ in BaseUnlockManager() }
-    }
-}

+ 0 - 21
FreeAPS/Sources/Containers/StorageContainer.swift

@@ -1,21 +0,0 @@
-import Foundation
-import Swinject
-
-private let resolver = FreeAPSApp.resolver
-
-enum StorageContainer: DependeciesContainer {
-    static func register(container: Container) {
-        container.register(FileManager.self) { _ in
-            Foundation.FileManager.default
-        }
-        container.register(FileStorage.self) { _ in BaseFileStorage() }
-        container.register(PumpHistoryStorage.self) { _ in BasePumpHistoryStorage(resolver: resolver) }
-        container.register(GlucoseStorage.self) { _ in BaseGlucoseStorage(resolver: resolver) }
-        container.register(TempTargetsStorage.self) { _ in BaseTempTargetsStorage(resolver: resolver) }
-        container.register(CarbsStorage.self) { _ in BaseCarbsStorage(resolver: resolver) }
-        container.register(AnnouncementsStorage.self) { _ in BaseAnnouncementsStorage(resolver: resolver) }
-        container.register(SettingsManager.self) { _ in BaseSettingsManager(resolver: resolver) }
-
-        container.register(Keychain.self) { _ in BaseKeychain() }
-    }
-}

+ 0 - 10
FreeAPS/Sources/Containers/UIContainer.swift

@@ -1,10 +0,0 @@
-import Swinject
-
-private let resolver = FreeAPSApp.resolver
-
-enum UIContainer: DependeciesContainer {
-    static func register(container: Container) {
-        container.register(AppearanceManager.self) { _ in BaseAppearanceManager() }
-        container.register(Router.self) { _ in BaseRouter(resolver: resolver) }
-    }
-}

+ 1 - 1
FreeAPS/Sources/Models/Preferences.swift

@@ -17,7 +17,7 @@ struct Preferences: JSON {
     var maxCOB: Decimal = 120
     var maxCOB: Decimal = 120
     var wideBGTargetRange: Bool = false
     var wideBGTargetRange: Bool = false
     var skipNeutralTemps: Bool = false
     var skipNeutralTemps: Bool = false
-    var unsuspendIfNoTemp: Bool = true
+    var unsuspendIfNoTemp: Bool = false
     var bolusSnoozeDIADivisor: Decimal = 2
     var bolusSnoozeDIADivisor: Decimal = 2
     var min5mCarbimpact: Decimal = 8
     var min5mCarbimpact: Decimal = 8
     var autotuneISFAdjustmentFraction: Decimal = 1.0
     var autotuneISFAdjustmentFraction: Decimal = 1.0

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

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

@@ -1,8 +1,10 @@
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 extension AddCarbs {
 extension AddCarbs {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
 
         private var formatter: NumberFormatter {
         private var formatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -13,7 +15,7 @@ extension AddCarbs {
 
 
         var body: some View {
         var body: some View {
             Form {
             Form {
-                if let carbsReq = viewModel.carbsRequired {
+                if let carbsReq = state.carbsRequired {
                     Section {
                     Section {
                         HStack {
                         HStack {
                             Text("Carbs required")
                             Text("Carbs required")
@@ -26,21 +28,22 @@ extension AddCarbs {
                     HStack {
                     HStack {
                         Text("Amount")
                         Text("Amount")
                         Spacer()
                         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)
                         Text("grams").foregroundColor(.secondary)
                     }
                     }
-                    DatePicker("Date", selection: $viewModel.date)
+                    DatePicker("Date", selection: $state.date)
                 }
                 }
 
 
                 Section {
                 Section {
-                    Button { viewModel.add() }
+                    Button { state.add() }
                     label: { Text("Add") }
                     label: { Text("Add") }
-                        .disabled(viewModel.carbs <= 0)
+                        .disabled(state.carbs <= 0)
                 }
                 }
             }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Add Carbs")
             .navigationTitle("Add Carbs")
             .navigationBarTitleDisplayMode(.automatic)
             .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
 import SwiftUI
 
 
 extension AddTempTarget {
 extension AddTempTarget {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: AddTempTargetProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Injected() private var storage: TempTargetsStorage!
         @Injected() private var storage: TempTargetsStorage!
         @Injected() private var settingsManager: SettingsManager!
         @Injected() private var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!
         @Injected() var apsManager: APSManager!

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

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

+ 0 - 3
FreeAPS/Sources/Modules/AuthorizedRoot/AuthorizedRootBuilder.swift

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

+ 0 - 7
FreeAPS/Sources/Modules/AuthorizedRoot/AuthorizedRootDataFlow.swift

@@ -1,7 +0,0 @@
-import SwiftUI
-
-enum AuthorizedRoot {
-    enum Config {}
-}
-
-protocol AuthorizedRootProvider: Provider {}

+ 0 - 3
FreeAPS/Sources/Modules/AuthorizedRoot/AuthorizedRootProvider.swift

@@ -1,3 +0,0 @@
-extension AuthorizedRoot {
-    final class Provider: BaseProvider, AuthorizedRootProvider {}
-}

+ 0 - 10
FreeAPS/Sources/Modules/AuthorizedRoot/AuthorizedRootViewModel.swift

@@ -1,10 +0,0 @@
-import SwiftUI
-import Swinject
-
-extension AuthorizedRoot {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: AuthorizedRootProvider {
-        override func subscribe() {}
-
-        lazy var rootView: some View = { router.view(for: .home) }()
-    }
-}

+ 0 - 13
FreeAPS/Sources/Modules/AuthorizedRoot/View/AuthotizedRootRootView.swift

@@ -1,13 +0,0 @@
-import SwiftUI
-
-extension AuthorizedRoot {
-    struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
-
-        var body: some View {
-            NavigationView {
-                viewModel.rootView
-            }
-        }
-    }
-}

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

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

@@ -1,8 +1,10 @@
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 extension AutotuneConfig {
 extension AutotuneConfig {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
 
         private var isfFormatter: NumberFormatter {
         private var isfFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -28,20 +30,20 @@ extension AutotuneConfig {
         var body: some View {
         var body: some View {
             Form {
             Form {
                 Section {
                 Section {
-                    Toggle("Use Autotune", isOn: $viewModel.useAutotune)
+                    Toggle("Use Autotune", isOn: $state.useAutotune)
                 }
                 }
 
 
                 Section {
                 Section {
                     HStack {
                     HStack {
                         Text("Last run")
                         Text("Last run")
                         Spacer()
                         Spacer()
-                        Text(dateFormatter.string(from: viewModel.publishedDate))
+                        Text(dateFormatter.string(from: state.publishedDate))
                     }
                     }
-                    Button { viewModel.run() }
+                    Button { state.run() }
                     label: { Text("Run now") }
                     label: { Text("Run now") }
                 }
                 }
 
 
-                if let autotune = viewModel.autotune {
+                if let autotune = state.autotune {
                     Section {
                     Section {
                         HStack {
                         HStack {
                             Text("Carb ratio")
                             Text("Carb ratio")
@@ -52,12 +54,12 @@ extension AutotuneConfig {
                         HStack {
                         HStack {
                             Text("Sensitivity")
                             Text("Sensitivity")
                             Spacer()
                             Spacer()
-                            if viewModel.units == .mmolL {
+                            if state.units == .mmolL {
                                 Text(isfFormatter.string(from: autotune.sensitivity.asMmolL as NSNumber) ?? "0")
                                 Text(isfFormatter.string(from: autotune.sensitivity.asMmolL as NSNumber) ?? "0")
                             } else {
                             } else {
                                 Text(isfFormatter.string(from: autotune.sensitivity as NSNumber) ?? "0")
                                 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 {
                     Section {
-                        Button { viewModel.delete() }
+                        Button { state.delete() }
                         label: { Text("Delete autotune data") }
                         label: { Text("Delete autotune data") }
                             .foregroundColor(.red)
                             .foregroundColor(.red)
                     }
                     }
                 }
                 }
             }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Autotune")
             .navigationTitle("Autotune")
             .navigationBarTitleDisplayMode(.automatic)
             .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
 import SwiftUI
 
 
 extension BasalProfileEditor {
 extension BasalProfileEditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: BasalProfileEditorProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Published var syncInProgress = false
         @Published var syncInProgress = false
         @Published var items: [Item] = []
         @Published var items: [Item] = []
 
 

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

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

+ 0 - 1
FreeAPS/Sources/Modules/Base/BaseProvider.swift

@@ -8,7 +8,6 @@ protocol Provider {
 
 
 class BaseProvider: Provider, Injectable {
 class BaseProvider: Provider, Injectable {
     var lifetime = Lifetime()
     var lifetime = Lifetime()
-    @Injected() var authorizationManager: AuthorizationManager!
     @Injected() var deviceManager: DeviceDataManager!
     @Injected() var deviceManager: DeviceDataManager!
     @Injected() var storage: FileStorage!
     @Injected() var storage: FileStorage!
 
 

+ 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 SwiftUI
+import Swinject
 
 
 protocol BaseView: View {
 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 {
 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
 import Swinject
 
 
 extension Bolus {
 extension Bolus {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: BolusProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Injected() var unlockmanager: UnlockManager!
         @Injected() var unlockmanager: UnlockManager!
         @Injected() var apsManager: APSManager!
         @Injected() var apsManager: APSManager!
         @Injected() var broadcaster: Broadcaster!
         @Injected() var broadcaster: Broadcaster!
@@ -11,18 +11,8 @@ extension Bolus {
         @Published var amount: Decimal = 0
         @Published var amount: Decimal = 0
         @Published var inslinRecommended: Decimal = 0
         @Published var inslinRecommended: Decimal = 0
         @Published var inslinRequired: 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() {
         override func subscribe() {
             setupInsulinRequired()
             setupInsulinRequired()
@@ -51,7 +41,8 @@ extension Bolus {
             let maxAmount = Double(min(amount, provider.pumpSettings().maxBolus))
             let maxAmount = Double(min(amount, provider.pumpSettings().maxBolus))
 
 
             unlockmanager.unlock()
             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.apsManager.enactBolus(amount: maxAmount, isSMB: false)
                     self.showModal(for: nil)
                     self.showModal(for: nil)
                 }
                 }
@@ -92,7 +83,7 @@ extension Bolus {
     }
     }
 }
 }
 
 
-extension Bolus.ViewModel: SuggestionObserver {
+extension Bolus.StateModel: SuggestionObserver {
     func suggestionDidUpdate(_: Suggestion) {
     func suggestionDidUpdate(_: Suggestion) {
         DispatchQueue.main.async {
         DispatchQueue.main.async {
             self.waitForSuggestion = false
             self.waitForSuggestion = false

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

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

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

@@ -1,37 +1,40 @@
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 extension CGM {
 extension CGM {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
 
         var body: some View {
         var body: some View {
             Form {
             Form {
                 Section {
                 Section {
-                    Picker("Type", selection: $viewModel.cgm) {
+                    Picker("Type", selection: $state.cgm) {
                         ForEach(CGMType.allCases) {
                         ForEach(CGMType.allCases) {
                             Text($0.displayName).tag($0)
                             Text($0.displayName).tag($0)
                         }
                         }
                     }
                     }
                 }
                 }
-                if [.dexcomG5, .dexcomG6].contains(viewModel.cgm) {
+                if [.dexcomG5, .dexcomG6].contains(state.cgm) {
                     Section(header: Text("Transmitter ID")) {
                     Section(header: Text("Transmitter ID")) {
-                        TextField("XXXXXX", text: $viewModel.transmitterID, onCommit: {
+                        TextField("XXXXXX", text: $state.transmitterID, onCommit: {
                             UIApplication.shared.endEditing()
                             UIApplication.shared.endEditing()
-                            viewModel.onChangeID()
+                            state.onChangeID()
                         })
                         })
                             .disableAutocorrection(true)
                             .disableAutocorrection(true)
                             .autocapitalization(.allCharacters)
                             .autocapitalization(.allCharacters)
                             .keyboardType(.asciiCapable)
                             .keyboardType(.asciiCapable)
                     }
                     }
                     .onDisappear {
                     .onDisappear {
-                        viewModel.onChangeID()
+                        state.onChangeID()
                     }
                     }
                 }
                 }
 
 
                 Section(header: Text("Other")) {
                 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")
             .navigationTitle("CGM")
             .navigationBarTitleDisplayMode(.automatic)
             .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
 import SwiftUI
 
 
 extension CREditor {
 extension CREditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: CREditorProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Published var items: [Item] = []
         @Published var items: [Item] = []
         @Published var autotune: Autotune?
         @Published var autotune: Autotune?
 
 

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

@@ -1,8 +1,10 @@
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 extension CREditor {
 extension CREditor {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
         @State private var editMode = EditMode.inactive
         @State private var editMode = EditMode.inactive
 
 
         private var dateFormatter: DateFormatter {
         private var dateFormatter: DateFormatter {
@@ -20,7 +22,7 @@ extension CREditor {
 
 
         var body: some View {
         var body: some View {
             Form {
             Form {
-                if let autotune = viewModel.autotune {
+                if let autotune = state.autotune {
                     Section(header: Text("Autotune")) {
                     Section(header: Text("Autotune")) {
                         HStack {
                         HStack {
                             Text("Calculated Ratio")
                             Text("Calculated Ratio")
@@ -35,13 +37,14 @@ extension CREditor {
                     addButton
                     addButton
                 }
                 }
                 Section {
                 Section {
-                    Button { viewModel.save() }
+                    Button { state.save() }
                     label: {
                     label: {
                         Text("Save")
                         Text("Save")
                     }
                     }
-                    .disabled(viewModel.items.isEmpty)
+                    .disabled(state.items.isEmpty)
                 }
                 }
             }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Carb Ratios")
             .navigationTitle("Carb Ratios")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(
             .navigationBarItems(
@@ -49,7 +52,7 @@ extension CREditor {
             )
             )
             .environment(\.editMode, $editMode)
             .environment(\.editMode, $editMode)
             .onAppear {
             .onAppear {
-                viewModel.validate()
+                state.validate()
             }
             }
         }
         }
 
 
@@ -61,12 +64,12 @@ extension CREditor {
                         Text("Time").frame(width: geometry.size.width / 2)
                         Text("Time").frame(width: geometry.size.width / 2)
                     }
                     }
                     HStack(spacing: 0) {
                     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(
                                 Text(
                                     (
                                     (
                                         self.rateFormatter
                                         self.rateFormatter
-                                            .string(from: viewModel.rateValues[i] as NSNumber) ?? ""
+                                            .string(from: state.rateValues[i] as NSNumber) ?? ""
                                     ) + " g/U"
                                     ) + " g/U"
                                 ).tag(i)
                                 ).tag(i)
                             }
                             }
@@ -74,12 +77,12 @@ extension CREditor {
                         .frame(maxWidth: geometry.size.width / 2)
                         .frame(maxWidth: geometry.size.width / 2)
                         .clipped()
                         .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(
                                 Text(
                                     self.dateFormatter
                                     self.dateFormatter
                                         .string(from: Date(
                                         .string(from: Date(
-                                            timeIntervalSince1970: viewModel
+                                            timeIntervalSince1970: state
                                                 .timeValues[i]
                                                 .timeValues[i]
                                         ))
                                         ))
                                 ).tag(i)
                                 ).tag(i)
@@ -94,17 +97,17 @@ extension CREditor {
 
 
         private var list: some View {
         private var list: some View {
             List {
             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)) {
                     NavigationLink(destination: pickers(for: index)) {
                         HStack {
                         HStack {
                             Text("Ratio").foregroundColor(.secondary)
                             Text("Ratio").foregroundColor(.secondary)
                             Text(
                             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()
                             Spacer()
                             Text("starts at").foregroundColor(.secondary)
                             Text("starts at").foregroundColor(.secondary)
                             Text(
                             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 {
         private var addButton: some View {
-            guard viewModel.canAdd else {
+            guard state.canAdd else {
                 return AnyView(EmptyView())
                 return AnyView(EmptyView())
             }
             }
 
 
@@ -128,12 +131,12 @@ extension CREditor {
         }
         }
 
 
         func onAdd() {
         func onAdd() {
-            viewModel.add()
+            state.add()
         }
         }
 
 
         private func onDelete(offsets: IndexSet) {
         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 {
+    final 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 SwiftUI
+import Swinject
 
 
 extension ConfigEditor {
 extension ConfigEditor {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        let file: String
+        @StateObject var state = StateModel()
         @State private var showShareSheet = false
         @State private var showShareSheet = false
 
 
         var body: some View {
         var body: some View {
-            TextEditor(text: $viewModel.configText)
+            TextEditor(text: $state.configText)
                 .keyboardType(.asciiCapable)
                 .keyboardType(.asciiCapable)
                 .font(.system(.subheadline, design: .monospaced))
                 .font(.system(.subheadline, design: .monospaced))
                 .allowsTightening(true)
                 .allowsTightening(true)
@@ -22,12 +25,17 @@ extension ConfigEditor {
                     }
                     }
                 }
                 }
                 .navigationBarItems(
                 .navigationBarItems(
-                    trailing: Button("Save", action: viewModel.save)
+                    trailing: Button("Save", action: state.save)
                 )
                 )
                 .sheet(isPresented: $showShareSheet) {
                 .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)
                 .navigationBarTitleDisplayMode(.inline)
                 .padding()
                 .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
 import SwiftUI
 
 
 extension DataTable {
 extension DataTable {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: DataTableProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Injected() var broadcaster: Broadcaster!
         @Injected() var broadcaster: Broadcaster!
         @Injected() var settingsManager: SettingsManager!
         @Injected() var settingsManager: SettingsManager!
         @Published var items: [Item] = []
         @Published var items: [Item] = []
@@ -83,7 +83,7 @@ extension DataTable {
     }
     }
 }
 }
 
 
-extension DataTable.ViewModel:
+extension DataTable.StateModel:
     SettingsObserver,
     SettingsObserver,
     PumpHistoryObserver,
     PumpHistoryObserver,
     TempTargetsObserver,
     TempTargetsObserver,

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

@@ -1,8 +1,10 @@
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 extension DataTable {
 extension DataTable {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
         @State private var isRemoveCarbsAlertPresented = false
         @State private var isRemoveCarbsAlertPresented = false
         @State private var removeCarbsAlert: Alert?
         @State private var removeCarbsAlert: Alert?
 
 
@@ -16,16 +18,17 @@ extension DataTable {
             Form {
             Form {
                 list
                 list
             }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("History")
             .navigationTitle("History")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(
             .navigationBarItems(
-                leading: Button("Close", action: viewModel.hideModal)
+                leading: Button("Close", action: state.hideModal)
             )
             )
         }
         }
 
 
         private var list: some View {
         private var list: some View {
             List {
             List {
-                ForEach(viewModel.items.indexed(), id: \.1.id) { _, item in
+                ForEach(state.items.indexed(), id: \.1.id) { _, item in
                     HStack {
                     HStack {
                         Image(systemName: "circle.fill").foregroundColor(item.color)
                         Image(systemName: "circle.fill").foregroundColor(item.color)
                         Text(dateFormatter.string(from: item.date))
                         Text(dateFormatter.string(from: item.date))
@@ -47,7 +50,7 @@ extension DataTable {
                                         message: Text(item.amountText),
                                         message: Text(item.amountText),
                                         primaryButton: .destructive(
                                         primaryButton: .destructive(
                                             Text("Delete"),
                                             Text("Delete"),
-                                            action: { viewModel.deleteCarbs(at: item.date) }
+                                            action: { state.deleteCarbs(at: item.date) }
                                         ),
                                         ),
                                         secondaryButton: .cancel()
                                         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
 import SwiftUI
 
 
 extension Home {
 extension Home {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: HomeProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Injected() var broadcaster: Broadcaster!
         @Injected() var broadcaster: Broadcaster!
         @Injected() var settingsManager: SettingsManager!
         @Injected() var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!
         @Injected() var apsManager: APSManager!
@@ -281,7 +281,7 @@ extension Home {
     }
     }
 }
 }
 
 
-extension Home.ViewModel:
+extension Home.StateModel:
     GlucoseObserver,
     GlucoseObserver,
     SuggestionObserver,
     SuggestionObserver,
     SettingsObserver,
     SettingsObserver,
@@ -346,7 +346,7 @@ extension Home.ViewModel:
     }
     }
 }
 }
 
 
-extension Home.ViewModel: CompletionDelegate {
+extension Home.StateModel: CompletionDelegate {
     func completionNotifyingDidComplete(_: CompletionNotifying) {
     func completionNotifyingDidComplete(_: CompletionNotifying) {
         setupPump = false
         setupPump = false
     }
     }

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

@@ -1,9 +1,12 @@
 import SwiftDate
 import SwiftDate
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 extension Home {
 extension Home {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+
+        @StateObject var state = StateModel()
         @State var isStatusPopupPresented = false
         @State var isStatusPopupPresented = false
 
 
         private var numberFormatter: NumberFormatter {
         private var numberFormatter: NumberFormatter {
@@ -33,7 +36,7 @@ extension Home {
                     HStack {
                     HStack {
                         Text("IOB").font(.caption2).foregroundColor(.secondary)
                         Text("IOB").font(.caption2).foregroundColor(.secondary)
                         Text(
                         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")
                                 NSLocalizedString(" U", comment: "Insulin unit")
                         )
                         )
                         .font(.system(size: 12, weight: .bold))
                         .font(.system(size: 12, weight: .bold))
@@ -41,7 +44,7 @@ extension Home {
                     HStack {
                     HStack {
                         Text("COB").font(.caption2).foregroundColor(.secondary)
                         Text("COB").font(.caption2).foregroundColor(.secondary)
                         Text(
                         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")
                                 NSLocalizedString(" g", comment: "gram of carbs")
                         )
                         )
                         .font(.system(size: 12, weight: .bold))
                         .font(.system(size: 12, weight: .bold))
@@ -50,41 +53,41 @@ extension Home {
                 Spacer()
                 Spacer()
 
 
                 CurrentGlucoseView(
                 CurrentGlucoseView(
-                    recentGlucose: $viewModel.recentGlucose,
-                    delta: $viewModel.glucoseDelta,
-                    units: $viewModel.units
+                    recentGlucose: $state.recentGlucose,
+                    delta: $state.glucoseDelta,
+                    units: $state.units
                 )
                 )
                 .onTapGesture {
                 .onTapGesture {
-                    viewModel.openCGM()
+                    state.openCGM()
                 }
                 }
                 Spacer()
                 Spacer()
                 PumpView(
                 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 {
                 .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()
                 Spacer()
                 LoopView(
                 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 {
                 ).onTapGesture {
                     isStatusPopupPresented = true
                     isStatusPopupPresented = true
                 }.onLongPressGesture {
                 }.onLongPressGesture {
-                    viewModel.runLoop()
+                    state.runLoop()
                 }
                 }
                 Spacer()
                 Spacer()
             }.frame(maxWidth: .infinity)
             }.frame(maxWidth: .infinity)
@@ -92,11 +95,11 @@ extension Home {
 
 
         var infoPanal: some View {
         var infoPanal: some View {
             HStack(alignment: .center) {
             HStack(alignment: .center) {
-                if viewModel.pumpSuspended {
+                if state.pumpSuspended {
                     Text("Pump suspended")
                     Text("Pump suspended")
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.loopGray)
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.loopGray)
                         .padding(.leading, 8)
                         .padding(.leading, 8)
-                } else if let tempRate = viewModel.tempRate {
+                } else if let tempRate = state.tempRate {
                     Text(
                     Text(
                         (numberFormatter.string(from: tempRate as NSNumber) ?? "0") +
                         (numberFormatter.string(from: tempRate as NSNumber) ?? "0") +
                             NSLocalizedString(" U/hr", comment: "Unit per hour with space")
                             NSLocalizedString(" U/hr", comment: "Unit per hour with space")
@@ -105,9 +108,9 @@ extension Home {
                     .padding(.leading, 8)
                     .padding(.leading, 8)
                 }
                 }
 
 
-                if let tempTarget = viewModel.tempTarget {
+                if let tempTarget = state.tempTarget {
                     Text(tempTarget.displayName).font(.caption).foregroundColor(.secondary)
                     Text(tempTarget.displayName).font(.caption).foregroundColor(.secondary)
-                    if viewModel.units == .mmolL {
+                    if state.units == .mmolL {
                         Text(
                         Text(
                             targetFormatter
                             targetFormatter
                                 .string(from: (tempTarget.targetBottom?.asMmolL ?? 0) as NSNumber)!
                                 .string(from: (tempTarget.targetBottom?.asMmolL ?? 0) as NSNumber)!
@@ -120,12 +123,12 @@ extension Home {
                             Text(
                             Text(
                                 targetFormatter
                                 targetFormatter
                                     .string(from: (tempTarget.targetTop?.asMmolL ?? 0) as NSNumber)! +
                                     .string(from: (tempTarget.targetTop?.asMmolL ?? 0) as NSNumber)! +
-                                    " \(viewModel.units.rawValue)"
+                                    " \(state.units.rawValue)"
                             )
                             )
                             .font(.caption)
                             .font(.caption)
                             .foregroundColor(.secondary)
                             .foregroundColor(.secondary)
                         } else {
                         } else {
-                            Text(viewModel.units.rawValue).font(.caption)
+                            Text(state.units.rawValue).font(.caption)
                                 .foregroundColor(.secondary)
                                 .foregroundColor(.secondary)
                         }
                         }
 
 
@@ -138,25 +141,25 @@ extension Home {
                                 .foregroundColor(.secondary)
                                 .foregroundColor(.secondary)
                             Text(
                             Text(
                                 targetFormatter
                                 targetFormatter
-                                    .string(from: (tempTarget.targetTop ?? 0) as NSNumber)! + " \(viewModel.units.rawValue)"
+                                    .string(from: (tempTarget.targetTop ?? 0) as NSNumber)! + " \(state.units.rawValue)"
                             )
                             )
                             .font(.caption)
                             .font(.caption)
                             .foregroundColor(.secondary)
                             .foregroundColor(.secondary)
                         } else {
                         } else {
-                            Text(viewModel.units.rawValue).font(.caption)
+                            Text(state.units.rawValue).font(.caption)
                                 .foregroundColor(.secondary)
                                 .foregroundColor(.secondary)
                         }
                         }
                     }
                     }
                 }
                 }
                 Spacer()
                 Spacer()
-                if let progress = viewModel.bolusProgress {
+                if let progress = state.bolusProgress {
                     Text("Bolusing")
                     Text("Bolusing")
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
                     ProgressView(value: Double(progress))
                     ProgressView(value: Double(progress))
                         .progressViewStyle(BolusProgressViewStyle())
                         .progressViewStyle(BolusProgressViewStyle())
                         .padding(.trailing, 8)
                         .padding(.trailing, 8)
                         .onTapGesture {
                         .onTapGesture {
-                            viewModel.cancelBolus()
+                            state.cancelBolus()
                         }
                         }
                 }
                 }
             }
             }
@@ -195,10 +198,10 @@ extension Home {
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.uam)
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.uam)
                 }
                 }
 
 
-                if let eventualBG = viewModel.eventualBG {
+                if let eventualBG = state.eventualBG {
                     Text(
                     Text(
                         "⇢ " + numberFormatter.string(
                         "⇢ " + 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)
                     .font(.system(size: 12, weight: .bold)).foregroundColor(.secondary)
@@ -217,19 +220,19 @@ extension Home {
 
 
                     infoPanal
                     infoPanal
                     MainChartView(
                     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)
                     .padding(.bottom)
                     .modal(for: .dataTable, from: self)
                     .modal(for: .dataTable, from: self)
@@ -240,7 +243,7 @@ extension Home {
                         Rectangle().fill(Color.gray.opacity(0.2)).frame(height: 50 + geo.safeAreaInsets.bottom)
                         Rectangle().fill(Color.gray.opacity(0.2)).frame(height: 50 + geo.safeAreaInsets.bottom)
 
 
                         HStack {
                         HStack {
-                            Button { viewModel.showModal(for: .addCarbs) }
+                            Button { state.showModal(for: .addCarbs) }
                             label: {
                             label: {
                                 ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
                                 ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
                                     Image("carbs")
                                     Image("carbs")
@@ -249,7 +252,7 @@ extension Home {
                                         .frame(width: 24, height: 24)
                                         .frame(width: 24, height: 24)
                                         .foregroundColor(.loopGreen)
                                         .foregroundColor(.loopGreen)
                                         .padding(8)
                                         .padding(8)
-                                    if let carbsReq = viewModel.carbsRequired {
+                                    if let carbsReq = state.carbsRequired {
                                         Text(numberFormatter.string(from: carbsReq as NSNumber)!)
                                         Text(numberFormatter.string(from: carbsReq as NSNumber)!)
                                             .font(.caption)
                                             .font(.caption)
                                             .foregroundColor(.white)
                                             .foregroundColor(.white)
@@ -259,7 +262,7 @@ extension Home {
                                 }
                                 }
                             }
                             }
                             Spacer()
                             Spacer()
-                            Button { viewModel.showModal(for: .addTempTarget) }
+                            Button { state.showModal(for: .addTempTarget) }
                             label: {
                             label: {
                                 Image("target")
                                 Image("target")
                                     .renderingMode(.template)
                                     .renderingMode(.template)
@@ -268,7 +271,7 @@ extension Home {
                                     .padding(8)
                                     .padding(8)
                             }.foregroundColor(.loopYellow)
                             }.foregroundColor(.loopYellow)
                             Spacer()
                             Spacer()
-                            Button { viewModel.showModal(for: .bolus(waitForDuggestion: false)) }
+                            Button { state.showModal(for: .bolus(waitForDuggestion: false)) }
                             label: {
                             label: {
                                 Image("bolus")
                                 Image("bolus")
                                     .renderingMode(.template)
                                     .renderingMode(.template)
@@ -277,8 +280,8 @@ extension Home {
                                     .padding(8)
                                     .padding(8)
                             }.foregroundColor(.insulin)
                             }.foregroundColor(.insulin)
                             Spacer()
                             Spacer()
-                            if viewModel.allowManualTemp {
-                                Button { viewModel.showModal(for: .manualTempBasal) }
+                            if state.allowManualTemp {
+                                Button { state.showModal(for: .manualTempBasal) }
                                 label: {
                                 label: {
                                     Image("bolus1")
                                     Image("bolus1")
                                         .renderingMode(.template)
                                         .renderingMode(.template)
@@ -288,7 +291,7 @@ extension Home {
                                 }.foregroundColor(.insulin)
                                 }.foregroundColor(.insulin)
                                 Spacer()
                                 Spacer()
                             }
                             }
-                            Button { viewModel.showModal(for: .settings) }
+                            Button { state.showModal(for: .settings) }
                             label: {
                             label: {
                                 Image("settings1")
                                 Image("settings1")
                                     .renderingMode(.template)
                                     .renderingMode(.template)
@@ -303,16 +306,17 @@ extension Home {
                 }
                 }
                 .edgesIgnoringSafeArea(.vertical)
                 .edgesIgnoringSafeArea(.vertical)
             }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Home")
             .navigationTitle("Home")
             .navigationBarHidden(true)
             .navigationBarHidden(true)
             .ignoresSafeArea(.keyboard)
             .ignoresSafeArea(.keyboard)
             .popup(isPresented: isStatusPopupPresented, alignment: .top, direction: .top) {
             .popup(isPresented: isStatusPopupPresented, alignment: .top, direction: .top) {
                 VStack(alignment: .leading) {
                 VStack(alignment: .leading) {
-                    Text(viewModel.statusTitle).foregroundColor(.white)
+                    Text(state.statusTitle).foregroundColor(.white)
                         .padding(.bottom, 4)
                         .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)
                         Text("Error at \(dateFormatter.string(from: date))").foregroundColor(.white)
                             .padding(.bottom, 4)
                             .padding(.bottom, 4)
                             .padding(.top, 8)
                             .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
 import SwiftUI
 
 
 extension ISFEditor {
 extension ISFEditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: ISFEditorProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Injected() var settingsManager: SettingsManager!
         @Injected() var settingsManager: SettingsManager!
         @Published var items: [Item] = []
         @Published var items: [Item] = []
         private(set) var autosensISF: Decimal?
         private(set) var autosensISF: Decimal?

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

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

+ 0 - 3
FreeAPS/Sources/Modules/Login/LoginBuilder.swift

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

+ 0 - 11
FreeAPS/Sources/Modules/Login/LoginDataFlow.swift

@@ -1,11 +0,0 @@
-
-enum Login {
-    enum Config {
-        static let credentialsKey = "FreeAPS.Credentials"
-    }
-}
-
-protocol LoginProvider: Provider {
-    func authorize(credentials: Credentials)
-    var credentials: Credentials? { get }
-}

+ 0 - 19
FreeAPS/Sources/Modules/Login/LoginProvider.swift

@@ -1,19 +0,0 @@
-import AuthenticationServices
-
-extension Login {
-    final class Provider: BaseProvider, LoginProvider {
-        @Injected() private var keychain: Keychain!
-
-        func authorize(credentials: Credentials) {
-            authorizationManager.authorize(credentials: credentials)
-                .sink { _ in
-                    self.keychain.setValue(credentials, forKey: Config.credentialsKey)
-                }
-                .store(in: &lifetime)
-        }
-
-        var credentials: Credentials? {
-            keychain.getValue(Credentials.self, forKey: Config.credentialsKey)
-        }
-    }
-}

+ 0 - 23
FreeAPS/Sources/Modules/Login/LoginViewModel.swift

@@ -1,23 +0,0 @@
-import AuthenticationServices
-import SwiftUI
-
-extension Login {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: LoginProvider {
-        @Published var credentials: Credentials?
-
-        override func subscribe() {
-            credentials = provider.credentials
-
-            $credentials
-                .compactMap { $0 }
-                .sink { [weak self] in
-                    self?.provider.authorize(credentials: $0)
-                }
-                .store(in: &lifetime)
-        }
-
-        func login() {
-            credentials = Credentials()
-        }
-    }
-}

+ 0 - 24
FreeAPS/Sources/Modules/Login/View/LoginRootView.swift

@@ -1,24 +0,0 @@
-import SwiftUI
-
-extension Login {
-    struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
-
-        var body: some View {
-            VStack {
-                Text("Disclaimer").font(.title)
-                Spacer()
-                Text(
-                    "FreeAPS X is in an active development state. We do not recommend to use the system for everyday control of blood glucose! Use it for testing purposes only at your own risk. We are not responsible for your decisions and actions."
-                )
-                Spacer()
-                Button(action: viewModel.login) {
-                    Text("Agree and continue")
-                        .frame(maxWidth: .infinity)
-                        .foregroundColor(.white)
-                        .buttonBackground()
-                }
-            }.padding()
-        }
-    }
-}

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

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

+ 1 - 20
FreeAPS/Sources/Modules/Main/MainDataFlow.swift

@@ -9,25 +9,6 @@ enum Main {
 
 
         var id: Int { screen.id }
         var id: Int { screen.id }
     }
     }
-
-    enum Scene {
-        case loading
-        case authorized
-        case onboarding
-
-        var screen: Screen {
-            switch self {
-            case .loading:
-                return .loading
-            case .authorized:
-                return .authorizedRoot
-            case .onboarding:
-                return .onboarding
-            }
-        }
-    }
 }
 }
 
 
-protocol MainProvider: Provider {
-    var authorizationManager: AuthorizationManager! { get }
-}
+protocol MainProvider: Provider {}

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

@@ -1,39 +1,24 @@
-import Combine
 import SwiftUI
 import SwiftUI
 import Swinject
 import Swinject
 
 
 extension Main {
 extension Main {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: MainProvider {
-        @Published private(set) var isAuthotized = false
+    final class StateModel: BaseStateModel<Provider> {
         private(set) var modal: Modal?
         private(set) var modal: Modal?
         @Published var isModalPresented = false
         @Published var isModalPresented = false
         @Published var isAlertPresented = false
         @Published var isAlertPresented = false
         @Published var alertMessage = ""
         @Published var alertMessage = ""
-        @Published private(set) var scene: Scene = .loading
 
 
         override func subscribe() {
         override func subscribe() {
             router.mainModalScreen
             router.mainModalScreen
-                .map { $0?.modal(resolver: self.resolver) }
+                .map { $0?.modal(resolver: self.resolver!) }
                 .removeDuplicates { $0?.id == $1?.id }
                 .removeDuplicates { $0?.id == $1?.id }
-                .receive(on: RunLoop.main)
+                .receive(on: DispatchQueue.main)
                 .sink { modal in
                 .sink { modal in
                     self.modal = modal
                     self.modal = modal
                     self.isModalPresented = modal != nil
                     self.isModalPresented = modal != nil
                 }
                 }
                 .store(in: &lifetime)
                 .store(in: &lifetime)
 
 
-            provider.authorizationManager
-                .authorizationPublisher
-                .receive(on: RunLoop.main)
-                .assign(to: \.isAuthotized, on: self)
-                .store(in: &lifetime)
-
-            $isAuthotized
-                .sink { isAuthotized in
-                    self.scene = isAuthotized ? .authorized : .onboarding
-                }
-                .store(in: &lifetime)
-
             $isModalPresented
             $isModalPresented
                 .filter { !$0 }
                 .filter { !$0 }
                 .sink { _ in
                 .sink { _ in

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

@@ -1,26 +1,28 @@
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 extension Main {
 extension Main {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
-
-        @ViewBuilder func presentedView() -> some View {
-            viewModel.cachedView(for: viewModel.scene.screen)
-        }
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
 
         var body: some View {
         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())
                         .navigationViewStyle(StackNavigationViewStyle())
                 }
                 }
-                .alert(isPresented: $viewModel.isAlertPresented) {
+                .alert(isPresented: $state.isAlertPresented) {
                     Alert(
                     Alert(
                         title: Text("Important message"),
                         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
 import SwiftUI
 
 
 extension ManualTempBasal {
 extension ManualTempBasal {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: ManualTempBasalProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Injected() var apsManager: APSManager!
         @Injected() var apsManager: APSManager!
         @Published var rate: Decimal = 0
         @Published var rate: Decimal = 0
         @Published var durationIndex = 0
         @Published var durationIndex = 0

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

@@ -1,8 +1,10 @@
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 extension ManualTempBasal {
 extension ManualTempBasal {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
 
         private var formatter: NumberFormatter {
         private var formatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -17,16 +19,16 @@ extension ManualTempBasal {
                     HStack {
                     HStack {
                         Text("Amount")
                         Text("Amount")
                         Spacer()
                         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)
                         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(
                             Text(
                                 String(
                                 String(
                                     format: "%.0f h %02.0f min",
                                     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)
                             ).tag(index)
                         }
                         }
@@ -34,15 +36,16 @@ extension ManualTempBasal {
                 }
                 }
 
 
                 Section {
                 Section {
-                    Button { viewModel.enact() }
+                    Button { state.enact() }
                     label: { Text("Enact") }
                     label: { Text("Enact") }
-                    Button { viewModel.cancel() }
+                    Button { state.cancel() }
                     label: { Text("Cancel Temp Basal") }
                     label: { Text("Cancel Temp Basal") }
                 }
                 }
             }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Manual Temp Basal")
             .navigationTitle("Manual Temp Basal")
             .navigationBarTitleDisplayMode(.automatic)
             .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
 import SwiftUI
 
 
 extension NightscoutConfig {
 extension NightscoutConfig {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: NightscoutConfigProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Injected() var keychain: Keychain!
         @Injected() var keychain: Keychain!
         @Injected() var settingsManager: SettingsManager!
         @Injected() var settingsManager: SettingsManager!
 
 

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

@@ -1,8 +1,10 @@
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 extension NightscoutConfig {
 extension NightscoutConfig {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
 
         private var portFormater: NumberFormatter {
         private var portFormater: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -13,20 +15,20 @@ extension NightscoutConfig {
         var body: some View {
         var body: some View {
             Form {
             Form {
                 Section {
                 Section {
-                    TextField("URL", text: $viewModel.url)
+                    TextField("URL", text: $state.url)
                         .disableAutocorrection(true)
                         .disableAutocorrection(true)
                         .textContentType(.URL)
                         .textContentType(.URL)
                         .autocapitalization(.none)
                         .autocapitalization(.none)
                         .keyboardType(.URL)
                         .keyboardType(.URL)
-                    SecureField("API secret", text: $viewModel.secret)
+                    SecureField("API secret", text: $state.secret)
                         .disableAutocorrection(true)
                         .disableAutocorrection(true)
                         .autocapitalization(.none)
                         .autocapitalization(.none)
                         .textContentType(.password)
                         .textContentType(.password)
                         .keyboardType(.asciiCapable)
                         .keyboardType(.asciiCapable)
-                    if !viewModel.message.isEmpty {
-                        Text(viewModel.message)
+                    if !state.message.isEmpty {
+                        Text(state.message)
                     }
                     }
-                    if viewModel.connecting {
+                    if state.connecting {
                         HStack {
                         HStack {
                             Text("Connecting...")
                             Text("Connecting...")
                             Spacer()
                             Spacer()
@@ -36,24 +38,26 @@ extension NightscoutConfig {
                 }
                 }
 
 
                 Section {
                 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 {
                 Section {
-                    Toggle("Allow uploads", isOn: $viewModel.isUploadEnabled)
+                    Toggle("Allow uploads", isOn: $state.isUploadEnabled)
                 }
                 }
 
 
                 Section(header: Text("Local glucose source")) {
                 Section(header: Text("Local glucose source")) {
-                    Toggle("Use local glucose server", isOn: $viewModel.useLocalSource)
+                    Toggle("Use local glucose server", isOn: $state.useLocalSource)
                     HStack {
                     HStack {
                         Text("Port")
                         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/Onboarding/OnboardingBuilder.swift

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

+ 0 - 19
FreeAPS/Sources/Modules/Onboarding/OnboardingDataFlow.swift

@@ -1,19 +0,0 @@
-enum Onboarding {
-    enum Config {}
-
-    enum Stage {
-        case login
-        case requestPermissions
-
-        var screen: Screen {
-            switch self {
-            case .login:
-                return .login
-            case .requestPermissions:
-                return .requestPermissions
-            }
-        }
-    }
-}
-
-protocol OnboardingProvider: Provider {}

+ 0 - 3
FreeAPS/Sources/Modules/Onboarding/OnboardingProvider.swift

@@ -1,3 +0,0 @@
-extension Onboarding {
-    final class Provider: BaseProvider, OnboardingProvider {}
-}

+ 0 - 13
FreeAPS/Sources/Modules/Onboarding/OnboardingViewModel.swift

@@ -1,13 +0,0 @@
-import SwiftUI
-import Swinject
-
-extension Onboarding {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: OnboardingProvider {
-        @Published var stage: Stage
-
-        required init(provider: Provider, resolver: Resolver) {
-            stage = .login
-            super.init(provider: provider, resolver: resolver)
-        }
-    }
-}

+ 0 - 11
FreeAPS/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -1,11 +0,0 @@
-import SwiftUI
-
-extension Onboarding {
-    struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
-
-        var body: some View {
-            viewModel.view(for: viewModel.stage.screen)
-        }
-    }
-}

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

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

+ 67 - 10
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorDataFlow.swift

@@ -1,15 +1,67 @@
 import Foundation
 import Foundation
+import LoopKit
+
+protocol SettableValue {}
+extension Bool: SettableValue {}
+extension Decimal: SettableValue {}
+extension InsulinCurve: SettableValue {}
 
 
 enum PreferencesEditor {
 enum PreferencesEditor {
     enum Config {}
     enum Config {}
 
 
-    class Field<T>: Identifiable {
+    enum FieldType {
+        case boolean(keypath: WritableKeyPath<Preferences, Bool>)
+        case decimal(keypath: WritableKeyPath<Preferences, Decimal>)
+        case insulinCurve(keypath: WritableKeyPath<Preferences, InsulinCurve>)
+    }
+
+    class Field: Identifiable {
         var displayName: String
         var displayName: String
-        var keypath: WritableKeyPath<Preferences, T>
+        var type: FieldType
         var infoText: String
         var infoText: String
-        var value: T {
-            didSet {
-                settable?.onSet(keypath, value: value)
+
+        var boolValue: Bool {
+            get {
+                switch type {
+                case let .boolean(keypath):
+                    return settable?.get(keypath) ?? false
+                default: return false
+                }
+            }
+            set { set(value: newValue) }
+        }
+
+        var decimalValue: Decimal {
+            get {
+                switch type {
+                case let .decimal(keypath):
+                    return settable?.get(keypath) ?? 0
+                default: return 0
+                }
+            }
+            set { set(value: newValue) }
+        }
+
+        var insulinCurveValue: InsulinCurve {
+            get {
+                switch type {
+                case let .insulinCurve(keypath):
+                    return settable?.get(keypath) ?? .rapidActing
+                default: return .rapidActing
+                }
+            }
+            set { set(value: newValue) }
+        }
+
+        private func set<T: SettableValue>(value: T) {
+            switch (type, value) {
+            case let (.boolean(keypath), value as Bool):
+                settable?.set(keypath, value: value)
+            case let (.decimal(keypath), value as Decimal):
+                settable?.set(keypath, value: value)
+            case let (.insulinCurve(keypath), value as InsulinCurve):
+                settable?.set(keypath, value: value)
+            default: break
             }
             }
         }
         }
 
 
@@ -17,20 +69,24 @@ enum PreferencesEditor {
 
 
         init(
         init(
             displayName: String,
             displayName: String,
-            keypath: WritableKeyPath<Preferences, T>,
-            value: T,
+            type: FieldType,
             infoText: String,
             infoText: String,
             settable: PreferencesSettable? = nil
             settable: PreferencesSettable? = nil
         ) {
         ) {
             self.displayName = displayName
             self.displayName = displayName
-            self.keypath = keypath
-            self.value = value
+            self.type = type
             self.infoText = infoText
             self.infoText = infoText
             self.settable = settable
             self.settable = settable
         }
         }
 
 
         let id = UUID()
         let id = UUID()
     }
     }
+
+    struct FieldSection: Identifiable {
+        let displayName: String
+        var fields: [Field]
+        let id = UUID()
+    }
 }
 }
 
 
 protocol PreferencesEditorProvider: Provider {
 protocol PreferencesEditorProvider: Provider {
@@ -40,5 +96,6 @@ protocol PreferencesEditorProvider: Provider {
 }
 }
 
 
 protocol PreferencesSettable: AnyObject {
 protocol PreferencesSettable: AnyObject {
-    func onSet<T>(_ keypath: WritableKeyPath<Preferences, T>, value: T)
+    func set<T>(_ keypath: WritableKeyPath<Preferences, T>, value: T)
+    func get<T>(_ keypath: WritableKeyPath<Preferences, T>) -> T
 }
 }

Разница между файлами не показана из-за своего большого размера
+ 199 - 211
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorViewModel.swift


+ 52 - 28
FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift

@@ -1,4 +1,5 @@
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 struct InfoText: Identifiable {
 struct InfoText: Identifiable {
     var id: String { description }
     var id: String { description }
@@ -8,7 +9,8 @@ struct InfoText: Identifiable {
 
 
 extension PreferencesEditor {
 extension PreferencesEditor {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
 
         private var formatter: NumberFormatter {
         private var formatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -21,52 +23,74 @@ extension PreferencesEditor {
         var body: some View {
         var body: some View {
             Form {
             Form {
                 Section(header: Text("FreeAPS X")) {
                 Section(header: Text("FreeAPS X")) {
-                    Picker("Glucose units", selection: $viewModel.unitsIndex) {
+                    Picker("Glucose units", selection: $state.unitsIndex) {
                         Text("mg/dL").tag(0)
                         Text("mg/dL").tag(0)
                         Text("mmol/L").tag(1)
                         Text("mmol/L").tag(1)
                     }
                     }
 
 
-                    Toggle("Remote control", isOn: $viewModel.allowAnnouncements)
+                    Toggle("Remote control", isOn: $state.allowAnnouncements)
 
 
                     HStack {
                     HStack {
                         Text("Recommended Insulin Fraction")
                         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)) {
-                        ForEach(InsulinCurve.allCases) { v in
-                            Text(v.rawValue).tag(v)
-                        }
-                    }
-
-                    ForEach(viewModel.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)
-                        }
-                    }
-
-                    ForEach(viewModel.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)
+                ForEach(state.sections.indexed(), id: \.1.id) { sectionIndex, section in
+                    Section(header: Text(section.displayName)) {
+                        ForEach(section.fields.indexed(), id: \.1.id) { fieldIndex, field in
+                            HStack {
+                                switch field.type {
+                                case .boolean:
+                                    ZStack {
+                                        Button("", action: {
+                                            infoButtonPressed = InfoText(
+                                                description: field.infoText,
+                                                oref0Variable: field.displayName
+                                            )
+                                        })
+                                        Toggle(isOn: self.$state.sections[sectionIndex].fields[fieldIndex].boolValue) {
+                                            Text(field.displayName)
+                                        }
+                                    }
+                                case .decimal:
+                                    ZStack {
+                                        Button("", action: {
+                                            infoButtonPressed = InfoText(
+                                                description: field.infoText,
+                                                oref0Variable: field.displayName
+                                            )
+                                        })
+                                        Text(field.displayName)
+                                    }
+                                    DecimalTextField(
+                                        "0",
+                                        value: self.$state.sections[sectionIndex].fields[fieldIndex].decimalValue,
+                                        formatter: formatter
+                                    )
+                                case .insulinCurve:
+                                    Picker(
+                                        selection: $state.sections[sectionIndex].fields[fieldIndex].insulinCurveValue,
+                                        label: Text(field.displayName)
+                                    ) {
+                                        ForEach(InsulinCurve.allCases) { v in
+                                            Text(v.rawValue).tag(v)
+                                        }
+                                    }
+                                }
+                            }
                         }
                         }
                     }
                     }
                 }
                 }
+
                 Section {
                 Section {
                     Text("Edit settings json")
                     Text("Edit settings json")
                         .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
                         .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
                 }
                 }
             }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Preferences")
             .navigationTitle("Preferences")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
             .alert(item: $infoButtonPressed) { infoButton in
             .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
 import SwiftUI
 
 
 extension PumpConfig {
 extension PumpConfig {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: PumpConfigProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Published var setupPump = false
         @Published var setupPump = false
         private(set) var setupPumpType: PumpType = .minimed
         private(set) var setupPumpType: PumpType = .minimed
         @Published var pumpState: PumpDisplayState?
         @Published var pumpState: PumpDisplayState?
@@ -38,13 +38,13 @@ extension PumpConfig {
     }
     }
 }
 }
 
 
-extension PumpConfig.ViewModel: CompletionDelegate {
+extension PumpConfig.StateModel: CompletionDelegate {
     func completionNotifyingDidComplete(_: CompletionNotifying) {
     func completionNotifyingDidComplete(_: CompletionNotifying) {
         setupPump = false
         setupPump = false
     }
     }
 }
 }
 
 
-extension PumpConfig.ViewModel: PumpManagerSetupViewControllerDelegate {
+extension PumpConfig.StateModel: PumpManagerSetupViewControllerDelegate {
     func pumpManagerSetupViewController(_: PumpManagerSetupViewController, didSetUpPumpManager pumpManager: PumpManagerUI) {
     func pumpManagerSetupViewController(_: PumpManagerSetupViewController, didSetUpPumpManager pumpManager: PumpManagerUI) {
         provider.setPumpManager(pumpManager)
         provider.setPumpManager(pumpManager)
         setupPump = false
         setupPump = false

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

@@ -1,15 +1,17 @@
 import SwiftUI
 import SwiftUI
+import Swinject
 
 
 extension PumpConfig {
 extension PumpConfig {
     struct RootView: BaseView {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
 
         var body: some View {
         var body: some View {
             Form {
             Form {
                 Section(header: Text("Model")) {
                 Section(header: Text("Model")) {
-                    if let pumpState = viewModel.pumpState {
+                    if let pumpState = state.pumpState {
                         Button {
                         Button {
-                            viewModel.setupPump = true
+                            state.setupPump = true
                         } label: {
                         } label: {
                             HStack {
                             HStack {
                                 Image(uiImage: pumpState.image ?? UIImage()).padding()
                                 Image(uiImage: pumpState.image ?? UIImage()).padding()
@@ -17,23 +19,24 @@ extension PumpConfig {
                             }
                             }
                         }
                         }
                     } else {
                     } 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")
             .navigationTitle("Pump config")
             .navigationBarTitleDisplayMode(.automatic)
             .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 {
                 } else {
                     PumpSetupView(
                     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
 import SwiftUI
 
 
 extension PumpSettingsEditor {
 extension PumpSettingsEditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: PumpSettingsEditorProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Published var maxBasal: Decimal = 0.0
         @Published var maxBasal: Decimal = 0.0
         @Published var maxBolus: Decimal = 0.0
         @Published var maxBolus: Decimal = 0.0
         @Published var dia: Decimal = 0.0
         @Published var dia: Decimal = 0.0

+ 0 - 0
FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift


Некоторые файлы не были показаны из-за большого количества измененных файлов