Преглед изворни кода

Merge branch 'croTOdev' into Crowdin

Jon B Mårtensson пре 4 година
родитељ
комит
7c36618a57

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -165,6 +165,7 @@
 		38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D0B3D825EC07C400CB6E88 /* CarbsEntry.swift */; };
 		38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DAB27F260CBB7F00F74C1A /* PumpView.swift */; };
 		38DAB28A260D349500F74C1A /* FetchGlucoseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */; };
+		38DF1786276A73D400B3528F /* TagCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DF1785276A73D400B3528F /* TagCloudView.swift */; };
 		38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E4451D274DB04600EC9A94 /* AppDelegate.swift */; };
 		38E44522274E3DDC00EC9A94 /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E44521274E3DDC00EC9A94 /* NetworkReachabilityManager.swift */; };
 		38E44528274E401C00EC9A94 /* Protected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E44527274E401C00EC9A94 /* Protected.swift */; };
@@ -566,6 +567,7 @@
 		38D0B3D825EC07C400CB6E88 /* CarbsEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbsEntry.swift; sourceTree = "<group>"; };
 		38DAB27F260CBB7F00F74C1A /* PumpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpView.swift; sourceTree = "<group>"; };
 		38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchGlucoseManager.swift; sourceTree = "<group>"; };
+		38DF1785276A73D400B3528F /* TagCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagCloudView.swift; sourceTree = "<group>"; };
 		38E4451D274DB04600EC9A94 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		38E44521274E3DDC00EC9A94 /* NetworkReachabilityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkReachabilityManager.swift; sourceTree = "<group>"; };
 		38E44527274E401C00EC9A94 /* Protected.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protected.swift; sourceTree = "<group>"; };
@@ -1180,6 +1182,7 @@
 				383420D825FFEB3F002D46C1 /* Popup.swift */,
 				389ECDFD2601061500D86C4F /* View+Snapshot.swift */,
 				38EA05FF262091870064E39B /* BolusProgressViewStyle.swift */,
+				38DF1785276A73D400B3528F /* TagCloudView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -2065,6 +2068,7 @@
 				3894873A2614928B004DF424 /* DispatchTimer.swift in Sources */,
 				3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */,
 				386A124F271707F000DDC61C /* DexcomSource.swift in Sources */,
+				38DF1786276A73D400B3528F /* TagCloudView.swift in Sources */,
 				38B4F3CD25E5031100E76A18 /* Broadcaster.swift in Sources */,
 				383420D925FFEB3F002D46C1 /* Popup.swift in Sources */,
 				3811DE3025C9D49500A708ED /* HomeStateModel.swift in Sources */,

+ 1 - 1
FreeAPS.xcodeproj/xcuserdata/i.valkou.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -43,7 +43,7 @@
 		<key>FreeAPSWatch.xcscheme_^#shared#^_</key>
 		<dict>
 			<key>orderHint</key>
-			<integer>8</integer>
+			<integer>10</integer>
 		</dict>
 		<key>ReactiveSwift (Playground) 1.xcscheme</key>
 		<dict>

+ 15 - 12
FreeAPS/Sources/APS/APSManager.swift

