浏览代码

Merge branch 'dev' into Crowdin

Jon B.M 4 年之前
父节点
当前提交
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 rapidActingChild
     case fiasp
+    case lyumjev
 }
 
 
@@ -22,6 +23,8 @@ extension ExponentialInsulinModelPreset {
             return .minutes(360)
         case .fiasp:
             return .minutes(360)
+        case .lyumjev:
+            return .minutes(330)
         }
     }
 
@@ -33,6 +36,8 @@ extension ExponentialInsulinModelPreset {
             return .minutes(65)
         case .fiasp:
             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 {
         case .fiasp:
             return ExponentialInsulinModelPreset.fiasp
+        case .lyumjev:
+            return ExponentialInsulinModelPreset.lyumjev
         default:
             switch self {
             case .exponentialPreset(let model):
@@ -150,6 +152,8 @@ public extension InsulinModelSettings {
         switch storedSettingsInsulinModel.modelType {
         case .fiasp:
             self = .exponentialPreset(.fiasp)
+        case .lyumjev:
+            self = .exponentialPreset(.lyumjev)
         case .rapidAdult:
             self = .exponentialPreset(.rapidActingAdult)
         case .rapidChild:
@@ -175,6 +179,8 @@ public extension StoredInsulinModel {
                 modelType = .rapidChild
             case .fiasp:
                 modelType = .fiasp
+            case .lyumjev:
+                modelType = .lyumjev
             }
             actionDuration = preset.actionDuration
             peakActivity = preset.peakActivity

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

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

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

@@ -13,6 +13,7 @@ public enum InsulinType: Int, Codable, CaseIterable {
     case humalog
     case apidra
     case fiasp
+    case lyumjev
     
     public var title: String {
         switch self {
@@ -24,6 +25,8 @@ public enum InsulinType: Int, Codable, CaseIterable {
             return LocalizedString("Apidra (insulin glulisine)", comment: "Title for Apidra insulin type")
         case .fiasp:
             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")
         case .fiasp:
             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")
         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")
+        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 enum ModelType: String, Codable {
         case fiasp
+        case lyumjev
         case rapidAdult
         case rapidChild
         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")
         case .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")
         case .fiasp:
             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
                 show(customOverrideVC, sender: collectionView.cellForItem(at: indexPath))
             case .history:
-                let model = OverrideHistoryViewModel(
+                let model = OverrideHistorystate(
                     overrides: overrideHistory,
                     glucoseUnit: glucoseUnit
                 )

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

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

文件差异内容过多而无法显示
+ 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(
             insulinTintColor: .accentColor,
             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(
             insulinTintColor: .accentColor,
             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 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 {
     @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(DeviceDataManager.self)!
         _ = resolver.resolve(APSManager.self)!
@@ -37,27 +37,15 @@ private extension Swinject.Resolver {
     }
 
     init() {
-        FreeAPSApp.resolver.setup()
-        FreeAPSApp.loadServices()
+        loadServices()
     }
 
-    private let mainView = Main.Builder(resolver: FreeAPSApp.resolver).buildView()
-
     var body: some Scene {
         WindowGroup {
-            mainView
+            Main.RootView(resolver: resolver)
         }
         .onChange(of: scenePhase) { newScenePhase in
-            switch newScenePhase {
-            case .active:
-                debug(.default, "APPLICATION is active")
-            case .inactive:
-                debug(.default, "APPLICATION is inactive")
-            case .background:
-                debug(.default, "APPLICATION is in background")
-            @unknown default:
-                debug(.default, "APPLICATION: Received an unexpected scenePhase.")
-            }
+            debug(.default, "APPLICATION PHASE: \(newScenePhase)")
         }
     }
 }

+ 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 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(Broadcaster.self) { _ in BaseBroadcaster() }
         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 wideBGTargetRange: Bool = false
     var skipNeutralTemps: Bool = false
-    var unsuspendIfNoTemp: Bool = true
+    var unsuspendIfNoTemp: Bool = false
     var bolusSnoozeDIADivisor: Decimal = 2
     var min5mCarbimpact: Decimal = 8
     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
 
 extension AddCarbs {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: AddCarbsProvider {
+    final class StateModel: BaseStateModel<Provider> {
         @Injected() var carbsStorage: CarbsStorage!
         @Injected() var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -8,7 +8,6 @@ protocol Provider {
 
 class BaseProvider: Provider, Injectable {
     var lifetime = Lifetime()
-    @Injected() var authorizationManager: AuthorizationManager!
     @Injected() var deviceManager: DeviceDataManager!
     @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 Swinject
 
 protocol BaseView: View {
-    associatedtype ViewModel: FreeAPS.ViewModel
-    var viewModel: ViewModel { get }
-    init()
+    associatedtype StateModelType: StateModel
+    var resolver: Resolver { get }
+    var state: StateModelType { get }
+    var router: Router { get }
+    func configureView()
+    func configureView(_ configure: (() -> Void)?)
 }
 
 extension BaseView {
-    init() { self.init() }
+    var router: Router { resolver.resolve(Router.self)! }
+}
+
+extension BaseView {
+    func configureView() {
+        configureView(nil)
+    }
+
+    func configureView(_ configure: (() -> Void)?) {
+        if state.isInitial {
+            configure?()
+            state.resolver = resolver
+            state.isInitial = false
+        }
+    }
 }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 0 - 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 }
     }
-
-    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 Swinject
 
 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?
         @Published var isModalPresented = false
         @Published var isAlertPresented = false
         @Published var alertMessage = ""
-        @Published private(set) var scene: Scene = .loading
 
         override func subscribe() {
             router.mainModalScreen
-                .map { $0?.modal(resolver: self.resolver) }
+                .map { $0?.modal(resolver: self.resolver!) }
                 .removeDuplicates { $0?.id == $1?.id }
-                .receive(on: RunLoop.main)
+                .receive(on: DispatchQueue.main)
                 .sink { modal in
                     self.modal = modal
                     self.isModalPresented = modal != nil
                 }
                 .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
                 .filter { !$0 }
                 .sink { _ in

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 0 - 3
FreeAPS/Sources/Modules/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 LoopKit
+
+protocol SettableValue {}
+extension Bool: SettableValue {}
+extension Decimal: SettableValue {}
+extension InsulinCurve: SettableValue {}
 
 enum PreferencesEditor {
     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 keypath: WritableKeyPath<Preferences, T>
+        var type: FieldType
         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(
             displayName: String,
-            keypath: WritableKeyPath<Preferences, T>,
-            value: T,
+            type: FieldType,
             infoText: String,
             settable: PreferencesSettable? = nil
         ) {
             self.displayName = displayName
-            self.keypath = keypath
-            self.value = value
+            self.type = type
             self.infoText = infoText
             self.settable = settable
         }
 
         let id = UUID()
     }
+
+    struct FieldSection: Identifiable {
+        let displayName: String
+        var fields: [Field]
+        let id = UUID()
+    }
 }
 
 protocol PreferencesEditorProvider: Provider {
@@ -40,5 +96,6 @@ protocol PreferencesEditorProvider: Provider {
 }
 
 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 Swinject
 
 struct InfoText: Identifiable {
     var id: String { description }
@@ -8,7 +9,8 @@ struct InfoText: Identifiable {
 
 extension PreferencesEditor {
     struct RootView: BaseView {
-        @EnvironmentObject var viewModel: ViewModel<Provider>
+        let resolver: Resolver
+        @StateObject var state = StateModel()
 
         private var formatter: NumberFormatter {
             let formatter = NumberFormatter()
@@ -21,52 +23,74 @@ extension PreferencesEditor {
         var body: some View {
             Form {
                 Section(header: Text("FreeAPS X")) {
-                    Picker("Glucose units", selection: $viewModel.unitsIndex) {
+                    Picker("Glucose units", selection: $state.unitsIndex) {
                         Text("mg/dL").tag(0)
                         Text("mmol/L").tag(1)
                     }
 
-                    Toggle("Remote control", isOn: $viewModel.allowAnnouncements)
+                    Toggle("Remote control", isOn: $state.allowAnnouncements)
 
                     HStack {
                         Text("Recommended Insulin Fraction")
-                        DecimalTextField("", value: $viewModel.insulinReqFraction, formatter: formatter)
+                        DecimalTextField("", value: $state.insulinReqFraction, formatter: formatter)
                     }
 
-                    Toggle("Skip Bolus screen after carbs", isOn: $viewModel.skipBolusScreenAfterCarbs)
+                    Toggle("Skip Bolus screen after carbs", isOn: $state.skipBolusScreenAfterCarbs)
                 }
 
-                Section(header: Text("OpenAPS")) {
-                    Picker(selection: $viewModel.insulinCurveField.value, label: Text(viewModel.insulinCurveField.displayName)) {
-                        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 {
                     Text("Edit settings json")
                         .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
                 }
             }
+            .onAppear(perform: configureView)
             .navigationTitle("Preferences")
             .navigationBarTitleDisplayMode(.automatic)
             .alert(item: $infoButtonPressed) { infoButton in

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

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

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

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

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

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

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

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

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

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

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


部分文件因为文件数量过多而无法显示