Bläddra i källkod

Merge remote-tracking branch 'ivalkou/dev' into Crowdin

Jon B.M 4 år sedan
förälder
incheckning
c9dca1b2a3
24 ändrade filer med 118 tillägg och 98 borttagningar
  1. 24 0
      FreeAPS.xcodeproj/project.pbxproj
  2. 4 1
      FreeAPS/Resources/Info.plist
  3. 0 1
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift
  4. 0 1
      FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetStateModel.swift
  5. 0 1
      FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigStateModel.swift
  6. 16 0
      FreeAPS/Sources/Modules/Base/BaseStateModel.swift
  7. 0 1
      FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift
  8. 3 17
      FreeAPS/Sources/Modules/CGM/CGMStateModel.swift
  9. 0 1
      FreeAPS/Sources/Modules/Calibrations/CalibrationsStateModel.swift
  10. 0 1
      FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift
  11. 14 1
      FreeAPS/Sources/Modules/Home/HomeStateModel.swift
  12. 15 0
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  13. 0 5
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  14. 0 1
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorStateModel.swift
  15. 0 1
      FreeAPS/Sources/Modules/LibreConfig/LibreConfigStateModel.swift
  16. 18 0
      FreeAPS/Sources/Modules/Main/MainStateModel.swift
  17. 7 0
      FreeAPS/Sources/Modules/Main/View/MainRootView.swift
  18. 3 21
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift
  19. 9 35
      FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift
  20. 1 1
      FreeAPS/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift
  21. 1 7
      FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift
  22. 0 1
      FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift
  23. 2 0
      FreeAPS/Sources/Router/Router.swift
  24. 1 1
      FreeAPS/Sources/Services/SettingsManager/SettingsManager.swift

+ 24 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -174,6 +174,8 @@
 		38E4453B274E411700EC9A94 /* Disk+VolumeInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E44531274E411700EC9A94 /* Disk+VolumeInformation.swift */; };
 		38E4453C274E411700EC9A94 /* Disk+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E44532274E411700EC9A94 /* Disk+Codable.swift */; };
 		38E4453D274E411700EC9A94 /* Disk+Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E44533274E411700EC9A94 /* Disk+Errors.swift */; };
+		38E87401274F77E400975559 /* CoreNFC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38E873FD274F761800975559 /* CoreNFC.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
+		38E87403274F78C000975559 /* libswiftCoreNFC.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 38E87402274F78C000975559 /* libswiftCoreNFC.tbd */; settings = {ATTRIBUTES = (Weak, ); }; };
 		38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E989DC25F5021400C0CED0 /* PumpStatus.swift */; };
 		38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E98A1B25F52C9300C0CED0 /* Signpost.swift */; };
 		38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E98A1C25F52C9300C0CED0 /* Logger.swift */; };
@@ -491,6 +493,8 @@
 		38E44531274E411700EC9A94 /* Disk+VolumeInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Disk+VolumeInformation.swift"; sourceTree = "<group>"; };
 		38E44532274E411700EC9A94 /* Disk+Codable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Disk+Codable.swift"; sourceTree = "<group>"; };
 		38E44533274E411700EC9A94 /* Disk+Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Disk+Errors.swift"; sourceTree = "<group>"; };
+		38E873FD274F761800975559 /* CoreNFC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreNFC.framework; path = System/Library/Frameworks/CoreNFC.framework; sourceTree = SDKROOT; };
+		38E87402274F78C000975559 /* libswiftCoreNFC.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreNFC.tbd; path = usr/lib/swift/libswiftCoreNFC.tbd; sourceTree = SDKROOT; };
 		38E989DC25F5021400C0CED0 /* PumpStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpStatus.swift; sourceTree = "<group>"; };
 		38E98A1B25F52C9300C0CED0 /* Signpost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signpost.swift; sourceTree = "<group>"; };
 		38E98A1C25F52C9300C0CED0 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
@@ -592,6 +596,8 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				38E87403274F78C000975559 /* libswiftCoreNFC.tbd in Frameworks */,
+				38E87401274F77E400975559 /* CoreNFC.framework in Frameworks */,
 				3818AA71274C278200843DB3 /* LoopTestingKit.framework in Frameworks */,
 				3818AA47274C255A00843DB3 /* LibreTransmitter in Frameworks */,
 				3818AA4A274C267000843DB3 /* CGMBLEKit.framework in Frameworks */,