@@ -150,7 +150,7 @@ final class BaseAPSManager: APSManager, Injectable {
         debug(.apsManager, "Starting loop")
         isLooping.send(true)
         determineBasal()
-            .sink { _ in } receiveValue: { [weak self] ok in
+            .sink { [weak self] ok in
                 guard let self = self else { return }
 
                 if ok {
@@ -170,7 +170,7 @@ final class BaseAPSManager: APSManager, Injectable {
     private func verifyStatus() -> Bool {
         guard let pump = pumpManager else {
             debug(.apsManager, "Pump is not set")
-            processError(APSError.invalidPumpState(message: "Pump is not set"))
+            processError(APSError.invalidPumpState(message: "Pump not set"))
             return false
         }
         let status = pump.status.pumpStatus
@@ -286,8 +286,10 @@ final class BaseAPSManager: APSManager, Injectable {
     }
 
     func roundBolus(amount: Decimal) -> Decimal {
-        guard let pump = pumpManager, verifyStatus() else { return amount }
-        return Decimal(pump.roundToSupportedBolusVolume(units: Double(amount)))
+        guard let pump = pumpManager else { return amount }
+        let rounded = Decimal(pump.roundToSupportedBolusVolume(units: Double(amount)))
+        let maxBolus = Decimal(pump.roundToSupportedBolusVolume(units: Double(settingsManager.pumpSettings.maxBolus))) 
+        return min(rounded, maxBolus)
     }
 
     private var bolusReporter: DoseProgressReporter?
@@ -486,14 +488,15 @@ final class BaseAPSManager: APSManager, Injectable {
             return
         }
 
-        guard let pump = pumpManager, verifyStatus() else {
+        guard let pump = pumpManager else {
             isLooping.send(false)
-            warning(.apsManager, "Invalid pump state")
+            warning(.apsManager, "Pump not set")
+            processError(APSError.invalidPumpState(message: "Pump not set"))
             return
         }
 
-        let basalPublisher: AnyPublisher<Void, Error> = {
-            guard let rate = suggested.rate, let duration = suggested.duration else {
+        let basalPublisher: AnyPublisher<Void, Error> = Deferred { () -> AnyPublisher<Void, Error> in
+            guard let rate = suggested.rate, let duration = suggested.duration, self.verifyStatus() else {
                 return Just(()).setFailureType(to: Error.self)
                     .eraseToAnyPublisher()
             }
@@ -503,10 +506,10 @@ final class BaseAPSManager: APSManager, Injectable {
                 return ()
             }
             .eraseToAnyPublisher()
-        }()
+        }.eraseToAnyPublisher()
 
-        let bolusPublisher: AnyPublisher<Void, Error> = {
-            guard let units = suggested.units else {
+        let bolusPublisher: AnyPublisher<Void, Error> = Deferred { () -> AnyPublisher<Void, Error> in
+            guard let units = suggested.units, self.verifyStatus() else {
                 return Just(()).setFailureType(to: Error.self)
                     .eraseToAnyPublisher()
             }
@@ -515,7 +518,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 return ()
             }
             .eraseToAnyPublisher()
-        }()
+        }.eraseToAnyPublisher()
 
         basalPublisher
             .flatMap { bolusPublisher }

+ 1 - 1
FreeAPS/Sources/APS/CGM/GlucoseSimulatorSource.swift

@@ -79,7 +79,7 @@ final class GlucoseSimulatorSource: GlucoseSource {
             lastFetchDate = Date()
         }
 
-        return Just(glucoses.reversed()).eraseToAnyPublisher()
+        return Just(glucoses).eraseToAnyPublisher()
     }
 }
 

+ 14 - 11
FreeAPS/Sources/APS/FetchGlucoseManager.swift

@@ -70,19 +70,22 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
                 .eraseToAnyPublisher()
             }
             .sink { date, syncDate, glucose, glucoseFromHealth in
+                let allGlucose = glucose + glucoseFromHealth
+                guard allGlucose.isNotEmpty else { return }
                 // Because of Spike dosn't respect a date query
-                let filteredByDate = (glucose + glucoseFromHealth).filter { $0.dateString > syncDate }
+                let filteredByDate = allGlucose.filter { $0.dateString > syncDate }
                 let filtered = self.glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
-                if filtered.isNotEmpty {
-                    debug(.nightscout, "New glucose found")
-                    self.glucoseStorage.storeGlucose(filtered)
-                    self.apsManager.heartbeat(date: date, force: false)
-                    self.nightscoutManager.uploadGlucose()
-                    let glucoseForHealth = filteredByDate.filter { !glucoseFromHealth.contains($0) }
-                    if glucoseForHealth.isNotEmpty {
-                        self.healthKitManager.save(bloodGlucoses: glucoseForHealth, completion: nil)
-                    }
-                }
+
+                guard filtered.isNotEmpty else { return }
+                debug(.nightscout, "New glucose found")
+
+                self.glucoseStorage.storeGlucose(filtered)
+                self.apsManager.heartbeat(date: date, force: false)
+                self.nightscoutManager.uploadGlucose()
+                let glucoseForHealth = filteredByDate.filter { !glucoseFromHealth.contains($0) }
+
+                guard glucoseForHealth.isNotEmpty else { return }
+                self.healthKitManager.save(bloodGlucoses: glucoseForHealth, completion: nil)
             }
             .store(in: &lifetime)
         timer.fire()

+ 4 - 3
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -91,8 +91,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at date: Date) -> [BloodGlucose] {
         var lastDate = date
         var filtered: [BloodGlucose] = []
+        let sorted = glucose.sorted { $0.date < $1.date }
 
-        for entry in glucose.reversed() {
+        for entry in sorted {
             guard entry.dateString.addingTimeInterval(-Config.filterTime) > lastDate else {
                 continue
             }
@@ -126,11 +127,11 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         guard let glucose = recent().last, glucose.dateString.addingTimeInterval(20.minutes.timeInterval) > Date(),
               let glucoseValue = glucose.glucose else { return nil }
 
-        if Decimal(glucoseValue) < settingsManager.settings.lowGlucose {
+        if Decimal(glucoseValue) <= settingsManager.settings.lowGlucose {
             return .low
         }
 
-        if Decimal(glucoseValue) > settingsManager.settings.highGlucose {
+        if Decimal(glucoseValue) >= settingsManager.settings.highGlucose {
             return .high
         }
 

+ 1 - 1
FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift

@@ -46,7 +46,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                     )]
                 case .tempBasal:
                     // get only start of TBR
-                    guard let dose = event.dose, dose.deliveredUnits == nil else { return [] }
+                    guard let dose = event.dose, dose.deliveredUnits ?? 0 == 0 else { return [] }
                     let rate = Decimal(string: dose.unitsPerHour.description)
                     let minutes = Int((dose.endDate - dose.startDate).timeInterval / 60)
                     return [

+ 21 - 0
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings

@@ -894,6 +894,27 @@ Enact a temp Basal or a temp target */
 /* Online or internal server */
 "Online or internal server" = "Online eller intern server";
 
+/* Shared app group */
+"Shared app group" = "Shared app group";
+
+/* Native G6 app */
+"Native G6 app" = "Native G6 app";
+
+/* Native G5 app */
+"Native G5 app" = "Native G5 app";
+
+/* Minilink transmitter */
+"Minilink transmitter" = "Minilink transmitter";
+
+/* Simple simulator */
+"Simple simulator" = "Simple simulator";
+
+/* Direct connection with Libre 1 transmitters or Libre 2 */
+"Direct connection with Libre 1 transmitters or Libre 2" = "Direct connection with Libre 1 transmitters or Libre 2";
+
+/* Online or internal server */
+"Online or internal server" = "Online or internal server";
+
 /* HealthKit intergration --------------------*/
 /* */
 "Apple Health" = "Apple Helse";

+ 10 - 0
FreeAPS/Sources/Models/Suggestion.swift

@@ -65,3 +65,13 @@ protocol SuggestionObserver {
 protocol EnactedSuggestionObserver {
     func enactedSuggestionDidUpdate(_ suggestion: Suggestion)
 }
+
+extension Suggestion {
+    var reasonParts: [String] {
+        reason.components(separatedBy: "; ").first?.components(separatedBy: ", ") ?? []
+    }
+
+    var reasonConclusion: String {
+        reason.components(separatedBy: "; ").last ?? ""
+    }
+}

+ 5 - 1
FreeAPS/Sources/Modules/CREditor/View/CREditorRootView.swift

@@ -37,7 +37,11 @@ extension CREditor {
                     addButton
                 }
                 Section {
-                    Button { state.save() }
+                    Button {
+                        let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                        impactHeavy.impactOccurred()
+                        state.save()
+                    }
                     label: {
                         Text("Save")
                     }

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

@@ -11,6 +11,8 @@ extension ConfigEditor {
         }
 
         func save() {
+            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+            impactHeavy.impactOccurred()
             provider.save(configText, as: file)
         }
     }

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

@@ -876,12 +876,12 @@ extension MainChartView {
     }
 
     private func firstHourDate() -> Date {
-        let firstDate = glucose.first?.dateString ?? Date()
+        let firstDate = Date().addingTimeInterval(-1.days.timeInterval)
         return firstDate.dateTruncated(from: .minute)!
     }
 
     private func firstHourPosition(viewWidth: CGFloat) -> CGFloat {
-        let firstDate = glucose.first?.dateString ?? Date()
+        let firstDate = Date().addingTimeInterval(-1.days.timeInterval)
         let firstHour = firstHourDate()
 
         let lastDeltaTime = firstHour.timeIntervalSince(firstDate)

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

@@ -325,16 +325,23 @@ extension Home {
             .navigationBarHidden(true)
             .ignoresSafeArea(.keyboard)
             .popup(isPresented: isStatusPopupPresented, alignment: .top, direction: .top) {
-                VStack(alignment: .leading) {
-                    Text(state.statusTitle).foregroundColor(.white)
+                VStack(alignment: .leading, spacing: 4) {
+                    Text(state.statusTitle).font(.headline).foregroundColor(.white)
                         .padding(.bottom, 4)
-                    Text(state.suggestion?.reason ?? "No sugestion found").font(.caption).foregroundColor(.white)
+                    if let suggestion = state.suggestion {
+                        TagCloudView(tags: suggestion.reasonParts).animation(.none, value: false)
+                        Text(suggestion.reasonConclusion.capitalizingFirstLetter()).font(.body).foregroundColor(.white)
+                    } else {
+                        Text("No sugestion found").font(.body).foregroundColor(.white)
+                    }
 
                     if let errorMessage = state.errorMessage, let date = state.errorDate {
-                        Text("Error at \(dateFormatter.string(from: date))").foregroundColor(.white)
+                        Text("Error at \(dateFormatter.string(from: date))")
+                            .foregroundColor(.white)
+                            .font(.headline)
                             .padding(.bottom, 4)
                             .padding(.top, 8)
-                        Text(errorMessage).font(.caption).foregroundColor(.white)
+                        Text(errorMessage).font(.caption).foregroundColor(.loopRed)
                     }
                 }
                 .padding()

+ 5 - 1
FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -57,7 +57,11 @@ extension ISFEditor {
                     addButton
                 }
                 Section {
-                    Button { state.save() }
+                    Button {
+                        let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                        impactHeavy.impactOccurred()
+                        state.save()
+                    }
                     label: {
                         Text("Save")
                     }

+ 0 - 5
FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift

@@ -84,11 +84,6 @@ extension PreferencesEditor {
                         }
                     }
                 }
-
-                Section {
-                    Text("Edit settings json")
-                        .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
-                }
             }
             .onAppear(perform: configureView)
             .navigationTitle("Preferences")

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

@@ -11,8 +11,7 @@ extension Settings {
         private(set) var buildNumber = ""
 
         override func subscribe() {
-            debugOptions = settingsManager.settings.debugOptions
-
+            subscribeSetting(\.debugOptions, on: $debugOptions) { debugOptions = $0 }
             subscribeSetting(\.closedLoop, on: $closedLoop) { closedLoop = $0 }
 
             broadcaster.register(SettingsObserver.self, observer: self)

+ 5 - 2
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -34,8 +34,9 @@ extension Settings {
                     Text("Autotune").navigationLink(to: .autotuneConfig, from: self)
                 }
 
-                if state.debugOptions {
-                    Section(header: Text("Config files")) {
+                Section(header: Text("Developer")) {
+                    Toggle("Debug options", isOn: $state.debugOptions)
+                    if state.debugOptions {
                         Group {
                             Text("Preferences")
                                 .navigationLink(to: .configEditor(file: OpenAPS.Settings.preferences), from: self)
@@ -91,6 +92,8 @@ extension Settings {
                                 .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.calibrations), from: self)
                             Text("Middleware")
                                 .navigationLink(to: .configEditor(file: OpenAPS.Middleware.determineBasal), from: self)
+                            Text("Edit settings json")
+                                .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
                         }
                     }
                 }

+ 5 - 1
FreeAPS/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift

@@ -27,7 +27,11 @@ extension TargetsEditor {
                     addButton
                 }
                 Section {
-                    Button { state.save() }
+                    Button {
+                        let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                        impactHeavy.impactOccurred()
+                        state.save()
+                    }
                     label: {
                         Text("Save")
                     }

+ 12 - 8
FreeAPS/Sources/Services/UserNotifiactions/UserNotificationsManager.swift

@@ -290,17 +290,21 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         let request = UNNotificationRequest(identifier: identifier.rawValue, content: content, trigger: trigger)
 
         if deleteOld {
-            center.removeDeliveredNotifications(withIdentifiers: [identifier.rawValue])
-            center.removePendingNotificationRequests(withIdentifiers: [identifier.rawValue])
+            DispatchQueue.main.async {
+                self.center.removeDeliveredNotifications(withIdentifiers: [identifier.rawValue])
+                self.center.removePendingNotificationRequests(withIdentifiers: [identifier.rawValue])
+            }
         }
 
-        center.add(request) { error in
-            if let error = error {
-                warning(.service, "Unable to addNotificationRequest", error: error)
-                return
-            }
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+            self.center.add(request) { error in
+                if let error = error {
+                    warning(.service, "Unable to addNotificationRequest", error: error)
+                    return
+                }
 
-            debug(.service, "Sending \(identifier) notification")
+                debug(.service, "Sending \(identifier) notification")
+            }
         }
     }
 

+ 84 - 0
FreeAPS/Sources/Views/TagCloudView.swift

@@ -0,0 +1,84 @@
+import SwiftUI
+
+struct TagCloudView: View {
+    var tags: [String]
+
+    @State private var totalHeight
+//          = CGFloat.zero       // << variant for ScrollView/List
+        = CGFloat.infinity // << variant for VStack
+
+    var body: some View {
+        VStack {
+            GeometryReader { geometry in
+                self.generateContent(in: geometry)
+            }
+        }
+//        .frame(height: totalHeight)// << variant for ScrollView/List
+        .frame(maxHeight: totalHeight) // << variant for VStack
+    }
+
+    private func generateContent(in g: GeometryProxy) -> some View {
+        var width = CGFloat.zero
+        var height = CGFloat.zero
+
+        return ZStack(alignment: .topLeading) {
+            ForEach(self.tags, id: \.self) { tag in
+                self.item(for: tag)
+                    .padding([.horizontal, .vertical], 4)
+                    .alignmentGuide(.leading, computeValue: { d in
+                        if abs(width - d.width) > g.size.width
+                        {
+                            width = 0
+                            height -= d.height
+                        }
+                        let result = width
+                        if tag == self.tags.last! {
+                            width = 0 // last item
+                        } else {
+                            width -= d.width
+                        }
+                        return result
+                    })
+                    .alignmentGuide(.top, computeValue: { _ in
+                        let result = height
+                        if tag == self.tags.last! {
+                            height = 0 // last item
+                        }
+                        return result
+                    })
+            }
+        }.background(viewHeightReader($totalHeight))
+    }
+
+    private func item(for text: String) -> some View {
+        Text(text)
+            .padding(.all, 5)
+            .font(.body)
+            .background(Color.insulin)
+            .foregroundColor(Color.white)
+            .cornerRadius(5)
+    }
+
+    private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
+        GeometryReader { geometry -> Color in
+            let rect = geometry.frame(in: .local)
+            DispatchQueue.main.async {
+                binding.wrappedValue = rect.size.height
+            }
+            return .clear
+        }
+    }
+}
+
+struct TestTagCloudView: View {
+    var body: some View {
+        VStack {
+            Text("Header").font(.largeTitle)
+            TagCloudView(tags: ["Ninetendo", "XBox", "PlayStation", "PlayStation 2", "PlayStation 3", "PlayStation 4"])
+            Text("Some other text")
+            Divider()
+            Text("Some other cloud")
+            TagCloudView(tags: ["Apple", "Google", "Amazon", "Microsoft", "Oracle", "Facebook"])
+        }
+    }
+}

+ 11 - 8
README.md

@@ -63,23 +63,26 @@ Bug reports and feature requests are accepted on the [Issues page](https://githu
 - Autosens
 - Nightscout BG data source as a CGM (Online)
 - Applications that mimic Nightscout as a CGM (apps like Spike and Diabox) (Offline)
-- xDrip4iOS data source as a CGM via shared app gpoup (Offline)
+- [xDrip4iOS](https://github.com/JohanDegraeve/xdripswift) data source as a CGM via shared app gpoup (Offline)
+- [GlucoseDirectApp](https://github.com/creepymonster/GlucoseDirectApp) data source as a CGM via shared app gpoup (Offline)
+- Libre 1 transmitters and Libre 2 direct as a CGM
+- Simple glucose simulator
 - System state upload to Nightscout
 - Remote carbs enter and temporary targets through Nightscout
 - Remote bolusing and insulin pump control
 - Dexcom offline support (beta)
-- Detailed functions description inside the app (beta)
+- Detailed oref preferences description inside the app (beta)
+- User notifications of the system and connected devices state (beta)
+- Apple Watch app (beta)
+- Enlite support (beta)
+- Apple Health support for blood glucose (beta)
 
 ## Not implemented (plans for future)
 
 - Open loop mode
-- Phone notifications of the system and connected devices state
 - Profile upload to Nightscout
-- Desktop widget
-- Apple Watch app
-- Plugins
-- Enlite support
-- Apple Health support
+- Home screen widget
+- Apple Health support for carbs and insulin
 
 ## Community
 

+ 10 - 7
README_RU.md

@@ -63,23 +63,26 @@ FreeAPS X находится в состоянии активной разраб
 - Autosens
 - Использование Nightscout в качестве CGM
 - Использование оффлайн локального сервера в качестве CGM (программы Spike, Diabox)
-- Использование xDrip4iOS оффлан в качестве CGM через shared app gpoup
+- Использование [xDrip4iOS](https://github.com/JohanDegraeve/xdripswift) оффлан в качестве CGM через shared app gpoup
+- Использование [GlucoseDirectApp](https://github.com/creepymonster/GlucoseDirectApp) оффлан в качестве CGM через shared app gpoup
+- Использование передатчиков Libre 1 и Libre 2 напрямую в качаесте CGM
+- Простой симулятор глюкозы
 - Загрузка состояния системы в Nightscout
 - Удаленный ввод углеводов и временных целей через Nightscout
 - Удаленный ввод болюса и управление помпой
 - Поддержка Dexcom (beta)
-- Поробное описание функций внутри приложения (beta)
+- Поробное описание настроек oref внутри приложения (beta)
+- Уведомления на смартфоне о состоянии системы и подключенных к ней устройств (beta)
+- Приложение для часов (beta)
+- Поддержка Enlite (beta)
+- Поддержка программы Здоровье, глюкоза (beta)
 
 ## Не реализовано (планируется в будущих версиях)
 
 - Режим открытой петли
-- Уведомления на смартфоне о состоянии системы и подключенных к ней устройств
 - Загрузка профиля в Nightscout
 - Виджет на рабочий стол
-- Приложение для часов
-- Плагины
-- Поддержка Enlite
-- Поддержка программы Здоровье
+- Поддержка программы Здоровье, углеводы и инсулин
 
 ## Сообщество