Procházet zdrojové kódy

Release/0.2.0 (#63)

* Fix the first suspension time

* Fix bolus indicator

* Fix authorization logic

* weakAssign

* tmp remove logout button

* Accept uppercased remote commands

* Fix CR range

* Fix bolus auth

* Fix pump settings placeholders

* carbsReq badge

* Bump version

* README update

* Clean signing
Ivan před 5 roky
rodič
revize
60f1e7a4dd
32 změnil soubory, kde provedl 212 přidání a 100 odebrání
  1. 19 19
      FreeAPS.xcodeproj/project.pbxproj
  2. 1 1
      FreeAPS/Resources/Config.xcconfig
  3. 2 0
      FreeAPS/Resources/Info.plist
  4. 11 4
      FreeAPS/Sources/APS/APSManager.swift
  5. 11 0
      FreeAPS/Sources/Helpers/Publisher.swift
  6. 2 2
      FreeAPS/Sources/Models/Announcement.swift
  7. 3 1
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsDataFlow.swift
  8. 5 1
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsProvider.swift
  9. 4 1
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsViewModel.swift
  10. 9 0
      FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift
  11. 1 1
      FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootBuilder.swift
  12. 7 0
      FreeAPS/Sources/Modules/AuthorizedRoot/AuthorizedRootDataFlow.swift
  13. 3 0
      FreeAPS/Sources/Modules/AuthorizedRoot/AuthorizedRootProvider.swift
  14. 2 2
      FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootViewModel.swift
  15. 1 1
      FreeAPS/Sources/Modules/AuthotizedRoot/View/AuthotizedRootRootView.swift
  16. 0 7
      FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootDataFlow.swift
  17. 0 3
      FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootProvider.swift
  18. 1 1
      FreeAPS/Sources/Modules/CREditor/CREditorViewModel.swift
  19. 9 7
      FreeAPS/Sources/Modules/Home/HomeViewModel.swift
  20. 12 2
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  21. 20 5
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  22. 3 1
      FreeAPS/Sources/Modules/Login/LoginViewModel.swift
  23. 3 0
      FreeAPS/Sources/Modules/Main/MainDataFlow.swift
  24. 1 6
      FreeAPS/Sources/Modules/Main/MainViewModel.swift
  25. 2 2
      FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift
  26. 6 0
      FreeAPS/Sources/Modules/Settings/SettingsViewModel.swift
  27. 4 0
      FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift
  28. 4 1
      FreeAPS/Sources/Router/Screen.swift
  29. 8 4
      FreeAPS/Sources/Services/AuthorizationManager/AuthorizationManager.swift
  30. 2 8
      FreeAPS/Sources/Services/UnlockManager/UnlockManager.swift
  31. 28 10
      README.md
  32. 28 10
      README_RU.md

+ 19 - 19
FreeAPS.xcodeproj/project.pbxproj

@@ -45,11 +45,11 @@
 		3811DE4125C9D4A100A708ED /* SettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3C25C9D4A100A708ED /* SettingsRootView.swift */; };
 		3811DE4225C9D4A100A708ED /* SettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3D25C9D4A100A708ED /* SettingsDataFlow.swift */; };
 		3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3E25C9D4A100A708ED /* SettingsProvider.swift */; };
-		3811DE4C25C9D4B800A708ED /* AuthotizedRootBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4625C9D4B800A708ED /* AuthotizedRootBuilder.swift */; };
-		3811DE4D25C9D4B800A708ED /* AuthotizedRootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4725C9D4B800A708ED /* AuthotizedRootViewModel.swift */; };
+		3811DE4C25C9D4B800A708ED /* AuthorizedRootBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4625C9D4B800A708ED /* AuthorizedRootBuilder.swift */; };
+		3811DE4D25C9D4B800A708ED /* AuthorizedRootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4725C9D4B800A708ED /* AuthorizedRootViewModel.swift */; };
 		3811DE4E25C9D4B800A708ED /* AuthotizedRootRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4925C9D4B800A708ED /* AuthotizedRootRootView.swift */; };