@@ -942,6 +948,8 @@
 		3818AA48274C267000843DB3 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				38E87402274F78C000975559 /* libswiftCoreNFC.tbd */,
+				38E873FD274F761800975559 /* CoreNFC.framework */,
 				3818AA70274C278200843DB3 /* LoopTestingKit.framework */,
 				3818AA4C274C26A300843DB3 /* LoopKit.framework */,
 				3818AA4D274C26A300843DB3 /* LoopKitUI.framework */,
@@ -2129,7 +2137,15 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(SDKROOT)/usr/lib/swift",
+				);
 				MARKETING_VERSION = "$(CURRENT_PROJECT_VERSION)";
+				OTHER_LDFLAGS = (
+					"-weak_framework",
+					CoreNFC,
+				);
 				PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_VERSION = 5.0;
@@ -2155,7 +2171,15 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(SDKROOT)/usr/lib/swift",
+				);
 				MARKETING_VERSION = "$(CURRENT_PROJECT_VERSION)";
+				OTHER_LDFLAGS = (
+					"-weak_framework",
+					CoreNFC,
+				);
 				PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_VERSION = 5.0;

+ 4 - 1
FreeAPS/Resources/Info.plist

@@ -63,7 +63,7 @@
 	<key>UIApplicationSceneManifest</key>
 	<dict>
 		<key>UIApplicationSupportsMultipleScenes</key>
-		<true/>
+		<false/>
 	</dict>
 	<key>UIApplicationSupportsIndirectInputEvents</key>
 	<true/>
@@ -80,9 +80,12 @@
 	<array>
 		<string>armv7</string>
 	</array>
+	<key>UIRequiresFullScreen</key>
+	<false/>
 	<key>UISupportedInterfaceOrientations</key>
 	<array>
 		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
 	</array>
 	<key>UISupportedInterfaceOrientations~ipad</key>
 	<array>

+ 0 - 1
FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift

