diff --git a/Common/Settings/GlucoseSchedules.swift b/Common/Settings/GlucoseSchedules.swift index 5f26362..dd69bc5 100644 --- a/Common/Settings/GlucoseSchedules.swift +++ b/Common/Settings/GlucoseSchedules.swift @@ -147,9 +147,6 @@ class GlucoseSchedule: Codable, CustomStringConvertible { var highAlarm: Double? var enabled: Bool? - init() { - } - // glucose schedules are stored as standalone datecomponents (i.e. offsets) // this takes the current start of day and adds those offsets, // and returns a Dateinterval with those offsets applied diff --git a/LibreTransmitter.xcodeproj/project.pbxproj b/LibreTransmitter.xcodeproj/project.pbxproj index 71239c0..574b368 100644 --- a/LibreTransmitter.xcodeproj/project.pbxproj +++ b/LibreTransmitter.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 53; objects = { /* Begin PBXBuildFile section */ @@ -718,8 +718,9 @@ 432B0E7F1CDFC3C50045347B /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "LoopKit Authors"; TargetAttributes = { 432B0E871CDFC3C50045347B = { @@ -1040,6 +1041,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1109,6 +1111,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1138,7 +1141,8 @@ ); MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1153,16 +1157,23 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = LibreTransmitter/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.mddub.LibreTransmitter; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1175,16 +1186,23 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = LibreTransmitter/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.mddub.LibreTransmitter; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1208,12 +1226,19 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = LibreTransmitterUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.LibreTransmitterUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1239,12 +1264,19 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = LibreTransmitterUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.LibreTransmitterUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1268,7 +1300,11 @@ INFOPLIST_FILE = LibreTransmitterPlugin/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACH_O_TYPE = mh_dylib; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; @@ -1301,7 +1337,11 @@ INFOPLIST_FILE = LibreTransmitterPlugin/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACH_O_TYPE = mh_dylib; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_FAST_MATH = YES; diff --git a/LibreTransmitter.xcodeproj/xcshareddata/xcschemes/Shared-watchOS.xcscheme b/LibreTransmitter.xcodeproj/xcshareddata/xcschemes/Shared-watchOS.xcscheme index 442faa1..4d87245 100644 --- a/LibreTransmitter.xcodeproj/xcshareddata/xcschemes/Shared-watchOS.xcscheme +++ b/LibreTransmitter.xcodeproj/xcshareddata/xcschemes/Shared-watchOS.xcscheme @@ -1,6 +1,6 @@ String { - guard let glucoseUnit = UserDefaults.standard.mmGlucoseUnit else { - logger.debug("glucose unit was not recognized, aborting") - return "Unknown" - } - - var stringValue = [String]() - - var diff = newValue.glucoseDouble - oldValue.glucoseDouble - let sign = diff < 0 ? "-" : "+" - - if diff == 0 { - stringValue.append( "\(sign) 0") - } else { - diff = abs(diff) - let asObj = LibreGlucose( - unsmoothedGlucose: diff, - glucoseDouble: diff, - timestamp: Date()) - if let formatted = dynamicFormatter?.string(from: asObj.quantity, for: glucoseUnit) { - stringValue.append( "\(sign) \(formatted)") - } - } - - return stringValue.joined(separator: ",") - } -} - extension LibreGlucose { static func calculateSlope(current: Self, last: Self) -> Double { if current.timestamp == last.timestamp { diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index 512a6f8..434be5b 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -6,7 +6,7 @@ import Foundation import LoopKit -// import LoopKitUI +import LoopKitUI import UIKit import UserNotifications import Combine @@ -22,6 +22,8 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega public let isOnboarded = true // No distinction between created and onboarded + private var alertsUnitPreference = DisplayGlucosePreference(displayGlucoseUnit: .milligramsPerDeciliter) + public var hasValidSensorSession: Bool { lastConnected != nil } @@ -107,9 +109,9 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega "## LibreTransmitterManager", "Testdata: foo", "lastConnected: \(String(describing: lastConnected))", - "Connection state: \(self.proxy?.connectionStateString)", - "Sensor state: \(proxy?.sensorData?.state.description)", - "transmitterbattery: \(proxy?.metadata?.batteryString)", + "Connection state: \(String(describing: self.proxy?.connectionStateString))", + "Sensor state: \(String(describing: proxy?.sensorData?.state.description))", + "transmitterbattery: \(String(describing: proxy?.metadata?.batteryString))", "SensorData: \(getPersistedSensorDataForDebug())", "providesBLEHeartbeat: \(providesBLEHeartbeat)", "Metainfo::\n\(AppMetaData.allProperties)", @@ -142,10 +144,11 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega defer { logger.debug("sending glucose notification") - NotificationHelper.sendGlucoseNotitifcationIfNeeded(glucose: newValue, - oldValue: oldValue, - trend: trend, - battery: proxy?.metadata?.batteryString ?? "n/a") + NotificationHelper.sendGlucoseNotificationIfNeeded(glucose: newValue, + oldValue: oldValue, + trend: trend, + battery: proxy?.metadata?.batteryString ?? "n/a", + glucoseFormatter: alertsUnitPreference.formatter) // once we have a new glucose value, we can update the isalarming property if let activeAlarms = UserDefaults.standard.glucoseSchedules?.getActiveAlarms(newValue.glucoseDouble) { @@ -162,7 +165,7 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega } - logger.debug("latestBackfill set, newvalue is \(newValue.description)") + logger.debug("latestBackfill set, newvalue is \(newValue.glucose)") if let oldValue { // the idea here is to use the diff between the old and the new glucose to calculate slope and direction, rather than using trend from the glucose value. @@ -256,14 +259,6 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega public var sensorInfoObservable = SensorInfo() public var glucoseInfoObservable = GlucoseInfo() - var longDateFormatter: DateFormatter = ({ - let df = DateFormatter() - df.dateStyle = .long - df.timeStyle = .long - df.doesRelativeDateFormatting = true - return df - })() - var dateFormatter: DateFormatter = ({ let df = DateFormatter() df.dateStyle = .long @@ -298,11 +293,11 @@ extension LibreTransmitterManagerV3 { if let predicted = allGlucoses.predictBloodSugar(glucosePredictionMinutes) { let currentBg = predicted.calibratedGlucose(calibrationInfo: calibration) let bgDate = predicted.date.addingTimeInterval(60 * -glucosePredictionMinutes) - return LibreGlucose(unsmoothedGlucose: currentBg, glucoseDouble: currentBg, timestamp: bgDate) logger.debug("Predicted glucose (not used) was: \(currentBg)") + return LibreGlucose(unsmoothedGlucose: currentBg, glucoseDouble: currentBg, timestamp: bgDate) } else { - return nil logger.debug("Tried to predict glucose value but failed!") + return nil } } @@ -418,41 +413,19 @@ extension LibreTransmitterManagerV3 { } - let formatter = QuantityFormatter() - let preferredUnit = UserDefaults.standard.mmGlucoseUnit ?? .millimolesPerLiter - if let d = self.latestBackfill { self.logger.debug("will set glucoseInfoObservable") - - formatter.setPreferredNumberFormatter(for: .millimolesPerLiter) - self.glucoseInfoObservable.glucoseMMOL = formatter.string(from: d.quantity, for: .millimolesPerLiter) ?? "-" - - formatter.setPreferredNumberFormatter(for: .milligramsPerDeciliter) - self.glucoseInfoObservable.glucoseMGDL = formatter.string(from: d.quantity, for: .milligramsPerDeciliter) ?? "-" - - // backward compat - if preferredUnit == .millimolesPerLiter { - self.glucoseInfoObservable.glucose = self.glucoseInfoObservable.glucoseMMOL - } else if preferredUnit == .milligramsPerDeciliter { - self.glucoseInfoObservable.glucose = self.glucoseInfoObservable.glucoseMGDL - } - - self.glucoseInfoObservable.date = self.longDateFormatter.string(from: d.timestamp) + self.glucoseInfoObservable.glucose = d.quantity + self.glucoseInfoObservable.date = d.timestamp } if let d = self.latestPrediction { - formatter.setPreferredNumberFormatter(for: .millimolesPerLiter) - self.glucoseInfoObservable.predictionMMOL = formatter.string(from: d.quantity, for: .millimolesPerLiter) ?? "-" - - formatter.setPreferredNumberFormatter(for: .milligramsPerDeciliter) - self.glucoseInfoObservable.predictionMGDL = formatter.string(from: d.quantity, for: .milligramsPerDeciliter) ?? "-" - self.glucoseInfoObservable.predictionDate = self.longDateFormatter.string(from: d.timestamp) + self.glucoseInfoObservable.prediction = d.quantity + self.glucoseInfoObservable.predictionDate = d.timestamp } else { - self.glucoseInfoObservable.predictionMMOL = "" - self.glucoseInfoObservable.predictionMGDL = "" - self.glucoseInfoObservable.predictionDate = "" - + self.glucoseInfoObservable.prediction = nil + self.glucoseInfoObservable.predictionDate = nil } } @@ -492,22 +465,20 @@ extension LibreTransmitterManagerV3 { } logger.debug("tried creating trendarrow using \(glucoses.count) elements for trend calc") - return - glucoses - .filterDateRange(startDate, nil) - .compactMap { - return NewGlucoseSample( - date: $0.startDate, - quantity: $0.quantity, - condition: nil, - trend: trend, - trendRate: nil, - isDisplayOnly: false, - wasUserEntered: false, - syncIdentifier: $0.syncId, - device: self.proxy?.device) - } - + return glucoses + .filterDateRange(startDate, nil) + .compactMap { + return NewGlucoseSample( + date: $0.startDate, + quantity: $0.quantity, + condition: nil, + trend: trend, + trendRate: nil, + isDisplayOnly: false, + wasUserEntered: false, + syncIdentifier: $0.syncId, + device: self.proxy?.device) + } } public var calibrationData: SensorData.CalibrationInfo? { @@ -518,3 +489,10 @@ extension LibreTransmitterManagerV3 { proxy?.activePluginType?.smallImage ?? UIImage(named: "libresensor", in: Bundle.current, compatibleWith: nil)! } } + + +extension LibreTransmitterManagerV3: DisplayGlucoseUnitObserver { + public func unitDidChange(to displayGlucoseUnit: HKUnit) { + self.alertsUnitPreference.unitDidChange(to: displayGlucoseUnit) + } +} diff --git a/LibreTransmitter/NotificationHelper.swift b/LibreTransmitter/NotificationHelper.swift index 646ebc9..0606129 100644 --- a/LibreTransmitter/NotificationHelper.swift +++ b/LibreTransmitter/NotificationHelper.swift @@ -142,13 +142,6 @@ public enum NotificationHelper { } } - static func ensureCanSendGlucoseNotification(_ completion: @escaping (_ unit: HKUnit) -> Void ) { - ensureCanSendNotification { - if let glucoseUnit = UserDefaults.standard.mmGlucoseUnit, GlucoseUnitIsSupported(unit: glucoseUnit) { - completion(glucoseUnit) - } - } - } } // MARK: Sensor related notification sendouts @@ -370,7 +363,7 @@ public extension NotificationHelper { private static var glucoseNotifyCalledCount = 0 - static func sendGlucoseNotitifcationIfNeeded(glucose: LibreGlucose, oldValue: LibreGlucose?, trend: GlucoseTrend?, battery: String?) { + static func sendGlucoseNotificationIfNeeded(glucose: LibreGlucose, oldValue: LibreGlucose?, trend: GlucoseTrend?, battery: String?, glucoseFormatter: QuantityFormatter) { glucoseNotifyCalledCount &+= 1 let shouldSendGlucoseAlternatingTimes = glucoseNotifyCalledCount != 0 && UserDefaults.standard.mmNotifyEveryXTimes != 0 @@ -391,84 +384,88 @@ public extension NotificationHelper { // even if glucose notifications are disabled in the UI if shouldSend || alarm.isAlarming() { - sendGlucoseNotitifcation(glucose: glucose, oldValue: oldValue, - alarm: alarm, isSnoozed: isSnoozed, - trend: trend, showPhoneBattery: shouldShowPhoneBattery, - transmitterBattery: transmitterBattery) + sendGlucoseNotification(glucose: glucose, oldValue: oldValue, + glucoseFormatter: glucoseFormatter, + alarm: alarm, isSnoozed: isSnoozed, + trend: trend, showPhoneBattery: shouldShowPhoneBattery, + transmitterBattery: transmitterBattery) } else { logger.debug("\(#function) not sending glucose, shouldSend and alarmIsActive was false") return } } - private static func sendGlucoseNotitifcation(glucose: LibreGlucose, oldValue: LibreGlucose?, - alarm: GlucoseScheduleAlarmResult = .none, - isSnoozed: Bool = false, - trend: GlucoseTrend?, - showPhoneBattery: Bool = false, - transmitterBattery: String?) { - ensureCanSendGlucoseNotification { _ in - let content = UNMutableNotificationContent() - let glucoseDesc = glucose.description - var titles = [String]() - var body = [String]() - var body2 = [String]() - - var isCritical = false - switch alarm { - case .none: - titles.append("Glucose") - case .low: - titles.append("LOWALERT!") - isCritical = true - case .high: - titles.append("HIGHALERT!") - isCritical = true - } - - if isSnoozed { - titles.append("(Snoozed)") - } else if alarm.isAlarming() { - content.sound = .default - vibrateIfNeeded() - } - titles.append(glucoseDesc) - - body.append("Glucose: \(glucoseDesc)") + private static func sendGlucoseNotification(glucose: LibreGlucose, oldValue: LibreGlucose?, + glucoseFormatter: QuantityFormatter, + alarm: GlucoseScheduleAlarmResult = .none, + isSnoozed: Bool = false, + trend: GlucoseTrend?, + showPhoneBattery: Bool = false, + transmitterBattery: String?) { + let content = UNMutableNotificationContent() + let glucoseDesc = glucoseFormatter.string(from: glucose.quantity)! + var titles = [String]() + var body = [String]() + var body2 = [String]() + + var isCritical = false + switch alarm { + case .none: + titles.append("Glucose") + case .low: + titles.append("LOWALERT!") + isCritical = true + case .high: + titles.append("HIGHALERT!") + isCritical = true + } + + if isSnoozed { + titles.append("(Snoozed)") + } else if alarm.isAlarming() { + content.sound = .default + vibrateIfNeeded() + } + titles.append(glucoseDesc) - if let oldValue { - body.append( LibreGlucose.glucoseDiffDesc(oldValue: oldValue, newValue: glucose)) - } + body.append("Glucose: \(glucoseDesc)") - if let trend = trend?.localizedDescription { - body.append("\(trend)") + if let oldValue { + let diff = glucose.glucoseDouble - oldValue.glucoseDouble + if diff >= 0 { + body.append("+") } + body.append( glucoseFormatter.string(from: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: diff))!) + } - if showPhoneBattery { - if !UIDevice.current.isBatteryMonitoringEnabled { - UIDevice.current.isBatteryMonitoringEnabled = true - } + if let trend = trend?.localizedDescription { + body.append("\(trend)") + } - let battery = Double(UIDevice.current.batteryLevel * 100 ).roundTo(places: 1) - body2.append("Phone: \(battery)%") + if showPhoneBattery { + if !UIDevice.current.isBatteryMonitoringEnabled { + UIDevice.current.isBatteryMonitoringEnabled = true } - if let transmitterBattery { - body2.append("Transmitter: \(transmitterBattery)") - } + let battery = Double(UIDevice.current.batteryLevel * 100 ).roundTo(places: 1) + body2.append("Phone: \(battery)%") + } - // these are texts that naturally fit on their own line in the body - var body2s = "" - if !body2.isEmpty { - body2s = "\n" + body2.joined(separator: "\n") - } + if let transmitterBattery { + body2.append("Transmitter: \(transmitterBattery)") + } - content.title = titles.joined(separator: " ") - content.body = body.joined(separator: ", ") + body2s - addRequest(identifier: .glucocoseNotifications, - content: content, - deleteOld: true, isCritical: isCritical && !isSnoozed) + // these are texts that naturally fit on their own line in the body + var body2s = "" + if !body2.isEmpty { + body2s = "\n" + body2.joined(separator: "\n") } + + content.title = titles.joined(separator: " ") + content.body = body.joined(separator: ", ") + body2s + addRequest(identifier: .glucocoseNotifications, + content: content, + deleteOld: true, isCritical: isCritical && !isSnoozed) } private static var lastBatteryWarning: Date? diff --git a/LibreTransmitter/Observables/GlucoseInfo.swift b/LibreTransmitter/Observables/GlucoseInfo.swift index 983f95d..2b51dd1 100644 --- a/LibreTransmitter/Observables/GlucoseInfo.swift +++ b/LibreTransmitter/Observables/GlucoseInfo.swift @@ -7,20 +7,17 @@ // import Foundation +import HealthKit public class GlucoseInfo: ObservableObject, Equatable, Hashable { - @Published public var glucose = "" // dynamic based users preference - @Published public var glucoseMMOL = "" - @Published public var glucoseMGDL = "" - @Published public var date = "" + @Published public var glucose: HKQuantity? + @Published public var date: Date? @Published public var checksum = "" // @Published var entryErrors = "" - @Published public var prediction = "" - @Published public var predictionMMOL = "" - @Published public var predictionMGDL = "" - @Published public var predictionDate = "" + @Published public var prediction: HKQuantity? + @Published public var predictionDate: Date? public static func ==(lhs: GlucoseInfo, rhs: GlucoseInfo) -> Bool { lhs.glucose == rhs.glucose && lhs.date == rhs.date && diff --git a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift index b1085c6..eff386f 100644 --- a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift +++ b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift @@ -21,17 +21,16 @@ class LibreTransmitterSetupViewController: UINavigationController, CGMManagerOnb lazy var cgmManager: LibreTransmitterManagerV3? = LibreTransmitterManagerV3() - var modeSelection: UIHostingController! - - init() { + init(displayGlucosePreference: DisplayGlucosePreference) { SelectionState.shared.selectedStringIdentifier = UserDefaults.standard.preSelectedDevice let cancelNotifier = GenericObservableObject() let saveNotifier = GenericObservableObject() - modeSelection = UIHostingController(rootView: ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier)) + let myView = ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier) + .environmentObject(displayGlucosePreference) - super.init(rootViewController: modeSelection) + super.init(rootViewController: UIHostingController(rootView: myView)) cancelNotifier.listenOnce { [weak self] in self?.cancel() diff --git a/LibreTransmitterUI/LibreTransmitterManager+UI.swift b/LibreTransmitterUI/LibreTransmitterManager+UI.swift index 77d4f4b..f8f462e 100644 --- a/LibreTransmitterUI/LibreTransmitterManager+UI.swift +++ b/LibreTransmitterUI/LibreTransmitterManager+UI.swift @@ -24,12 +24,12 @@ extension LibreTransmitterManagerV3: CGMManagerUI { nil } - public static func setupViewController(bluetoothProvider: BluetoothProvider, displayGlucoseUnitObservable: DisplayGlucoseUnitObservable, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> SetupUIResult { + public static func setupViewController(bluetoothProvider: BluetoothProvider, displayGlucosePreference: DisplayGlucosePreference, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool, prefersToSkipUserInteraction: Bool) -> SetupUIResult { - return .userInteractionRequired(LibreTransmitterSetupViewController()) + return .userInteractionRequired(LibreTransmitterSetupViewController(displayGlucosePreference: displayGlucosePreference)) } - public func settingsViewController(bluetoothProvider: BluetoothProvider, displayGlucoseUnitObservable: DisplayGlucoseUnitObservable, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> CGMManagerViewController { + public func settingsViewController(bluetoothProvider: BluetoothProvider, displayGlucosePreference: DisplayGlucosePreference, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> CGMManagerViewController { let doneNotifier = GenericObservableObject() let wantToTerminateNotifier = GenericObservableObject() @@ -39,7 +39,7 @@ extension LibreTransmitterManagerV3: CGMManagerUI { let wantToRestablishConnectionNotifier = GenericObservableObject() let settings = SettingsView.asHostedViewController( - displayGlucoseUnitObservable: displayGlucoseUnitObservable, + displayGlucosePreference: displayGlucosePreference, notifyComplete: doneNotifier, notifyDelete: wantToTerminateNotifier, notifyReset: wantToResetCGMManagerNotifier, notifyReconnect:wantToRestablishConnectionNotifier, transmitterInfoObservable: self.transmitterInfoObservable, diff --git a/LibreTransmitterUI/Views/Settings/GlucoseSettingsView.swift b/LibreTransmitterUI/Views/Settings/GlucoseSettingsView.swift index 0afa41c..eafb8d9 100644 --- a/LibreTransmitterUI/Views/Settings/GlucoseSettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/GlucoseSettingsView.swift @@ -15,18 +15,6 @@ struct GlucoseSettingsView: View { @State private var presentableStatus: StatusMessage? - private var glucoseUnit: HKUnit - - public init(glucoseUnit: HKUnit) { - if let savedGlucoseUnit = UserDefaults.standard.mmGlucoseUnit { - self.glucoseUnit = savedGlucoseUnit - } else { - self.glucoseUnit = glucoseUnit - UserDefaults.standard.mmGlucoseUnit = glucoseUnit - } - - } - @AppStorage("com.loopkit.libreSyncToNs") var mmSyncToNS: Bool = true @AppStorage("com.loopkit.libreBackfillFromHistory") var mmBackfillFromHistory: Bool = true @AppStorage("com.loopkit.libreshouldPersistSensorData") var shouldPersistSensorData: Bool = false @@ -78,6 +66,6 @@ struct GlucoseSettingsView: View { struct GlucoseSettingsView_Previews: PreviewProvider { static var previews: some View { - GlucoseSettingsView(glucoseUnit: HKUnit.millimolesPerLiter) + GlucoseSettingsView() } } diff --git a/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift b/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift index 4c297c6..ee088bf 100644 --- a/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift @@ -10,26 +10,16 @@ import SwiftUI import Combine import LibreTransmitter import HealthKit +import LoopKitUI struct NotificationSettingsView: View { + @EnvironmentObject private var displayGlucosePreference: DisplayGlucosePreference @State private var presentableStatus: StatusMessage? - private var glucoseUnit: HKUnit - private let glucoseSegments = [HKUnit.millimolesPerLiter, HKUnit.milligramsPerDeciliter] private lazy var glucoseSegmentStrings = self.glucoseSegments.map({ $0.localizedShortUnitString }) - public init(glucoseUnit: HKUnit) { - if let savedGlucoseUnit = UserDefaults.standard.mmGlucoseUnit { - self.glucoseUnit = savedGlucoseUnit - } else { - self.glucoseUnit = glucoseUnit - UserDefaults.standard.mmGlucoseUnit = glucoseUnit - } - - } - private enum Key: String { // case glucoseSchedules = "com.loopkit.libreglucoseschedules" @@ -142,6 +132,6 @@ struct NotificationSettingsView: View { struct NotificationSettingsView_Previews: PreviewProvider { static var previews: some View { - NotificationSettingsView(glucoseUnit: HKUnit.millimolesPerLiter) + NotificationSettingsView() } } diff --git a/LibreTransmitterUI/Views/Settings/SettingsView.swift b/LibreTransmitterUI/Views/Settings/SettingsView.swift index bee129e..ef4f0ca 100644 --- a/LibreTransmitterUI/Views/Settings/SettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/SettingsView.swift @@ -15,6 +15,7 @@ import LoopKitUI import UniformTypeIdentifiers public struct SettingsItem: View { + @State var title: String = "" // we don't want this to change after it is set @Binding var detail: String @@ -47,8 +48,16 @@ public struct SettingsItem: View { } struct SettingsView: View { + @EnvironmentObject private var displayGlucosePreference: DisplayGlucosePreference + + var longDateFormatter: DateFormatter = ({ + let df = DateFormatter() + df.dateStyle = .long + df.timeStyle = .long + df.doesRelativeDateFormatting = true + return df + })() - @ObservedObject private var displayGlucoseUnitObservable: DisplayGlucoseUnitObservable @ObservedObject private var transmitterInfo: LibreTransmitter.TransmitterInfo @ObservedObject private var sensorInfo: LibreTransmitter.SensorInfo @@ -58,7 +67,7 @@ struct SettingsView: View { @ObservedObject private var notifyDelete: GenericObservableObject @ObservedObject private var notifyReset: GenericObservableObject @ObservedObject private var notifyReconnect: GenericObservableObject - + @State private var presentableStatus: StatusMessage? @ObservedObject var alarmStatus: LibreTransmitter.AlarmStatus @@ -67,7 +76,7 @@ struct SettingsView: View { // @Environment(\.presentationMode) var presentationMode static func asHostedViewController( - displayGlucoseUnitObservable: DisplayGlucoseUnitObservable, + displayGlucosePreference: DisplayGlucosePreference, notifyComplete: GenericObservableObject, notifyDelete: GenericObservableObject, notifyReset: GenericObservableObject, @@ -75,9 +84,9 @@ struct SettingsView: View { transmitterInfoObservable: LibreTransmitter.TransmitterInfo, sensorInfoObervable: LibreTransmitter.SensorInfo, glucoseInfoObservable: LibreTransmitter.GlucoseInfo, - alarmStatus: LibreTransmitter.AlarmStatus) -> DismissibleHostingController { - DismissibleHostingController(rootView: self.init( - displayGlucoseUnitObservable: displayGlucoseUnitObservable, + alarmStatus: LibreTransmitter.AlarmStatus) -> DismissibleHostingController + { + let view = self.init( transmitterInfo: transmitterInfoObservable, sensorInfo: sensorInfoObervable, glucoseMeasurement: glucoseInfoObservable, @@ -86,12 +95,13 @@ struct SettingsView: View { notifyReset: notifyReset, notifyReconnect: notifyReconnect, alarmStatus: alarmStatus - - )) + ).environmentObject(displayGlucosePreference) + return DismissibleHostingController(rootView: view) } + private var glucoseUnit: HKUnit { - displayGlucoseUnitObservable.displayGlucoseUnit + displayGlucosePreference.unit } static let formatter = NumberFormatter() @@ -104,8 +114,11 @@ struct SettingsView: View { headerSection snoozeSection measurementSection - if !glucoseMeasurement.predictionDate.isEmpty { - predictionSection + if let date = glucoseMeasurement.predictionDate, let prediction = glucoseMeasurement.prediction { + Section(header: Text(LocalizedString("Last Blood Sugar prediction", comment: "Text describing header for Blood Sugar prediction section"))) { + SettingsItem(title: "CurrentBG", detail: displayGlucosePreference.format(prediction)) + SettingsItem(title: "Date", detail: longDateFormatter.string(from: date) ) + } } NavigationLink(destination: deviceInfoSection) { @@ -122,13 +135,6 @@ struct SettingsView: View { destructSection }.listStyle(InsetGroupedListStyle()) - .onAppear { - // only override savedglucose unit if we haven't saved this locally before - if UserDefaults.standard.mmGlucoseUnit == nil { - UserDefaults.standard.mmGlucoseUnit = glucoseUnit - } - - } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { doneButton @@ -151,28 +157,19 @@ struct SettingsView: View { } var measurementSection : some View { - Section(header: Text(LocalizedString("Last measurement", comment: "Text describing header for last measurement section"))) { - if glucoseUnit == .millimolesPerLiter { - SettingsItem(title: "Glucose", detail: $glucoseMeasurement.glucoseMMOL) - } else if glucoseUnit == .milligramsPerDeciliter { - SettingsItem(title: "Glucose", detail: $glucoseMeasurement.glucoseMGDL) - } - - SettingsItem(title: "Date", detail: $glucoseMeasurement.date ) - SettingsItem(title: "Sensor Footer checksum", detail: $glucoseMeasurement.checksum ) + var glucoseText: String = "" + var glucoseDateText: String = "" + if let glucose = glucoseMeasurement.glucose { + glucoseText = displayGlucosePreference.format(glucose) + } + if let date = glucoseMeasurement.date { + glucoseDateText = longDateFormatter.string(from: date) } - } - - var predictionSection : some View { - Section(header: Text(LocalizedString("Last Blood Sugar prediction", comment: "Text describing header for Blood Sugar prediction section"))) { - if glucoseUnit == .millimolesPerLiter { - SettingsItem(title: "CurrentBG", detail: $glucoseMeasurement.predictionMMOL) - } else if glucoseUnit == .milligramsPerDeciliter { - SettingsItem(title: "CurrentBG", detail: $glucoseMeasurement.predictionMGDL) - } - - SettingsItem(title: "Date", detail: $glucoseMeasurement.predictionDate ) + return Section(header: Text(LocalizedString("Last measurement", comment: "Text describing header for last measurement section"))) { + SettingsItem(title: "Glucose", detail: glucoseText) + SettingsItem(title: "Date", detail: glucoseDateText ) + SettingsItem(title: "Sensor Footer checksum", detail: $glucoseMeasurement.checksum ) } } @@ -271,11 +268,11 @@ struct SettingsView: View { } } - NavigationLink(destination: GlucoseSettingsView(glucoseUnit: self.glucoseUnit)) { + NavigationLink(destination: GlucoseSettingsView()) { SettingsItem(title: "Glucose Settings") } - NavigationLink(destination: NotificationSettingsView(glucoseUnit: self.glucoseUnit)) { + NavigationLink(destination: NotificationSettingsView()) { SettingsItem(title: "Notifications") } @@ -324,7 +321,7 @@ struct SettingsView: View { var showProgress : Bool { - if let expiresAt = sensorInfo.expiresAt,let activatedAt = sensorInfo.activatedAt { + if let expiresAt = sensorInfo.expiresAt { return expiresAt.timeIntervalSinceNow > 0 } @@ -463,6 +460,6 @@ struct SettingsView: View { struct SettingsOverview_Previews: PreviewProvider { static var previews: some View { - NotificationSettingsView(glucoseUnit: HKUnit.millimolesPerLiter) + NotificationSettingsView() } } diff --git a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift index 861bbee..64e33bc 100644 --- a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift +++ b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift @@ -197,10 +197,6 @@ struct BluetoothSelection: View { private var searcher: BluetoothSearchManager! - /*static func asHostedViewController() -> UIHostingController { - UIHostingController(rootView: self.init()) - }*/ - // Should contain all discovered and compatible devices // This list is expected to contain 10 or 20 items at the most @State var allDevices = [SomePeripheral]()