-		3811DE4F25C9D4B800A708ED /* AuthotizedRootDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4A25C9D4B800A708ED /* AuthotizedRootDataFlow.swift */; };
-		3811DE5025C9D4B800A708ED /* AuthotizedRootProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4B25C9D4B800A708ED /* AuthotizedRootProvider.swift */; };
+		3811DE4F25C9D4B800A708ED /* AuthorizedRootDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4A25C9D4B800A708ED /* AuthorizedRootDataFlow.swift */; };
+		3811DE5025C9D4B800A708ED /* AuthorizedRootProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4B25C9D4B800A708ED /* AuthorizedRootProvider.swift */; };
 		3811DE5C25C9D4D500A708ED /* Formatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5425C9D4D500A708ED /* Formatters.swift */; };
 		3811DE5D25C9D4D500A708ED /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5525C9D4D500A708ED /* Publisher.swift */; };
 		3811DE5F25C9D4D500A708ED /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5725C9D4D500A708ED /* ProgressBar.swift */; };
@@ -341,11 +341,11 @@
 		3811DE3C25C9D4A100A708ED /* SettingsRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsRootView.swift; sourceTree = "<group>"; };
 		3811DE3D25C9D4A100A708ED /* SettingsDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDataFlow.swift; sourceTree = "<group>"; };
 		3811DE3E25C9D4A100A708ED /* SettingsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsProvider.swift; sourceTree = "<group>"; };
-		3811DE4625C9D4B800A708ED /* AuthotizedRootBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthotizedRootBuilder.swift; sourceTree = "<group>"; };
-		3811DE4725C9D4B800A708ED /* AuthotizedRootViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthotizedRootViewModel.swift; sourceTree = "<group>"; };
+		3811DE4625C9D4B800A708ED /* AuthorizedRootBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizedRootBuilder.swift; sourceTree = "<group>"; };
+		3811DE4725C9D4B800A708ED /* AuthorizedRootViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizedRootViewModel.swift; sourceTree = "<group>"; };
 		3811DE4925C9D4B800A708ED /* AuthotizedRootRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthotizedRootRootView.swift; sourceTree = "<group>"; };
-		3811DE4A25C9D4B800A708ED /* AuthotizedRootDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthotizedRootDataFlow.swift; sourceTree = "<group>"; };
-		3811DE4B25C9D4B800A708ED /* AuthotizedRootProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthotizedRootProvider.swift; sourceTree = "<group>"; };
+		3811DE4A25C9D4B800A708ED /* AuthorizedRootDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizedRootDataFlow.swift; sourceTree = "<group>"; };
+		3811DE4B25C9D4B800A708ED /* AuthorizedRootProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizedRootProvider.swift; sourceTree = "<group>"; };
 		3811DE5425C9D4D500A708ED /* Formatters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Formatters.swift; sourceTree = "<group>"; };
 		3811DE5525C9D4D500A708ED /* Publisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = "<group>"; };
 		3811DE5725C9D4D500A708ED /* ProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = "<group>"; };
@@ -637,7 +637,7 @@
 			children = (
 				6DC5D590658EF8B8DF94F9F5 /* AddCarbs */,
 				A9A4C88374496B3C89058A89 /* AddTempTarget */,
-				3811DE4525C9D4B800A708ED /* AuthotizedRoot */,
+				3811DE4525C9D4B800A708ED /* AuthorizedRoot */,
 				672F63EEAE27400625E14BAD /* AutotuneConfig */,
 				A42F1FEDFFD0DDE00AAD54D3 /* BasalProfileEditor */,
 				3811DE0425C9D32E00A708ED /* Base */,
@@ -762,16 +762,16 @@
 			path = View;
 			sourceTree = "<group>";
 		};