@@ -3,7 +3,6 @@ import SwiftUI
 extension AddCarbs {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var carbsStorage: CarbsStorage!
-        @Injected() var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!
         @Published var carbs: Decimal = 0
         @Published var date = Date()

+ 0 - 1
FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetStateModel.swift

@@ -3,7 +3,6 @@ import SwiftUI
 extension AddTempTarget {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() private var storage: TempTargetsStorage!
-        @Injected() private var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!
 
         @Published var low: Decimal = 0

+ 0 - 1
FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigStateModel.swift

@@ -3,7 +3,6 @@ import SwiftUI
 
 extension AutotuneConfig {
     final class StateModel: BaseStateModel<Provider> {
-        @Injected() var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!
         @Published var useAutotune = false
         @Published var autotune: Autotune?

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

@@ -1,3 +1,4 @@
+import Combine
 import SwiftUI
 import Swinject
 
@@ -12,6 +13,7 @@ protocol StateModel: ObservableObject {
 
 class BaseStateModel<Provider>: StateModel, Injectable where Provider: FreeAPS.Provider {
     @Injected() var router: Router!
+    @Injected() var settingsManager: SettingsManager!
     var isInitial: Bool = true
     private(set) var provider: Provider!
 
@@ -40,4 +42,18 @@ class BaseStateModel<Provider>: StateModel, Injectable where Provider: FreeAPS.P
     func view(for screen: Screen) -> AnyView {
         router.view(for: screen)
     }
+
+    func subscribeSetting<T: Equatable, U: Publisher>(
+        _ keyPath: WritableKeyPath<FreeAPSSettings, T>,
+        on settingPublisher: U, initial: (T) -> Void, didSet: ((T) -> Void)? = nil
+    ) where U.Output == T, U.Failure == Never {
+        initial(settingsManager.settings[keyPath: keyPath])
+        settingPublisher
+            .removeDuplicates()
+            .sink { [weak self] value in
+                self?.settingsManager.settings[keyPath: keyPath] = value
+                didSet?(value)
+            }
+            .store(in: &lifetime)
+    }
 }

+ 0 - 1
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -6,7 +6,6 @@ extension Bolus {
         @Injected() var unlockmanager: UnlockManager!
         @Injected() var apsManager: APSManager!
         @Injected() var broadcaster: Broadcaster!
-        @Injected() var settingsManager: SettingsManager!
         @Injected() var pumpHistotyStorage: PumpHistoryStorage!
         @Published var amount: Decimal = 0
         @Published var inslinRecommended: Decimal = 0

+ 3 - 17
FreeAPS/Sources/Modules/CGM/CGMStateModel.swift

@@ -3,7 +3,6 @@ import SwiftUI
 
 extension CGM {
     final class StateModel: BaseStateModel<Provider> {
-        @Injected() var settingsManager: SettingsManager!
         @Injected() var libreSource: LibreTransmitterSource!
         @Injected() var calendarManager: CalendarManager!
 
@@ -17,11 +16,12 @@ extension CGM {
 
         override func subscribe() {
             cgm = settingsManager.settings.cgm
-            uploadGlucose = settingsManager.settings.uploadGlucose
             transmitterID = UserDefaults.standard.dexcomTransmitterID ?? ""
             currentCalendarID = storedCalendarID ?? ""
             calendarIDs = calendarManager.calendarIDs()
-            createCalendarEvents = settingsManager.settings.useCalendar
+
+            subscribeSetting(\.useCalendar, on: $createCalendarEvents) { createCalendarEvents = $0 }
+            subscribeSetting(\.uploadGlucose, on: $uploadGlucose) { uploadGlucose = $0 }
 
             $cgm
                 .removeDuplicates()
@@ -31,13 +31,6 @@ extension CGM {
                 }
                 .store(in: &lifetime)
 
-            $uploadGlucose
-                .removeDuplicates()
-                .sink { [weak self] value in
-                    self?.settingsManager.settings.uploadGlucose = value
-                }
-                .store(in: &lifetime)
-
             $createCalendarEvents
                 .removeDuplicates()
                 .flatMap { [weak self] ok -> AnyPublisher<Bool, Never> in
@@ -52,13 +45,6 @@ extension CGM {
                 .weakAssign(to: \.calendarIDs, on: self)
                 .store(in: &lifetime)
 
-            $createCalendarEvents
-                .removeDuplicates()
-                .sink { [weak self] use in
-                    self?.settingsManager.settings.useCalendar = use
-                }
-                .store(in: &lifetime)
-
             $currentCalendarID
                 .removeDuplicates()
                 .sink { [weak self] id in

+ 0 - 1
FreeAPS/Sources/Modules/Calibrations/CalibrationsStateModel.swift

@@ -5,7 +5,6 @@ extension Calibrations {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var glucoseStorage: GlucoseStorage!
         @Injected() var calibrationService: CalibrationService!
-        @Injected() var settingsManager: SettingsManager!
 
         @Published var slope: Double = 1
         @Published var intercept: Double = 1

+ 0 - 1
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -3,7 +3,6 @@ import SwiftUI
 extension DataTable {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var broadcaster: Broadcaster!
-        @Injected() var settingsManager: SettingsManager!
         @Published var items: [Item] = []
 
         override func subscribe() {

+ 14 - 1
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -1,3 +1,4 @@
+import Combine
 import LoopKitUI
 import SwiftDate
 import SwiftUI
@@ -5,7 +6,6 @@ import SwiftUI
 extension Home {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var broadcaster: Broadcaster!
-        @Injected() var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!
         @Injected() var nightscoutManager: NightscoutManager!
         @Injected() var calendarManager: CalendarManager!
@@ -142,6 +142,19 @@ extension Home {
                     }
                 }
                 .store(in: &lifetime)
+
+            $setupPump
+                .removeDuplicates()
+                .sink { [weak self] show in
+                    guard let self = self else { return }
+                    if show, let pumpManager = self.provider.apsManager.pumpManager {
+                        let view = PumpConfig.PumpSettingsView(pumpManager: pumpManager, completionDelegate: self).asAny()
+                        self.router.mainSecondaryModalView.value = view
+                    } else {
+                        self.router.mainSecondaryModalView.value = nil
+                    }
+                }
+                .store(in: &lifetime)
         }
 
         func addCarbs() {

+ 15 - 0
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -93,6 +93,9 @@ struct MainChartView: View {
         return formatter
     }
 
+    @Environment(\.horizontalSizeClass) var hSizeClass
+    @Environment(\.verticalSizeClass) var vSizeClass
+
     // MARK: - Views
 
     var body: some View {
@@ -102,6 +105,18 @@ struct MainChartView: View {
                 mainScrollView(fullSize: geo.size)
                 glucoseLabelsView(fullSize: geo.size)
             }
+            .onChange(of: hSizeClass) { _ in
+                update(fullSize: geo.size)
+            }
+            .onChange(of: vSizeClass) { _ in
+                update(fullSize: geo.size)
+            }
+            .onReceive(
+                Foundation.NotificationCenter.default
+                    .publisher(for: UIDevice.orientationDidChangeNotification)
+            ) { _ in
+                update(fullSize: geo.size)
+            }
         }
     }
 

+ 0 - 5
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -73,11 +73,6 @@ extension Home {
                         state.setupPump = true
                     }
                 }
-                .popover(isPresented: $state.setupPump) {
-                    if let pumpManager = state.provider.apsManager.pumpManager {
-                        PumpConfig.PumpSettingsView(pumpManager: pumpManager, completionDelegate: state)
-                    }
-                }
                 Spacer()
                 LoopView(
                     suggestion: $state.suggestion,

+ 0 - 1
FreeAPS/Sources/Modules/ISFEditor/ISFEditorStateModel.swift

@@ -2,7 +2,6 @@ import SwiftUI
 
 extension ISFEditor {
     final class StateModel: BaseStateModel<Provider> {
-        @Injected() var settingsManager: SettingsManager!
         @Published var items: [Item] = []
         private(set) var autosensISF: Decimal?
         private(set) var autosensRatio: Decimal = 0

+ 0 - 1
FreeAPS/Sources/Modules/LibreConfig/LibreConfigStateModel.swift

@@ -4,7 +4,6 @@ import SwiftUI
 extension LibreConfig {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var source: LibreTransmitterSource!
-        @Injected() var settingsManager: SettingsManager!
 
         @Published var configured = false
 

+ 18 - 0
FreeAPS/Sources/Modules/Main/MainStateModel.swift

@@ -5,8 +5,10 @@ extension Main {
     final class StateModel: BaseStateModel<Provider> {
         private(set) var modal: Modal?
         @Published var isModalPresented = false
+        @Published var isSecondaryModalPresented = false
         @Published var isAlertPresented = false
         @Published var alertMessage = ""
+        @Published var secondaryModalView: AnyView? = nil
 
         override func subscribe() {
             router.mainModalScreen
@@ -33,6 +35,22 @@ extension Main {
                     self.alertMessage = message
                 }
                 .store(in: &lifetime)
+
+            router.mainSecondaryModalView
+                .receive(on: DispatchQueue.main)
+                .sink { view in
+                    self.secondaryModalView = view
+                    self.isSecondaryModalPresented = view != nil
+                }
+                .store(in: &lifetime)
+
+            $isSecondaryModalPresented
+                .removeDuplicates()
+                .filter { !$0 }
+                .sink { _ in
+                    self.router.mainSecondaryModalView.send(nil)
+                }
+                .store(in: &lifetime)
         }
     }
 }

+ 7 - 0
FreeAPS/Sources/Modules/Main/View/MainRootView.swift

@@ -13,6 +13,13 @@ extension Main {
                     NavigationView { self.state.modal!.view }
                         .navigationViewStyle(StackNavigationViewStyle())
                 }
+                .sheet(isPresented: $state.isSecondaryModalPresented) {
+                    if let view = state.secondaryModalView {
+                        view
+                    } else {
+                        EmptyView()
+                    }
+                }
                 .alert(isPresented: $state.isAlertPresented) {
                     Alert(
                         title: Text("Important message"),

+ 3 - 21
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -4,7 +4,6 @@ import SwiftUI
 extension NightscoutConfig {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var keychain: Keychain!
-        @Injected() var settingsManager: SettingsManager!
 
         @Published var url = ""
         @Published var secret = ""
@@ -18,27 +17,10 @@ extension NightscoutConfig {
         override func subscribe() {
             url = keychain.getValue(String.self, forKey: Config.urlKey) ?? ""
             secret = keychain.getValue(String.self, forKey: Config.secretKey) ?? ""
-            isUploadEnabled = settingsManager.settings.isUploadEnabled
-            useLocalSource = settingsManager.settings.useLocalGlucoseSource
-            localPort = Decimal(settingsManager.settings.localGlucosePort)
 
-            $isUploadEnabled
-                .removeDuplicates()
-                .sink { [weak self] enabled in
-                    self?.settingsManager.settings.isUploadEnabled = enabled
-                }.store(in: &lifetime)
-
-            $useLocalSource
-                .removeDuplicates()
-                .sink { [weak self] use in
-                    self?.settingsManager.settings.useLocalGlucoseSource = use
-                }.store(in: &lifetime)
-
-            $localPort
-                .removeDuplicates()
-                .sink { [weak self] port in
-                    self?.settingsManager.settings.localGlucosePort = Int(port)
-                }.store(in: &lifetime)
+            subscribeSetting(\.isUploadEnabled, on: $isUploadEnabled) { isUploadEnabled = $0 }
+            subscribeSetting(\.useLocalGlucoseSource, on: $useLocalSource) { useLocalSource = $0 }
+            subscribeSetting(\.localGlucosePort, on: $localPort.map(Int.init)) { localPort = Decimal($0) }
         }
 
         func connect() {

+ 9 - 35
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift

@@ -2,9 +2,7 @@ import Foundation
 import SwiftUI
 
 extension PreferencesEditor {
-    final class StateModel: BaseStateModel<Provider>, PreferencesSettable {
-        @Injected() var settingsManager: SettingsManager!
-        private(set) var preferences = Preferences()
+    final class StateModel: BaseStateModel<Provider>, PreferencesSettable { private(set) var preferences = Preferences()
         @Published var unitsIndex = 1
         @Published var allowAnnouncements = false
         @Published var insulinReqFraction: Decimal = 0.7
@@ -14,40 +12,16 @@ extension PreferencesEditor {
 
         override func subscribe() {
             preferences = provider.preferences
-            unitsIndex = settingsManager.settings.units == .mgdL ? 0 : 1
-            allowAnnouncements = settingsManager.settings.allowAnnouncements
 
-            insulinReqFraction = settingsManager.settings.insulinReqFraction
-            skipBolusScreenAfterCarbs = settingsManager.settings.skipBolusScreenAfterCarbs
+            subscribeSetting(\.allowAnnouncements, on: $allowAnnouncements) { allowAnnouncements = $0 }
+            subscribeSetting(\.insulinReqFraction, on: $insulinReqFraction) { insulinReqFraction = $0 }
+            subscribeSetting(\.skipBolusScreenAfterCarbs, on: $skipBolusScreenAfterCarbs) { skipBolusScreenAfterCarbs = $0 }
 
-            $unitsIndex
-                .removeDuplicates()
-                .sink { [weak self] index in
-                    self?.settingsManager.settings.units = index == 0 ? .mgdL : .mmolL
-                    self?.provider.migrateUnits()
-                }
-                .store(in: &lifetime)
-
-            $allowAnnouncements
-                .removeDuplicates()
-                .sink { [weak self] allow in
-                    self?.settingsManager.settings.allowAnnouncements = allow
-                }
-                .store(in: &lifetime)
-
-            $insulinReqFraction
-                .removeDuplicates()
-                .sink { [weak self] fraction in
-                    self?.settingsManager.settings.insulinReqFraction = fraction
-                }
-                .store(in: &lifetime)
-
-            $skipBolusScreenAfterCarbs
-                .removeDuplicates()
-                .sink { [weak self] skip in
-                    self?.settingsManager.settings.skipBolusScreenAfterCarbs = skip
-                }
-                .store(in: &lifetime)
+            subscribeSetting(\.units, on: $unitsIndex.map { $0 == 0 ? GlucoseUnits.mgdL : .mmolL }) {
+                unitsIndex = $0 == .mgdL ? 0 : 1
+            } didSet: { [weak self] _ in
+                self?.provider.migrateUnits()
+            }
 
             // MARK: - Main fields
 

+ 1 - 1
FreeAPS/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift

@@ -28,7 +28,7 @@ extension PumpConfig {
             .onAppear(perform: configureView)
             .navigationTitle("Pump config")
             .navigationBarTitleDisplayMode(.automatic)
-            .popover(isPresented: $state.setupPump) {
+            .sheet(isPresented: $state.setupPump) {
                 if let pumpManager = state.provider.apsManager.pumpManager {
                     PumpSettingsView(pumpManager: pumpManager, completionDelegate: state)
                 } else {

+ 1 - 7
FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift

@@ -2,7 +2,6 @@ import SwiftUI
 
 extension Settings {
     final class StateModel: BaseStateModel<Provider> {
-        @Injected() private var settingsManager: SettingsManager!
         @Injected() private var broadcaster: Broadcaster!
         @Injected() private var fileManager: FileManager!
         @Published var closedLoop = false
@@ -12,14 +11,9 @@ extension Settings {
         private(set) var buildNumber = ""
 
         override func subscribe() {
-            closedLoop = settingsManager.settings.closedLoop
             debugOptions = settingsManager.settings.debugOptions
 
-            $closedLoop
-                .removeDuplicates()
-                .sink { [weak self] value in
-                    self?.settingsManager.settings.closedLoop = value
-                }.store(in: &lifetime)
+            subscribeSetting(\.closedLoop, on: $closedLoop) { closedLoop = $0 }
 
             broadcaster.register(SettingsObserver.self, observer: self)
 

+ 0 - 1
FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift

@@ -2,7 +2,6 @@ import SwiftUI
 
 extension TargetsEditor {
     final class StateModel: BaseStateModel<Provider> {
-        @Injected() var settingsManager: SettingsManager!
         @Published var items: [Item] = []
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }

+ 2 - 0
FreeAPS/Sources/Router/Router.swift

@@ -4,12 +4,14 @@ import Swinject
 
 protocol Router {
     var mainModalScreen: CurrentValueSubject<Screen?, Never> { get }
+    var mainSecondaryModalView: CurrentValueSubject<AnyView?, Never> { get }
     var alertMessage: PassthroughSubject<String, Never> { get }
     func view(for screen: Screen) -> AnyView
 }
 
 final class BaseRouter: Router {
     let mainModalScreen = CurrentValueSubject<Screen?, Never>(nil)
+    let mainSecondaryModalView = CurrentValueSubject<AnyView?, Never>(nil)
     let alertMessage = PassthroughSubject<String, Never>()
 
     private let resolver: Resolver

+ 1 - 1
FreeAPS/Sources/Services/SettingsManager/SettingsManager.swift

@@ -1,7 +1,7 @@
 import Foundation
 import Swinject
 
-protocol SettingsManager {
+protocol SettingsManager: AnyObject {
     var settings: FreeAPSSettings { get set }
     var preferences: Preferences { get }
 }