-		3811DE4525C9D4B800A708ED /* AuthotizedRoot */ = {
+		3811DE4525C9D4B800A708ED /* AuthorizedRoot */ = {
 			isa = PBXGroup;
 			children = (
-				3811DE4625C9D4B800A708ED /* AuthotizedRootBuilder.swift */,
-				3811DE4A25C9D4B800A708ED /* AuthotizedRootDataFlow.swift */,
-				3811DE4B25C9D4B800A708ED /* AuthotizedRootProvider.swift */,
-				3811DE4725C9D4B800A708ED /* AuthotizedRootViewModel.swift */,
+				3811DE4625C9D4B800A708ED /* AuthorizedRootBuilder.swift */,
+				3811DE4A25C9D4B800A708ED /* AuthorizedRootDataFlow.swift */,
+				3811DE4B25C9D4B800A708ED /* AuthorizedRootProvider.swift */,
+				3811DE4725C9D4B800A708ED /* AuthorizedRootViewModel.swift */,
 				3811DE4825C9D4B800A708ED /* View */,
 			);
-			path = AuthotizedRoot;
+			path = AuthorizedRoot;
 			sourceTree = "<group>";
 		};
 		3811DE4825C9D4B800A708ED /* View */ = {
@@ -1625,7 +1625,7 @@
 				3811DEEB25CA063400A708ED /* PersistedProperty.swift in Sources */,
 				388E5A6025B6F2310019842D /* Autosens.swift in Sources */,
 				3811DE8B25C9D6DD00A708ED /* RequestPermissionsBuilder.swift in Sources */,
-				3811DE4C25C9D4B800A708ED /* AuthotizedRootBuilder.swift in Sources */,
+				3811DE4C25C9D4B800A708ED /* AuthorizedRootBuilder.swift in Sources */,
 				3811DE8F25C9D80400A708ED /* User.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
 				385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */,
@@ -1727,9 +1727,9 @@
 				38BF021D25E7E3AF00579895 /* Reservoir.swift in Sources */,
 				38BF021B25E7D06400579895 /* PumpSettingsView.swift in Sources */,
 				3811DEEA25CA063400A708ED /* SyncAccess.swift in Sources */,
-				3811DE4F25C9D4B800A708ED /* AuthotizedRootDataFlow.swift in Sources */,
+				3811DE4F25C9D4B800A708ED /* AuthorizedRootDataFlow.swift in Sources */,
 				38BF021F25E7F0DE00579895 /* DeviceDataManager.swift in Sources */,
-				3811DE5025C9D4B800A708ED /* AuthotizedRootProvider.swift in Sources */,
+				3811DE5025C9D4B800A708ED /* AuthorizedRootProvider.swift in Sources */,
 				38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */,
 				38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */,
 				3883581C25EE79BB00E024B2 /* DecimalTextField.swift in Sources */,
@@ -1744,7 +1744,7 @@
 				38E98A2525F52C9300C0CED0 /* IssueReporter.swift in Sources */,
 				3811DF0825CAAA4700A708ED /* ServiceContainer.swift in Sources */,
 				3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */,
-				3811DE4D25C9D4B800A708ED /* AuthotizedRootViewModel.swift in Sources */,
+				3811DE4D25C9D4B800A708ED /* AuthorizedRootViewModel.swift in Sources */,
 				3811DE6A25C9D62600A708ED /* OnboardingBuilder.swift in Sources */,
 				3811DEC425C9D99900A708ED /* NetworkContainer.swift in Sources */,
 				3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */,

+ 1 - 1
FreeAPS/Resources/Config.xcconfig

@@ -1 +1 @@
-BUILD_VERSION = 0.1.23
+BUILD_VERSION = 0.2.0

+ 2 - 0
FreeAPS/Resources/Info.plist

@@ -22,6 +22,8 @@
 	<string>$(MARKETING_VERSION)</string>
 	<key>CFBundleVersion</key>
 	<string>$(BUILD_VERSION)</string>
+	<key>ITSAppUsesNonExemptEncryption</key>
+	<false/>
 	<key>LSApplicationQueriesSchemes</key>
 	<array>
 		<string>dexcomg6</string>

+ 11 - 4
FreeAPS/Sources/APS/APSManager.swift

@@ -307,13 +307,14 @@ final class BaseAPSManager: APSManager, Injectable {
                 if !isSMB {
                     self.determineBasal().sink { _ in }.store(in: &self.lifetime)
                 }
+                self.bolusProgress.send(0)
             }
         } receiveValue: { _ in }
             .store(in: &lifetime)
     }
 
     func cancelBolus() {
-        guard let pump = pumpManager else { return }
+        guard let pump = pumpManager, pump.status.pumpStatus.bolusing else { return }
         debug(.apsManager, "Cancel bolus")
         pump.cancelBolus().sink { completion in
             if case let .failure(error) = completion {
@@ -391,6 +392,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 case .success:
                     debug(.apsManager, "Announcement Bolus succeeded")
                     self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
+                    self.bolusProgress.send(0)
                 case let .failure(error):
                     warning(.apsManager, "Announcement Bolus failed with error: \(error.localizedDescription)")
                 }
@@ -507,8 +509,11 @@ final class BaseAPSManager: APSManager, Injectable {
                 return Just(()).setFailureType(to: Error.self)
                     .eraseToAnyPublisher()
             }
-            return pump.enactBolus(units: Double(units), automatic: true).map { _ in () }
-                .eraseToAnyPublisher()
+            return pump.enactBolus(units: Double(units), automatic: true).map { _ in
+                self.bolusProgress.send(0)
+                return ()
+            }
+            .eraseToAnyPublisher()
         }()
 
         basalPublisher
@@ -558,7 +563,9 @@ final class BaseAPSManager: APSManager, Injectable {
     private func clearBolusReporter() {
         bolusReporter?.removeObserver(self)
         bolusReporter = nil
-        bolusProgress.send(nil)
+        processQueue.asyncAfter(deadline: .now() + 1) {
+            self.bolusProgress.send(nil)
+        }
     }
 }
 

+ 11 - 0
FreeAPS/Sources/Helpers/Publisher.swift

@@ -41,3 +41,14 @@ extension Publisher where Failure == Never {
 }
 
 typealias Lifetime = Set<AnyCancellable>
+
+extension Publisher where Failure == Never {
+    func weakAssign<T: AnyObject>(
+        to keyPath: ReferenceWritableKeyPath<T, Output>,
+        on object: T
+    ) -> AnyCancellable {
+        sink { [weak object] value in
+            object?[keyPath: keyPath] = value
+        }
+    }
+}

+ 2 - 2
FreeAPS/Sources/Models/Announcement.swift

@@ -12,8 +12,8 @@ struct Announcement: JSON {
         guard components.count == 2 else {
             return nil
         }
-        let command = String(components[0])
-        let arguments = String(components[1])
+        let command = String(components[0]).lowercased()
+        let arguments = String(components[1]).lowercased()
         switch command {
         case "bolus":
             guard let amount = Decimal(from: arguments) else { return nil }

+ 3 - 1
FreeAPS/Sources/Modules/AddCarbs/AddCarbsDataFlow.swift

@@ -2,4 +2,6 @@ enum AddCarbs {
     enum Config {}
 }
 
-protocol AddCarbsProvider: Provider {}
+protocol AddCarbsProvider: Provider {
+    var suggestion: Suggestion? { get }
+}

+ 5 - 1
FreeAPS/Sources/Modules/AddCarbs/AddCarbsProvider.swift

@@ -1,3 +1,7 @@
 extension AddCarbs {
-    final class Provider: BaseProvider, AddCarbsProvider {}
+    final class Provider: BaseProvider, AddCarbsProvider {
+        var suggestion: Suggestion? {
+            storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
+        }
+    }
 }

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

@@ -7,8 +7,11 @@ extension AddCarbs {
         @Injected() var apsManager: APSManager!
         @Published var carbs: Decimal = 0
         @Published var date = Date()
+        @Published var carbsRequired: Decimal?
 
-        override func subscribe() {}
+        override func subscribe() {
+            carbsRequired = provider.suggestion?.carbsReq
+        }
 
         func add() {
             guard carbs > 0 else {

+ 9 - 0
FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift

@@ -13,6 +13,15 @@ extension AddCarbs {
 
         var body: some View {
             Form {
+                if let carbsReq = viewModel.carbsRequired {
+                    Section {
+                        HStack {
+                            Text("Carbs required")
+                            Spacer()
+                            Text(formatter.string(from: carbsReq as NSNumber)! + " g")
+                        }
+                    }
+                }
                 Section {
                     HStack {
                         Text("Amount")

+ 1 - 1
FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootBuilder.swift

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

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

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

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

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

+ 2 - 2
FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootViewModel.swift

@@ -1,8 +1,8 @@
 import SwiftUI
 import Swinject
 
-extension AuthotizedRoot {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: AuthotizedRootProvider {
+extension AuthorizedRoot {
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: AuthorizedRootProvider {
         override func subscribe() {}
 
         var rootView: some View {

+ 1 - 1
FreeAPS/Sources/Modules/AuthotizedRoot/View/AuthotizedRootRootView.swift

@@ -1,6 +1,6 @@
 import SwiftUI
 
-extension AuthotizedRoot {
+extension AuthorizedRoot {
     struct RootView: BaseView {
         @EnvironmentObject var viewModel: ViewModel<Provider>
 

+ 0 - 7
FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootDataFlow.swift

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

+ 0 - 3
FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootProvider.swift

@@ -1,3 +0,0 @@
-extension AuthotizedRoot {
-    final class Provider: BaseProvider, AuthotizedRootProvider {}
-}

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

@@ -7,7 +7,7 @@ extension CREditor {
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
 
-        let rateValues = stride(from: 2, to: 50.01, by: 0.1).map { $0 }
+        let rateValues = stride(from: 3, to: 50.01, by: 0.1).map { $0 }
 
         var canAdd: Bool {
             guard let lastItem = items.last else { return true }

+ 9 - 7
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -41,7 +41,7 @@ extension Home {
         @Published var errorDate: Date? = nil
         @Published var bolusProgress: Decimal?
         @Published var eventualBG: Int?
-
+        @Published var carbsRequired: Decimal?
         @Published var allowManualTemp = false
         @Published var units: GlucoseUnits = .mmolL
 
@@ -63,6 +63,7 @@ extension Home {
             allowManualTemp = !settingsManager.settings.closedLoop
             closedLoop = settingsManager.settings.closedLoop
             lastLoopDate = apsManager.lastLoopDate
+            carbsRequired = suggestion?.carbsReq
 
             setStatusTitle()
             setupCurrentTempTarget()
@@ -89,22 +90,22 @@ extension Home {
 
             apsManager.isLooping
                 .receive(on: DispatchQueue.main)
-                .assign(to: \.isLooping, on: self)
+                .weakAssign(to: \.isLooping, on: self)
                 .store(in: &lifetime)
 
             apsManager.lastLoopDateSubject
                 .receive(on: DispatchQueue.main)
-                .assign(to: \.lastLoopDate, on: self)
+                .weakAssign(to: \.lastLoopDate, on: self)
                 .store(in: &lifetime)
 
             apsManager.pumpName
                 .receive(on: DispatchQueue.main)
-                .assign(to: \.pumpName, on: self)
+                .weakAssign(to: \.pumpName, on: self)
                 .store(in: &lifetime)
 
             apsManager.pumpExpiresAtDate
                 .receive(on: DispatchQueue.main)
-                .assign(to: \.pumpExpiresAtDate, on: self)
+                .weakAssign(to: \.pumpExpiresAtDate, on: self)
                 .store(in: &lifetime)
 
             apsManager.lastError
@@ -113,12 +114,12 @@ extension Home {
                     self.errorDate = error == nil ? nil : Date()
                     return error?.localizedDescription
                 }
-                .assign(to: \.errorMessage, on: self)
+                .weakAssign(to: \.errorMessage, on: self)
                 .store(in: &lifetime)
 
             apsManager.bolusProgress
                 .receive(on: DispatchQueue.main)
-                .assign(to: \.bolusProgress, on: self)
+                .weakAssign(to: \.bolusProgress, on: self)
                 .store(in: &lifetime)
         }
 
@@ -289,6 +290,7 @@ extension Home.ViewModel:
 
     func suggestionDidUpdate(_ suggestion: Suggestion) {
         self.suggestion = suggestion
+        carbsRequired = suggestion.carbsReq
         setStatusTitle()
     }
 

+ 12 - 2
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -540,8 +540,18 @@ extension MainChartView {
 
             let firstRec = self.suspensions.first.flatMap { event -> CGRect? in
                 guard event.type == .pumpResume else { return nil }
-                let width = self.timeToXCoordinate(event.timestamp.timeIntervalSince1970, fullSize: fullSize)
-                return CGRect(x: 0, y: 0, width: width, height: Config.basalHeight)
+                let tbrTime = self.tempBasals.last { $0.timestamp < event.timestamp }
+                    .map { $0.timestamp.timeIntervalSince1970 + TimeInterval($0.durationMin ?? 0) * 60 } ?? Date()
+                    .addingTimeInterval(-1.days.timeInterval).timeIntervalSince1970
+
+                let x0 = self.timeToXCoordinate(tbrTime, fullSize: fullSize)
+                let x1 = self.timeToXCoordinate(event.timestamp.timeIntervalSince1970, fullSize: fullSize)
+                return CGRect(
+                    x: x0,
+                    y: 0,
+                    width: x1 - x0,
+                    height: Config.basalHeight
+                )
             }
 
             let lastRec = self.suspensions.last.flatMap { event -> CGRect? in

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

@@ -233,11 +233,22 @@ extension Home {
                         HStack {
                             Button { viewModel.showModal(for: .addCarbs) }
                             label: {
-                                Image("carbs")
-                                    .renderingMode(.template)
-                                    .resizable()
-                                    .frame(width: 24, height: 24)
-                            }.foregroundColor(.loopGreen)
+                                ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
+                                    Image("carbs")
+                                        .renderingMode(.template)
+                                        .resizable()
+                                        .frame(width: 24, height: 24)
+                                        .foregroundColor(.loopGreen)
+                                        .padding(8)
+                                    if let carbsReq = viewModel.carbsRequired {
+                                        Text(numberFormatter.string(from: carbsReq as NSNumber)!)
+                                            .font(.caption)
+                                            .foregroundColor(.white)
+                                            .padding(4)
+                                            .background(Capsule().fill(Color.red))
+                                    }
+                                }
+                            }
                             Spacer()
                             Button { viewModel.showModal(for: .addTempTarget) }
                             label: {
@@ -245,6 +256,7 @@ extension Home {
                                     .renderingMode(.template)
                                     .resizable()
                                     .frame(width: 24, height: 24)
+                                    .padding(8)
                             }.foregroundColor(.loopYellow)
                             Spacer()
                             Button { viewModel.showModal(for: .bolus(waitForDuggestion: false)) }
@@ -253,6 +265,7 @@ extension Home {
                                     .renderingMode(.template)
                                     .resizable()
                                     .frame(width: 24, height: 24)
+                                    .padding(8)
                             }.foregroundColor(.insulin)
                             Spacer()
                             if viewModel.allowManualTemp {
@@ -262,6 +275,7 @@ extension Home {
                                         .renderingMode(.template)
                                         .resizable()
                                         .frame(width: 24, height: 24)
+                                        .padding(8)
                                 }.foregroundColor(.insulin)
                                 Spacer()
                             }
@@ -271,6 +285,7 @@ extension Home {
                                     .renderingMode(.template)
                                     .resizable()
                                     .frame(width: 24, height: 24)
+                                    .padding(8)
                             }.foregroundColor(.loopGray)
                         }
                         .padding(.horizontal, 24)

+ 3 - 1
FreeAPS/Sources/Modules/Login/LoginViewModel.swift

@@ -10,7 +10,9 @@ extension Login {
 
             $credentials
                 .compactMap { $0 }
-                .sink { self.provider.authorize(credentials: $0) }
+                .sink { [weak self] in
+                    self?.provider.authorize(credentials: $0)
+                }
                 .store(in: &lifetime)
         }
 

+ 3 - 0
FreeAPS/Sources/Modules/Main/MainDataFlow.swift

@@ -11,11 +11,14 @@ enum Main {
     }
 
     enum Scene {
+        case loading
         case authorized
         case onboarding
 
         var screen: Screen {
             switch self {
+            case .loading:
+                return .loading
             case .authorized:
                 return .authorizedRoot
             case .onboarding:

+ 1 - 6
FreeAPS/Sources/Modules/Main/MainViewModel.swift

@@ -9,12 +9,7 @@ extension Main {
         @Published var isModalPresented = false
         @Published var isAlertPresented = false
         @Published var alertMessage = ""
-        @Published private(set) var scene: Scene!
-
-        required init(provider: Provider, resolver: Resolver) {
-            super.init(provider: provider, resolver: resolver)
-            scene = isAuthotized ? .authorized : .onboarding
-        }
+        @Published private(set) var scene: Scene = .loading
 
         override func subscribe() {
             router.mainModalScreen

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

@@ -15,11 +15,11 @@ extension PumpSettingsEditor {
                 Section(header: Text("Delivery limits")) {
                     HStack {
                         Text("Max Basal")
-                        DecimalTextField("hours", value: $viewModel.maxBasal, formatter: formatter)
+                        DecimalTextField("U/hr", value: $viewModel.maxBasal, formatter: formatter)
                     }
                     HStack {
                         Text("Max Bolus")
-                        DecimalTextField("U/hr", value: $viewModel.maxBolus, formatter: formatter)
+                        DecimalTextField("U", value: $viewModel.maxBolus, formatter: formatter)
                     }
                 }
 

+ 6 - 0
FreeAPS/Sources/Modules/Settings/SettingsViewModel.swift

@@ -5,6 +5,7 @@ extension Settings {
         @Injected() private var settingsManager: SettingsManager!
         @Injected() private var broadcaster: Broadcaster!
         @Injected() private var fileManager: FileManager!
+        @Injected() private var authorizationManager: AuthorizationManager!
         @Published var closedLoop = false
 
         @Published var debugOptions = false
@@ -39,6 +40,11 @@ extension Settings {
 
             return items
         }
+
+        func logout() {
+            authorizationManager.logout()
+            showModal(for: nil)
+        }
     }
 }
 

+ 4 - 0
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -91,6 +91,10 @@ extension Settings {
                         .onTapGesture {
                             showShareSheet = true
                         }
+//                    Text("Read disclaimer").chevronCell()
+//                        .onTapGesture {
+//                            viewModel.logout()
+//                        }
                 }
             }
             .sheet(isPresented: $showShareSheet) {

+ 4 - 1
FreeAPS/Sources/Router/Screen.swift

@@ -2,6 +2,7 @@ import SwiftUI
 import Swinject
 
 enum Screen: Identifiable {
+    case loading
     case home
     case settings
     case onboarding
@@ -30,6 +31,8 @@ enum Screen: Identifiable {
 extension Screen {
     func view(resolver: Resolver) -> AnyView {
         switch self {
+        case .loading:
+            return ProgressView().asAny()
         case .home:
             return Home.Builder(resolver: resolver).buildView()
         case .settings:
@@ -37,7 +40,7 @@ extension Screen {
         case .onboarding:
             return Onboarding.Builder(resolver: resolver).buildView()
         case .authorizedRoot:
-            return AuthotizedRoot.Builder(resolver: resolver).buildView()
+            return AuthorizedRoot.Builder(resolver: resolver).buildView()
         case .login:
             return Login.Builder(resolver: resolver).buildView()
         case .requestPermissions:

+ 8 - 4
FreeAPS/Sources/Services/AuthorizationManager/AuthorizationManager.swift

@@ -3,17 +3,15 @@ import Combine
 import Swinject
 
 protocol AuthorizationManager {
-    var isAuthorized: Bool { get }
     var authorizationPublisher: AnyPublisher<Bool, Never> { get }
     func authorize(credentials: Credentials) -> AnyPublisher<Void, Never>
     func logout()
 }
 
 final class BaseAuthorizationManager: AuthorizationManager, Injectable {
-    private let isAuthorizedSubject = CurrentValueSubject<Bool, Never>(false)
+    private let isAuthorizedSubject = CurrentValueSubject<Bool?, Never>(nil)
 
-    var authorizationPublisher: AnyPublisher<Bool, Never> { isAuthorizedSubject.eraseToAnyPublisher() }
-    var isAuthorized: Bool { isAuthorizedSubject.value }
+    var authorizationPublisher: AnyPublisher<Bool, Never> { isAuthorizedSubject.ignoreNil().eraseToAnyPublisher() }
 
     let credentials = CurrentValueSubject<Credentials?, Never>(nil)
 
@@ -23,6 +21,12 @@ final class BaseAuthorizationManager: AuthorizationManager, Injectable {
 
     init(resolver: Resolver) {
         injectServices(resolver)
+        if let creds = keychain.getValue(Credentials.self, forKey: Login.Config.credentialsKey) {
+            credentials.send(creds)
+            isAuthorizedSubject.send(true)
+        } else {
+            isAuthorizedSubject.send(false)
+        }
     }
 
     func authorize(credentials: Credentials) -> AnyPublisher<Void, Never> {

+ 2 - 8
FreeAPS/Sources/Services/UnlockManager/UnlockManager.swift

@@ -25,20 +25,14 @@ final class BaseUnlockManager: UnlockManager {
 
             let reason = "We need to make sure you are the owner of the device."
 
-            if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
-                context.evaluatePolicy(
-                    .deviceOwnerAuthenticationWithBiometrics,
-                    localizedReason: reason,
-                    reply: handler
-                )
-            } else if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
+            if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
                 context.evaluatePolicy(
                     .deviceOwnerAuthentication,
                     localizedReason: reason,
                     reply: handler
                 )
             } else {
-                promise(.failure(UnlockError(error: error)))
+                handler(true, nil)
             }
         }
         .eraseToAnyPublisher()

+ 28 - 10
README.md

@@ -2,10 +2,14 @@
 
 FreeAPS X - an artificial pancreas system for iOS based on [OpenAPS Reference](https://github.com/openaps/oref0) algorithms
 
-[OpenAPS documentation](https://openaps.readthedocs.io/en/latest/)
-
 FreeAPS X uses original JavaScript files of oref0 and provides a user interface (UI) to control and set up the system
 
+## Documentation
+
+[Overview on Loop&Learn](https://www.loopandlearn.org/freeaps-x/)
+
+[OpenAPS documentation](https://openaps.readthedocs.io/en/latest/)
+
 ## Smartphone requirements
 
 - All iPhones which support iOS 14 and up.
@@ -21,15 +25,33 @@ To control an insulin pump FreeAPS X uses modified [rileylink_ios](https://githu
 - Medtronic Canadian/Australian Veo 554 or 754 (firmware 2.7A or lower)
 - Omnipod "Eros" pods
 
-To control an insulin you need to have a [RileyLink](https://getrileylink.org), oRange, Pickle, GNARL, Emalink or similar device
+To control an insulin you need to have a [RileyLink](https://getrileylink.org), OrangeLink, Pickle, GNARL, Emalink or similar device
 
 ## Current state of FreeAPS X
 
-FreeAPS X is in an active development state
+FreeAPS X is in an active development state and changes frequently.
+
+You can find a description of versions on the [releases page](https://github.com/ivalkou/freeaps/releases).
+
+### Stable versions
+
+A stable version means that it has been tested for a long time and does not contain critical bugs. We consider it ready for everyday use.
 
-**We do not recommend to use the system for everyday control of blood glucose**
+Stable version numbers end in .0 and the code can be found in the [master branch](https://github.com/ivalkou/freeaps/tree/master).
 
-If you want to test it, there is a beta-version available
+The current stable version is 0.2.0.
+
+### Beta versions
+
+Beta versions are the first to introduce new functionality. They are designed to test and identify issues and bugs.
+
+**Beta versions are not recommended for daily use for blood glucose control!**
+
+Beta numbers end with a number greater than 0, and the code can be found in the [dev brunch](https://github.com/ivalkou/freeaps/tree/dev).
+
+Pull requests are accepted on the dev branch.
+
+Bug reports and feature requests are accepted on the [Issues page](https://github.com/ivalkou/freeaps/issues).
 
 ## Implemented
 
@@ -57,10 +79,6 @@ If you want to test it, there is a beta-version available
 - Apple Health support
 - Detailed functions description inside the app
 
-## Documentation
-
-*In progress*
-
 ## Community
 
 - [English Telegram group](https://t.me/freeapsx_eng)

+ 28 - 10
README_RU.md

@@ -2,10 +2,14 @@
 
 FreeAPS X - система искуственной поджелудочной железы для iOS на основе алгоритмов [OpenAPS Reference](https://github.com/openaps/oref0)
 
-[Полная документация OpenAPS](https://openaps.readthedocs.io/en/latest/)
-
 FreeAPS X использует оригинальные JavaScript файлы oref0 и предоставляет пользовательский интерфейс (UI) для управления и настроек системы.
 
+## Документация
+
+[Краткий обзор на Loop&Learn](https://www.loopandlearn.org/freeaps-x/)
+
+[Полная документация OpenAPS](https://openaps.readthedocs.io/en/latest/)
+
 ## Требования к смартфону
 
 - Все iPhone с поддержкой iOS 14 и выше.
@@ -21,15 +25,33 @@ FreeAPS X использует оригинальные JavaScript файлы or
 - Medtronic Canadian/Australian Veo 554 or 754 (firmware 2.7A or lower)
 - Omnipod "Eros" pods
 
-Для управления помпой необходимо устройство [RileyLink](https://getrileylink.org), oRange, Pickle, GNARL, Emalink или аналоги.
+Для управления помпой необходимо устройство [RileyLink](https://getrileylink.org), OrangeLink, Pickle, GNARL, Emalink или аналоги.
 
 ## Текущее состояние FreeAPS X
 
-FreeAPS X находится в состоянии активной разработки.
+FreeAPS X находится в состоянии активной разработки и часто меняется.
+
+Описание версий вы можете найти на [странице релизов](https://github.com/ivalkou/freeaps/releases).
+
+### Стабильные версии
+
+Стабильная версия означет, что она была протестирована долгое время и не содерждит критических багов. Мы считаем её готовой для повседневного использования.
 
-**Не рекомендуется использовать систему повседневно для контроля гликемии!**
+Номера стабильных версий заканчиваются на .0, а код можно найти в [master ветке](https://github.com/ivalkou/freeaps/tree/master).
 
-Для тестирования на текущий момент доступна бета-версия приложения
+Текущая стабильная версия - 0.2.0.
+
+### Бета-версии
+
+В бета-версиях впервые появляется новая функциональность. Они предназначены для тестирования и выявления проблем и багов.
+
+**Бета-версии не рекомендуется использовать  повседневно для контроля гликемии!**
+
+Номера бета-версий заканчиваются на число больше 0, а код можно найти в [dev ветке](https://github.com/ivalkou/freeaps/tree/dev).
+
+Пулл-реквесты принимаются в dev ветку.
+
+Отчеты об ошибка их запросы на новую функциональность приимаются на странице [Issues](https://github.com/ivalkou/freeaps/issues).
 
 ## Реализовано
 
@@ -57,10 +79,6 @@ FreeAPS X находится в состоянии активной разраб
 - Пооддержка программы Здоровье
 - Поробное описание функций внутри приложения
 
-## Документация
-
-*В разработке*
-
 ## Сообщество
 
 - [Английская Telegram группа](https://t.me/freeapsx_eng)