From c61a47b541aeadb7e71c3290f1a434a16a5efca1 Mon Sep 17 00:00:00 2001 From: Paul Plant Date: Fri, 29 Dec 2023 11:42:10 +0100 Subject: [PATCH 01/89] remove old Today widget to leave clean build - remove old widget target - remove references - remove files - keep a copy of the localized text files and extensions used by the old widget as they can be re-used (stored in TodayWidgetSaved folder) - app builds cleanly without anything left of old widget --- .../Date.swift | 0 .../Glucose+GlucoseKit.swift | 0 .../Texts => TodayWidgetSaved}/Texts.swift | 0 .../ar.lproj/Common.strings | 0 .../da.lproj/Common.strings | 0 .../de.lproj/Common.strings | 0 .../el.lproj/Common.strings | 0 .../en.lproj/Common.strings | 0 .../es-ES.lproj/Common.strings | 0 .../es-MX.lproj/Common.strings | 0 .../es.lproj/Common.strings | 0 .../fi.lproj/Common.strings | 0 .../fr.lproj/Common.strings | 0 .../it.lproj/Common.strings | 0 .../nl.lproj/Common.strings | 0 .../pl-PL.lproj/Common.strings | 0 .../pt.lproj/Common.strings | 0 .../ru.lproj/Common.strings | 0 .../sl.lproj/Common.strings | 0 .../sv.lproj/Common.strings | 0 .../tr.lproj/Common.strings | 0 .../uk.lproj/Common.strings | 0 .../zh.lproj/Common.strings | 0 .../Base.lproj/MainInterface.storyboard | 83 ---- xDrip4iOS Widget/Info.plist | 33 -- xDrip4iOS Widget/TodayViewController.swift | 365 ------------------ .../da.lproj/MainInterface.strings | 9 - .../el.lproj/MainInterface.strings | 9 - .../es.lproj/MainInterface.strings | 9 - .../fi.lproj/MainInterface.strings | 9 - .../tr.lproj/MainInterface.strings | 9 - .../xDrip4iOS Widget.entitlements | 10 - .../xDripClient/ConstantsBGGraphBuilder.swift | 10 - .../xDripClient/ConstantsBloodGlucose.swift | 6 - xDrip4iOS Widget/xDripClient/Double.swift | 35 -- xDrip4iOS Widget/xDripClient/Trace.swift | 13 - .../xDripClient/XDripClient.swift | 95 ----- xdrip.xcodeproj/project.pbxproj | 202 +--------- .../G5/DexcomG5+BluetoothPeripheral.swift | 1 - .../NightScout/NightScoutUploadManager.swift | 1 - xdrip/xDrip.xcconfig | 4 +- 41 files changed, 5 insertions(+), 898 deletions(-) rename {xDrip4iOS Widget/xDripClient => TodayWidgetSaved}/Date.swift (100%) rename {xDrip4iOS Widget/xDripClient => TodayWidgetSaved}/Glucose+GlucoseKit.swift (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/Texts.swift (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/ar.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/da.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/de.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/el.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/en.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/es-ES.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/es-MX.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/es.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/fi.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/fr.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/it.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/nl.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/pl-PL.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/pt.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/ru.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/sl.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/sv.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/tr.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/uk.lproj/Common.strings (100%) rename {xDrip4iOS Widget/Texts => TodayWidgetSaved}/zh.lproj/Common.strings (100%) delete mode 100644 xDrip4iOS Widget/Base.lproj/MainInterface.storyboard delete mode 100644 xDrip4iOS Widget/Info.plist delete mode 100644 xDrip4iOS Widget/TodayViewController.swift delete mode 100644 xDrip4iOS Widget/da.lproj/MainInterface.strings delete mode 100644 xDrip4iOS Widget/el.lproj/MainInterface.strings delete mode 100644 xDrip4iOS Widget/es.lproj/MainInterface.strings delete mode 100644 xDrip4iOS Widget/fi.lproj/MainInterface.strings delete mode 100644 xDrip4iOS Widget/tr.lproj/MainInterface.strings delete mode 100644 xDrip4iOS Widget/xDrip4iOS Widget.entitlements delete mode 100644 xDrip4iOS Widget/xDripClient/ConstantsBGGraphBuilder.swift delete mode 100644 xDrip4iOS Widget/xDripClient/ConstantsBloodGlucose.swift delete mode 100644 xDrip4iOS Widget/xDripClient/Double.swift delete mode 100644 xDrip4iOS Widget/xDripClient/Trace.swift delete mode 100644 xDrip4iOS Widget/xDripClient/XDripClient.swift diff --git a/xDrip4iOS Widget/xDripClient/Date.swift b/TodayWidgetSaved/Date.swift similarity index 100% rename from xDrip4iOS Widget/xDripClient/Date.swift rename to TodayWidgetSaved/Date.swift diff --git a/xDrip4iOS Widget/xDripClient/Glucose+GlucoseKit.swift b/TodayWidgetSaved/Glucose+GlucoseKit.swift similarity index 100% rename from xDrip4iOS Widget/xDripClient/Glucose+GlucoseKit.swift rename to TodayWidgetSaved/Glucose+GlucoseKit.swift diff --git a/xDrip4iOS Widget/Texts/Texts.swift b/TodayWidgetSaved/Texts.swift similarity index 100% rename from xDrip4iOS Widget/Texts/Texts.swift rename to TodayWidgetSaved/Texts.swift diff --git a/xDrip4iOS Widget/Texts/ar.lproj/Common.strings b/TodayWidgetSaved/ar.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/ar.lproj/Common.strings rename to TodayWidgetSaved/ar.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/da.lproj/Common.strings b/TodayWidgetSaved/da.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/da.lproj/Common.strings rename to TodayWidgetSaved/da.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/de.lproj/Common.strings b/TodayWidgetSaved/de.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/de.lproj/Common.strings rename to TodayWidgetSaved/de.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/el.lproj/Common.strings b/TodayWidgetSaved/el.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/el.lproj/Common.strings rename to TodayWidgetSaved/el.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/en.lproj/Common.strings b/TodayWidgetSaved/en.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/en.lproj/Common.strings rename to TodayWidgetSaved/en.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/es-ES.lproj/Common.strings b/TodayWidgetSaved/es-ES.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/es-ES.lproj/Common.strings rename to TodayWidgetSaved/es-ES.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/es-MX.lproj/Common.strings b/TodayWidgetSaved/es-MX.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/es-MX.lproj/Common.strings rename to TodayWidgetSaved/es-MX.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/es.lproj/Common.strings b/TodayWidgetSaved/es.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/es.lproj/Common.strings rename to TodayWidgetSaved/es.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/fi.lproj/Common.strings b/TodayWidgetSaved/fi.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/fi.lproj/Common.strings rename to TodayWidgetSaved/fi.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/fr.lproj/Common.strings b/TodayWidgetSaved/fr.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/fr.lproj/Common.strings rename to TodayWidgetSaved/fr.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/it.lproj/Common.strings b/TodayWidgetSaved/it.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/it.lproj/Common.strings rename to TodayWidgetSaved/it.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/nl.lproj/Common.strings b/TodayWidgetSaved/nl.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/nl.lproj/Common.strings rename to TodayWidgetSaved/nl.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/pl-PL.lproj/Common.strings b/TodayWidgetSaved/pl-PL.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/pl-PL.lproj/Common.strings rename to TodayWidgetSaved/pl-PL.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/pt.lproj/Common.strings b/TodayWidgetSaved/pt.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/pt.lproj/Common.strings rename to TodayWidgetSaved/pt.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/ru.lproj/Common.strings b/TodayWidgetSaved/ru.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/ru.lproj/Common.strings rename to TodayWidgetSaved/ru.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/sl.lproj/Common.strings b/TodayWidgetSaved/sl.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/sl.lproj/Common.strings rename to TodayWidgetSaved/sl.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/sv.lproj/Common.strings b/TodayWidgetSaved/sv.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/sv.lproj/Common.strings rename to TodayWidgetSaved/sv.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/tr.lproj/Common.strings b/TodayWidgetSaved/tr.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/tr.lproj/Common.strings rename to TodayWidgetSaved/tr.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/uk.lproj/Common.strings b/TodayWidgetSaved/uk.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/uk.lproj/Common.strings rename to TodayWidgetSaved/uk.lproj/Common.strings diff --git a/xDrip4iOS Widget/Texts/zh.lproj/Common.strings b/TodayWidgetSaved/zh.lproj/Common.strings similarity index 100% rename from xDrip4iOS Widget/Texts/zh.lproj/Common.strings rename to TodayWidgetSaved/zh.lproj/Common.strings diff --git a/xDrip4iOS Widget/Base.lproj/MainInterface.storyboard b/xDrip4iOS Widget/Base.lproj/MainInterface.storyboard deleted file mode 100644 index b9410627..00000000 --- a/xDrip4iOS Widget/Base.lproj/MainInterface.storyboard +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/xDrip4iOS Widget/Info.plist b/xDrip4iOS Widget/Info.plist deleted file mode 100644 index 5e036edb..00000000 --- a/xDrip4iOS Widget/Info.plist +++ /dev/null @@ -1,33 +0,0 @@ - - - - - AppGroupIdentifier - $(APP_GROUP_IDENTIFIER) - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - $(MAIN_APP_DISPLAY_NAME) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSExtension - - NSExtensionMainStoryboard - MainInterface - NSExtensionPointIdentifier - com.apple.widget-extension - - - diff --git a/xDrip4iOS Widget/TodayViewController.swift b/xDrip4iOS Widget/TodayViewController.swift deleted file mode 100644 index fc755944..00000000 --- a/xDrip4iOS Widget/TodayViewController.swift +++ /dev/null @@ -1,365 +0,0 @@ -import Foundation -import NotificationCenter -import os - -class TodayViewController: UIViewController, NCWidgetProviding { - - // MARK: - Properties - Outlets and Actions - - - @IBOutlet weak var minutesLabelOutlet: UILabel! - @IBOutlet weak var minutesAgoLabelOutlet: UILabel! - - @IBOutlet weak var diffLabelOutlet: UILabel! - @IBOutlet weak var diffLabelUnitOutlet: UILabel! - - @IBOutlet weak var valueLabelOutlet: UILabel! - - - - // MARK: - private properties - - /// xDripClient - private var xDripClient: XDripClient = XDripClient() - - // MARK: - overriden functions - - override func viewDidLoad() { - - super.viewDidLoad() - - setupView() - - } - - func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) { - // Perform any setup necessary in order to update the view. - - // If an error is encountered, use NCUpdateResult.Failed - // If there's no update required, use NCUpdateResult.NoData - // If there's an update, use NCUpdateResult.NewData - - xDripClient.fetchLast(2, callback: { (error, glucoseArray) in - - if error != nil { - return - } - - guard let glucoseArray = glucoseArray, glucoseArray.count > 0 else { - return - } - - self.updateLabels(latestReadings: glucoseArray) - - }) - - completionHandler(NCUpdateResult.newData) - - } - - // MARK: - private functions - - /// setup colors and so - private func setupView() { - - // set background color to black - self.view.backgroundColor = UIColor.black - - // set minutesLabelOutlet.textColor to white - self.minutesLabelOutlet.textColor = UIColor.white - - // set diffLabelOutlet.textColor to white - self.diffLabelOutlet.textColor = UIColor.white - - self.minutesAgoLabelOutlet.textColor = UIColor.lightGray - - self.diffLabelUnitOutlet.textColor = UIColor.lightGray - - } - - /// - updates the labels - private func updateLabels(latestReadings: [Glucose]) { - - // unwrap shared userdefaults - guard let sharedUserDefaults = xDripClient.shared else {return} - - // unwrap bloodGlucoseUnitIsMgDl in userdefaults - //default value for bool in userdefaults is false, false is for mgdl, true is for mmol - let bloodGlucoseUnitIsMgDl = !sharedUserDefaults.bool(forKey: "bloodGlucoseUnit") - - // get urgentLowMarkValueInUserChosenUnit - let urgentLowMarkValueInUserChosenUnit = getMarkValueInUserChosenUnit(forKey: "urgentLowMarkValue", bloodGlucoseUnitIsMgDl: bloodGlucoseUnitIsMgDl, withDefaultValue: ConstantsBGGraphBuilder.defaultUrgentLowMarkInMgdl) - - // get urgentHighMarkValueInUserChosenUnit - let urgentHighMarkValueInUserChosenUnit = getMarkValueInUserChosenUnit(forKey: "urgentHighMarkValue", bloodGlucoseUnitIsMgDl: bloodGlucoseUnitIsMgDl, withDefaultValue: ConstantsBGGraphBuilder.defaultUrgentHighMarkInMgdl) - - // get highMarkValueInUserChoenUnit - let highMarkValueInUserChosenUnit = getMarkValueInUserChosenUnit(forKey: "highMarkValue", bloodGlucoseUnitIsMgDl: bloodGlucoseUnitIsMgDl, withDefaultValue: ConstantsBGGraphBuilder.defaultHighMarkInMgdl) - - // get lowMarkValueInUserChosenUnit - let lowMarkValueInUserChosenUnit = getMarkValueInUserChosenUnit(forKey: "lowMarkValue", bloodGlucoseUnitIsMgDl: bloodGlucoseUnitIsMgDl, withDefaultValue: ConstantsBGGraphBuilder.defaultLowMarkInMgdl) - - // if there's no readings, then give empty fields - guard latestReadings.count > 0 else { - - valueLabelOutlet.textColor = UIColor.darkGray - minutesLabelOutlet.text = "" - minutesAgoLabelOutlet.text = "" - diffLabelOutlet.text = "" - diffLabelUnitOutlet.text = "" - - let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: "---") - attributeString.addAttribute(.strikethroughStyle, value: 0, range: NSMakeRange(0, attributeString.length)) - - valueLabelOutlet.attributedText = attributeString - - return - } - - // assign last reading - let lastReading = latestReadings[0] - - // assign last but one reading - let lastButOneReading = latestReadings.count > 1 ? latestReadings[1]:nil - - // start creating text for valueLabelOutlet, first the calculated value - var calculatedValueAsString = unitizedString(calculatedValue: Double(lastReading.glucose), unitIsMgDl: bloodGlucoseUnitIsMgDl) - - // if latestReading is older than 11 minutes, then it should be strikethrough - if lastReading.timestamp < Date(timeIntervalSinceNow: -60 * 11) { - - let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: calculatedValueAsString) - attributeString.addAttribute(.strikethroughStyle, value: 2, range: NSMakeRange(0, attributeString.length)) - - valueLabelOutlet.attributedText = attributeString - - } else { - - // if lastButOneReading is available and is less than maxSlopeInMinutes earlier than lastReading, then show slopeArrow - if let lastButOneReading = lastButOneReading { - - if lastReading.timestamp.timeIntervalSince(lastButOneReading.timestamp) <= Double(ConstantsBGGraphBuilder.maxSlopeInMinutes * 60) { - - // don't show delta if there are not enough values or the values are more than 20 mintes apart - calculatedValueAsString = calculatedValueAsString + " " + slopeArrow(slopeOrdinal: lastReading.trend) - - } - - } - - // no strikethrough needed, but attributedText may still be set to strikethrough from previous period during which there was no recent reading. - let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: calculatedValueAsString) - attributeString.addAttribute(.strikethroughStyle, value: 0, range: NSMakeRange(0, attributeString.length)) - - valueLabelOutlet.attributedText = attributeString - - } - - // if data is stale (over 11 minutes old), show it as gray colour to indicate that it isn't current - // if not, then set color, depending on value lower than low mark or higher than high mark - // set both HIGH and LOW BG values to red as previous yellow for hig is now not so obvious due to in-range colour of green. - if lastReading.timestamp < Date(timeIntervalSinceNow: -60 * 11) { - - valueLabelOutlet.textColor = UIColor.lightGray - - } else if lastReading.glucose >= UInt16(urgentHighMarkValueInUserChosenUnit.mmolToMgdl(mgdl: bloodGlucoseUnitIsMgDl)) || lastReading.glucose <= UInt16(urgentLowMarkValueInUserChosenUnit.mmolToMgdl(mgdl: bloodGlucoseUnitIsMgDl)) { - - // BG is higher than urgentHigh or lower than urgentLow objectives - valueLabelOutlet.textColor = UIColor.red - - } else if lastReading.glucose >= UInt16(highMarkValueInUserChosenUnit.mmolToMgdl(mgdl: bloodGlucoseUnitIsMgDl)) || lastReading.glucose <= UInt16(lowMarkValueInUserChosenUnit.mmolToMgdl(mgdl: bloodGlucoseUnitIsMgDl)) { - - // BG is between urgentHigh/high and low/urgentLow objectives - valueLabelOutlet.textColor = UIColor.yellow - - } else { - - // BG is between high and low objectives so considered "in range" - valueLabelOutlet.textColor = UIColor.green - - } - - // get minutes ago and create text for minutes ago label - let minutesAgo = -Int(lastReading.timestamp.timeIntervalSinceNow) / 60 - let minutesAgoText = minutesAgo.description // + " " + (minutesAgo == 1 ? Texts.minute:Texts.minutes) + " " + Texts.ago - - minutesLabelOutlet.text = minutesAgoText - - // configure the localized text in the "mins ago" label - let minutesAgoMinAgoText = (minutesAgo == 1 ? Texts.minute : Texts.minutes) + " " + Texts.ago - - minutesAgoLabelOutlet.text = minutesAgoMinAgoText - - minutesLabelOutlet.text = minutesAgoText - - // create delta value text (without the units) - diffLabelOutlet.text = unitizedDeltaString(bgReading: lastReading, previousBgReading: lastButOneReading, mgdl: bloodGlucoseUnitIsMgDl) - - // set the delta unit label text - let diffLabelUnitText = bloodGlucoseUnitIsMgDl ? Texts.mgdl : Texts.mmol - diffLabelUnitOutlet.text = diffLabelUnitText - - } - - /// creates string with bg value in correct unit or "HIGH" or "LOW", or other like ??? - private func unitizedString(calculatedValue: Double, unitIsMgDl:Bool) -> String { - var returnValue:String - if (calculatedValue >= 400) { - returnValue = Texts.HIGH - } else if (calculatedValue >= 40) { - returnValue = calculatedValue.mgdlToMmolAndToString(mgdl: unitIsMgDl) - } else if (calculatedValue > 12) { - returnValue = Texts.LOW - } else { - switch(calculatedValue) { - case 0: - returnValue = "??0" - break - case 1: - returnValue = "?SN" - break - case 2: - returnValue = "??2" - break - case 3: - returnValue = "?NA" - break - case 5: - returnValue = "?NC" - break - case 6: - returnValue = "?CD" - break - case 9: - returnValue = "?AD" - break - case 12: - returnValue = "?RF" - break - default: - returnValue = "???" - break - } - } - return returnValue - } - - private func slopeArrow(slopeOrdinal: UInt8) -> String { - - switch slopeOrdinal { - - case 7: - return "\u{2193}\u{2193}" - - case 6: - return "\u{2193}" - - case 5: - return "\u{2198}" - - case 4: - return "\u{2192}" - - case 3: - return "\u{2197}" - - case 2: - return "\u{2191}" - - case 1: - return "\u{2191}\u{2191}" - - default: - return "" - - } - - } - - /// creates string with difference from previous reading and also unit - private func unitizedDeltaString(bgReading:Glucose, previousBgReading:Glucose?, mgdl:Bool) -> String { - - guard let previousBgReading = previousBgReading else { - return "???" - } - - if bgReading.timestamp.timeIntervalSince(previousBgReading.timestamp) > Double(ConstantsBGGraphBuilder.maxSlopeInMinutes * 60) { - // don't show delta if there are not enough values or the values are more than 20 mintes apart - return "???"; - } - - // delta value recalculated aligned with time difference between previous and this reading - let value = currentSlope(thisBgReading: bgReading, previousBgReading: previousBgReading) * bgReading.timestamp.timeIntervalSince(previousBgReading.timestamp) * 1000; - - if(abs(value) > 100){ - // a delta > 100 will not happen with real BG values -> problematic sensor data - return "ERR"; - } - - let valueAsString = value.mgdlToMmolAndToString(mgdl: mgdl) - - var deltaSign:String = "" - if (value > 0) { deltaSign = "+"; } - - // quickly check "value" and prevent "-0mg/dl" or "-0.0mmol/l" being displayed - if (mgdl) { - if (value > -1) && (value < 1) { - return "0"// + " " + Texts.mgdl; - } else { - return deltaSign + valueAsString //+ " " + Texts.mgdl; - } - } else { - if (value > -0.1) && (value < 0.1) { - return "0.0" //+ " " + Texts.mmol; - } else { - return deltaSign + valueAsString //+ " " + Texts.mmol; - } - } - } - - private func currentSlope(thisBgReading:Glucose, previousBgReading:Glucose?) -> Double { - - if let previousBgReading = previousBgReading { - let (slope,_) = calculateSlope(thisBgReading: thisBgReading, previousBgReading: previousBgReading); - return slope - } else { - return 0.0 - } - - } - - private func calculateSlope(thisBgReading:Glucose, previousBgReading:Glucose) -> (Double, Bool) { - - if thisBgReading.timestamp == previousBgReading.timestamp - || - thisBgReading.timestamp.toMillisecondsAsDouble() - previousBgReading.timestamp.toMillisecondsAsDouble() > Double(ConstantsBGGraphBuilder.maxSlopeInMinutes * 60 * 1000) { - return (0,true) - } - - return ( ( Double(previousBgReading.glucose) - Double(thisBgReading.glucose) ) / (previousBgReading.timestamp.toMillisecondsAsDouble() - thisBgReading.timestamp.toMillisecondsAsDouble()), false) - - } - - private func getMarkValueInUserChosenUnit(forKey key: String, bloodGlucoseUnitIsMgDl: Bool, withDefaultValue defaultValue: Double) -> Double { - - // unwrap shared userdefaults - guard let sharedUserDefaults = xDripClient.shared else {return defaultValue} - - // unwrap urgentLowMarkValueInUserChosenUnit, if key not found then markValue will be 0.0 - var markValue = sharedUserDefaults.double(forKey: key) - - // if 0 set to defaultvalue - if markValue == 0.0 { - markValue = defaultValue - } - - // check if conversion to mmol is needed - if !bloodGlucoseUnitIsMgDl { - markValue = markValue.mgdlToMmol() - } - - return markValue - - } - -} diff --git a/xDrip4iOS Widget/da.lproj/MainInterface.strings b/xDrip4iOS Widget/da.lproj/MainInterface.strings deleted file mode 100644 index d3ac4f06..00000000 --- a/xDrip4iOS Widget/da.lproj/MainInterface.strings +++ /dev/null @@ -1,9 +0,0 @@ - -/* Class = "UILabel"; text = "Label"; ObjectID = "Usa-lp-vmV"; */ -"Usa-lp-vmV.text" = "Label"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "chW-dl-9hs"; */ -"chW-dl-9hs.text" = "Label"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "otr-CI-Hqi"; */ -"otr-CI-Hqi.text" = "Label"; diff --git a/xDrip4iOS Widget/el.lproj/MainInterface.strings b/xDrip4iOS Widget/el.lproj/MainInterface.strings deleted file mode 100644 index d3ac4f06..00000000 --- a/xDrip4iOS Widget/el.lproj/MainInterface.strings +++ /dev/null @@ -1,9 +0,0 @@ - -/* Class = "UILabel"; text = "Label"; ObjectID = "Usa-lp-vmV"; */ -"Usa-lp-vmV.text" = "Label"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "chW-dl-9hs"; */ -"chW-dl-9hs.text" = "Label"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "otr-CI-Hqi"; */ -"otr-CI-Hqi.text" = "Label"; diff --git a/xDrip4iOS Widget/es.lproj/MainInterface.strings b/xDrip4iOS Widget/es.lproj/MainInterface.strings deleted file mode 100644 index d3ac4f06..00000000 --- a/xDrip4iOS Widget/es.lproj/MainInterface.strings +++ /dev/null @@ -1,9 +0,0 @@ - -/* Class = "UILabel"; text = "Label"; ObjectID = "Usa-lp-vmV"; */ -"Usa-lp-vmV.text" = "Label"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "chW-dl-9hs"; */ -"chW-dl-9hs.text" = "Label"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "otr-CI-Hqi"; */ -"otr-CI-Hqi.text" = "Label"; diff --git a/xDrip4iOS Widget/fi.lproj/MainInterface.strings b/xDrip4iOS Widget/fi.lproj/MainInterface.strings deleted file mode 100644 index d3ac4f06..00000000 --- a/xDrip4iOS Widget/fi.lproj/MainInterface.strings +++ /dev/null @@ -1,9 +0,0 @@ - -/* Class = "UILabel"; text = "Label"; ObjectID = "Usa-lp-vmV"; */ -"Usa-lp-vmV.text" = "Label"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "chW-dl-9hs"; */ -"chW-dl-9hs.text" = "Label"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "otr-CI-Hqi"; */ -"otr-CI-Hqi.text" = "Label"; diff --git a/xDrip4iOS Widget/tr.lproj/MainInterface.strings b/xDrip4iOS Widget/tr.lproj/MainInterface.strings deleted file mode 100644 index d3ac4f06..00000000 --- a/xDrip4iOS Widget/tr.lproj/MainInterface.strings +++ /dev/null @@ -1,9 +0,0 @@ - -/* Class = "UILabel"; text = "Label"; ObjectID = "Usa-lp-vmV"; */ -"Usa-lp-vmV.text" = "Label"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "chW-dl-9hs"; */ -"chW-dl-9hs.text" = "Label"; - -/* Class = "UILabel"; text = "Label"; ObjectID = "otr-CI-Hqi"; */ -"otr-CI-Hqi.text" = "Label"; diff --git a/xDrip4iOS Widget/xDrip4iOS Widget.entitlements b/xDrip4iOS Widget/xDrip4iOS Widget.entitlements deleted file mode 100644 index 96f2950d..00000000 --- a/xDrip4iOS Widget/xDrip4iOS Widget.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.application-groups - - group.com.${DEVELOPMENT_TEAM}.loopkit.LoopGroup - - - diff --git a/xDrip4iOS Widget/xDripClient/ConstantsBGGraphBuilder.swift b/xDrip4iOS Widget/xDripClient/ConstantsBGGraphBuilder.swift deleted file mode 100644 index 0a691a62..00000000 --- a/xDrip4iOS Widget/xDripClient/ConstantsBGGraphBuilder.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -enum ConstantsBGGraphBuilder { - static let maxSlopeInMinutes = 21 - static let defaultUrgentHighMarkInMgdl = 230.0 - static let defaultHighMarkInMgdl = 180.0 - static let defaultTargetMarkInMgdl = 120.0 - static let defaultLowMarkInMgdl = 70.0 - static let defaultUrgentLowMarkInMgdl = 50.0 -} diff --git a/xDrip4iOS Widget/xDripClient/ConstantsBloodGlucose.swift b/xDrip4iOS Widget/xDripClient/ConstantsBloodGlucose.swift deleted file mode 100644 index fdba7137..00000000 --- a/xDrip4iOS Widget/xDripClient/ConstantsBloodGlucose.swift +++ /dev/null @@ -1,6 +0,0 @@ -enum ConstantsBloodGlucose { - static let mmollToMgdl = 18.01801801801802 - static let mgDlToMmoll = 0.0555 - static let libreMultiplier = 117.64705 -} - diff --git a/xDrip4iOS Widget/xDripClient/Double.swift b/xDrip4iOS Widget/xDripClient/Double.swift deleted file mode 100644 index d9e896f8..00000000 --- a/xDrip4iOS Widget/xDripClient/Double.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation - -extension Double { - - /// converts mmol to mgdl if parametermgdl = false and, converts value to string, round. Number of digits after decimal seperator depends on the unit. For mg/dl 0 digits after decimal seperator, for mmol, 1 digit after decimal seperator - /// - /// this function is actually a combination of mmolToMgdl if mgdl = true and bgValuetoString - func mgdlToMmolAndToString(mgdl:Bool) -> String { - if mgdl { - return String(format:"%.0f", self) - } else { - return String(format:"%.1f", self.mgdlToMmol()) - } - } - - /// converts mgdl to mmol - func mgdlToMmol() -> Double { - return self * ConstantsBloodGlucose.mgDlToMmoll - } - - /// converts mmol to mgdl if parameter mgdl = false. If mgdl = true then just returns self - func mmolToMgdl(mgdl:Bool) -> Double { - if mgdl { - return self - } else { - return self.mmolToMgdl() - } - } - - /// converts mmol to mgdl - func mmolToMgdl() -> Double { - return self * ConstantsBloodGlucose.mmollToMgdl - } - -} diff --git a/xDrip4iOS Widget/xDripClient/Trace.swift b/xDrip4iOS Widget/xDripClient/Trace.swift deleted file mode 100644 index 00eff8d0..00000000 --- a/xDrip4iOS Widget/xDripClient/Trace.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation -import os - -/// log only used for debuglogging -fileprivate var log:OSLog = { - let log:OSLog = OSLog(subsystem: "TodayView", category: "TodayView") - return log -}() - -/// will only be used during development -func debuglogging(_ logtext:String) { - os_log("%{public}@", log: log, type: .debug, logtext) -} diff --git a/xDrip4iOS Widget/xDripClient/XDripClient.swift b/xDrip4iOS Widget/xDripClient/XDripClient.swift deleted file mode 100644 index d50a158a..00000000 --- a/xDrip4iOS Widget/xDripClient/XDripClient.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// XDripClient.swift -// XDripClient -// -// Created by Mark Wilson on 5/7/16. -// Copyright © 2016 Mark Wilson. All rights reserved. -// - -import Foundation - -public enum ClientError: Error { - case fetchError - case dataError(reason: String) - case dateError -} - -private let maxAttempts = 2 - -public class XDripClient { - - public let shared: UserDefaults? - - public init(_ group: String? = Bundle.main.appGroupSuiteName) { - shared = UserDefaults(suiteName: group) - } - - public func fetchLast(_ n: Int, callback: @escaping (ClientError?, [Glucose]?) -> Void) { - fetchLastWithRetries(n, remaining: maxAttempts, callback: callback) - } - - private func fetchLastWithRetries(_ n: Int, remaining: Int, callback: @escaping (ClientError?, [Glucose]?) -> Void) { - do { - guard let sharedData = shared?.data(forKey: "latestReadings") else { - throw ClientError.fetchError - } - - let decoded = try? JSONSerialization.jsonObject(with: sharedData, options: []) - guard let sgvs = decoded as? Array else { - if remaining > 0 { - return self.fetchLastWithRetries(n, remaining: remaining - 1, callback: callback) - } else { - throw ClientError.dataError(reason: "Failed to decode SGVs as array from recieved data.") - } - } - - var transformed: Array = [] - for sgv in sgvs.prefix(n) { - // Collector might not be available - var collector : String? = nil - if let _col = sgv["Collector"] as? String { - collector = _col - } - - if let glucose = sgv["Value"] as? Int, let trend = sgv["Trend"] as? Int, let dt = sgv["DT"] as? String { - transformed.append(Glucose( - glucose: UInt16(glucose), - trend: UInt8(trend), - timestamp: try self.parseDate(dt), - collector: collector - )) - } else { - throw ClientError.dataError(reason: "Failed to decode an SGV record.") - } - } - - callback(nil, transformed) - } catch let error as ClientError { - callback(error, nil) - } catch { - callback(.fetchError, nil) - } - } - - private func parseDate(_ wt: String) throws -> Date { - // wt looks like "/Date(1462404576000)/" - let re = try NSRegularExpression(pattern: "\\((.*)\\)") - if let match = re.firstMatch(in: wt, range: NSMakeRange(0, wt.count)) { - #if swift(>=4) - let matchRange = match.range(at: 1) - #else - let matchRange = match.rangeAt(1) - #endif - let epoch = Double((wt as NSString).substring(with: matchRange))! / 1000 - return Date(timeIntervalSince1970: epoch) - } else { - throw ClientError.dateError - } - } -} - -extension Bundle { - public var appGroupSuiteName: String { - return object(forInfoDictionaryKey: "AppGroupIdentifier") as! String - } -} diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 57f3329d..ce8b0389 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -129,7 +129,6 @@ F81F370325C1583400520946 /* WatlaaView.strings in Resources */ = {isa = PBXBuildFile; fileRef = F81F370525C1583400520946 /* WatlaaView.strings */; }; F81F370825C1584A00520946 /* LibreStates.strings in Resources */ = {isa = PBXBuildFile; fileRef = F81F370A25C1584A00520946 /* LibreStates.strings */; }; F81F3C4225D1D91300520946 /* CoreNFC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F81F3C4125D1D91300520946 /* CoreNFC.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - F81F3C4325D1DA1300520946 /* CoreNFC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F81F3C4125D1D91300520946 /* CoreNFC.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; F81F9FF822861E6D0028C70F /* KeyValueObserverTimeKeeper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81F9FF722861E6D0028C70F /* KeyValueObserverTimeKeeper.swift */; }; F81F9FFC2288C7530028C70F /* NewAlertSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81F9FFB2288C7530028C70F /* NewAlertSettingsViewController.swift */; }; F81FA0002289E4990028C70F /* AlertSettingsViewControllerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81F9FFF2289E4990028C70F /* AlertSettingsViewControllerData.swift */; }; @@ -300,13 +299,6 @@ F867E2612252ADAB000FD265 /* Calibration+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F867E25D2252ADAB000FD265 /* Calibration+CoreDataProperties.swift */; }; F8691888239CEEFA0065B607 /* BluetoothPeripheralViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8691887239CEEFA0065B607 /* BluetoothPeripheralViewModel.swift */; }; F869188C23A044340065B607 /* TextsM5StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F869188B23A044340065B607 /* TextsM5StackView.swift */; }; - F870D3D325126A49008967B0 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F870D3D225126A49008967B0 /* NotificationCenter.framework */; }; - F870D3D625126A49008967B0 /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F870D3D525126A49008967B0 /* TodayViewController.swift */; }; - F870D3D925126A49008967B0 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F870D3D725126A49008967B0 /* MainInterface.storyboard */; }; - F870D3DD25126A49008967B0 /* xDrip4iOS Widget.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F870D3D125126A49008967B0 /* xDrip4iOS Widget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - F870D3EA25129C43008967B0 /* Glucose+GlucoseKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F870D3E925129C43008967B0 /* Glucose+GlucoseKit.swift */; }; - F870D3EC25129FC2008967B0 /* XDripClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F870D3EB25129FC2008967B0 /* XDripClient.swift */; }; - F870D3EE2513B786008967B0 /* Trace.swift in Sources */ = {isa = PBXBuildFile; fileRef = F870D3ED2513B786008967B0 /* Trace.swift */; }; F8797CEA255B43960033956B /* GlucoseData+Smoothable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8797CE9255B43960033956B /* GlucoseData+Smoothable.swift */; }; F889CB6F236D84AC00A81068 /* M5StackView.strings in Resources */ = {isa = PBXBuildFile; fileRef = F889CB71236D84AC00A81068 /* M5StackView.strings */; }; F88EC27A260120C000DF0EAF /* ConstantsAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88EC279260120C000DF0EAF /* ConstantsAlerts.swift */; }; @@ -486,12 +478,6 @@ F8E51D65244BA790001C9E5A /* WatlaaBluetoothPeripheralViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E51D64244BA790001C9E5A /* WatlaaBluetoothPeripheralViewModel.swift */; }; F8E51D67244BAE0E001C9E5A /* WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E51D66244BAE0E001C9E5A /* WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift */; }; F8E51D6924549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E51D6824549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift */; }; - F8E53FAB2517733700052CE5 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E53FAA2517733700052CE5 /* Double.swift */; }; - F8E53FAF2517739700052CE5 /* ConstantsBloodGlucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E53FAE2517739700052CE5 /* ConstantsBloodGlucose.swift */; }; - F8E53FB92517F79400052CE5 /* ConstantsBGGraphBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E53FB82517F79400052CE5 /* ConstantsBGGraphBuilder.swift */; }; - F8E53FBD2517F96800052CE5 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E53FBC2517F96800052CE5 /* Date.swift */; }; - F8E53FCE251D35FB00052CE5 /* Common.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8E53FD0251D35FB00052CE5 /* Common.strings */; }; - F8E53FE9251F7B8800052CE5 /* Texts.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E53FE8251F7B8800052CE5 /* Texts.swift */; }; F8E5404C2522624800052CE5 /* ConstantsHousekeeping.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E5404B2522624800052CE5 /* ConstantsHousekeeping.swift */; }; F8E6C78C24CDDB83007C1199 /* SnoozeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E6C78B24CDDB83007C1199 /* SnoozeViewController.swift */; }; F8E6C79024CEC22A007C1199 /* TextsSnooze.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E6C78F24CEC22A007C1199 /* TextsSnooze.swift */; }; @@ -577,13 +563,6 @@ remoteGlobalIDString = 470B6185270C448000561E56; remoteInfo = "Watch App"; }; - F870D3DB25126A49008967B0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = F8AC425221ADEBD60078C348 /* Project object */; - proxyType = 1; - remoteGlobalIDString = F870D3D025126A49008967B0; - remoteInfo = "xDrip4iOS Widget"; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -615,7 +594,6 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( - F870D3DD25126A49008967B0 /* xDrip4iOS Widget.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -645,7 +623,6 @@ 198D44D7260A3A3400A2B4A2 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Snooze.strings; sourceTree = ""; }; 198D44D8260A3A3400A2B4A2 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/LibreNFC.strings; sourceTree = ""; }; 198D44E7260A822000A2B4A2 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Main.strings; sourceTree = ""; }; - 2867F5C725BC209400AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/MainInterface.strings; sourceTree = ""; }; 2867F5C825BC209400AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/LaunchScreen.strings; sourceTree = ""; }; 2867F5C925BC209500AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Main.strings; sourceTree = ""; }; 2867F5CA25BC209500AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Common.strings; sourceTree = ""; }; @@ -666,7 +643,6 @@ 2867F5D925BC209B00AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/LibreErrors.strings; sourceTree = ""; }; 2867F5DA25BC209C00AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Snooze.strings; sourceTree = ""; }; 2867F5DB25BC209C00AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/LibreNFC.strings; sourceTree = ""; }; - 4166BFB528C3501400199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/MainInterface.strings; sourceTree = ""; }; 4166BFB628C3501400199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Interface.strings; sourceTree = ""; }; 4166BFB728C3501500199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/LaunchScreen.strings; sourceTree = ""; }; 4166BFB828C3501500199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Main.strings; sourceTree = ""; }; @@ -716,7 +692,6 @@ 477F45E5285B993100AC8475 /* GlucoseMiniChartManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseMiniChartManager.swift; sourceTree = ""; }; 4798BAC727BA6AA8002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/LaunchScreen.strings; sourceTree = ""; }; 4798BAC827BA6AA8002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Main.strings; sourceTree = ""; }; - 4798BAC927BA766A002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/MainInterface.strings; sourceTree = ""; }; 4798BACA27BA766A002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Interface.strings; sourceTree = ""; }; 4798BACB27BA7688002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Alerts.strings; sourceTree = ""; }; 4798BACC27BA7691002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/AlertTypesSettingsView.strings; sourceTree = ""; }; @@ -921,7 +896,6 @@ F81F39E925C616C000520946 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LibreStates.strings; sourceTree = ""; }; F81F39EA25C616C500520946 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Snooze.strings; sourceTree = ""; }; F81F39EB25C616C900520946 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LibreNFC.strings; sourceTree = ""; }; - F81F3A5F25C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/MainInterface.strings; sourceTree = ""; }; F81F3A6025C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = ""; }; F81F3A6125C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; F81F3A6225C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Common.strings; sourceTree = ""; }; @@ -1211,15 +1185,8 @@ F86A3C78247718C700EE7E46 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/SpeakReading.strings; sourceTree = ""; }; F86A3C79247718C800EE7E46 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/LaunchScreen.strings; sourceTree = ""; }; F86A3C7A247718C800EE7E46 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Main.strings; sourceTree = ""; }; - F870D3D125126A49008967B0 /* xDrip4iOS Widget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "xDrip4iOS Widget.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; F870D3D225126A49008967B0 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; - F870D3D525126A49008967B0 /* TodayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewController.swift; sourceTree = ""; }; - F870D3D825126A49008967B0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; - F870D3DA25126A49008967B0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F870D3E22512701E008967B0 /* xDrip4iOS Widget.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "xDrip4iOS Widget.entitlements"; sourceTree = ""; }; F870D3E925129C43008967B0 /* Glucose+GlucoseKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Glucose+GlucoseKit.swift"; sourceTree = ""; }; - F870D3EB25129FC2008967B0 /* XDripClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XDripClient.swift; sourceTree = ""; }; - F870D3ED2513B786008967B0 /* Trace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trace.swift; sourceTree = ""; }; F8797CE9255B43960033956B /* GlucoseData+Smoothable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GlucoseData+Smoothable.swift"; sourceTree = ""; }; F87F5EFD2560686C00FFA395 /* xdrip v15.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "xdrip v15.xcdatamodel"; sourceTree = ""; }; F889CB70236D84AC00A81068 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/M5StackView.strings; sourceTree = ""; }; @@ -1470,7 +1437,6 @@ F8E3A2AA23DA520B00E5E98A /* ConstantsWatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsWatch.swift; sourceTree = ""; }; F8E3C3AA21FE17B700907A04 /* StringProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringProtocol.swift; sourceTree = ""; }; F8E3C3AC21FE551C00907A04 /* DexcomCalibrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrator.swift; sourceTree = ""; }; - F8E4DCD82805F7FA007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/MainInterface.strings; sourceTree = ""; }; F8E4DCD92805F7FA007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Interface.strings; sourceTree = ""; }; F8E4DCDA2805F7FA007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/LaunchScreen.strings; sourceTree = ""; }; F8E4DCDB2805F7FA007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Main.strings; sourceTree = ""; }; @@ -1502,9 +1468,6 @@ F8E51D64244BA790001C9E5A /* WatlaaBluetoothPeripheralViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatlaaBluetoothPeripheralViewModel.swift; sourceTree = ""; }; F8E51D66244BAE0E001C9E5A /* WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift"; sourceTree = ""; }; F8E51D6824549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewTraceSettingsViewModel.swift; sourceTree = ""; }; - F8E53FAA2517733700052CE5 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; - F8E53FAE2517739700052CE5 /* ConstantsBloodGlucose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsBloodGlucose.swift; sourceTree = ""; }; - F8E53FB82517F79400052CE5 /* ConstantsBGGraphBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsBGGraphBuilder.swift; sourceTree = ""; }; F8E53FBC2517F96800052CE5 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; F8E53FCF251D35FB00052CE5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Common.strings; sourceTree = ""; }; F8E53FD1251D360200052CE5 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Common.strings; sourceTree = ""; }; @@ -1601,15 +1564,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F870D3CE25126A49008967B0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F81F3C4325D1DA1300520946 /* CoreNFC.framework in Frameworks */, - F870D3D325126A49008967B0 /* NotificationCenter.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; F8AC425721ADEBD60078C348 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2191,40 +2145,15 @@ path = Settings; sourceTree = ""; }; - F870D3D425126A49008967B0 /* xDrip4iOS Widget */ = { + F870D3EF25149EA0008967B0 /* TodayWidgetSaved */ = { isa = PBXGroup; children = ( - F870D3E22512701E008967B0 /* xDrip4iOS Widget.entitlements */, - F870D3DA25126A49008967B0 /* Info.plist */, - F870D3D525126A49008967B0 /* TodayViewController.swift */, - F870D3D725126A49008967B0 /* MainInterface.storyboard */, - F870D3EF25149EA0008967B0 /* Texts */, - F870D3E825129C0B008967B0 /* xDripClient */, - ); - path = "xDrip4iOS Widget"; - sourceTree = ""; - }; - F870D3E825129C0B008967B0 /* xDripClient */ = { - isa = PBXGroup; - children = ( - F870D3EB25129FC2008967B0 /* XDripClient.swift */, F870D3E925129C43008967B0 /* Glucose+GlucoseKit.swift */, - F870D3ED2513B786008967B0 /* Trace.swift */, - F8E53FAA2517733700052CE5 /* Double.swift */, - F8E53FAE2517739700052CE5 /* ConstantsBloodGlucose.swift */, - F8E53FB82517F79400052CE5 /* ConstantsBGGraphBuilder.swift */, F8E53FBC2517F96800052CE5 /* Date.swift */, - ); - path = xDripClient; - sourceTree = ""; - }; - F870D3EF25149EA0008967B0 /* Texts */ = { - isa = PBXGroup; - children = ( F8E53FE8251F7B8800052CE5 /* Texts.swift */, F8E53FD0251D35FB00052CE5 /* Common.strings */, ); - path = Texts; + path = TodayWidgetSaved; sourceTree = ""; }; F8A2BBFB25D9D36C001D1E78 /* Atom */ = { @@ -2287,6 +2216,7 @@ F8AC425121ADEBD60078C348 = { isa = PBXGroup; children = ( + F870D3EF25149EA0008967B0 /* TodayWidgetSaved */, F8E3A2A223D4E7E200E5E98A /* Default-568h@2x.png */, 666E283926F7E54C00ACE4DF /* Version.xcconfig */, 666E283826F7E54C00ACE4DF /* xDrip.xcconfig */, @@ -2296,7 +2226,6 @@ 470B6187270C448000561E56 /* Watch App */, 470B6195270C448100561E56 /* Watch App WatchKit Extension */, F8AC425C21ADEBD60078C348 /* xdrip */, - F870D3D425126A49008967B0 /* xDrip4iOS Widget */, ); sourceTree = ""; }; @@ -2304,7 +2233,6 @@ isa = PBXGroup; children = ( F8AC425A21ADEBD60078C348 /* xdrip.app */, - F870D3D125126A49008967B0 /* xDrip4iOS Widget.appex */, 470B6186270C448000561E56 /* xDrip4iO5.app */, ); name = Products; @@ -3228,23 +3156,6 @@ productReference = 470B6186270C448000561E56 /* xDrip4iO5.app */; productType = "com.apple.product-type.application"; }; - F870D3D025126A49008967B0 /* xDrip4iOS Widget */ = { - isa = PBXNativeTarget; - buildConfigurationList = F870D3E125126A49008967B0 /* Build configuration list for PBXNativeTarget "xDrip4iOS Widget" */; - buildPhases = ( - F870D3CD25126A49008967B0 /* Sources */, - F870D3CE25126A49008967B0 /* Frameworks */, - F870D3CF25126A49008967B0 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "xDrip4iOS Widget"; - productName = "xDrip4iOS Widget"; - productReference = F870D3D125126A49008967B0 /* xDrip4iOS Widget.appex */; - productType = "com.apple.product-type.app-extension"; - }; F8AC425921ADEBD60078C348 /* xdrip */ = { isa = PBXNativeTarget; buildConfigurationList = F8AC426E21ADEBD70078C348 /* Build configuration list for PBXNativeTarget "xdrip" */; @@ -3259,7 +3170,6 @@ buildRules = ( ); dependencies = ( - F870D3DC25126A49008967B0 /* PBXTargetDependency */, 470B61A0270C448200561E56 /* PBXTargetDependency */, ); name = xdrip; @@ -3288,9 +3198,6 @@ 470B6185270C448000561E56 = { CreatedOnToolsVersion = 13.0; }; - F870D3D025126A49008967B0 = { - CreatedOnToolsVersion = 11.7; - }; F8AC425921ADEBD60078C348 = { CreatedOnToolsVersion = 10.1; LastSwiftMigration = 1030; @@ -3342,7 +3249,6 @@ projectRoot = ""; targets = ( F8AC425921ADEBD60078C348 /* xdrip */, - F870D3D025126A49008967B0 /* xDrip4iOS Widget */, 470B6185270C448000561E56 /* Watch App */, ); }; @@ -3359,15 +3265,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F870D3CF25126A49008967B0 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F870D3D925126A49008967B0 /* MainInterface.storyboard in Resources */, - F8E53FCE251D35FB00052CE5 /* Common.strings in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; F8AC425821ADEBD60078C348 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3541,22 +3438,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F870D3CD25126A49008967B0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F870D3D625126A49008967B0 /* TodayViewController.swift in Sources */, - F8E53FBD2517F96800052CE5 /* Date.swift in Sources */, - F8E53FAF2517739700052CE5 /* ConstantsBloodGlucose.swift in Sources */, - F870D3EE2513B786008967B0 /* Trace.swift in Sources */, - F870D3EC25129FC2008967B0 /* XDripClient.swift in Sources */, - F870D3EA25129C43008967B0 /* Glucose+GlucoseKit.swift in Sources */, - F8E53FAB2517733700052CE5 /* Double.swift in Sources */, - F8E53FE9251F7B8800052CE5 /* Texts.swift in Sources */, - F8E53FB92517F79400052CE5 /* ConstantsBGGraphBuilder.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; F8AC425621ADEBD60078C348 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3951,11 +3832,6 @@ target = 470B6185270C448000561E56 /* Watch App */; targetProxy = 470B619F270C448200561E56 /* PBXContainerItemProxy */; }; - F870D3DC25126A49008967B0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = F870D3D025126A49008967B0 /* xDrip4iOS Widget */; - targetProxy = F870D3DB25126A49008967B0 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -4146,19 +4022,6 @@ name = LibreStates.strings; sourceTree = ""; }; - F870D3D725126A49008967B0 /* MainInterface.storyboard */ = { - isa = PBXVariantGroup; - children = ( - F870D3D825126A49008967B0 /* Base */, - 2867F5C725BC209400AA1E98 /* fi */, - F81F3A5F25C9E5A800520946 /* es */, - 4798BAC927BA766A002583BC /* tr */, - F8E4DCD82805F7FA007CF822 /* da */, - 4166BFB528C3501400199980 /* el */, - ); - name = MainInterface.storyboard; - sourceTree = ""; - }; F889CB71236D84AC00A81068 /* M5StackView.strings */ = { isa = PBXVariantGroup; children = ( @@ -4652,56 +4515,6 @@ }; name = Release; }; - F870D3DF25126A49008967B0 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - APP_GROUP_IDENTIFIER = "group.com.${DEVELOPMENT_TEAM}.loopkit.LoopGroup"; - CODE_SIGN_ENTITLEMENTS = "$(XDRIP_WIDGET_ENTITLEMENTS_DEBUG)"; - CODE_SIGN_IDENTITY = "$(XDRIP_CODE_SIGN_IDENTITY_DEBUG)"; - CODE_SIGN_STYLE = "$(XDRIP_CODE_SIGN_STYLE)"; - CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = "$(XDRIP_DEVELOPMENT_TEAM)"; - INFOPLIST_FILE = "xDrip4iOS Widget/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = "$(XDRIP_MARKETING_VERSION)"; - PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).xDrip4iOS-Widget"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - F870D3E025126A49008967B0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - APP_GROUP_IDENTIFIER = "group.com.${DEVELOPMENT_TEAM}.loopkit.LoopGroup"; - CODE_SIGN_ENTITLEMENTS = "$(XDRIP_WIDGET_ENTITLEMENTS_RELEASE)"; - CODE_SIGN_IDENTITY = "$(XDRIP_CODE_SIGN_IDENTITY_RELEASE)"; - CODE_SIGN_STYLE = "$(XDRIP_CODE_SIGN_STYLE)"; - CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = "$(XDRIP_DEVELOPMENT_TEAM)"; - INFOPLIST_FILE = "xDrip4iOS Widget/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = "$(XDRIP_MARKETING_VERSION)"; - PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).xDrip4iOS-Widget"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; F8AC426C21ADEBD70078C348 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 666E283826F7E54C00ACE4DF /* xDrip.xcconfig */; @@ -4898,15 +4711,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F870D3E125126A49008967B0 /* Build configuration list for PBXNativeTarget "xDrip4iOS Widget" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F870D3DF25126A49008967B0 /* Debug */, - F870D3E025126A49008967B0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; F8AC425521ADEBD60078C348 /* Build configuration list for PBXProject "xdrip" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/xdrip/BluetoothPeripheral/CGM/Dexcom/G5/DexcomG5+BluetoothPeripheral.swift b/xdrip/BluetoothPeripheral/CGM/Dexcom/G5/DexcomG5+BluetoothPeripheral.swift index 0346ce85..61405d12 100644 --- a/xdrip/BluetoothPeripheral/CGM/Dexcom/G5/DexcomG5+BluetoothPeripheral.swift +++ b/xdrip/BluetoothPeripheral/CGM/Dexcom/G5/DexcomG5+BluetoothPeripheral.swift @@ -1,5 +1,4 @@ import Foundation -import xDrip4iOS_Widget extension DexcomG5: BluetoothPeripheral { diff --git a/xdrip/Managers/NightScout/NightScoutUploadManager.swift b/xdrip/Managers/NightScout/NightScoutUploadManager.swift index 9ff5bd68..1a65549b 100644 --- a/xdrip/Managers/NightScout/NightScoutUploadManager.swift +++ b/xdrip/Managers/NightScout/NightScoutUploadManager.swift @@ -1,7 +1,6 @@ import Foundation import os import UIKit -import xDrip4iOS_Widget public class NightScoutUploadManager: NSObject, ObservableObject { diff --git a/xdrip/xDrip.xcconfig b/xdrip/xDrip.xcconfig index 9c8dbd2f..c05496a0 100644 --- a/xdrip/xDrip.xcconfig +++ b/xdrip/xDrip.xcconfig @@ -13,8 +13,8 @@ MAIN_APP_DISPLAY_NAME = xDrip4iO5 XDRIP_ENTITLEMENTS_RELEASE = xdrip/xDrip.entitlements XDRIP_ENTITLEMENTS_DEBUG = xdrip/xdripDebug.entitlements -XDRIP_WIDGET_ENTITLEMENTS_RELEASE = xDrip4iOS Widget/xDrip4iOS Widget.entitlements -XDRIP_WIDGET_ENTITLEMENTS_DEBUG = $(XDRIP_WIDGET_ENTITLEMENTS_RELEASE) +//XDRIP_WIDGET_ENTITLEMENTS_RELEASE = xDrip4iOS Widget/xDrip4iOS Widget.entitlements +//XDRIP_WIDGET_ENTITLEMENTS_DEBUG = $(XDRIP_WIDGET_ENTITLEMENTS_RELEASE) // Code signing and provisioning [DEFAULT] XDRIP_CODE_SIGN_IDENTITY_DEBUG = Apple Development From fcdc5685ba0b2a9564f258cdcf5d0716caca8676 Mon Sep 17 00:00:00 2001 From: Paul Plant Date: Sat, 30 Dec 2023 17:59:57 +0100 Subject: [PATCH 02/89] initial attributes + view + variable groups + version etc --- .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 ++ xDripWidget/Assets.xcassets/Contents.json | 6 + .../WidgetBackground.colorset/Contents.json | 11 + .../Extensions}/Date.swift | 0 .../Extensions}/es-ES.lproj/Common.strings | 0 .../Extensions}/es-MX.lproj/Common.strings | 0 .../Extensions}/tr.lproj/Common.strings | 0 xDripWidget/Info.plist | 11 + .../Models}/Glucose+GlucoseKit.swift | 0 .../Models/XDripWidgetAttributes.swift | 102 +++++++++ .../Texts/Common.swift | 0 .../Texts}/ar.lproj/Common.strings | 0 .../Texts}/da.lproj/Common.strings | 0 .../Texts}/de.lproj/Common.strings | 0 .../Texts}/el.lproj/Common.strings | 0 .../Texts}/en.lproj/Common.strings | 0 .../Texts}/es.lproj/Common.strings | 0 .../Texts}/fi.lproj/Common.strings | 0 .../Texts}/fr.lproj/Common.strings | 0 .../Texts}/it.lproj/Common.strings | 0 .../Texts}/nl.lproj/Common.strings | 0 .../Texts}/pl-PL.lproj/Common.strings | 0 .../Texts}/pt.lproj/Common.strings | 0 .../Texts}/ru.lproj/Common.strings | 0 .../Texts}/sl.lproj/Common.strings | 0 .../Texts}/sv.lproj/Common.strings | 0 .../Texts}/uk.lproj/Common.strings | 0 .../Texts}/zh.lproj/Common.strings | 0 xDripWidget/XDripWidget.swift | 81 +++++++ xDripWidget/XDripWidgetBundle.swift | 18 ++ xDripWidget/XDripWidgetLiveActivity.swift | 93 ++++++++ xdrip.xcodeproj/project.pbxproj | 211 +++++++++++++++++- .../xcschemes/xDrip4iOS Widget.xcscheme | 97 -------- xdrip/Supporting Files/Info.plist | 2 + 35 files changed, 550 insertions(+), 106 deletions(-) create mode 100644 xDripWidget/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 xDripWidget/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 xDripWidget/Assets.xcassets/Contents.json create mode 100644 xDripWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json rename {TodayWidgetSaved => xDripWidget/Extensions}/Date.swift (100%) rename {TodayWidgetSaved => xDripWidget/Extensions}/es-ES.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Extensions}/es-MX.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Extensions}/tr.lproj/Common.strings (100%) create mode 100644 xDripWidget/Info.plist rename {TodayWidgetSaved => xDripWidget/Models}/Glucose+GlucoseKit.swift (100%) create mode 100644 xDripWidget/Models/XDripWidgetAttributes.swift rename TodayWidgetSaved/Texts.swift => xDripWidget/Texts/Common.swift (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/ar.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/da.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/de.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/el.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/en.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/es.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/fi.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/fr.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/it.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/nl.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/pl-PL.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/pt.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/ru.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/sl.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/sv.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/uk.lproj/Common.strings (100%) rename {TodayWidgetSaved => xDripWidget/Texts}/zh.lproj/Common.strings (100%) create mode 100644 xDripWidget/XDripWidget.swift create mode 100644 xDripWidget/XDripWidgetBundle.swift create mode 100644 xDripWidget/XDripWidgetLiveActivity.swift delete mode 100644 xdrip.xcodeproj/xcshareddata/xcschemes/xDrip4iOS Widget.xcscheme diff --git a/xDripWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/xDripWidget/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/xDripWidget/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/xDripWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/xDripWidget/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..13613e3e --- /dev/null +++ b/xDripWidget/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/xDripWidget/Assets.xcassets/Contents.json b/xDripWidget/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/xDripWidget/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/xDripWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/xDripWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/xDripWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TodayWidgetSaved/Date.swift b/xDripWidget/Extensions/Date.swift similarity index 100% rename from TodayWidgetSaved/Date.swift rename to xDripWidget/Extensions/Date.swift diff --git a/TodayWidgetSaved/es-ES.lproj/Common.strings b/xDripWidget/Extensions/es-ES.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/es-ES.lproj/Common.strings rename to xDripWidget/Extensions/es-ES.lproj/Common.strings diff --git a/TodayWidgetSaved/es-MX.lproj/Common.strings b/xDripWidget/Extensions/es-MX.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/es-MX.lproj/Common.strings rename to xDripWidget/Extensions/es-MX.lproj/Common.strings diff --git a/TodayWidgetSaved/tr.lproj/Common.strings b/xDripWidget/Extensions/tr.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/tr.lproj/Common.strings rename to xDripWidget/Extensions/tr.lproj/Common.strings diff --git a/xDripWidget/Info.plist b/xDripWidget/Info.plist new file mode 100644 index 00000000..0f118fb7 --- /dev/null +++ b/xDripWidget/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/TodayWidgetSaved/Glucose+GlucoseKit.swift b/xDripWidget/Models/Glucose+GlucoseKit.swift similarity index 100% rename from TodayWidgetSaved/Glucose+GlucoseKit.swift rename to xDripWidget/Models/Glucose+GlucoseKit.swift diff --git a/xDripWidget/Models/XDripWidgetAttributes.swift b/xDripWidget/Models/XDripWidgetAttributes.swift new file mode 100644 index 00000000..83bd8b28 --- /dev/null +++ b/xDripWidget/Models/XDripWidgetAttributes.swift @@ -0,0 +1,102 @@ +// +// XDripWidgetAttributes.swift +// xDripWidgetExtension +// +// Created by Paul Plant on 30/12/23. +// Copyright © 2023 Johan Degraeve. All rights reserved. +// + +import ActivityKit +import WidgetKit +import SwiftUI + +struct XDripWidgetAttributes: ActivityAttributes { + public struct ContentState: Codable, Hashable { + + enum EventType: Float, Codable, Hashable { + case urgentLowDropping + case urgentLow + case urgentLowRising + case lowDropping + case low + case lowRising + case inRangeDropping + case inRange + case inRangeRising + case highDropping + case high + case highRising + case urgentHighDropping + case urgentHigh + case urgentHighRising + + var title: String { + switch self { + + case .high, .highDropping, .highRising: + return "HIGH" + case .inRange, .inRangeDropping, .inRangeRising: + return "IN RANGE" + case .low, .lowDropping, .lowRising: + return "LOW" + case .urgentHigh, .urgentHighDropping, .urgentHighRising: + return "VERY HIGH" + case .urgentLow, .urgentLowDropping, .urgentLowRising: + return "VERY LOW" + } + } + + var explanation: String { + switch self { + + case .high: + return "You're not rising anymore" + case .highDropping: + return "You're starting to drop down to range" + case .highRising: + return "⚠️ You're still rising" + case .inRange: + return "Everything's looking good" + case .inRangeDropping: + return "All good, but dropping" + case .inRangeRising: + return "All good, but rising" + case .low: + return "You're not dropping anymore" + case .lowDropping: + return "⚠️ You're still dropping" + case .lowRising: + return "You're starting to rise back up" + case .urgentHigh: + return "You're not rising anymore but still very high" + case .urgentHighDropping: + return "You're starting to drop down to range" + case .urgentHighRising: + return "‼️ You're still rising" + case .urgentLow: + return "‼️ You are still too low" + case .urgentLowDropping: + return "‼️ You're low and still dropping" + case .urgentLowRising: + return "You're starting to come up to range" + } + } + } + + + // Dynamic stateful properties about your activity go here! + var eventType: EventType + var trendArrow: String + var bgValueString: String + + init(eventType: EventType = .lowDropping, trendArrow: String = "↘", bgValueString: String = "64") { + self.eventType = eventType + self.trendArrow = trendArrow + self.bgValueString = bgValueString + } + } + + // Fixed non-changing properties about your activity go here! + var bgValueUnitString: String + var eventStartDate: Date +} diff --git a/TodayWidgetSaved/Texts.swift b/xDripWidget/Texts/Common.swift similarity index 100% rename from TodayWidgetSaved/Texts.swift rename to xDripWidget/Texts/Common.swift diff --git a/TodayWidgetSaved/ar.lproj/Common.strings b/xDripWidget/Texts/ar.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/ar.lproj/Common.strings rename to xDripWidget/Texts/ar.lproj/Common.strings diff --git a/TodayWidgetSaved/da.lproj/Common.strings b/xDripWidget/Texts/da.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/da.lproj/Common.strings rename to xDripWidget/Texts/da.lproj/Common.strings diff --git a/TodayWidgetSaved/de.lproj/Common.strings b/xDripWidget/Texts/de.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/de.lproj/Common.strings rename to xDripWidget/Texts/de.lproj/Common.strings diff --git a/TodayWidgetSaved/el.lproj/Common.strings b/xDripWidget/Texts/el.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/el.lproj/Common.strings rename to xDripWidget/Texts/el.lproj/Common.strings diff --git a/TodayWidgetSaved/en.lproj/Common.strings b/xDripWidget/Texts/en.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/en.lproj/Common.strings rename to xDripWidget/Texts/en.lproj/Common.strings diff --git a/TodayWidgetSaved/es.lproj/Common.strings b/xDripWidget/Texts/es.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/es.lproj/Common.strings rename to xDripWidget/Texts/es.lproj/Common.strings diff --git a/TodayWidgetSaved/fi.lproj/Common.strings b/xDripWidget/Texts/fi.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/fi.lproj/Common.strings rename to xDripWidget/Texts/fi.lproj/Common.strings diff --git a/TodayWidgetSaved/fr.lproj/Common.strings b/xDripWidget/Texts/fr.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/fr.lproj/Common.strings rename to xDripWidget/Texts/fr.lproj/Common.strings diff --git a/TodayWidgetSaved/it.lproj/Common.strings b/xDripWidget/Texts/it.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/it.lproj/Common.strings rename to xDripWidget/Texts/it.lproj/Common.strings diff --git a/TodayWidgetSaved/nl.lproj/Common.strings b/xDripWidget/Texts/nl.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/nl.lproj/Common.strings rename to xDripWidget/Texts/nl.lproj/Common.strings diff --git a/TodayWidgetSaved/pl-PL.lproj/Common.strings b/xDripWidget/Texts/pl-PL.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/pl-PL.lproj/Common.strings rename to xDripWidget/Texts/pl-PL.lproj/Common.strings diff --git a/TodayWidgetSaved/pt.lproj/Common.strings b/xDripWidget/Texts/pt.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/pt.lproj/Common.strings rename to xDripWidget/Texts/pt.lproj/Common.strings diff --git a/TodayWidgetSaved/ru.lproj/Common.strings b/xDripWidget/Texts/ru.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/ru.lproj/Common.strings rename to xDripWidget/Texts/ru.lproj/Common.strings diff --git a/TodayWidgetSaved/sl.lproj/Common.strings b/xDripWidget/Texts/sl.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/sl.lproj/Common.strings rename to xDripWidget/Texts/sl.lproj/Common.strings diff --git a/TodayWidgetSaved/sv.lproj/Common.strings b/xDripWidget/Texts/sv.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/sv.lproj/Common.strings rename to xDripWidget/Texts/sv.lproj/Common.strings diff --git a/TodayWidgetSaved/uk.lproj/Common.strings b/xDripWidget/Texts/uk.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/uk.lproj/Common.strings rename to xDripWidget/Texts/uk.lproj/Common.strings diff --git a/TodayWidgetSaved/zh.lproj/Common.strings b/xDripWidget/Texts/zh.lproj/Common.strings similarity index 100% rename from TodayWidgetSaved/zh.lproj/Common.strings rename to xDripWidget/Texts/zh.lproj/Common.strings diff --git a/xDripWidget/XDripWidget.swift b/xDripWidget/XDripWidget.swift new file mode 100644 index 00000000..9bbda1a7 --- /dev/null +++ b/xDripWidget/XDripWidget.swift @@ -0,0 +1,81 @@ +// +// XDripWidget.swift +// XDripWidget +// +// Created by Paul Plant on 30/12/23. +// Copyright © 2023 Johan Degraeve. All rights reserved. +// + +import WidgetKit +import SwiftUI + +struct Provider: TimelineProvider { + func placeholder(in context: Context) -> SimpleEntry { + SimpleEntry(date: Date(), emoji: "😀") + } + + func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { + let entry = SimpleEntry(date: Date(), emoji: "😀") + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { + var entries: [SimpleEntry] = [] + + // Generate a timeline consisting of five entries an hour apart, starting from the current date. + let currentDate = Date() + for hourOffset in 0 ..< 5 { + let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! + let entry = SimpleEntry(date: entryDate, emoji: "😀") + entries.append(entry) + } + + let timeline = Timeline(entries: entries, policy: .atEnd) + completion(timeline) + } +} + +struct SimpleEntry: TimelineEntry { + let date: Date + let emoji: String +} + +struct XDripWidgetEntryView : View { + var entry: Provider.Entry + + var body: some View { + VStack { + Text("Time:") + Text(entry.date, style: .time) + + Text("Emoji:") + Text(entry.emoji) + } + } +} + +struct XDripWidget: Widget { + let kind: String = "xDripWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + if #available(iOS 17.0, *) { + XDripWidgetEntryView(entry: entry) + .containerBackground(.fill.tertiary, for: .widget) + } else { + XDripWidgetEntryView(entry: entry) + .padding() + .background() + } + } + .configurationDisplayName("My Widget") + .description("This is an example widget.") + } +} + +#Preview(as: .systemSmall) { + XDripWidget() +} timeline: { + SimpleEntry(date: .now, emoji: "😀") + SimpleEntry(date: .now, emoji: "🤩") +} diff --git a/xDripWidget/XDripWidgetBundle.swift b/xDripWidget/XDripWidgetBundle.swift new file mode 100644 index 00000000..867230f2 --- /dev/null +++ b/xDripWidget/XDripWidgetBundle.swift @@ -0,0 +1,18 @@ +// +// xDripWidgetBundle.swift +// XDripWidget +// +// Created by Paul Plant on 30/12/23. +// Copyright © 2023 Johan Degraeve. All rights reserved. +// + +import WidgetKit +import SwiftUI + +@main +struct XDripWidgetBundle: WidgetBundle { + var body: some Widget { + XDripWidget() + XDripWidgetLiveActivity() + } +} diff --git a/xDripWidget/XDripWidgetLiveActivity.swift b/xDripWidget/XDripWidgetLiveActivity.swift new file mode 100644 index 00000000..fe6f9cb2 --- /dev/null +++ b/xDripWidget/XDripWidgetLiveActivity.swift @@ -0,0 +1,93 @@ +// +// XDripWidgetLiveActivity.swift +// XDripWidget +// +// Created by Paul Plant on 29/12/23. +// Copyright © 2023 Johan Degraeve. All rights reserved. +// + +import ActivityKit +import WidgetKit +import SwiftUI + +struct XDripWidgetLiveActivity: Widget { + var body: some WidgetConfiguration { + ActivityConfiguration(for: XDripWidgetAttributes.self) { context in + // Lock screen/banner UI goes here + VStack { + HStack { + Text(context.state.eventType.title) + Spacer() + Text("\(context.state.bgValueString) \(context.state.trendArrow)") + }.font(.largeTitle).bold().foregroundStyle(.red) + HStack { + HStack { + Text("Started \(context.attributes.eventStartDate.formatted(date: .omitted, time: .shortened))") + } + Spacer() + Text(context.attributes.bgValueUnitString) + .foregroundStyle(.gray) + } + .font(.headline) + Spacer() + + HStack { + Text(context.state.eventType.explanation) + Spacer() + } + .font(.body) + } + .padding(15) + //.activityBackgroundTint(Color.cyan) + //.activitySystemActionForegroundColor(Color.black) + //.background(.ultraThinMaterial) + //.activityBackgroundTint(Color.red) + //.activitySystemActionForegroundColor(Color.black) + + } dynamicIsland: { context in + DynamicIsland { + // Expanded UI goes here. Compose the expanded UI through + // various regions, like leading/trailing/center/bottom + DynamicIslandExpandedRegion(.leading) { + Text(" \(context.state.eventType.title)") + } + DynamicIslandExpandedRegion(.trailing) { + Text("\(context.state.bgValueString) \(context.state.trendArrow) ") + } + DynamicIslandExpandedRegion(.bottom) { + Text(context.state.eventType.explanation) + } + } compactLeading: { + Text(context.state.eventType.title) + } compactTrailing: { + Text("\(context.state.bgValueString) \(context.state.trendArrow)") + } minimal: { + Text(context.state.bgValueString) + } + .widgetURL(URL(string: "xdripswift")) + .keylineTint(Color.red) + } + } +} + +@available(iOS 16.2, *) +struct XDripWidgetLiveActivity_Previews: PreviewProvider { + static let attributes = XDripWidgetAttributes(bgValueUnitString: "mg/dL", eventStartDate: Date().addingTimeInterval(-1000)) + static let contentState = XDripWidgetAttributes.ContentState(eventType: .highRising, trendArrow: "↘", bgValueString: "364") + + static var previews: some View { + attributes + .previewContext(contentState, viewKind: .content) + .previewDisplayName("Notification") + attributes + .previewContext(contentState, viewKind: .dynamicIsland(.compact)) + .previewDisplayName("Compact") + attributes + .previewContext(contentState, viewKind: .dynamicIsland(.expanded)) + .previewDisplayName("Expanded") + attributes + .previewContext(contentState, viewKind: .dynamicIsland(.minimal)) + .previewDisplayName("Minimal") + } +} + diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index ce8b0389..fb6c1011 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -20,6 +20,15 @@ 470B61A1270C448200561E56 /* xDrip4iO5.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 470B6186270C448000561E56 /* xDrip4iO5.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 470CE1FC246802EB00D5CB74 /* BluetoothPeripheralsView.strings in Resources */ = {isa = PBXBuildFile; fileRef = 470CE1FE246802EB00D5CB74 /* BluetoothPeripheralsView.strings */; }; 47150A4027F6211C00DB2994 /* SettingsViewTreatmentsSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47150A3F27F6211C00DB2994 /* SettingsViewTreatmentsSettingsViewModel.swift */; }; + 4716A4EF2B406C3D00419052 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4716A4EE2B406C3D00419052 /* WidgetKit.framework */; }; + 4716A4F12B406C3D00419052 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4716A4F02B406C3D00419052 /* SwiftUI.framework */; }; + 4716A4F42B406C3D00419052 /* XDripWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A4F32B406C3D00419052 /* XDripWidgetBundle.swift */; }; + 4716A4F62B406C3D00419052 /* XDripWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A4F52B406C3D00419052 /* XDripWidgetLiveActivity.swift */; }; + 4716A4F82B406C3D00419052 /* XDripWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A4F72B406C3D00419052 /* XDripWidget.swift */; }; + 4716A4FA2B406C3F00419052 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4716A4F92B406C3F00419052 /* Assets.xcassets */; }; + 4716A4FE2B406C3F00419052 /* xDripWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4716A4ED2B406C3D00419052 /* xDripWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 4716A5052B40709E00419052 /* XDripWidgetAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A5042B40709E00419052 /* XDripWidgetAttributes.swift */; }; + 4716A5072B4082ED00419052 /* XDripWidgetAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A5042B40709E00419052 /* XDripWidgetAttributes.swift */; }; 47228B152996BDD2008725DB /* BgReadingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47228B142996BDD2008725DB /* BgReadingsView.swift */; }; 4733B93E2AD17C99001D609D /* FollowerBgReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4733B93D2AD17C99001D609D /* FollowerBgReading.swift */; }; 4733B9402AD17D15001D609D /* FollowerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4733B93F2AD17D15001D609D /* FollowerDelegate.swift */; }; @@ -563,6 +572,13 @@ remoteGlobalIDString = 470B6185270C448000561E56; remoteInfo = "Watch App"; }; + 4716A4FC2B406C3F00419052 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F8AC425221ADEBD60078C348 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4716A4EC2B406C3D00419052; + remoteInfo = xDripWidgetExtension; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -594,6 +610,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + 4716A4FE2B406C3F00419052 /* xDripWidgetExtension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -680,6 +697,15 @@ 470B619E270C448200561E56 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 470CE1FF246802F400D5CB74 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/BluetoothPeripheralsView.strings; sourceTree = ""; }; 47150A3F27F6211C00DB2994 /* SettingsViewTreatmentsSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewTreatmentsSettingsViewModel.swift; sourceTree = ""; }; + 4716A4ED2B406C3D00419052 /* xDripWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = xDripWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 4716A4EE2B406C3D00419052 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 4716A4F02B406C3D00419052 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 4716A4F32B406C3D00419052 /* XDripWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDripWidgetBundle.swift; sourceTree = ""; }; + 4716A4F52B406C3D00419052 /* XDripWidgetLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDripWidgetLiveActivity.swift; sourceTree = ""; }; + 4716A4F72B406C3D00419052 /* XDripWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDripWidget.swift; sourceTree = ""; }; + 4716A4F92B406C3F00419052 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 4716A4FB2B406C3F00419052 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4716A5042B40709E00419052 /* XDripWidgetAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDripWidgetAttributes.swift; sourceTree = ""; }; 47228B142996BDD2008725DB /* BgReadingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BgReadingsView.swift; sourceTree = ""; }; 4733B93D2AD17C99001D609D /* FollowerBgReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerBgReading.swift; sourceTree = ""; }; 4733B93F2AD17D15001D609D /* FollowerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerDelegate.swift; sourceTree = ""; }; @@ -1474,7 +1500,7 @@ F8E53FD2251D360300052CE5 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/Common.strings; sourceTree = ""; }; F8E53FD3251D361600052CE5 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Common.strings; sourceTree = ""; }; F8E53FD7251D363B00052CE5 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Common.strings; sourceTree = ""; }; - F8E53FE8251F7B8800052CE5 /* Texts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Texts.swift; sourceTree = ""; }; + F8E53FE8251F7B8800052CE5 /* Common.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; }; F8E5404B2522624800052CE5 /* ConstantsHousekeeping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsHousekeeping.swift; sourceTree = ""; }; F8E6C78B24CDDB83007C1199 /* SnoozeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnoozeViewController.swift; sourceTree = ""; }; F8E6C78F24CEC22A007C1199 /* TextsSnooze.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsSnooze.swift; sourceTree = ""; }; @@ -1564,6 +1590,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4716A4EA2B406C3D00419052 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4716A4F12B406C3D00419052 /* SwiftUI.framework in Frameworks */, + 4716A4EF2B406C3D00419052 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F8AC425721ADEBD60078C348 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1602,6 +1637,39 @@ path = "Watch App WatchKit Extension"; sourceTree = ""; }; + 4716A4F22B406C3D00419052 /* xDripWidget */ = { + isa = PBXGroup; + children = ( + 4716A4FB2B406C3F00419052 /* Info.plist */, + 4716A4F72B406C3D00419052 /* XDripWidget.swift */, + 4716A4F32B406C3D00419052 /* XDripWidgetBundle.swift */, + 4716A4F52B406C3D00419052 /* XDripWidgetLiveActivity.swift */, + 4716A4F92B406C3F00419052 /* Assets.xcassets */, + F870D3EF25149EA0008967B0 /* Extensions */, + 4716A5032B40704000419052 /* Models */, + 4716A5022B40702D00419052 /* Texts */, + ); + path = xDripWidget; + sourceTree = ""; + }; + 4716A5022B40702D00419052 /* Texts */ = { + isa = PBXGroup; + children = ( + F8E53FE8251F7B8800052CE5 /* Common.swift */, + F8E53FD0251D35FB00052CE5 /* Common.strings */, + ); + path = Texts; + sourceTree = ""; + }; + 4716A5032B40704000419052 /* Models */ = { + isa = PBXGroup; + children = ( + F870D3E925129C43008967B0 /* Glucose+GlucoseKit.swift */, + 4716A5042B40709E00419052 /* XDripWidgetAttributes.swift */, + ); + path = Models; + sourceTree = ""; + }; 47DB06CC2A7013EF00267BE3 /* Followers */ = { isa = PBXGroup; children = ( @@ -1637,6 +1705,8 @@ F81F3C4125D1D91300520946 /* CoreNFC.framework */, F821CF9622AE589E005C1E43 /* HealthKit.framework */, F870D3D225126A49008967B0 /* NotificationCenter.framework */, + 4716A4EE2B406C3D00419052 /* WidgetKit.framework */, + 4716A4F02B406C3D00419052 /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -2145,15 +2215,12 @@ path = Settings; sourceTree = ""; }; - F870D3EF25149EA0008967B0 /* TodayWidgetSaved */ = { + F870D3EF25149EA0008967B0 /* Extensions */ = { isa = PBXGroup; children = ( - F870D3E925129C43008967B0 /* Glucose+GlucoseKit.swift */, F8E53FBC2517F96800052CE5 /* Date.swift */, - F8E53FE8251F7B8800052CE5 /* Texts.swift */, - F8E53FD0251D35FB00052CE5 /* Common.strings */, ); - path = TodayWidgetSaved; + path = Extensions; sourceTree = ""; }; F8A2BBFB25D9D36C001D1E78 /* Atom */ = { @@ -2216,10 +2283,10 @@ F8AC425121ADEBD60078C348 = { isa = PBXGroup; children = ( - F870D3EF25149EA0008967B0 /* TodayWidgetSaved */, F8E3A2A223D4E7E200E5E98A /* Default-568h@2x.png */, 666E283926F7E54C00ACE4DF /* Version.xcconfig */, 666E283826F7E54C00ACE4DF /* xDrip.xcconfig */, + 4716A4F22B406C3D00419052 /* xDripWidget */, 48C0E851274A3BB6D42C6F20 /* Frameworks */, F8AC425B21ADEBD60078C348 /* Products */, F85DC29B21CFCEB800B9F74A /* Recovered References */, @@ -2234,6 +2301,7 @@ children = ( F8AC425A21ADEBD60078C348 /* xdrip.app */, 470B6186270C448000561E56 /* xDrip4iO5.app */, + 4716A4ED2B406C3D00419052 /* xDripWidgetExtension.appex */, ); name = Products; sourceTree = ""; @@ -3156,6 +3224,23 @@ productReference = 470B6186270C448000561E56 /* xDrip4iO5.app */; productType = "com.apple.product-type.application"; }; + 4716A4EC2B406C3D00419052 /* xDripWidgetExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4716A5012B406C3F00419052 /* Build configuration list for PBXNativeTarget "xDripWidgetExtension" */; + buildPhases = ( + 4716A4E92B406C3D00419052 /* Sources */, + 4716A4EA2B406C3D00419052 /* Frameworks */, + 4716A4EB2B406C3D00419052 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = xDripWidgetExtension; + productName = xDripWidgetExtension; + productReference = 4716A4ED2B406C3D00419052 /* xDripWidgetExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; F8AC425921ADEBD60078C348 /* xdrip */ = { isa = PBXNativeTarget; buildConfigurationList = F8AC426E21ADEBD70078C348 /* Build configuration list for PBXNativeTarget "xdrip" */; @@ -3171,6 +3256,7 @@ ); dependencies = ( 470B61A0270C448200561E56 /* PBXTargetDependency */, + 4716A4FD2B406C3F00419052 /* PBXTargetDependency */, ); name = xdrip; packageProductDependencies = ( @@ -3190,14 +3276,17 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - DefaultBuildSystemTypeForWorkspace = Original; - LastSwiftUpdateCheck = 1310; + DefaultBuildSystemTypeForWorkspace = Latest; + LastSwiftUpdateCheck = 1510; LastUpgradeCheck = 1500; ORGANIZATIONNAME = "Johan Degraeve"; TargetAttributes = { 470B6185270C448000561E56 = { CreatedOnToolsVersion = 13.0; }; + 4716A4EC2B406C3D00419052 = { + CreatedOnToolsVersion = 15.1; + }; F8AC425921ADEBD60078C348 = { CreatedOnToolsVersion = 10.1; LastSwiftMigration = 1030; @@ -3250,6 +3339,7 @@ targets = ( F8AC425921ADEBD60078C348 /* xdrip */, 470B6185270C448000561E56 /* Watch App */, + 4716A4EC2B406C3D00419052 /* xDripWidgetExtension */, ); }; /* End PBXProject section */ @@ -3265,6 +3355,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4716A4EB2B406C3D00419052 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4716A4FA2B406C3F00419052 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F8AC425821ADEBD60078C348 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3438,6 +3536,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4716A4E92B406C3D00419052 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4716A5052B40709E00419052 /* XDripWidgetAttributes.swift in Sources */, + 4716A4F62B406C3D00419052 /* XDripWidgetLiveActivity.swift in Sources */, + 4716A4F42B406C3D00419052 /* XDripWidgetBundle.swift in Sources */, + 4716A4F82B406C3D00419052 /* XDripWidget.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F8AC425621ADEBD60078C348 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3786,6 +3895,7 @@ F808D2D2240329E80084B5DB /* Bubble+BluetoothPeripheral.swift in Sources */, F8025C1321DA683400ECF0C0 /* Data.swift in Sources */, F80859272364355F00F3829D /* ConstantsGlucoseChart.swift in Sources */, + 4716A5072B4082ED00419052 /* XDripWidgetAttributes.swift in Sources */, F816E10324367389009EE65B /* GNSEntryBluetoothPeripheralViewModel.swift in Sources */, D4E499AD277B4CE7000F8CBA /* DateOnly.swift in Sources */, F8E51D5D2448D8B5001C9E5A /* LoopManager.swift in Sources */, @@ -3832,6 +3942,11 @@ target = 470B6185270C448000561E56 /* Watch App */; targetProxy = 470B619F270C448200561E56 /* PBXContainerItemProxy */; }; + 4716A4FD2B406C3F00419052 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4716A4EC2B406C3D00419052 /* xDripWidgetExtension */; + targetProxy = 4716A4FC2B406C3F00419052 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -4515,6 +4630,73 @@ }; name = Release; }; + 4716A4FF2B406C3F00419052 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_IDENTITY = "$(XDRIP_CODE_SIGN_IDENTITY_DEBUG)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; + DEVELOPMENT_TEAM = "$(XDRIP_DEVELOPMENT_TEAM)"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = xDripWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = xDripWidget; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Johan Degraeve. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = "$(XDRIP_MARKETING_VERSION)"; + PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).xDripWidget"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 4716A5002B406C3F00419052 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_IDENTITY = "$(XDRIP_CODE_SIGN_IDENTITY_RELEASE)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; + DEVELOPMENT_TEAM = "$(XDRIP_DEVELOPMENT_TEAM)"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = xDripWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = xDripWidget; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Johan Degraeve. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = "$(XDRIP_MARKETING_VERSION)"; + PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).xDripWidget"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; F8AC426C21ADEBD70078C348 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 666E283826F7E54C00ACE4DF /* xDrip.xcconfig */; @@ -4647,6 +4829,7 @@ F8AC426F21ADEBD70078C348 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APP_GROUP_IDENTIFIER = "group.com.${DEVELOPMENT_TEAM}.loopkit.LoopGroup"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -4675,6 +4858,7 @@ F8AC427021ADEBD70078C348 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APP_GROUP_IDENTIFIER = "group.com.${DEVELOPMENT_TEAM}.loopkit.LoopGroup"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -4711,6 +4895,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 4716A5012B406C3F00419052 /* Build configuration list for PBXNativeTarget "xDripWidgetExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4716A4FF2B406C3F00419052 /* Debug */, + 4716A5002B406C3F00419052 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F8AC425521ADEBD60078C348 /* Build configuration list for PBXProject "xdrip" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/xdrip.xcodeproj/xcshareddata/xcschemes/xDrip4iOS Widget.xcscheme b/xdrip.xcodeproj/xcshareddata/xcschemes/xDrip4iOS Widget.xcscheme deleted file mode 100644 index af00631d..00000000 --- a/xdrip.xcodeproj/xcshareddata/xcschemes/xDrip4iOS Widget.xcscheme +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/xdrip/Supporting Files/Info.plist b/xdrip/Supporting Files/Info.plist index 0366f0eb..b6eabdec 100644 --- a/xdrip/Supporting Files/Info.plist +++ b/xdrip/Supporting Files/Info.plist @@ -96,5 +96,7 @@ Light view controller-based status bar + NSSupportsLiveActivities + From f9e00b52dc4291414971f16e3f6e3775d052fe6c Mon Sep 17 00:00:00 2001 From: Paul Plant Date: Sun, 31 Dec 2023 10:12:28 +0100 Subject: [PATCH 03/89] update --- .../Models/XDripWidgetAttributes.swift | 38 +++++++++++-- xDripWidget/XDripWidgetLiveActivity.swift | 53 +++++++++++++++---- 2 files changed, 77 insertions(+), 14 deletions(-) diff --git a/xDripWidget/Models/XDripWidgetAttributes.swift b/xDripWidget/Models/XDripWidgetAttributes.swift index 83bd8b28..84b88393 100644 --- a/xDripWidget/Models/XDripWidgetAttributes.swift +++ b/xDripWidget/Models/XDripWidgetAttributes.swift @@ -32,7 +32,6 @@ struct XDripWidgetAttributes: ActivityAttributes { var title: String { switch self { - case .high, .highDropping, .highRising: return "HIGH" case .inRange, .inRangeDropping, .inRangeRising: @@ -48,7 +47,6 @@ struct XDripWidgetAttributes: ActivityAttributes { var explanation: String { switch self { - case .high: return "You're not rising anymore" case .highDropping: @@ -81,6 +79,17 @@ struct XDripWidgetAttributes: ActivityAttributes { return "You're starting to come up to range" } } + + var bgColorInt: Int { + switch self { + case .inRange, .inRangeDropping, .inRangeRising: + return 1 + case .urgentHigh, .urgentHighDropping, .urgentHighRising, .urgentLow, .urgentLowDropping, .urgentLowRising: + return 2 + case .high, .highDropping, .highRising, .low, .lowDropping, .lowRising: + return 3 + } + } } @@ -88,11 +97,34 @@ struct XDripWidgetAttributes: ActivityAttributes { var eventType: EventType var trendArrow: String var bgValueString: String + var bgValueColorInt: Int - init(eventType: EventType = .lowDropping, trendArrow: String = "↘", bgValueString: String = "64") { + init(eventType: EventType , trendArrow: String, bgValueString: String, bgValueColorInt: Int) { self.eventType = eventType self.trendArrow = trendArrow self.bgValueString = bgValueString + self.bgValueColorInt = bgValueColorInt + + self.bgValueColorInt = getBgValueColor(bgValueString: bgValueString) + } + + func getBgValueColor(bgValueString: String) -> Int { + + let bgValue: Float = Float(bgValueString) ?? 1 + + switch bgValue { + case 0..<70: + return 1 + case 71..<80: + return 3 + case 81..<140: + return 2 + case 141...: + return 3 + default: + return 2 + } + } } diff --git a/xDripWidget/XDripWidgetLiveActivity.swift b/xDripWidget/XDripWidgetLiveActivity.swift index fe6f9cb2..075659d6 100644 --- a/xDripWidget/XDripWidgetLiveActivity.swift +++ b/xDripWidget/XDripWidgetLiveActivity.swift @@ -16,14 +16,12 @@ struct XDripWidgetLiveActivity: Widget { // Lock screen/banner UI goes here VStack { HStack { - Text(context.state.eventType.title) + Text(context.state.eventType.title).foregroundStyle(getBgColor(bgColorInt: context.state.bgValueColorInt, isTitle: true)) Spacer() - Text("\(context.state.bgValueString) \(context.state.trendArrow)") - }.font(.largeTitle).bold().foregroundStyle(.red) + Text("\(context.state.bgValueString) \(context.state.trendArrow)").foregroundStyle(getBgColor(bgColorInt: context.state.bgValueColorInt, isTitle: false)) + }.font(.largeTitle).bold().foregroundStyle(getBgColor(bgColorInt: context.state.bgValueColorInt, isTitle: false)) HStack { - HStack { - Text("Started \(context.attributes.eventStartDate.formatted(date: .omitted, time: .shortened))") - } + Text("Started \(context.attributes.eventStartDate.formatted(date: .omitted, time: .shortened))") Spacer() Text(context.attributes.bgValueUnitString) .foregroundStyle(.gray) @@ -33,9 +31,9 @@ struct XDripWidgetLiveActivity: Widget { HStack { Text(context.state.eventType.explanation) - Spacer() } .font(.body) + Spacer() } .padding(15) //.activityBackgroundTint(Color.cyan) @@ -49,13 +47,32 @@ struct XDripWidgetLiveActivity: Widget { // Expanded UI goes here. Compose the expanded UI through // various regions, like leading/trailing/center/bottom DynamicIslandExpandedRegion(.leading) { - Text(" \(context.state.eventType.title)") + Text(context.state.eventType.title) + .font(.largeTitle).bold().foregroundStyle(getBgColor(bgColorInt: context.state.eventType.bgColorInt, isTitle: true)) } DynamicIslandExpandedRegion(.trailing) { Text("\(context.state.bgValueString) \(context.state.trendArrow) ") + .font(.largeTitle).bold().foregroundStyle(getBgColor(bgColorInt: context.state.eventType.bgColorInt, isTitle: false)) + } + DynamicIslandExpandedRegion(.center) { + EmptyView() } DynamicIslandExpandedRegion(.bottom) { - Text(context.state.eventType.explanation) + VStack { + HStack { + Text("Started \(context.attributes.eventStartDate.formatted(date: .omitted, time: .shortened))") + Spacer() + Text(context.attributes.bgValueUnitString) + .foregroundStyle(.gray) + } + .font(.headline) + Spacer() + HStack { + Text(context.state.eventType.explanation) + } + .font(.body) + Spacer() + } } } compactLeading: { Text(context.state.eventType.title) @@ -63,9 +80,23 @@ struct XDripWidgetLiveActivity: Widget { Text("\(context.state.bgValueString) \(context.state.trendArrow)") } minimal: { Text(context.state.bgValueString) + .foregroundStyle(getBgColor(bgColorInt: context.state.eventType.bgColorInt, isTitle: false)) } .widgetURL(URL(string: "xdripswift")) - .keylineTint(Color.red) + } + } + + + func getBgColor(bgColorInt: Int, isTitle: Bool) -> Color { + switch bgColorInt { + case 1: + return .red + case 2: + return isTitle ? .primary : .green + case 3: + return .yellow + default: + return .green } } } @@ -73,7 +104,7 @@ struct XDripWidgetLiveActivity: Widget { @available(iOS 16.2, *) struct XDripWidgetLiveActivity_Previews: PreviewProvider { static let attributes = XDripWidgetAttributes(bgValueUnitString: "mg/dL", eventStartDate: Date().addingTimeInterval(-1000)) - static let contentState = XDripWidgetAttributes.ContentState(eventType: .highRising, trendArrow: "↘", bgValueString: "364") + static let contentState = XDripWidgetAttributes.ContentState(eventType: .inRangeDropping, trendArrow: "↘", bgValueString: "134") static var previews: some View { attributes From 58f52c79571468e049d9d6178affa4f5454c0254 Mon Sep 17 00:00:00 2001 From: Paul Plant Date: Sun, 31 Dec 2023 12:21:38 +0100 Subject: [PATCH 04/89] new attributes model --- xDripWidget/Constants/ConstantsWidget.swift | 13 ++ xDripWidget/Extensions/Double.swift | 140 ++++++++++++++++ .../Models/XDripWidgetAttributes.swift | 150 ++++++------------ .../Texts/{Common.swift => TextsWidget.swift} | 16 +- xDripWidget/Texts/ar.lproj/Common.strings | 7 - .../Texts/ar.lproj/TextsWidget.strings | 7 + xDripWidget/Texts/da.lproj/Common.strings | 7 - .../Texts/da.lproj/TextsWidget.strings | 7 + .../{Common.strings => TextsWidget.strings} | 12 +- xDripWidget/Texts/el.lproj/Common.strings | 7 - .../Texts/el.lproj/TextsWidget.strings | 7 + xDripWidget/Texts/en.lproj/Common.strings | 7 - .../Texts/en.lproj/TextsWidget.strings | 7 + xDripWidget/Texts/es.lproj/Common.strings | 7 - .../Texts/es.lproj/TextsWidget.strings | 7 + xDripWidget/Texts/fi.lproj/Common.strings | 7 - .../Texts/fi.lproj/TextsWidget.strings | 7 + xDripWidget/Texts/fr.lproj/Common.strings | 8 - .../Texts/fr.lproj/TextsWidget.strings | 8 + .../{Common.strings => TextsWidget.strings} | 12 +- xDripWidget/Texts/nl.lproj/Common.strings | 7 - .../Texts/nl.lproj/TextsWidget.strings | 7 + .../{Common.strings => TextsWidget.strings} | 12 +- xDripWidget/Texts/pt.lproj/Common.strings | 7 - .../Texts/pt.lproj/TextsWidget.strings | 7 + xDripWidget/Texts/ru.lproj/Common.strings | 7 - .../Texts/ru.lproj/TextsWidget.strings | 7 + .../{Common.strings => TextsWidget.strings} | 12 +- xDripWidget/Texts/sv.lproj/Common.strings | 7 - .../Texts/sv.lproj/TextsWidget.strings | 7 + xDripWidget/Texts/uk.lproj/Common.strings | 7 - .../Texts/uk.lproj/TextsWidget.strings | 7 + xDripWidget/Texts/zh.lproj/Common.strings | 7 - .../Texts/zh.lproj/TextsWidget.strings | 7 + xDripWidget/XDripWidgetLiveActivity.swift | 52 +++--- xdrip.xcodeproj/project.pbxproj | 68 +++++--- 36 files changed, 389 insertions(+), 282 deletions(-) create mode 100644 xDripWidget/Constants/ConstantsWidget.swift create mode 100644 xDripWidget/Extensions/Double.swift rename xDripWidget/Texts/{Common.swift => TextsWidget.swift} (67%) delete mode 100644 xDripWidget/Texts/ar.lproj/Common.strings create mode 100644 xDripWidget/Texts/ar.lproj/TextsWidget.strings delete mode 100644 xDripWidget/Texts/da.lproj/Common.strings create mode 100644 xDripWidget/Texts/da.lproj/TextsWidget.strings rename xDripWidget/Texts/de.lproj/{Common.strings => TextsWidget.strings} (61%) delete mode 100644 xDripWidget/Texts/el.lproj/Common.strings create mode 100644 xDripWidget/Texts/el.lproj/TextsWidget.strings delete mode 100644 xDripWidget/Texts/en.lproj/Common.strings create mode 100644 xDripWidget/Texts/en.lproj/TextsWidget.strings delete mode 100644 xDripWidget/Texts/es.lproj/Common.strings create mode 100644 xDripWidget/Texts/es.lproj/TextsWidget.strings delete mode 100644 xDripWidget/Texts/fi.lproj/Common.strings create mode 100644 xDripWidget/Texts/fi.lproj/TextsWidget.strings delete mode 100644 xDripWidget/Texts/fr.lproj/Common.strings create mode 100644 xDripWidget/Texts/fr.lproj/TextsWidget.strings rename xDripWidget/Texts/it.lproj/{Common.strings => TextsWidget.strings} (78%) delete mode 100644 xDripWidget/Texts/nl.lproj/Common.strings create mode 100644 xDripWidget/Texts/nl.lproj/TextsWidget.strings rename xDripWidget/Texts/pl-PL.lproj/{Common.strings => TextsWidget.strings} (78%) delete mode 100644 xDripWidget/Texts/pt.lproj/Common.strings create mode 100644 xDripWidget/Texts/pt.lproj/TextsWidget.strings delete mode 100644 xDripWidget/Texts/ru.lproj/Common.strings create mode 100644 xDripWidget/Texts/ru.lproj/TextsWidget.strings rename xDripWidget/Texts/sl.lproj/{Common.strings => TextsWidget.strings} (78%) delete mode 100644 xDripWidget/Texts/sv.lproj/Common.strings create mode 100644 xDripWidget/Texts/sv.lproj/TextsWidget.strings delete mode 100644 xDripWidget/Texts/uk.lproj/Common.strings create mode 100644 xDripWidget/Texts/uk.lproj/TextsWidget.strings delete mode 100644 xDripWidget/Texts/zh.lproj/Common.strings create mode 100644 xDripWidget/Texts/zh.lproj/TextsWidget.strings diff --git a/xDripWidget/Constants/ConstantsWidget.swift b/xDripWidget/Constants/ConstantsWidget.swift new file mode 100644 index 00000000..23a03143 --- /dev/null +++ b/xDripWidget/Constants/ConstantsWidget.swift @@ -0,0 +1,13 @@ +// +// ConstantsWidget.swift +// xDripWidgetExtension +// +// Created by Paul Plant on 31/12/23. +// Copyright © 2023 Johan Degraeve. All rights reserved. +// + +enum ConstantsWidget { + static let mmollToMgdl = 18.01801801801802 + static let mgDlToMmoll = 0.0555 + +} diff --git a/xDripWidget/Extensions/Double.swift b/xDripWidget/Extensions/Double.swift new file mode 100644 index 00000000..9b4767c4 --- /dev/null +++ b/xDripWidget/Extensions/Double.swift @@ -0,0 +1,140 @@ +// +// Double.swift +// xDripWidgetExtension +// +// Created by Paul Plant on 31/12/23. +// Copyright © 2023 Johan Degraeve. All rights reserved. +// + +import Foundation + +extension Double { + + // MARK: - copied to xDripWidgetExtension from main xDrip target + + /// Converts to a string and removes trailing .0 + public var stringWithoutTrailingZeroes: String { + var description = String(self.description) + // Checks if ends with .0 and removes if so + if description.suffix(2) == ".0" { + description = String(description.dropLast(2)) + } + return description + } + + /// converts mgdl to mmol + func mgdlToMmol() -> Double { + return self * ConstantsWidget.mgDlToMmoll + } + + /// converts mgdl to mmol if parameter mgdl = false. If mgdl = true then just returns self + func mgdlToMmol(mgdl:Bool) -> Double { + if mgdl { + return self + } else { + return self * ConstantsWidget.mgDlToMmoll + } + } + + /// converts mmol to mgdl if parameter mgdl = false. If mgdl = true then just returns self + func mmolToMgdl(mgdl:Bool) -> Double { + if mgdl { + return self + } else { + return self.mmolToMgdl() + } + } + + /// converts mmol to mgdl + func mmolToMgdl() -> Double { + return self * ConstantsWidget.mmollToMgdl + } + + /// returns the value rounded to fractionDigits + func round(toDecimalPlaces: Int) -> Double { + let multiplier = pow(10, Double(toDecimalPlaces)) + return Darwin.round(self * multiplier) / multiplier + } + + /// takes self as Double as bloodglucose value, converts value to string, round. Number of digits after decimal seperator depends on the unit. For mg/dl 0 digits after decimal seperator, for mmol, 1 digit after decimal seperator + func bgValuetoString(mgdl:Bool) -> String { + if mgdl { + return String(format:"%.0f", self) + } else { + return String(format:"%.1f", self) + } + } + + /// if mgdl, then returns self, unchanged. If not mgdl, return self rounded to 1 decimal place + func bgValueRounded(mgdl: Bool) -> Double { + + if mgdl { + + return self.round(toDecimalPlaces: 0) + + } else { + + return self.round(toDecimalPlaces: 1) + + } + + } + + /// converts mmol to mgdl if parametermgdl = false and, converts value to string, round. Number of digits after decimal seperator depends on the unit. For mg/dl 0 digits after decimal seperator, for mmol, 1 digit after decimal seperator + /// + /// this function is actually a combination of mmolToMgdl if mgdl = true and bgValuetoString + func mgdlToMmolAndToString(mgdl:Bool) -> String { + if mgdl { + return String(format:"%.0f", self) + } else { + return String(format:"%.1f", self.mgdlToMmol()) + } + } + + /// treats the double as timestamp in milliseconds, since 1970 and prints as date string + func asTimeStampInMilliSecondsToString() -> String { + let asDate = Date(timeIntervalSince1970: self/1000) + return asDate.description(with: .current) + } + + /// returns the Nightscout style string showing the days and hours for the number of minutes + /// Example: 9300.minutesToDaysAndHours() would return -> "6d11h" + /// Example: 78.minutesToDaysAndHours() would return -> "1h18m" + /// Example: 12.minutesToDaysAndHours() would return -> "12m" + func minutesToDaysAndHours() -> String { + + // set a default value assuming that we're unable to calculate the hours + days + var daysAndHoursString: String = "n/a" + + let days = floor(self / (24 * 60)) + let hoursInMinutes = self.truncatingRemainder(dividingBy: 24 * 60) + let hours = hoursInMinutes / 60 + let minutes = self.truncatingRemainder(dividingBy: 24 * 60 * 60) + + + if days == 0 && hours < 1 { + + // show just minutes for less than one hour + daysAndHoursString = abs(minutes).description + "m" + + } else if days == 0 && hours < 12 { + + // show just hours and minutes for less than twelve hours + daysAndHoursString = abs(hours).description + "h" + abs(minutes).description + "m" + + } else { + + // default show days and hours + daysAndHoursString = Int(days).description + "d" + Int(hours).description + "h" + + } + + + return daysAndHoursString + + } + +} + + + diff --git a/xDripWidget/Models/XDripWidgetAttributes.swift b/xDripWidget/Models/XDripWidgetAttributes.swift index 84b88393..3b5f4f29 100644 --- a/xDripWidget/Models/XDripWidgetAttributes.swift +++ b/xDripWidget/Models/XDripWidgetAttributes.swift @@ -11,124 +11,64 @@ import WidgetKit import SwiftUI struct XDripWidgetAttributes: ActivityAttributes { + public struct ContentState: Codable, Hashable { - enum EventType: Float, Codable, Hashable { - case urgentLowDropping - case urgentLow - case urgentLowRising - case lowDropping - case low - case lowRising - case inRangeDropping - case inRange - case inRangeRising - case highDropping - case high - case highRising - case urgentHighDropping - case urgentHigh - case urgentHighRising - - var title: String { - switch self { - case .high, .highDropping, .highRising: - return "HIGH" - case .inRange, .inRangeDropping, .inRangeRising: - return "IN RANGE" - case .low, .lowDropping, .lowRising: - return "LOW" - case .urgentHigh, .urgentHighDropping, .urgentHighRising: - return "VERY HIGH" - case .urgentLow, .urgentLowDropping, .urgentLowRising: - return "VERY LOW" - } - } - - var explanation: String { - switch self { - case .high: - return "You're not rising anymore" - case .highDropping: - return "You're starting to drop down to range" - case .highRising: - return "⚠️ You're still rising" - case .inRange: - return "Everything's looking good" - case .inRangeDropping: - return "All good, but dropping" - case .inRangeRising: - return "All good, but rising" - case .low: - return "You're not dropping anymore" - case .lowDropping: - return "⚠️ You're still dropping" - case .lowRising: - return "You're starting to rise back up" - case .urgentHigh: - return "You're not rising anymore but still very high" - case .urgentHighDropping: - return "You're starting to drop down to range" - case .urgentHighRising: - return "‼️ You're still rising" - case .urgentLow: - return "‼️ You are still too low" - case .urgentLowDropping: - return "‼️ You're low and still dropping" - case .urgentLowRising: - return "You're starting to come up to range" - } - } - - var bgColorInt: Int { - switch self { - case .inRange, .inRangeDropping, .inRangeRising: - return 1 - case .urgentHigh, .urgentHighDropping, .urgentHighRising, .urgentLow, .urgentLowDropping, .urgentLowRising: - return 2 - case .high, .highDropping, .highRising, .low, .lowDropping, .lowRising: - return 3 - } - } - } - - // Dynamic stateful properties about your activity go here! - var eventType: EventType + var bgValueInMgDl: Double + var isMgDl: Bool var trendArrow: String - var bgValueString: String - var bgValueColorInt: Int + var deltaChangeInMgDl: Double + var urgentLowLimitInMgDl: Double + var lowLimitInMgDl: Double + var highLimitInMgDl: Double + var urgentHighLimitInMgDl: Double - init(eventType: EventType , trendArrow: String, bgValueString: String, bgValueColorInt: Int) { - self.eventType = eventType + var bgValueStringInUserChosenUnit: String + var bgUnitString: String + + init(bgValueInMgDl: Double, isMgDl: Bool, trendArrow: String, deltaChangeInMgDl: Double, urgentLowLimitInMgDl: Double, lowLimitInMgDl: Double, highLimitInMgDl: Double, urgentHighLimitInMgDl: Double) { + + // these are the "passed in" stateful values used to initialize + self.bgValueInMgDl = bgValueInMgDl + self.isMgDl = isMgDl self.trendArrow = trendArrow - self.bgValueString = bgValueString - self.bgValueColorInt = bgValueColorInt + self.deltaChangeInMgDl = deltaChangeInMgDl + self.urgentLowLimitInMgDl = urgentLowLimitInMgDl + self.lowLimitInMgDl = lowLimitInMgDl + self.highLimitInMgDl = highLimitInMgDl + self.urgentHighLimitInMgDl = urgentHighLimitInMgDl + + // these are dynamically initialized based on the above + //self.bgValueInUserChosenUnit = bgValueInMgDl.mgdlToMmol(mgdl: isMgDl) + self.bgUnitString = isMgDl ? Texts_Widget.mgdl : Texts_Widget.mmol + self.bgValueStringInUserChosenUnit = bgValueInMgDl.mgdlToMmolAndToString(mgdl: isMgDl) - self.bgValueColorInt = getBgValueColor(bgValueString: bgValueString) } - func getBgValueColor(bgValueString: String) -> Int { - - let bgValue: Float = Float(bgValueString) ?? 1 - - switch bgValue { - case 0..<70: - return 1 - case 71..<80: - return 3 - case 81..<140: - return 2 - case 141...: - return 3 - default: - return 2 + func getBgColor() -> Color { + if bgValueInMgDl >= urgentHighLimitInMgDl || bgValueInMgDl <= urgentLowLimitInMgDl { + return .red + } else if bgValueInMgDl >= highLimitInMgDl || bgValueInMgDl <= lowLimitInMgDl { + return .yellow + } else { + return .green } - } + + func getBgTitle() -> String { + + if bgValueInMgDl >= highLimitInMgDl { + return Texts_Widget.HIGH + } else if bgValueInMgDl <= lowLimitInMgDl { + return Texts_Widget.LOW + } else { + return "" + } + } + } // Fixed non-changing properties about your activity go here! - var bgValueUnitString: String var eventStartDate: Date } diff --git a/xDripWidget/Texts/Common.swift b/xDripWidget/Texts/TextsWidget.swift similarity index 67% rename from xDripWidget/Texts/Common.swift rename to xDripWidget/Texts/TextsWidget.swift index 7f82c6da..1f0bf1f1 100644 --- a/xDripWidget/Texts/Common.swift +++ b/xDripWidget/Texts/TextsWidget.swift @@ -1,31 +1,31 @@ import Foundation // all common texts -class Texts { - static private let filename = "Common" +class Texts_Widget { + static private let filename = "TextsWidget" static let minutes = { - return NSLocalizedString("common_minutes", tableName: filename, bundle: Bundle.main, value: "mins", comment: "literal translation needed") + return NSLocalizedString("widget_minutes", tableName: filename, bundle: Bundle.main, value: "mins", comment: "literal translation needed") }() static let minute = { - return NSLocalizedString("common_minute", tableName: filename, bundle: Bundle.main, value: "min", comment: "literal translation needed") + return NSLocalizedString("widget_minute", tableName: filename, bundle: Bundle.main, value: "min", comment: "literal translation needed") }() static let HIGH = { - return NSLocalizedString("common_high", tableName: filename, bundle: Bundle.main, value: "HIGH", comment: "the word HIGH, in capitals") + return NSLocalizedString("widget_high", tableName: filename, bundle: Bundle.main, value: "HIGH", comment: "the word HIGH, in capitals") }() static let LOW = { - return NSLocalizedString("common_low", tableName: filename, bundle: Bundle.main, value: "LOW", comment: "the word LOW, in capitals") + return NSLocalizedString("widget_low", tableName: filename, bundle: Bundle.main, value: "LOW", comment: "the word LOW, in capitals") }() static let mgdl: String = { - return NSLocalizedString("common_mgdl", tableName: filename, bundle: Bundle.main, value: "mg/dL", comment: "mg/dL") + return NSLocalizedString("widget_mgdl", tableName: filename, bundle: Bundle.main, value: "mg/dL", comment: "mg/dL") }() static let mmol: String = { - return NSLocalizedString("common_mmol", tableName: filename, bundle: Bundle.main, value: "mmol/L", comment: "mmol/L") + return NSLocalizedString("widget_mmol", tableName: filename, bundle: Bundle.main, value: "mmol/L", comment: "mmol/L") }() static let ago:String = { diff --git a/xDripWidget/Texts/ar.lproj/Common.strings b/xDripWidget/Texts/ar.lproj/Common.strings deleted file mode 100644 index 20787281..00000000 --- a/xDripWidget/Texts/ar.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "مرتفع"; -"common_low" = "منخفض"; -"common_minutes" = "دقائق"; -"common_minute" = "دقيقة"; -"ago" = "مضت"; diff --git a/xDripWidget/Texts/ar.lproj/TextsWidget.strings b/xDripWidget/Texts/ar.lproj/TextsWidget.strings new file mode 100644 index 00000000..e54e50f9 --- /dev/null +++ b/xDripWidget/Texts/ar.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "مرتفع"; +"widget_low" = "منخفض"; +"widget_minutes" = "دقائق"; +"widget_minute" = "دقيقة"; +"ago" = "مضت"; diff --git a/xDripWidget/Texts/da.lproj/Common.strings b/xDripWidget/Texts/da.lproj/Common.strings deleted file mode 100644 index 6dc05e6b..00000000 --- a/xDripWidget/Texts/da.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "HIGH"; -"common_low" = "LOW"; -"common_minutes" = "mins"; -"common_minute" = "min"; -"ago" = "ago"; diff --git a/xDripWidget/Texts/da.lproj/TextsWidget.strings b/xDripWidget/Texts/da.lproj/TextsWidget.strings new file mode 100644 index 00000000..75d81de5 --- /dev/null +++ b/xDripWidget/Texts/da.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "HIGH"; +"widget_low" = "LOW"; +"widget_minutes" = "mins"; +"widget_minute" = "min"; +"ago" = "ago"; diff --git a/xDripWidget/Texts/de.lproj/Common.strings b/xDripWidget/Texts/de.lproj/TextsWidget.strings similarity index 61% rename from xDripWidget/Texts/de.lproj/Common.strings rename to xDripWidget/Texts/de.lproj/TextsWidget.strings index bbe1485f..9519b7c9 100644 --- a/xDripWidget/Texts/de.lproj/Common.strings +++ b/xDripWidget/Texts/de.lproj/TextsWidget.strings @@ -1,20 +1,20 @@ /// literal translation needed -"common_minute" = "min"; +"widget_minute" = "min"; /// the word HIGH, in capitals -"common_high" = "HOCH"; +"widget_high" = "HOCH"; /// the word LOW, in capitals -"common_low" = "NIEDRIG"; +"widget_low" = "NIEDRIG"; /// literal translation needed -"common_minutes" = "Minuten"; +"widget_minutes" = "Minuten"; /// mmol/l -"common_mmol" = "mmol/L"; +"widget_mmol" = "mmol/L"; /// mg/dl -"common_mgdl" = "mg/dL"; +"widget_mgdl" = "mg/dL"; /// where it say how old the reading is, 'x minutes ago', literaly translation of 'ago' "ago" = "her"; diff --git a/xDripWidget/Texts/el.lproj/Common.strings b/xDripWidget/Texts/el.lproj/Common.strings deleted file mode 100644 index 15c1242b..00000000 --- a/xDripWidget/Texts/el.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "ΥΨΗΛΗ"; -"common_low" = "ΧΑΜΗΛΗ"; -"common_minutes" = "mins"; -"common_minute" = "min"; -"ago" = "πριν"; diff --git a/xDripWidget/Texts/el.lproj/TextsWidget.strings b/xDripWidget/Texts/el.lproj/TextsWidget.strings new file mode 100644 index 00000000..67a91580 --- /dev/null +++ b/xDripWidget/Texts/el.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "ΥΨΗΛΗ"; +"widget_low" = "ΧΑΜΗΛΗ"; +"widget_minutes" = "mins"; +"widget_minute" = "min"; +"ago" = "πριν"; diff --git a/xDripWidget/Texts/en.lproj/Common.strings b/xDripWidget/Texts/en.lproj/Common.strings deleted file mode 100644 index 6dc05e6b..00000000 --- a/xDripWidget/Texts/en.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "HIGH"; -"common_low" = "LOW"; -"common_minutes" = "mins"; -"common_minute" = "min"; -"ago" = "ago"; diff --git a/xDripWidget/Texts/en.lproj/TextsWidget.strings b/xDripWidget/Texts/en.lproj/TextsWidget.strings new file mode 100644 index 00000000..75d81de5 --- /dev/null +++ b/xDripWidget/Texts/en.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "HIGH"; +"widget_low" = "LOW"; +"widget_minutes" = "mins"; +"widget_minute" = "min"; +"ago" = "ago"; diff --git a/xDripWidget/Texts/es.lproj/Common.strings b/xDripWidget/Texts/es.lproj/Common.strings deleted file mode 100644 index 6dc05e6b..00000000 --- a/xDripWidget/Texts/es.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "HIGH"; -"common_low" = "LOW"; -"common_minutes" = "mins"; -"common_minute" = "min"; -"ago" = "ago"; diff --git a/xDripWidget/Texts/es.lproj/TextsWidget.strings b/xDripWidget/Texts/es.lproj/TextsWidget.strings new file mode 100644 index 00000000..75d81de5 --- /dev/null +++ b/xDripWidget/Texts/es.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "HIGH"; +"widget_low" = "LOW"; +"widget_minutes" = "mins"; +"widget_minute" = "min"; +"ago" = "ago"; diff --git a/xDripWidget/Texts/fi.lproj/Common.strings b/xDripWidget/Texts/fi.lproj/Common.strings deleted file mode 100644 index 23243221..00000000 --- a/xDripWidget/Texts/fi.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "KORKEA"; -"common_low" = "MATALA"; -"common_minutes" = "minuuttia"; -"common_minute" = "min"; -"ago" = "sitten"; diff --git a/xDripWidget/Texts/fi.lproj/TextsWidget.strings b/xDripWidget/Texts/fi.lproj/TextsWidget.strings new file mode 100644 index 00000000..ead5cdaa --- /dev/null +++ b/xDripWidget/Texts/fi.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "KORKEA"; +"widget_low" = "MATALA"; +"widget_minutes" = "minuuttia"; +"widget_minute" = "min"; +"ago" = "sitten"; diff --git a/xDripWidget/Texts/fr.lproj/Common.strings b/xDripWidget/Texts/fr.lproj/Common.strings deleted file mode 100644 index d174c0a0..00000000 --- a/xDripWidget/Texts/fr.lproj/Common.strings +++ /dev/null @@ -1,8 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "HAUT"; -"common_low" = "BAS"; -"common_minutes" = "mins"; -"common_minute" = "min"; -"ago" = "depuis"; - diff --git a/xDripWidget/Texts/fr.lproj/TextsWidget.strings b/xDripWidget/Texts/fr.lproj/TextsWidget.strings new file mode 100644 index 00000000..59f34fd5 --- /dev/null +++ b/xDripWidget/Texts/fr.lproj/TextsWidget.strings @@ -0,0 +1,8 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "HAUT"; +"widget_low" = "BAS"; +"widget_minutes" = "mins"; +"widget_minute" = "min"; +"ago" = "depuis"; + diff --git a/xDripWidget/Texts/it.lproj/Common.strings b/xDripWidget/Texts/it.lproj/TextsWidget.strings similarity index 78% rename from xDripWidget/Texts/it.lproj/Common.strings rename to xDripWidget/Texts/it.lproj/TextsWidget.strings index 7d04c8ec..c84f2abb 100644 --- a/xDripWidget/Texts/it.lproj/Common.strings +++ b/xDripWidget/Texts/it.lproj/TextsWidget.strings @@ -4,22 +4,22 @@ ///////////////////////////////////////////////////////////////////////////////////////////// /// literal translation needed -"common_minute" = "min"; +"widget_minute" = "min"; /// the word HIGH, in capitals -"common_high" = "HIGH"; +"widget_high" = "HIGH"; /// the word LOW, in capitals -"common_low" = "LOW"; +"widget_low" = "LOW"; /// literal translation needed -"common_minutes" = "mins"; +"widget_minutes" = "mins"; /// mmol/l -"common_mmol" = "mmol/L"; +"widget_mmol" = "mmol/L"; /// mg/dl -"common_mgdl" = "mg/dL"; +"widget_mgdl" = "mg/dL"; /// where it say how old the reading is, 'x minutes ago', literaly translation of 'ago' "ago" = "ago"; diff --git a/xDripWidget/Texts/nl.lproj/Common.strings b/xDripWidget/Texts/nl.lproj/Common.strings deleted file mode 100644 index 239bff2f..00000000 --- a/xDripWidget/Texts/nl.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_high" = "HOOG"; -"common_low" = "LAAG"; -"common_minute" = "min"; -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_minutes" = "min"; -"ago" = "geleden"; diff --git a/xDripWidget/Texts/nl.lproj/TextsWidget.strings b/xDripWidget/Texts/nl.lproj/TextsWidget.strings new file mode 100644 index 00000000..27b1027f --- /dev/null +++ b/xDripWidget/Texts/nl.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_high" = "HOOG"; +"widget_low" = "LAAG"; +"widget_minute" = "min"; +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_minutes" = "min"; +"ago" = "geleden"; diff --git a/xDripWidget/Texts/pl-PL.lproj/Common.strings b/xDripWidget/Texts/pl-PL.lproj/TextsWidget.strings similarity index 78% rename from xDripWidget/Texts/pl-PL.lproj/Common.strings rename to xDripWidget/Texts/pl-PL.lproj/TextsWidget.strings index 7d04c8ec..c84f2abb 100644 --- a/xDripWidget/Texts/pl-PL.lproj/Common.strings +++ b/xDripWidget/Texts/pl-PL.lproj/TextsWidget.strings @@ -4,22 +4,22 @@ ///////////////////////////////////////////////////////////////////////////////////////////// /// literal translation needed -"common_minute" = "min"; +"widget_minute" = "min"; /// the word HIGH, in capitals -"common_high" = "HIGH"; +"widget_high" = "HIGH"; /// the word LOW, in capitals -"common_low" = "LOW"; +"widget_low" = "LOW"; /// literal translation needed -"common_minutes" = "mins"; +"widget_minutes" = "mins"; /// mmol/l -"common_mmol" = "mmol/L"; +"widget_mmol" = "mmol/L"; /// mg/dl -"common_mgdl" = "mg/dL"; +"widget_mgdl" = "mg/dL"; /// where it say how old the reading is, 'x minutes ago', literaly translation of 'ago' "ago" = "ago"; diff --git a/xDripWidget/Texts/pt.lproj/Common.strings b/xDripWidget/Texts/pt.lproj/Common.strings deleted file mode 100644 index 84c386d4..00000000 --- a/xDripWidget/Texts/pt.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "ALTA"; -"common_low" = "BAIXA"; -"common_minutes" = "mins"; -"common_minute" = "min"; -"ago" = "ago"; diff --git a/xDripWidget/Texts/pt.lproj/TextsWidget.strings b/xDripWidget/Texts/pt.lproj/TextsWidget.strings new file mode 100644 index 00000000..25ba7d18 --- /dev/null +++ b/xDripWidget/Texts/pt.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "ALTA"; +"widget_low" = "BAIXA"; +"widget_minutes" = "mins"; +"widget_minute" = "min"; +"ago" = "ago"; diff --git a/xDripWidget/Texts/ru.lproj/Common.strings b/xDripWidget/Texts/ru.lproj/Common.strings deleted file mode 100644 index d53ec235..00000000 --- a/xDripWidget/Texts/ru.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "ВЫСОКИЙ"; -"common_low" = "НИЗКИЙ"; -"common_minutes" = "мин"; -"common_minute" = "мин"; -"ago" = "назад"; diff --git a/xDripWidget/Texts/ru.lproj/TextsWidget.strings b/xDripWidget/Texts/ru.lproj/TextsWidget.strings new file mode 100644 index 00000000..0c61edcd --- /dev/null +++ b/xDripWidget/Texts/ru.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "ВЫСОКИЙ"; +"widget_low" = "НИЗКИЙ"; +"widget_minutes" = "мин"; +"widget_minute" = "мин"; +"ago" = "назад"; diff --git a/xDripWidget/Texts/sl.lproj/Common.strings b/xDripWidget/Texts/sl.lproj/TextsWidget.strings similarity index 78% rename from xDripWidget/Texts/sl.lproj/Common.strings rename to xDripWidget/Texts/sl.lproj/TextsWidget.strings index 7d04c8ec..c84f2abb 100644 --- a/xDripWidget/Texts/sl.lproj/Common.strings +++ b/xDripWidget/Texts/sl.lproj/TextsWidget.strings @@ -4,22 +4,22 @@ ///////////////////////////////////////////////////////////////////////////////////////////// /// literal translation needed -"common_minute" = "min"; +"widget_minute" = "min"; /// the word HIGH, in capitals -"common_high" = "HIGH"; +"widget_high" = "HIGH"; /// the word LOW, in capitals -"common_low" = "LOW"; +"widget_low" = "LOW"; /// literal translation needed -"common_minutes" = "mins"; +"widget_minutes" = "mins"; /// mmol/l -"common_mmol" = "mmol/L"; +"widget_mmol" = "mmol/L"; /// mg/dl -"common_mgdl" = "mg/dL"; +"widget_mgdl" = "mg/dL"; /// where it say how old the reading is, 'x minutes ago', literaly translation of 'ago' "ago" = "ago"; diff --git a/xDripWidget/Texts/sv.lproj/Common.strings b/xDripWidget/Texts/sv.lproj/Common.strings deleted file mode 100644 index 54ca6e4a..00000000 --- a/xDripWidget/Texts/sv.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "HÖGT"; -"common_low" = "LÅGT"; -"common_minutes" = "min"; -"common_minute" = "min"; -"ago" = "sedan"; diff --git a/xDripWidget/Texts/sv.lproj/TextsWidget.strings b/xDripWidget/Texts/sv.lproj/TextsWidget.strings new file mode 100644 index 00000000..167621f9 --- /dev/null +++ b/xDripWidget/Texts/sv.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "HÖGT"; +"widget_low" = "LÅGT"; +"widget_minutes" = "min"; +"widget_minute" = "min"; +"ago" = "sedan"; diff --git a/xDripWidget/Texts/uk.lproj/Common.strings b/xDripWidget/Texts/uk.lproj/Common.strings deleted file mode 100644 index 6dc05e6b..00000000 --- a/xDripWidget/Texts/uk.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "HIGH"; -"common_low" = "LOW"; -"common_minutes" = "mins"; -"common_minute" = "min"; -"ago" = "ago"; diff --git a/xDripWidget/Texts/uk.lproj/TextsWidget.strings b/xDripWidget/Texts/uk.lproj/TextsWidget.strings new file mode 100644 index 00000000..75d81de5 --- /dev/null +++ b/xDripWidget/Texts/uk.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "HIGH"; +"widget_low" = "LOW"; +"widget_minutes" = "mins"; +"widget_minute" = "min"; +"ago" = "ago"; diff --git a/xDripWidget/Texts/zh.lproj/Common.strings b/xDripWidget/Texts/zh.lproj/Common.strings deleted file mode 100644 index d8de330e..00000000 --- a/xDripWidget/Texts/zh.lproj/Common.strings +++ /dev/null @@ -1,7 +0,0 @@ -"common_mgdl" = "mg/dL"; -"common_mmol" = "mmol/L"; -"common_high" = "高"; -"common_low" = "低"; -"common_minutes" = "分钟"; -"common_minute" = "分钟"; -"ago" = "之前"; diff --git a/xDripWidget/Texts/zh.lproj/TextsWidget.strings b/xDripWidget/Texts/zh.lproj/TextsWidget.strings new file mode 100644 index 00000000..225a524a --- /dev/null +++ b/xDripWidget/Texts/zh.lproj/TextsWidget.strings @@ -0,0 +1,7 @@ +"widget_mgdl" = "mg/dL"; +"widget_mmol" = "mmol/L"; +"widget_high" = "高"; +"widget_low" = "低"; +"widget_minutes" = "分钟"; +"widget_minute" = "分钟"; +"ago" = "之前"; diff --git a/xDripWidget/XDripWidgetLiveActivity.swift b/xDripWidget/XDripWidgetLiveActivity.swift index 075659d6..4588b0fc 100644 --- a/xDripWidget/XDripWidgetLiveActivity.swift +++ b/xDripWidget/XDripWidgetLiveActivity.swift @@ -16,21 +16,21 @@ struct XDripWidgetLiveActivity: Widget { // Lock screen/banner UI goes here VStack { HStack { - Text(context.state.eventType.title).foregroundStyle(getBgColor(bgColorInt: context.state.bgValueColorInt, isTitle: true)) + Text(context.state.getBgTitle()).foregroundStyle(context.state.getBgColor()) Spacer() - Text("\(context.state.bgValueString) \(context.state.trendArrow)").foregroundStyle(getBgColor(bgColorInt: context.state.bgValueColorInt, isTitle: false)) - }.font(.largeTitle).bold().foregroundStyle(getBgColor(bgColorInt: context.state.bgValueColorInt, isTitle: false)) + Text("\(context.state.bgValueStringInUserChosenUnit) \(context.state.trendArrow)").foregroundStyle(context.state.getBgColor()) + }.font(.largeTitle).bold().foregroundStyle(context.state.getBgColor()) HStack { Text("Started \(context.attributes.eventStartDate.formatted(date: .omitted, time: .shortened))") Spacer() - Text(context.attributes.bgValueUnitString) + Text(context.state.bgUnitString) .foregroundStyle(.gray) } .font(.headline) Spacer() HStack { - Text(context.state.eventType.explanation) + Text("Message") } .font(.body) Spacer() @@ -41,18 +41,20 @@ struct XDripWidgetLiveActivity: Widget { //.background(.ultraThinMaterial) //.activityBackgroundTint(Color.red) //.activitySystemActionForegroundColor(Color.black) - + + + } dynamicIsland: { context in DynamicIsland { // Expanded UI goes here. Compose the expanded UI through // various regions, like leading/trailing/center/bottom DynamicIslandExpandedRegion(.leading) { - Text(context.state.eventType.title) - .font(.largeTitle).bold().foregroundStyle(getBgColor(bgColorInt: context.state.eventType.bgColorInt, isTitle: true)) + Text(context.state.getBgTitle()) + .font(.largeTitle).bold().foregroundStyle(context.state.getBgColor()) } DynamicIslandExpandedRegion(.trailing) { - Text("\(context.state.bgValueString) \(context.state.trendArrow) ") - .font(.largeTitle).bold().foregroundStyle(getBgColor(bgColorInt: context.state.eventType.bgColorInt, isTitle: false)) + Text("\(context.state.bgValueStringInUserChosenUnit) \(context.state.trendArrow)") + .font(.largeTitle).bold().foregroundStyle(context.state.getBgColor()) } DynamicIslandExpandedRegion(.center) { EmptyView() @@ -62,49 +64,37 @@ struct XDripWidgetLiveActivity: Widget { HStack { Text("Started \(context.attributes.eventStartDate.formatted(date: .omitted, time: .shortened))") Spacer() - Text(context.attributes.bgValueUnitString) + Text(context.state.bgUnitString) .foregroundStyle(.gray) } .font(.headline) Spacer() HStack { - Text(context.state.eventType.explanation) + Text("Message") } .font(.body) Spacer() } } } compactLeading: { - Text(context.state.eventType.title) + Text(context.state.getBgTitle()).foregroundStyle(context.state.getBgColor()) } compactTrailing: { - Text("\(context.state.bgValueString) \(context.state.trendArrow)") + Text("\(context.state.bgValueStringInUserChosenUnit) \(context.state.trendArrow)").foregroundStyle(context.state.getBgColor()) } minimal: { - Text(context.state.bgValueString) - .foregroundStyle(getBgColor(bgColorInt: context.state.eventType.bgColorInt, isTitle: false)) + Text("\(context.state.bgValueStringInUserChosenUnit)") + .foregroundStyle(context.state.getBgColor()) } .widgetURL(URL(string: "xdripswift")) } + } - - func getBgColor(bgColorInt: Int, isTitle: Bool) -> Color { - switch bgColorInt { - case 1: - return .red - case 2: - return isTitle ? .primary : .green - case 3: - return .yellow - default: - return .green - } - } } @available(iOS 16.2, *) struct XDripWidgetLiveActivity_Previews: PreviewProvider { - static let attributes = XDripWidgetAttributes(bgValueUnitString: "mg/dL", eventStartDate: Date().addingTimeInterval(-1000)) - static let contentState = XDripWidgetAttributes.ContentState(eventType: .inRangeDropping, trendArrow: "↘", bgValueString: "134") + static let attributes = XDripWidgetAttributes(eventStartDate: Date().addingTimeInterval(-1000)) + static let contentState = XDripWidgetAttributes.ContentState(bgValueInMgDl: 75, isMgDl: true, trendArrow: "↘", deltaChangeInMgDl: -2, urgentLowLimitInMgDl: 70, lowLimitInMgDl: 80, highLimitInMgDl: 140, urgentHighLimitInMgDl: 180) static var previews: some View { attributes diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index fb6c1011..84fcc08f 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -29,6 +29,12 @@ 4716A4FE2B406C3F00419052 /* xDripWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4716A4ED2B406C3D00419052 /* xDripWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4716A5052B40709E00419052 /* XDripWidgetAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A5042B40709E00419052 /* XDripWidgetAttributes.swift */; }; 4716A5072B4082ED00419052 /* XDripWidgetAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A5042B40709E00419052 /* XDripWidgetAttributes.swift */; }; + 4716A50A2B416E6500419052 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A5092B416E6500419052 /* Double.swift */; }; + 4716A50D2B416EE100419052 /* ConstantsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A50C2B416EE100419052 /* ConstantsWidget.swift */; }; + 4716A50E2B41707D00419052 /* TextsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E53FE8251F7B8800052CE5 /* TextsWidget.swift */; }; + 4716A50F2B41708000419052 /* TextsWidget.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8E53FD0251D35FB00052CE5 /* TextsWidget.strings */; }; + 4716A5102B41823600419052 /* TextsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E53FE8251F7B8800052CE5 /* TextsWidget.swift */; }; + 4716A5112B41823A00419052 /* TextsWidget.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8E53FD0251D35FB00052CE5 /* TextsWidget.strings */; }; 47228B152996BDD2008725DB /* BgReadingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47228B142996BDD2008725DB /* BgReadingsView.swift */; }; 4733B93E2AD17C99001D609D /* FollowerBgReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4733B93D2AD17C99001D609D /* FollowerBgReading.swift */; }; 4733B9402AD17D15001D609D /* FollowerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4733B93F2AD17D15001D609D /* FollowerDelegate.swift */; }; @@ -618,7 +624,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 198D44C3260A3A3300A2B4A2 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Common.strings; sourceTree = ""; }; + 198D44C3260A3A3300A2B4A2 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/TextsWidget.strings; sourceTree = ""; }; 198D44C4260A3A3300A2B4A2 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Alerts.strings; sourceTree = ""; }; 198D44C5260A3A3300A2B4A2 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/AlertTypesSettingsView.strings; sourceTree = ""; }; 198D44C6260A3A3300A2B4A2 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/BluetoothPeripheralsView.strings; sourceTree = ""; }; @@ -642,7 +648,7 @@ 198D44E7260A822000A2B4A2 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Main.strings; sourceTree = ""; }; 2867F5C825BC209400AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/LaunchScreen.strings; sourceTree = ""; }; 2867F5C925BC209500AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Main.strings; sourceTree = ""; }; - 2867F5CA25BC209500AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Common.strings; sourceTree = ""; }; + 2867F5CA25BC209500AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/TextsWidget.strings; sourceTree = ""; }; 2867F5CB25BC209500AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Alerts.strings; sourceTree = ""; }; 2867F5CC25BC209500AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/AlertTypesSettingsView.strings; sourceTree = ""; }; 2867F5CD25BC209500AA1E98 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/BluetoothPeripheralsView.strings; sourceTree = ""; }; @@ -663,7 +669,7 @@ 4166BFB628C3501400199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Interface.strings; sourceTree = ""; }; 4166BFB728C3501500199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/LaunchScreen.strings; sourceTree = ""; }; 4166BFB828C3501500199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Main.strings; sourceTree = ""; }; - 4166BFB928C3501500199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Common.strings; sourceTree = ""; }; + 4166BFB928C3501500199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/TextsWidget.strings; sourceTree = ""; }; 4166BFBA28C3501500199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Treatments.strings; sourceTree = ""; }; 4166BFBB28C3501500199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Alerts.strings; sourceTree = ""; }; 4166BFBC28C3501500199980 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/AlertTypesSettingsView.strings; sourceTree = ""; }; @@ -706,6 +712,8 @@ 4716A4F92B406C3F00419052 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4716A4FB2B406C3F00419052 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4716A5042B40709E00419052 /* XDripWidgetAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDripWidgetAttributes.swift; sourceTree = ""; }; + 4716A5092B416E6500419052 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; + 4716A50C2B416EE100419052 /* ConstantsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsWidget.swift; sourceTree = ""; }; 47228B142996BDD2008725DB /* BgReadingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BgReadingsView.swift; sourceTree = ""; }; 4733B93D2AD17C99001D609D /* FollowerBgReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerBgReading.swift; sourceTree = ""; }; 4733B93F2AD17D15001D609D /* FollowerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerDelegate.swift; sourceTree = ""; }; @@ -776,7 +784,7 @@ CE1B2FDD25D0264B00F642F5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/M5StackView.strings; sourceTree = ""; }; CE1B2FDE25D0264B00F642F5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Snooze.strings; sourceTree = ""; }; CE1B2FDF25D0264B00F642F5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/BluetoothPeripheralView.strings; sourceTree = ""; }; - CE1B2FE425D026B400F642F5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Common.strings; sourceTree = ""; }; + CE1B2FE425D026B400F642F5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/TextsWidget.strings; sourceTree = ""; }; D400F8022778BD8000B57648 /* TextsTreatmentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsTreatmentsView.swift; sourceTree = ""; }; D4028CBF2774A50600341476 /* TreatmentsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreatmentsViewController.swift; sourceTree = ""; }; D40C3DA3277542C400111B73 /* TreatmentEntry+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TreatmentEntry+CoreDataClass.swift"; sourceTree = ""; }; @@ -924,7 +932,7 @@ F81F39EB25C616C900520946 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LibreNFC.strings; sourceTree = ""; }; F81F3A6025C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = ""; }; F81F3A6125C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; - F81F3A6225C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Common.strings; sourceTree = ""; }; + F81F3A6225C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/TextsWidget.strings; sourceTree = ""; }; F81F3A6325C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Alerts.strings; sourceTree = ""; }; F81F3A6425C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/AlertTypesSettingsView.strings; sourceTree = ""; }; F81F3A6525C9E5A800520946 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/BluetoothPeripheralsView.strings; sourceTree = ""; }; @@ -1222,11 +1230,11 @@ F88EC12325F6CFB200DF0EAF /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/SettingsViews.strings; sourceTree = ""; }; F88EC12425F6CFC200DF0EAF /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/WatlaaView.strings; sourceTree = ""; }; F88EC12525F6CFC500DF0EAF /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/WatlaaView.strings; sourceTree = ""; }; - F88EC17325FABAAE00DF0EAF /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Common.strings; sourceTree = ""; }; - F88EC17425FABAB000DF0EAF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Common.strings; sourceTree = ""; }; - F88EC17525FABAB300DF0EAF /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Common.strings"; sourceTree = ""; }; - F88EC17625FABAB700DF0EAF /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Common.strings; sourceTree = ""; }; - F88EC17725FABAB800DF0EAF /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Common.strings; sourceTree = ""; }; + F88EC17325FABAAE00DF0EAF /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/TextsWidget.strings; sourceTree = ""; }; + F88EC17425FABAB000DF0EAF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/TextsWidget.strings; sourceTree = ""; }; + F88EC17525FABAB300DF0EAF /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/TextsWidget.strings"; sourceTree = ""; }; + F88EC17625FABAB700DF0EAF /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/TextsWidget.strings; sourceTree = ""; }; + F88EC17725FABAB800DF0EAF /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/TextsWidget.strings; sourceTree = ""; }; F88EC279260120C000DF0EAF /* ConstantsAlerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsAlerts.swift; sourceTree = ""; }; F890E079247687AE008FB2EC /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; F897AAF82200F2D200CDDD10 /* CBPeripheralState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBPeripheralState.swift; sourceTree = ""; }; @@ -1430,7 +1438,7 @@ F8CB59CD27444D6300BA199E /* DexcomSessionStopResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStopResponse.swift; sourceTree = ""; }; F8CB59D2274D94AE00BA199E /* DexcomSessionStartTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStartTxMessage.swift; sourceTree = ""; }; F8D0587B24BCB570008C8734 /* SettingsViewHomeScreenSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewHomeScreenSettingsViewModel.swift; sourceTree = ""; }; - F8D094EB2846BDD50087FFEA /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Common.strings; sourceTree = ""; }; + F8D094EB2846BDD50087FFEA /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/TextsWidget.strings; sourceTree = ""; }; F8D094EC2846BDD50087FFEA /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Treatments.strings; sourceTree = ""; }; F8D094ED2846BDD50087FFEA /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Alerts.strings; sourceTree = ""; }; F8D094EE2846BDD50087FFEA /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/AlertTypesSettingsView.strings; sourceTree = ""; }; @@ -1466,7 +1474,7 @@ F8E4DCD92805F7FA007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Interface.strings; sourceTree = ""; }; F8E4DCDA2805F7FA007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/LaunchScreen.strings; sourceTree = ""; }; F8E4DCDB2805F7FA007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Main.strings; sourceTree = ""; }; - F8E4DCDC2805F7FA007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Common.strings; sourceTree = ""; }; + F8E4DCDC2805F7FA007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/TextsWidget.strings; sourceTree = ""; }; F8E4DCDD2805F7FB007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Treatments.strings; sourceTree = ""; }; F8E4DCDE2805F7FB007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Alerts.strings; sourceTree = ""; }; F8E4DCDF2805F7FB007CF822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/AlertTypesSettingsView.strings; sourceTree = ""; }; @@ -1495,12 +1503,12 @@ F8E51D66244BAE0E001C9E5A /* WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift"; sourceTree = ""; }; F8E51D6824549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewTraceSettingsViewModel.swift; sourceTree = ""; }; F8E53FBC2517F96800052CE5 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; - F8E53FCF251D35FB00052CE5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Common.strings; sourceTree = ""; }; - F8E53FD1251D360200052CE5 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Common.strings; sourceTree = ""; }; - F8E53FD2251D360300052CE5 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/Common.strings; sourceTree = ""; }; - F8E53FD3251D361600052CE5 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Common.strings; sourceTree = ""; }; - F8E53FD7251D363B00052CE5 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Common.strings; sourceTree = ""; }; - F8E53FE8251F7B8800052CE5 /* Common.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; }; + F8E53FCF251D35FB00052CE5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/TextsWidget.strings; sourceTree = ""; }; + F8E53FD1251D360200052CE5 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/TextsWidget.strings; sourceTree = ""; }; + F8E53FD2251D360300052CE5 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/TextsWidget.strings; sourceTree = ""; }; + F8E53FD3251D361600052CE5 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/TextsWidget.strings; sourceTree = ""; }; + F8E53FD7251D363B00052CE5 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/TextsWidget.strings; sourceTree = ""; }; + F8E53FE8251F7B8800052CE5 /* TextsWidget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextsWidget.swift; sourceTree = ""; }; F8E5404B2522624800052CE5 /* ConstantsHousekeeping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsHousekeeping.swift; sourceTree = ""; }; F8E6C78B24CDDB83007C1199 /* SnoozeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnoozeViewController.swift; sourceTree = ""; }; F8E6C78F24CEC22A007C1199 /* TextsSnooze.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsSnooze.swift; sourceTree = ""; }; @@ -1645,6 +1653,7 @@ 4716A4F32B406C3D00419052 /* XDripWidgetBundle.swift */, 4716A4F52B406C3D00419052 /* XDripWidgetLiveActivity.swift */, 4716A4F92B406C3F00419052 /* Assets.xcassets */, + 4716A50B2B416ECF00419052 /* Constants */, F870D3EF25149EA0008967B0 /* Extensions */, 4716A5032B40704000419052 /* Models */, 4716A5022B40702D00419052 /* Texts */, @@ -1655,8 +1664,8 @@ 4716A5022B40702D00419052 /* Texts */ = { isa = PBXGroup; children = ( - F8E53FE8251F7B8800052CE5 /* Common.swift */, - F8E53FD0251D35FB00052CE5 /* Common.strings */, + F8E53FE8251F7B8800052CE5 /* TextsWidget.swift */, + F8E53FD0251D35FB00052CE5 /* TextsWidget.strings */, ); path = Texts; sourceTree = ""; @@ -1670,6 +1679,14 @@ path = Models; sourceTree = ""; }; + 4716A50B2B416ECF00419052 /* Constants */ = { + isa = PBXGroup; + children = ( + 4716A50C2B416EE100419052 /* ConstantsWidget.swift */, + ); + path = Constants; + sourceTree = ""; + }; 47DB06CC2A7013EF00267BE3 /* Followers */ = { isa = PBXGroup; children = ( @@ -2219,6 +2236,7 @@ isa = PBXGroup; children = ( F8E53FBC2517F96800052CE5 /* Date.swift */, + 4716A5092B416E6500419052 /* Double.swift */, ); path = Extensions; sourceTree = ""; @@ -3359,6 +3377,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4716A50F2B41708000419052 /* TextsWidget.strings in Resources */, 4716A4FA2B406C3F00419052 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3417,6 +3436,7 @@ F824379524CB7A9900BED341 /* Pager_Beeps.caf in Resources */, F8B3A7CD226CC0B7004BA588 /* shorthigh3.caf in Resources */, F824376A24CB7A9800BED341 /* Martian_Gun.caf in Resources */, + 4716A5112B41823A00419052 /* TextsWidget.strings in Resources */, F824378824CB7A9900BED341 /* Insistently.caf in Resources */, F82437B924CB7A9900BED341 /* Siri_Alert_Transmitter_Battery_Low.caf in Resources */, F8B3A7D0226CC0B7004BA588 /* modernalarm.caf in Resources */, @@ -3543,7 +3563,10 @@ 4716A5052B40709E00419052 /* XDripWidgetAttributes.swift in Sources */, 4716A4F62B406C3D00419052 /* XDripWidgetLiveActivity.swift in Sources */, 4716A4F42B406C3D00419052 /* XDripWidgetBundle.swift in Sources */, + 4716A50A2B416E6500419052 /* Double.swift in Sources */, 4716A4F82B406C3D00419052 /* XDripWidget.swift in Sources */, + 4716A50D2B416EE100419052 /* ConstantsWidget.swift in Sources */, + 4716A50E2B41707D00419052 /* TextsWidget.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3862,6 +3885,7 @@ F8A2BC2D25DB0D6D001D1E78 /* BluetoothPeripheralManager+CGMG5TransmitterDelegate.swift in Sources */, 47DB06E72A715EC500267BE3 /* ConstantsLibreLinkUp.swift in Sources */, F8FDFEA9260DE1A70047597D /* DTCustomColoredAccessory.m in Sources */, + 4716A5102B41823600419052 /* TextsWidget.swift in Sources */, F8B3A7DF226E48C1004BA588 /* SoundPlayer.swift in Sources */, D48E8F78278E49B300CCEE08 /* TreatmentNSResponse.swift in Sources */, F816E12C2439DFBA009EE65B /* DexcomG4+CoreDataProperties.swift in Sources */, @@ -4485,7 +4509,7 @@ name = SettingsViews.strings; sourceTree = ""; }; - F8E53FD0251D35FB00052CE5 /* Common.strings */ = { + F8E53FD0251D35FB00052CE5 /* TextsWidget.strings */ = { isa = PBXVariantGroup; children = ( F8E53FCF251D35FB00052CE5 /* en */, @@ -4506,7 +4530,7 @@ F8D094EB2846BDD50087FFEA /* uk */, 4166BFB928C3501500199980 /* el */, ); - name = Common.strings; + name = TextsWidget.strings; sourceTree = ""; }; F8E6C79324CEC2E3007C1199 /* Snooze.strings */ = { From ebd5dc2335c26021fde95ead2f5e2d7079128391 Mon Sep 17 00:00:00 2001 From: Paul Plant Date: Wed, 3 Jan 2024 16:13:30 +0100 Subject: [PATCH 05/89] update --- xDripWidget/Extensions/Double.swift | 9 +- .../Models/XDripWidgetAttributes.swift | 96 ++++++++- xDripWidget/Texts/TextsWidget.swift | 16 +- xDripWidget/XDripWidget.swift | 12 +- xDripWidget/XDripWidgetLiveActivity.swift | 131 +++++++----- xdrip.xcodeproj/project.pbxproj | 32 ++- xdrip/Constants/ConstantsLog.swift | 82 ++++---- xdrip/Extensions/String.swift | 98 +++------ xdrip/Extensions/UserDefaults.swift | 193 ++++++++++-------- .../LiveActivity/LiveActivityManager.swift | 145 +++++++++++++ .../LiveActivity/LiveActivityType.swift | 43 ++++ xdrip/Storyboards/Base.lproj/Main.storyboard | 24 ++- .../ar.lproj/SettingsViews.strings | 2 +- .../da.lproj/SettingsViews.strings | 2 +- .../de.lproj/SettingsViews.strings | 2 +- .../el.lproj/SettingsViews.strings | 2 +- .../en.lproj/SettingsViews.strings | 2 +- .../es.lproj/SettingsViews.strings | 2 +- .../fi.lproj/SettingsViews.strings | 2 +- .../fr.lproj/SettingsViews.strings | 2 +- .../it.lproj/SettingsViews.strings | 2 +- .../nl.lproj/SettingsViews.strings | 2 +- .../pl-PL.lproj/SettingsViews.strings | 2 +- .../pt.lproj/SettingsViews.strings | 2 +- .../ru.lproj/SettingsViews.strings | 2 +- .../sl.lproj/SettingsViews.strings | 2 +- .../sv.lproj/SettingsViews.strings | 2 +- .../tr.lproj/SettingsViews.strings | 2 +- .../uk.lproj/SettingsViews.strings | 2 +- .../zh.lproj/SettingsViews.strings | 2 +- xdrip/Texts/TextsSettingsView.swift | 46 ++++- .../RootViewController.swift | 126 +++++++++++- ...sViewM5StackGeneralSettingsViewModel.swift | 2 +- .../SettingsViewController.swift | 2 +- ...tingsViewDataSourceSettingsViewModel.swift | 47 +++-- .../SettingsViewHelpSettingModel.swift | 6 +- ...sViewNotificationsSettingsViewModel.swift} | 108 +++++++--- 37 files changed, 887 insertions(+), 367 deletions(-) create mode 100644 xdrip/Managers/LiveActivity/LiveActivityManager.swift create mode 100644 xdrip/Managers/LiveActivity/LiveActivityType.swift rename xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/{SettingsViewGeneralSettingsViewModel.swift => SettingsViewNotificationsSettingsViewModel.swift} (59%) diff --git a/xDripWidget/Extensions/Double.swift b/xDripWidget/Extensions/Double.swift index 9b4767c4..0584e1e6 100644 --- a/xDripWidget/Extensions/Double.swift +++ b/xDripWidget/Extensions/Double.swift @@ -67,17 +67,11 @@ extension Double { /// if mgdl, then returns self, unchanged. If not mgdl, return self rounded to 1 decimal place func bgValueRounded(mgdl: Bool) -> Double { - if mgdl { - return self.round(toDecimalPlaces: 0) - } else { - return self.round(toDecimalPlaces: 1) - } - } /// converts mmol to mgdl if parametermgdl = false and, converts value to string, round. Number of digits after decimal seperator depends on the unit. For mg/dl 0 digits after decimal seperator, for mmol, 1 digit after decimal seperator @@ -128,12 +122,11 @@ extension Double { daysAndHoursString = Int(days).description + "d" + Int(hours).description + "h" } - return daysAndHoursString } - + } diff --git a/xDripWidget/Models/XDripWidgetAttributes.swift b/xDripWidget/Models/XDripWidgetAttributes.swift index 3b5f4f29..0deb2af5 100644 --- a/xDripWidget/Models/XDripWidgetAttributes.swift +++ b/xDripWidget/Models/XDripWidgetAttributes.swift @@ -14,38 +14,53 @@ struct XDripWidgetAttributes: ActivityAttributes { public struct ContentState: Codable, Hashable { + // store the activity id here to enable us to remove it in case it is stale + //var previousActivityID: String + //var activityID: String + // Dynamic stateful properties about your activity go here! var bgValueInMgDl: Double var isMgDl: Bool - var trendArrow: String - var deltaChangeInMgDl: Double +// var trendArrow: String + var slopeOrdinal: Int + var deltaChangeInMgDl: Double? var urgentLowLimitInMgDl: Double var lowLimitInMgDl: Double var highLimitInMgDl: Double var urgentHighLimitInMgDl: Double + var bgReadingDate: Date + var updatedDate: Date var bgValueStringInUserChosenUnit: String var bgUnitString: String +// var deltaChangeStringInUserChosenUnit: String - init(bgValueInMgDl: Double, isMgDl: Bool, trendArrow: String, deltaChangeInMgDl: Double, urgentLowLimitInMgDl: Double, lowLimitInMgDl: Double, highLimitInMgDl: Double, urgentHighLimitInMgDl: Double) { + init(bgValueInMgDl: Double, isMgDl: Bool, slopeOrdinal: Int, deltaChangeInMgDl: Double?, urgentLowLimitInMgDl: Double, lowLimitInMgDl: Double, highLimitInMgDl: Double, urgentHighLimitInMgDl: Double, bgReadingDate: Date, updatedDate: Date) { // these are the "passed in" stateful values used to initialize self.bgValueInMgDl = bgValueInMgDl self.isMgDl = isMgDl - self.trendArrow = trendArrow - self.deltaChangeInMgDl = deltaChangeInMgDl +// self.trendArrow = trendArrow + self.slopeOrdinal = slopeOrdinal + self.deltaChangeInMgDl = deltaChangeInMgDl// ?? nil self.urgentLowLimitInMgDl = urgentLowLimitInMgDl self.lowLimitInMgDl = lowLimitInMgDl self.highLimitInMgDl = highLimitInMgDl self.urgentHighLimitInMgDl = urgentHighLimitInMgDl + self.bgReadingDate = bgReadingDate + self.updatedDate = updatedDate // these are dynamically initialized based on the above //self.bgValueInUserChosenUnit = bgValueInMgDl.mgdlToMmol(mgdl: isMgDl) self.bgUnitString = isMgDl ? Texts_Widget.mgdl : Texts_Widget.mmol self.bgValueStringInUserChosenUnit = bgValueInMgDl.mgdlToMmolAndToString(mgdl: isMgDl) + //self.deltaChangeStringInUserChosenUnit = deltaChangeInMgDl.mgdlToMmolAndToString(mgdl: isMgDl) + } + /// Blood glucose color dependant on the user defined limit values + /// - Returns: a Color object either red, yellow or green func getBgColor() -> Color { if bgValueInMgDl >= urgentHighLimitInMgDl || bgValueInMgDl <= urgentLowLimitInMgDl { return .red @@ -56,19 +71,82 @@ struct XDripWidgetAttributes: ActivityAttributes { } } + /// Show the bg event title if relevant + /// - Returns: a localized string such as "HIGH" or "LOW" as required func getBgTitle() -> String { - if bgValueInMgDl >= highLimitInMgDl { - return Texts_Widget.HIGH + if bgValueInMgDl >= urgentHighLimitInMgDl { + return Texts_Widget.urgentHigh + } else if bgValueInMgDl >= highLimitInMgDl { + return Texts_Widget.high } else if bgValueInMgDl <= lowLimitInMgDl { - return Texts_Widget.LOW + return Texts_Widget.low + } else if bgValueInMgDl <= urgentLowLimitInMgDl { + return Texts_Widget.urgentLow } else { return "" } } + /// convert the optional delta change int (in mg/dL) to a formatted change value in the user chosen unit making sure all zero values are shown as a positive change to follow Nightscout convention + /// - Returns: a string holding the formatted delta change value (i.e. +0.4 or -6) + func getDeltaChangeStringInUserChosenUnit() -> String { + + if let deltaChangeInMgDl = deltaChangeInMgDl { + + let valueAsString = deltaChangeInMgDl.mgdlToMmolAndToString(mgdl: isMgDl) + + var deltaSign: String = "" + if (deltaChangeInMgDl > 0) { deltaSign = "+"; } + + // quickly check "value" and prevent "-0mg/dl" or "-0.0mmol/l" being displayed + // show unitized zero deltas as +0 or +0.0 as per Nightscout format + if (isMgDl) { + if (deltaChangeInMgDl > -1) && (deltaChangeInMgDl < 1) { + return "+0" + } else { + return deltaSign + valueAsString + } + } else { + if (deltaChangeInMgDl > -0.1) && (deltaChangeInMgDl < 0.1) { + return "+0.0" + } else { + return deltaSign + valueAsString + } + } + } else { + return "" + } + } + + /// returns a string holding the trend arrow + /// - Returns: trend arrow string (i.e. "↑") + func trendArrow() -> String { + + switch slopeOrdinal { + case 7: + return "\u{2193}\u{2193}" // ↓↓ + case 6: + return "\u{2193}" // ↓ + case 5: + return "\u{2198}" // ↘ + case 4: + return "\u{2192}" // → + case 3: + return "\u{2197}" // ↗ + case 2: + return "\u{2191}" // ↑ + case 1: + return "\u{2191}\u{2191}" // ↑↑ + default: + return "n/a" + + } + + } + } - // Fixed non-changing properties about your activity go here! + // when was the live activity event started? We check this on each update cycle and dismiss/recreate it before the 8-hour limit. var eventStartDate: Date } diff --git a/xDripWidget/Texts/TextsWidget.swift b/xDripWidget/Texts/TextsWidget.swift index 1f0bf1f1..c300d622 100644 --- a/xDripWidget/Texts/TextsWidget.swift +++ b/xDripWidget/Texts/TextsWidget.swift @@ -12,12 +12,20 @@ class Texts_Widget { return NSLocalizedString("widget_minute", tableName: filename, bundle: Bundle.main, value: "min", comment: "literal translation needed") }() - static let HIGH = { - return NSLocalizedString("widget_high", tableName: filename, bundle: Bundle.main, value: "HIGH", comment: "the word HIGH, in capitals") + static let high = { + return NSLocalizedString("widget_high", tableName: filename, bundle: Bundle.main, value: "High", comment: "the word HIGH, in capitals") }() - static let LOW = { - return NSLocalizedString("widget_low", tableName: filename, bundle: Bundle.main, value: "LOW", comment: "the word LOW, in capitals") + static let low = { + return NSLocalizedString("widget_low", tableName: filename, bundle: Bundle.main, value: "Low", comment: "the word LOW, in capitals") + }() + + static let urgentHigh = { + return NSLocalizedString("widget_urgentHigh", tableName: filename, bundle: Bundle.main, value: "Urgent High", comment: "the words urgent HIGH, in capitals") + }() + + static let urgentLow = { + return NSLocalizedString("widget_urgentLow", tableName: filename, bundle: Bundle.main, value: "Urgent Low", comment: "the words urgent LOW, in capitals") }() static let mgdl: String = { diff --git a/xDripWidget/XDripWidget.swift b/xDripWidget/XDripWidget.swift index 9bbda1a7..3b6df726 100644 --- a/xDripWidget/XDripWidget.swift +++ b/xDripWidget/XDripWidget.swift @@ -73,9 +73,9 @@ struct XDripWidget: Widget { } } -#Preview(as: .systemSmall) { - XDripWidget() -} timeline: { - SimpleEntry(date: .now, emoji: "😀") - SimpleEntry(date: .now, emoji: "🤩") -} +//#Preview(as: .systemSmall) { +// XDripWidget() +//} timeline: { +// SimpleEntry(date: .now, emoji: "😀") +// SimpleEntry(date: .now, emoji: "🤩") +//} diff --git a/xDripWidget/XDripWidgetLiveActivity.swift b/xDripWidget/XDripWidgetLiveActivity.swift index 4588b0fc..bf0596f4 100644 --- a/xDripWidget/XDripWidgetLiveActivity.swift +++ b/xDripWidget/XDripWidgetLiveActivity.swift @@ -12,89 +12,114 @@ import SwiftUI struct XDripWidgetLiveActivity: Widget { var body: some WidgetConfiguration { + ActivityConfiguration(for: XDripWidgetAttributes.self) { context in - // Lock screen/banner UI goes here - VStack { - HStack { - Text(context.state.getBgTitle()).foregroundStyle(context.state.getBgColor()) - Spacer() - Text("\(context.state.bgValueStringInUserChosenUnit) \(context.state.trendArrow)").foregroundStyle(context.state.getBgColor()) - }.font(.largeTitle).bold().foregroundStyle(context.state.getBgColor()) - HStack { - Text("Started \(context.attributes.eventStartDate.formatted(date: .omitted, time: .shortened))") - Spacer() - Text(context.state.bgUnitString) - .foregroundStyle(.gray) - } - .font(.headline) - Spacer() - - HStack { - Text("Message") - } - .font(.body) - Spacer() - } - .padding(15) - //.activityBackgroundTint(Color.cyan) - //.activitySystemActionForegroundColor(Color.black) - //.background(.ultraThinMaterial) - //.activityBackgroundTint(Color.red) - //.activitySystemActionForegroundColor(Color.black) - + LiveActivityView(state: context.state) } dynamicIsland: { context in + DynamicIsland { - // Expanded UI goes here. Compose the expanded UI through - // various regions, like leading/trailing/center/bottom DynamicIslandExpandedRegion(.leading) { - Text(context.state.getBgTitle()) - .font(.largeTitle).bold().foregroundStyle(context.state.getBgColor()) + Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow())") + .font(.largeTitle) + .foregroundStyle(context.state.getBgColor()) } DynamicIslandExpandedRegion(.trailing) { - Text("\(context.state.bgValueStringInUserChosenUnit) \(context.state.trendArrow)") - .font(.largeTitle).bold().foregroundStyle(context.state.getBgColor()) - } - DynamicIslandExpandedRegion(.center) { - EmptyView() + Text(context.state.getBgTitle()) + .font(.largeTitle) + .foregroundStyle(context.state.getBgColor()) } + // DynamicIslandExpandedRegion(.center) { + // EmptyView() + // } DynamicIslandExpandedRegion(.bottom) { VStack { HStack { - Text("Started \(context.attributes.eventStartDate.formatted(date: .omitted, time: .shortened))") + Text("\(context.state.getDeltaChangeStringInUserChosenUnit()) \(context.state.bgUnitString)") + .font(.title3) + .foregroundStyle(Color(white: 0.8)) + .bold() Spacer() - Text(context.state.bgUnitString) - .foregroundStyle(.gray) + VStack { + Text("Reading \(context.state.bgReadingDate.formatted(date: .omitted, time: .shortened))") + .font(.subheadline) + .foregroundStyle(Color(white: 0.6)) + .bold() + .frame(maxWidth: .infinity, alignment: .trailing) + + Text("Updated \(context.state.updatedDate.formatted(date: .omitted, time: .shortened))") + .font(.subheadline) + .foregroundStyle(Color(white: 0.6)) + .bold() + .frame(maxWidth: .infinity, alignment: .trailing) + + } } - .font(.headline) - Spacer() - HStack { - Text("Message") - } - .font(.body) - Spacer() } } } compactLeading: { - Text(context.state.getBgTitle()).foregroundStyle(context.state.getBgColor()) + Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow())") + .foregroundStyle(context.state.getBgColor()) } compactTrailing: { - Text("\(context.state.bgValueStringInUserChosenUnit) \(context.state.trendArrow)").foregroundStyle(context.state.getBgColor()) + Text(context.state.getDeltaChangeStringInUserChosenUnit()) } minimal: { - Text("\(context.state.bgValueStringInUserChosenUnit)") + Text(context.state.bgValueStringInUserChosenUnit) .foregroundStyle(context.state.getBgColor()) } .widgetURL(URL(string: "xdripswift")) + .keylineTint(context.state.getBgColor()) } } } -@available(iOS 16.2, *) +struct LiveActivityView: View { + + let state: XDripWidgetAttributes.ContentState + + var body: some View { + // Lock screen/banner UI goes here + VStack { + HStack { + Text("\(state.bgValueStringInUserChosenUnit)\(state.trendArrow())") + .font(.largeTitle).bold() + .foregroundStyle(state.getBgColor()) + Spacer() + Text(state.getBgTitle()) + .font(.title).bold() + .foregroundStyle(state.getBgColor()) + } + + HStack { + Text("\(state.getDeltaChangeStringInUserChosenUnit()) \(state.bgUnitString)") + .font(.title3) + .foregroundStyle(Color(white: 0.8)) + .bold() + Spacer() + VStack { + Text("Reading \(state.bgReadingDate.formatted(date: .omitted, time: .shortened))") + .font(.subheadline) + .foregroundStyle(Color(white: 0.6)) + .frame(maxWidth: .infinity, alignment: .trailing) + .bold() + Text("Updated \(state.updatedDate.formatted(date: .omitted, time: .shortened))") + .font(.subheadline) + .foregroundStyle(Color(white: 0.6)) + .frame(maxWidth: .infinity, alignment: .trailing) + .bold() + } + } + } + .padding(15) + } +} + +//@available(iOS 16.2, *) struct XDripWidgetLiveActivity_Previews: PreviewProvider { static let attributes = XDripWidgetAttributes(eventStartDate: Date().addingTimeInterval(-1000)) - static let contentState = XDripWidgetAttributes.ContentState(bgValueInMgDl: 75, isMgDl: true, trendArrow: "↘", deltaChangeInMgDl: -2, urgentLowLimitInMgDl: 70, lowLimitInMgDl: 80, highLimitInMgDl: 140, urgentHighLimitInMgDl: 180) + static let contentState = XDripWidgetAttributes.ContentState(bgValueInMgDl: 252, isMgDl: true, slopeOrdinal:5, deltaChangeInMgDl: -2, urgentLowLimitInMgDl: 70, lowLimitInMgDl: 80, highLimitInMgDl: 140, urgentHighLimitInMgDl: 180, bgReadingDate: Date().addingTimeInterval(-180), updatedDate: Date()) static var previews: some View { attributes diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 84fcc08f..64fc01c9 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 4716A50F2B41708000419052 /* TextsWidget.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8E53FD0251D35FB00052CE5 /* TextsWidget.strings */; }; 4716A5102B41823600419052 /* TextsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E53FE8251F7B8800052CE5 /* TextsWidget.swift */; }; 4716A5112B41823A00419052 /* TextsWidget.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8E53FD0251D35FB00052CE5 /* TextsWidget.strings */; }; + 4716A5142B41CAD000419052 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A5132B41CAD000419052 /* LiveActivityManager.swift */; }; 47228B152996BDD2008725DB /* BgReadingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47228B142996BDD2008725DB /* BgReadingsView.swift */; }; 4733B93E2AD17C99001D609D /* FollowerBgReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4733B93D2AD17C99001D609D /* FollowerBgReading.swift */; }; 4733B9402AD17D15001D609D /* FollowerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4733B93F2AD17D15001D609D /* FollowerDelegate.swift */; }; @@ -46,6 +47,7 @@ 4779BCEE2974306300515714 /* ActionClosurable in Frameworks */ = {isa = PBXBuildFile; productRef = 4779BCED2974306300515714 /* ActionClosurable */; }; 4779BCF12974307700515714 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 4779BCF02974307700515714 /* CryptoSwift */; }; 4779BCF42974308F00515714 /* PieCharts in Frameworks */ = {isa = PBXBuildFile; productRef = 4779BCF32974308F00515714 /* PieCharts */; }; + 477B2C7E2B432775002F64A4 /* LiveActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477B2C7D2B432775002F64A4 /* LiveActivityType.swift */; }; 477F45E6285B993200AC8475 /* GlucoseMiniChartManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477F45E5285B993100AC8475 /* GlucoseMiniChartManager.swift */; }; 47AB72F327105EF4005E7CAB /* SettingsViewHelpSettingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AB72F227105EF4005E7CAB /* SettingsViewHelpSettingModel.swift */; }; 47ADD2DF27FAF8630025E2F4 /* ChartPointsScatterDownTrianglesLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47ADD2DE27FAF8630025E2F4 /* ChartPointsScatterDownTrianglesLayer.swift */; }; @@ -430,7 +432,7 @@ F8B3A847227F090E004BA588 /* SettingsViewNightScoutSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B3A83B227F090D004BA588 /* SettingsViewNightScoutSettingsViewModel.swift */; }; F8B3A848227F090E004BA588 /* SettingsViewHealthKitSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B3A83C227F090D004BA588 /* SettingsViewHealthKitSettingsViewModel.swift */; }; F8B3A849227F090E004BA588 /* SettingsViewSpeakSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B3A83D227F090D004BA588 /* SettingsViewSpeakSettingsViewModel.swift */; }; - F8B3A84A227F090E004BA588 /* SettingsViewGeneralSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B3A83E227F090D004BA588 /* SettingsViewGeneralSettingsViewModel.swift */; }; + F8B3A84A227F090E004BA588 /* SettingsViewNotificationsSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B3A83E227F090D004BA588 /* SettingsViewNotificationsSettingsViewModel.swift */; }; F8B3A84C227F090E004BA588 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B3A841227F090D004BA588 /* SettingsViewController.swift */; }; F8B3A850227F26F8004BA588 /* AlertTypesSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B3A84F227F26F8004BA588 /* AlertTypesSettingsViewController.swift */; }; F8B3A853227F2743004BA588 /* AlertsSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B3A852227F2743004BA588 /* AlertsSettingsViewController.swift */; }; @@ -714,6 +716,7 @@ 4716A5042B40709E00419052 /* XDripWidgetAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDripWidgetAttributes.swift; sourceTree = ""; }; 4716A5092B416E6500419052 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; 4716A50C2B416EE100419052 /* ConstantsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsWidget.swift; sourceTree = ""; }; + 4716A5132B41CAD000419052 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = ""; }; 47228B142996BDD2008725DB /* BgReadingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BgReadingsView.swift; sourceTree = ""; }; 4733B93D2AD17C99001D609D /* FollowerBgReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerBgReading.swift; sourceTree = ""; }; 4733B93F2AD17D15001D609D /* FollowerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerDelegate.swift; sourceTree = ""; }; @@ -723,6 +726,7 @@ 4752B4052635878E0081D551 /* SettingsViewStatisticsSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewStatisticsSettingsViewModel.swift; sourceTree = ""; }; 475DED96244AF92A00F78473 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Alerts.strings; sourceTree = ""; }; 476FE8FE2B2F1D1700537E0A /* ConstantsFollower.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstantsFollower.swift; sourceTree = ""; }; + 477B2C7D2B432775002F64A4 /* LiveActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityType.swift; sourceTree = ""; }; 477F45E5285B993100AC8475 /* GlucoseMiniChartManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseMiniChartManager.swift; sourceTree = ""; }; 4798BAC727BA6AA8002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/LaunchScreen.strings; sourceTree = ""; }; 4798BAC827BA6AA8002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Main.strings; sourceTree = ""; }; @@ -1355,7 +1359,7 @@ F8B3A83B227F090D004BA588 /* SettingsViewNightScoutSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewNightScoutSettingsViewModel.swift; sourceTree = ""; }; F8B3A83C227F090D004BA588 /* SettingsViewHealthKitSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewHealthKitSettingsViewModel.swift; sourceTree = ""; }; F8B3A83D227F090D004BA588 /* SettingsViewSpeakSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewSpeakSettingsViewModel.swift; sourceTree = ""; }; - F8B3A83E227F090D004BA588 /* SettingsViewGeneralSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewGeneralSettingsViewModel.swift; sourceTree = ""; }; + F8B3A83E227F090D004BA588 /* SettingsViewNotificationsSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewNotificationsSettingsViewModel.swift; sourceTree = ""; }; F8B3A841227F090D004BA588 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; F8B3A84F227F26F8004BA588 /* AlertTypesSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertTypesSettingsViewController.swift; sourceTree = ""; }; F8B3A852227F2743004BA588 /* AlertsSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertsSettingsViewController.swift; sourceTree = ""; }; @@ -1687,6 +1691,15 @@ path = Constants; sourceTree = ""; }; + 4716A5122B41CA9C00419052 /* LiveActivity */ = { + isa = PBXGroup; + children = ( + 4716A5132B41CAD000419052 /* LiveActivityManager.swift */, + 477B2C7D2B432775002F64A4 /* LiveActivityType.swift */, + ); + path = LiveActivity; + sourceTree = ""; + }; 47DB06CC2A7013EF00267BE3 /* Followers */ = { isa = PBXGroup; children = ( @@ -2142,6 +2155,7 @@ 47DB06CC2A7013EF00267BE3 /* Followers */, F821CF9122ADB064005C1E43 /* HealthKit */, 47DB06DB2A7136E900267BE3 /* LibreLinkUp */, + 4716A5122B41CA9C00419052 /* LiveActivity */, F8E51D5B2448D8A3001C9E5A /* Loop */, F821CF4F229BF43A005C1E43 /* NightScout */, F64039AE281C3F8D0051EFFE /* QuickActions */, @@ -2586,7 +2600,7 @@ 47DB06C12A6FC02200267BE3 /* SettingsViewDataSourceSettingsViewModel.swift */, F8A1584C22ECA445007F5B5D /* SettingsViewDevelopmentSettingsViewModel.swift */, F8B3A839227F090D004BA588 /* SettingsViewDexcomSettingsViewModel.swift */, - F8B3A83E227F090D004BA588 /* SettingsViewGeneralSettingsViewModel.swift */, + F8B3A83E227F090D004BA588 /* SettingsViewNotificationsSettingsViewModel.swift */, F8B3A83C227F090D004BA588 /* SettingsViewHealthKitSettingsViewModel.swift */, 47AB72F227105EF4005E7CAB /* SettingsViewHelpSettingModel.swift */, F8D0587B24BCB570008C8734 /* SettingsViewHomeScreenSettingsViewModel.swift */, @@ -3587,7 +3601,7 @@ F81FA006228E09D40028C70F /* TextsCalibration.swift in Sources */, F816E0F724367137009EE65B /* GNSEntry+CoreDataClass.swift in Sources */, F8F9721923A5915900C3F17D /* CGMGNSEntryTransmitter.swift in Sources */, - F8B3A84A227F090E004BA588 /* SettingsViewGeneralSettingsViewModel.swift in Sources */, + F8B3A84A227F090E004BA588 /* SettingsViewNotificationsSettingsViewModel.swift in Sources */, F83098FE23AD3F84005741DF /* UITabBarController.swift in Sources */, F816E11A243923B2009EE65B /* Droplet+CoreDataClass.swift in Sources */, F80D916D24F82A17006840B5 /* CGMLibre2TransmitterDelegate.swift in Sources */, @@ -3608,6 +3622,7 @@ F8E0475E28CC8E330049D8C9 /* GlucoseData+LoopShare.swift in Sources */, F816E1002436734C009EE65B /* CGMGNSEntryTransmitterDelegate.swift in Sources */, F82842322752CBE00097E0C9 /* DexcomSessionStopTxMessage.swift in Sources */, + 4716A5142B41CAD000419052 /* LiveActivityManager.swift in Sources */, F8C97853242AA70D00A09483 /* MiaoMiao+CoreDataClass.swift in Sources */, F8F9720D23A5915900C3F17D /* ResetMessage.swift in Sources */, F85FF3D7252FB1C0004E6FF1 /* SnoozeParametersAccessor.swift in Sources */, @@ -3812,6 +3827,7 @@ F816E0F02433C31B009EE65B /* Blucon+CoreDataProperties.swift in Sources */, F8A2BC3925DB0D6D001D1E78 /* BluetoothPeripheralManager+CGMLibre2TransmitterDelegate.swift in Sources */, F8A5EEAE25791F370085E660 /* Libre2BLEUtilities.swift in Sources */, + 477B2C7E2B432775002F64A4 /* LiveActivityType.swift in Sources */, F86697502867AA4A00025441 /* LoopDelayScheduleView.swift in Sources */, F8E3C3AB21FE17B700907A04 /* StringProtocol.swift in Sources */, F8F9720723A5915900C3F17D /* AuthChallengeTxMessage.swift in Sources */, @@ -4670,7 +4686,7 @@ INFOPLIST_FILE = xDripWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = xDripWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Johan Degraeve. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4704,7 +4720,7 @@ INFOPLIST_FILE = xDripWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = xDripWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Johan Degraeve. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4863,7 +4879,7 @@ CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; DEVELOPMENT_TEAM = "$(XDRIP_DEVELOPMENT_TEAM)"; INFOPLIST_FILE = "$(SRCROOT)/xdrip/Supporting Files/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4892,7 +4908,7 @@ CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; DEVELOPMENT_TEAM = "$(XDRIP_DEVELOPMENT_TEAM)"; INFOPLIST_FILE = "$(SRCROOT)/xdrip/Supporting Files/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/xdrip/Constants/ConstantsLog.swift b/xdrip/Constants/ConstantsLog.swift index a53dbd33..07939949 100644 --- a/xdrip/Constants/ConstantsLog.swift +++ b/xdrip/Constants/ConstantsLog.swift @@ -7,80 +7,83 @@ enum ConstantsLog { /// for use in NSLog static let tracePrefix = "xDrip-NSLog" - /// for use in OSLog - static let categoryBlueToothTransmitter = "BlueToothTransmitter " - - /// for use in cgm transmitter miaomiao - static let categoryCGMMiaoMiao = "CGMMiaoMiao " - - /// for use in cgm transmitter bubble - static let categoryCGMBubble = "CGMBubble " - - /// for use in cgm xdripg4 - static let categoryCGMxDripG4 = "CGMxDripG4 " - - /// for use in firstview - static let categoryRootView = "RootView " - - /// calibration - static let categoryCalibration = "Calibration " - /// debuglogging static let debuglogging = "xdripdebuglogging" /// timestamp format for nslog static let dateFormatNSLog = "y-MM-dd HH:mm:ss.SSSS" + // MARK: - Categories + + /// for use in OSLog + static let categoryBlueToothTransmitter = "BlueToothTransmitter " + + /// for use in cgm transmitter miaomiao + static let categoryCGMMiaoMiao = "CGMMiaoMiao " + + /// for use in cgm transmitter bubble + static let categoryCGMBubble = "CGMBubble " + + /// for use in cgm xdripg4 + static let categoryCGMxDripG4 = "CGMxDripG4 " + + /// for use in firstview + static let categoryRootView = "RootView " + + /// calibration + static let categoryCalibration = "Calibration " + /// G5 - static let categoryCGMG5 = "CGMG5 " + static let categoryCGMG5 = "CGMG5 " /// watlaa - static let categoryWatlaa = "Watlaa" + static let categoryWatlaa = "Watlaa" /// GNSEntry - static let categoryCGMGNSEntry = "CGMGNSEntry " + static let categoryCGMGNSEntry = "CGMGNSEntry " /// Blucon - static let categoryBlucon = "Blucon " + static let categoryBlucon = "Blucon " /// Libre2 - static let categoryCGMLibre2 = "Libre2 " + static let categoryCGMLibre2 = "Libre2 " /// core data manager - static let categoryCoreDataManager = "CoreDataManager " + static let categoryCoreDataManager = "CoreDataManager " /// application data bgreadings - static let categoryApplicationDataBgReadings = "ApplicationDataBgReadings " + static let categoryApplicationDataBgReadings = "ApplicationDataBgReadings " /// application data Treatments - static let categoryApplicationDataTreatments = "ApplicationDataTreatments " + static let categoryApplicationDataTreatments = "ApplicationDataTreatments " /// application data calibrations - static let categoryApplicationDataCalibrations = "ApplicationDataCalibrations " + static let categoryApplicationDataCalibrations = "ApplicationDataCalibrations " /// application data sensors - static let categoryApplicationDataSensors = "ApplicationDataSensors " + static let categoryApplicationDataSensors = "ApplicationDataSensors " /// application data alerttypes - static let categoryApplicationDataAlertTypes = "ApplicationDataAlertTypes " + static let categoryApplicationDataAlertTypes = "ApplicationDataAlertTypes " /// application data alertentries - static let categoryApplicationDataAlertEntries = "ApplicationDataAlertEntries " + static let categoryApplicationDataAlertEntries = "ApplicationDataAlertEntries " /// application data for M5Stack - static let categoryApplicationDataM5Stacks = "ApplicationDataM5Stacks " + static let categoryApplicationDataM5Stacks = "ApplicationDataM5Stacks " /// application data for M5Stack - static let categoryApplicationDataWatlaa = "ApplicationDataWatlaa" + static let categoryApplicationDataWatlaa = "ApplicationDataWatlaa" /// application data for BLEPeripheral - static let categoryApplicationDataBLEPeripheral = "ApplicationDataBLEPeripheral" + static let categoryApplicationDataBLEPeripheral = + "ApplicationDataBLEPeripheral" /// application data for DexcomG5 - static let categoryApplicationDataDexcomG5 = "ApplicationDataDexcomG5" + static let categoryApplicationDataDexcomG5 = "ApplicationDataDexcomG5" /// application for for M5StackName - static let categoryApplicationDataM5StackNames = "ApplicationDataM5StackNames " + static let categoryApplicationDataM5StackNames = "ApplicationDataM5StackNames " /// nightscout uploader static let categoryNightScoutUploadManager = "NightScoutUploadManager " @@ -89,7 +92,7 @@ enum ConstantsLog { static let categoryNightScoutFollowManager = "NightScoutFollowManager " /// nightscout follow - static let categoryLibreLinkUpFollowManager = "LibreLinkUpFollowManager " + static let categoryLibreLinkUpFollowManager = "LibreLinkUpFollowManager " /// alertmanager static let categoryAlertManager = "AlertManager " @@ -175,5 +178,12 @@ enum ConstantsLog { /// SettingsViewCalendarEventsSettingsViewModel logging static let categorySettingsViewDataSourceSettingsViewModel = "SettingsViewDataSourceSettingsViewModel" + /// SettingsViewNotificationsSettingsViewModel logging + static let categorySettingsViewNotificationsSettingsViewModel = + "NotificationsViewModel " + + /// for use in LiveActivityManager + static let categoryLiveActivityManager = "LiveActivityManager " + } diff --git a/xdrip/Extensions/String.swift b/xdrip/Extensions/String.swift index e170860a..164ab8d4 100644 --- a/xdrip/Extensions/String.swift +++ b/xdrip/Extensions/String.swift @@ -12,24 +12,18 @@ extension String { let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound)) return String(self[idx1.. Bool { let range = NSRange(self.startIndex..., in: self) let matchRange = regex.rangeOfFirstMatch(in: self, options: .reportProgress, range: range) return matchRange.location != NSNotFound } -} - -extension String { + func startsWith(_ prefix: String) -> Bool { return lowercased().hasPrefix(prefix.lowercased()) } -} - -extension String { + /// converts String to Double, works with decimal seperator . or , - if conversion fails then returns nil func toDouble() -> Double? { @@ -55,25 +49,19 @@ extension String { } return nil } -} - -extension String { + func contains(find: String) -> Bool{ return self.range(of: find) != nil } func containsIgnoringCase(find: String) -> Bool{ return self.range(of: find, options: .caseInsensitive) != nil } -} - -extension String { + func sha1() -> String { // sha1() here is a function in CryptoSwift Library return Data(self.utf8).sha1().hexEncodedString() } -} - -extension String { + /// creates uicolor interpreting hex as hex color code, example #CED430 func hexStringToUIColor () -> UIColor { var cString:String = self.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() @@ -96,10 +84,6 @@ extension String { alpha: CGFloat(1.0) ) } - -} - -extension String { /// checks if string length is > 0 and if so returns self, otherwise returns nil /// @@ -108,9 +92,6 @@ extension String { if self.count > 0 {return self} return nil } -} - -extension String { /// Percent escape value to be added to a URL query value as specified in RFC 3986 /// @@ -163,7 +144,7 @@ extension String { } isOn = !isOn } - + return isOn } @@ -197,10 +178,7 @@ extension String { return data } - -} - -extension String { + func dateFromISOString() -> Date? { let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US_POSIX") @@ -209,45 +187,12 @@ extension String { return dateFormatter.date(from: self) } -} - -extension Optional where Wrapped == String { - - /// - if string doesn't start with http, then add https - /// - if ending with /, remove it - /// - convert to lowercase - /// - if string nil, then returnvalue is nil - func addHttpsIfNeeded() -> String? { - - // if self nil, then return nil - guard var returnValue = self else {return nil} - - // if self doesn't start with http or https, then add https. This might not make sense, but it will guard against throwing fatal errors when trying to get the scheme of the Endpoint - if !returnValue.startsWith("http://") && !returnValue.startsWith("https://") { - returnValue = "https://" + returnValue - } - - // if url ends with /, remove it - if returnValue.last == "/" { - returnValue.removeLast() - } - - return returnValue - - } -} - -extension String { mutating func appendStringAndNewLine(_ stringToAdd: String) { self = self + stringToAdd + "\n" } - -} - -extension String { /// use this to partially obscure a password, API-SECRET, token or other sensitive data. We want the user to see that something recognisable is there that makes sense to them, but it won't reveal any useful private information if they screenshot it func obscured() -> String { @@ -290,5 +235,30 @@ extension String { } - +} + +extension Optional where Wrapped == String { + + /// - if string doesn't start with http, then add https + /// - if ending with /, remove it + /// - convert to lowercase + /// - if string nil, then returnvalue is nil + func addHttpsIfNeeded() -> String? { + + // if self nil, then return nil + guard var returnValue = self else {return nil} + + // if self doesn't start with http or https, then add https. This might not make sense, but it will guard against throwing fatal errors when trying to get the scheme of the Endpoint + if !returnValue.startsWith("http://") && !returnValue.startsWith("https://") { + returnValue = "https://" + returnValue + } + + // if url ends with /, remove it + if returnValue.last == "/" { + returnValue.removeLast() + } + + return returnValue + + } } diff --git a/xdrip/Extensions/UserDefaults.swift b/xdrip/Extensions/UserDefaults.swift index 11960ced..266e83e1 100644 --- a/xdrip/Extensions/UserDefaults.swift +++ b/xdrip/Extensions/UserDefaults.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import ActivityKit extension UserDefaults { @@ -70,6 +71,8 @@ extension UserDefaults { case multipleAppBadgeValueWith10 = "multipleAppBadgeValueWith10" /// minimum time between two notifications, set by user case notificationInterval = "notificationInterval" + /// which type of live activities should be shown? + case liveActivityType = "liveActivityType" // Home Screen and main chart settings @@ -536,27 +539,6 @@ extension UserDefaults { } } - /// LibreLinkUp version - @objc dynamic var libreLinkUpVersion: String? { - get { - var returnValue = string(forKey: Key.libreLinkUpVersion.rawValue) - - // if nil set to defaultvalue - if returnValue == nil { - - set(ConstantsLibreLinkUp.libreLinkUpVersionDefault, forKey: Key.libreLinkUpVersion.rawValue) - - returnValue = string(forKey: Key.libreLinkUpVersion.rawValue) - - } - - return returnValue - } - set { - set(newValue, forKey: Key.libreLinkUpVersion.rawValue) - } - } - /// keep track of if the terms of use must be re-accepted true or false, default false @objc dynamic var libreLinkUpReAcceptNeeded: Bool { get { @@ -616,30 +598,6 @@ extension UserDefaults { set(newValue, forKey: Key.notificationInterval.rawValue) } } - - /// to create artificial delay in readings stored in sharedUserDefaults for loop. Minutes - so that Loop receives more smoothed values. - /// - /// Default value 0, if used then recommended value is multiple of 5 (eg 5 ot 10) - @objc dynamic var loopDelaySchedule: String? { - get { - return string(forKey: Key.loopDelaySchedule.rawValue) - } - set { - set(newValue, forKey: Key.loopDelaySchedule.rawValue) - } - } - - /// to create artificial delay in readings stored in sharedUserDefaults for loop. Minutes - so that Loop receives more smoothed values. - /// - /// Default value 0, if used then recommended value is multiple of 5 (eg 5 ot 10) - @objc dynamic var loopDelayValueInMinutes: String? { - get { - return string(forKey: Key.loopDelayValueInMinutes.rawValue) - } - set { - set(newValue, forKey: Key.loopDelayValueInMinutes.rawValue) - } - } /// should reading be shown in app badge yes or no @objc dynamic var showReadingInAppBadge: Bool { @@ -663,6 +621,19 @@ extension UserDefaults { } } + /// holds the enum integer of the type of live activity to be shown + /// default to 0 (disabled) + var liveActivityType: LiveActivityType { + get { + let liveActivityTypeAsInt = integer(forKey: Key.liveActivityType.rawValue) + return LiveActivityType(rawValue: liveActivityTypeAsInt) ?? .disabled + } + set { + set(newValue.rawValue, forKey: Key.liveActivityType.rawValue) + } + } + + // MARK: Home Screen Settings /// the amount of hours to show in the mini-chart. Usually 24 hours but can be set to 48 hours by the user @@ -1844,8 +1815,7 @@ extension UserDefaults { } } - - // MARK: - ===== Loop Share Settings ====== + // MARK: - ===== Loopkit App Group Share variables ====== /// dictionary representation of readings that were shared with Loop. This is not the json representation, it's an array of dictionary var readingsStoredInSharedUserDefaultsAsDictionary: [Dictionary]? { @@ -1867,35 +1837,16 @@ extension UserDefaults { } } - /// Loop sharing will be limited to just once every 5 minutes if true - default false - var shareToLoopOnceEvery5Minutes: Bool { + + // MARK: - ===== Developer Settings ====== + + /// OSLogEnabled - default false + var OSLogEnabled: Bool { get { - return bool(forKey: Key.shareToLoopOnceEvery5Minutes.rawValue) + return bool(forKey: Key.OSLogEnabled.rawValue) } set { - set(newValue, forKey: Key.shareToLoopOnceEvery5Minutes.rawValue) - } - } - - // MARK: - ===== technical settings for testing ====== - - /// G6 factor 1 - @objc dynamic var G6v2ScalingFactor1:String? { - get { - return string(forKey: Key.G6v2ScalingFactor1.rawValue) - } - set { - set(newValue, forKey: Key.G6v2ScalingFactor1.rawValue) - } - } - - /// G6 factor 2 - @objc dynamic var G6v2ScalingFactor2:String? { - get { - return string(forKey: Key.G6v2ScalingFactor2.rawValue) - } - set { - set(newValue, forKey: Key.G6v2ScalingFactor2.rawValue) + set(newValue, forKey: Key.OSLogEnabled.rawValue) } } @@ -1919,13 +1870,15 @@ extension UserDefaults { } } - /// for Libre 2 : suppress sending unlockPayLoad, this will allow to run xDrip4iOS/Libre 2 in parallel with other app(s) - var suppressUnLockPayLoad: Bool { + /// to create artificial delay in readings stored in sharedUserDefaults for loop. Minutes - so that Loop receives more smoothed values. + /// + /// Default value 0, if used then recommended value is multiple of 5 (eg 5 ot 10) + @objc dynamic var loopDelaySchedule: String? { get { - return bool(forKey: Key.suppressUnLockPayLoad.rawValue) + return string(forKey: Key.loopDelaySchedule.rawValue) } set { - set(newValue, forKey: Key.suppressUnLockPayLoad.rawValue) + set(newValue, forKey: Key.loopDelaySchedule.rawValue) } } @@ -1938,6 +1891,82 @@ extension UserDefaults { set(newValue, forKey: Key.suppressLoopShare.rawValue) } } + + /// Loop sharing will be limited to just once every 5 minutes if true - default false + var shareToLoopOnceEvery5Minutes: Bool { + get { + return bool(forKey: Key.shareToLoopOnceEvery5Minutes.rawValue) + } + set { + set(newValue, forKey: Key.shareToLoopOnceEvery5Minutes.rawValue) + } + } + + /// for Libre 2 : suppress sending unlockPayLoad, this will allow to run xDrip4iOS/Libre 2 in parallel with other app(s) + var suppressUnLockPayLoad: Bool { + get { + return bool(forKey: Key.suppressUnLockPayLoad.rawValue) + } + set { + set(newValue, forKey: Key.suppressUnLockPayLoad.rawValue) + } + } + + /// to create artificial delay in readings stored in sharedUserDefaults for loop. Minutes - so that Loop receives more smoothed values. + /// + /// Default value 0, if used then recommended value is multiple of 5 (eg 5 ot 10) + @objc dynamic var loopDelayValueInMinutes: String? { + get { + return string(forKey: Key.loopDelayValueInMinutes.rawValue) + } + set { + set(newValue, forKey: Key.loopDelayValueInMinutes.rawValue) + } + } + + /// LibreLinkUp version + @objc dynamic var libreLinkUpVersion: String? { + get { + var returnValue = string(forKey: Key.libreLinkUpVersion.rawValue) + + // if nil set to defaultvalue + if returnValue == nil { + + set(ConstantsLibreLinkUp.libreLinkUpVersionDefault, forKey: Key.libreLinkUpVersion.rawValue) + + returnValue = string(forKey: Key.libreLinkUpVersion.rawValue) + + } + + return returnValue + } + set { + set(newValue, forKey: Key.libreLinkUpVersion.rawValue) + } + } + + + // MARK: - ===== technical settings for testing ====== + + /// G6 factor 1 + @objc dynamic var G6v2ScalingFactor1:String? { + get { + return string(forKey: Key.G6v2ScalingFactor1.rawValue) + } + set { + set(newValue, forKey: Key.G6v2ScalingFactor1.rawValue) + } + } + + /// G6 factor 2 + @objc dynamic var G6v2ScalingFactor2:String? { + get { + return string(forKey: Key.G6v2ScalingFactor2.rawValue) + } + set { + set(newValue, forKey: Key.G6v2ScalingFactor2.rawValue) + } + } /// used for Libre data parsing - for processing in LibreDataParser which is only in case of reading with NFC (ie bubble etc) var previousRawLibreValues: [Double] { @@ -1999,16 +2028,6 @@ extension UserDefaults { } } - /// OSLogEnabled - default false - var OSLogEnabled: Bool { - get { - return bool(forKey: Key.OSLogEnabled.rawValue) - } - set { - set(newValue, forKey: Key.OSLogEnabled.rawValue) - } - } - /// addDebugLevelLogsInTraceFileAndNSLog - default false var addDebugLevelLogsInTraceFileAndNSLog: Bool { get { diff --git a/xdrip/Managers/LiveActivity/LiveActivityManager.swift b/xdrip/Managers/LiveActivity/LiveActivityManager.swift new file mode 100644 index 00000000..f7988edd --- /dev/null +++ b/xdrip/Managers/LiveActivity/LiveActivityManager.swift @@ -0,0 +1,145 @@ +// +// LiveActivityManager.swift +// xdrip +// +// Created by Paul Plant on 31/12/23. +// Copyright © 2023 Johan Degraeve. All rights reserved. +// + +import Foundation +import ActivityKit +import OSLog + +//@available(iOS 16.2, *) +public final class LiveActivityManager { + + /// for trace + private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryLiveActivityManager) + + private var eventAttributes: XDripWidgetAttributes + private var eventActivity: Activity? + static let shared = LiveActivityManager() + + private init() { + eventAttributes = XDripWidgetAttributes(eventStartDate: Date()) + } + +} + +// MARK: - Helpers +//@available(iOS 16.2, *) +extension LiveActivityManager { + + /// start or update the live activity based upon whether it currently exists or not + /// - Parameter contentState: the contentState to show + func runActivity(contentState: XDripWidgetAttributes.ContentState) { + + trace("In runActivity", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) + + // checking whether 'Live activities' is enabled for the app in settings + if ActivityAuthorizationInfo().areActivitiesEnabled { + if eventActivity == nil { + trace(" eventActivity == nil, trying to start new activity", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) + + startActivity(contentState: contentState) + } else { + trace(" eventActivity != nil, trying to update existing", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) + + Task { + await updateActivity(to: contentState) + } + } + } else { + print("Live activities are disabled in the iPhone Settings or permission has not been given.") + trace("Live activities are disabled in the iPhone Settings or permission has not been given.", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) + } + } + + func startActivity(contentState: XDripWidgetAttributes.ContentState) { + + let content = ActivityContent(state: contentState, staleDate: nil, relevanceScore: 1.0) + + do { + print("Trying to start new live activity") + eventActivity = try Activity.request( + attributes: eventAttributes, + content: content, + pushType: nil + ) + print("New activity started: \(String(describing: eventActivity?.id))") + + let idString = "\(String(describing: eventActivity?.id))" + trace("New live activity started: %{public}@", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info, idString) + } catch { + print(error.localizedDescription) + + trace("error: %{public}@", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info, error.localizedDescription) + } + } + + func updateActivity(to contentState: XDripWidgetAttributes.ContentState) async { + + // check if the activity is dismissed by the user (by swiping away the notification) + // if so, then end it completely and start a new one + if eventActivity?.activityState == .dismissed { + Task { + print("Live activity state is \(String(describing: eventActivity?.activityState))") + + trace("Previous live activity was dismissed so will end it and try to start a new one.", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) + + endAllActivities() + + startActivity(contentState: contentState) + } + return + + } else { + await eventActivity?.update( + ActivityContent( + state: contentState, + staleDate: Date().addingTimeInterval(10) + ) + ) + } + } + + /// end the live activity if it is being shown, do nothing if there is no eventyActivity + func endActivity() { + + if eventActivity != nil { + Task { + print("Ending live activity: \(String(describing: eventActivity?.id))") + + let idString = "\(String(describing: eventActivity?.id))" + trace("Ending live activity: %{public}@", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info, idString) + + await eventActivity?.end(nil, dismissalPolicy: .immediate) + eventActivity = nil + } + } + } + + /// end all live activities that are spawned from the app + func endAllActivities() { + + // https://developer.apple.com/forums/thread/732418 + // Add a semaphore to force it to wait for the activities to end before returning from the method + let semaphore = DispatchSemaphore(value: 0) + + Task + { + for activity in Activity.activities + { + print("Force-close detected. Ending Live Activity: \(activity.id)") + + let idString = "\(String(describing: eventActivity?.id))" + trace("Force-close detected. Ending live activity: %{public}@", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info, idString) + + await activity.end(nil, dismissalPolicy: .immediate) + eventActivity = nil + } + semaphore.signal() + } + semaphore.wait() + } +} diff --git a/xdrip/Managers/LiveActivity/LiveActivityType.swift b/xdrip/Managers/LiveActivity/LiveActivityType.swift new file mode 100644 index 00000000..1e2cf2fb --- /dev/null +++ b/xdrip/Managers/LiveActivity/LiveActivityType.swift @@ -0,0 +1,43 @@ +// +// LiveActivityType.swift +// xdrip +// +// Created by Paul Plant on 1/1/24. +// Copyright © 2024 Johan Degraeve. All rights reserved. +// + +import Foundation + +/// types of live activity +public enum LiveActivityType: Int, CaseIterable { + + // when adding to LiveActivityType, add new cases at the end (ie 3, ...) + // if this is done in the middle then a database migration would be required, because the rawvalue is stored as Int16 in the coredata + // the order of the returned enum can be defined in allCases below + + case disabled = 0 + case always = 1 + case urgentLow = 2 + case low = 3 + case lowHigh = 4 + case urgentLowHigh = 5 + + var description: String { + switch self { + case .disabled: + return Texts_SettingsView.liveActivityTypeDisabled + case .always: + return Texts_SettingsView.liveActivityTypeAlways + case .urgentLow: + return Texts_SettingsView.liveActivityTypeUrgentLow + case .low: + return Texts_SettingsView.liveActivityTypeLow + case .lowHigh: + return Texts_SettingsView.liveActivityTypeLowHigh + case .urgentLowHigh: + return Texts_SettingsView.liveActivityTypeUrgentLowHigh + } + } + +} + diff --git a/xdrip/Storyboards/Base.lproj/Main.storyboard b/xdrip/Storyboards/Base.lproj/Main.storyboard index 95675075..670b147e 100644 --- a/xdrip/Storyboards/Base.lproj/Main.storyboard +++ b/xdrip/Storyboards/Base.lproj/Main.storyboard @@ -397,7 +397,7 @@ - + @@ -423,7 +423,13 @@ - + + + + + + + @@ -992,6 +998,7 @@ + @@ -2367,22 +2374,23 @@ - - + + + - + - + - + - + diff --git a/xdrip/Storyboards/ar.lproj/SettingsViews.strings b/xdrip/Storyboards/ar.lproj/SettingsViews.strings index 94c03b17..91f7e73f 100644 --- a/xdrip/Storyboards/ar.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/ar.lproj/SettingsViews.strings @@ -1,5 +1,5 @@ "settingsviews_settingstitle" = "الإعدادات"; -"settingsviews_sectiontitlegeneral" = "عام"; +"settingsviews_sectiontitleNotifications" = "عام"; "settingsviews_selectbgunit" = "وحدة قياس السكر"; "settingsviews_masterorfollower" = "الاستخدام كتطبيق رئيسي أو للمتابعة؟"; "settingsviews_master" = "رئيسي"; diff --git a/xdrip/Storyboards/da.lproj/SettingsViews.strings b/xdrip/Storyboards/da.lproj/SettingsViews.strings index aa571531..e31d19e3 100644 --- a/xdrip/Storyboards/da.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/da.lproj/SettingsViews.strings @@ -4,7 +4,7 @@ "settingsviews_translateOnlineHelp" = "Translate Automatically"; "settingsviews_showHelpIcon" = "Show Help Icon"; "settingsviews_restartNeeded" = "(Restart required)"; -"settingsviews_sectiontitlegeneral" = "General"; +"settingsviews_sectiontitleNotifications" = "Notifications"; "settingsviews_selectbgunit" = "Blood Glucose Units"; "settingsviews_masterorfollower" = "Master or Follower"; "settingsviews_master" = "Master"; diff --git a/xdrip/Storyboards/de.lproj/SettingsViews.strings b/xdrip/Storyboards/de.lproj/SettingsViews.strings index 609cdb8b..d2031cbc 100644 --- a/xdrip/Storyboards/de.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/de.lproj/SettingsViews.strings @@ -105,7 +105,7 @@ "settingsviews_row_alert_types" = "Eigene Alarme"; /// general settings, section title -"settingsviews_sectiontitlegeneral" = "Haupteinstellungen"; +"settingsviews_sectiontitleNotifications" = "Haupteinstellungen"; /// speak settings, where enable or disable speak trend "settingsviews_speakTrend" = "Trend vorlesen"; diff --git a/xdrip/Storyboards/el.lproj/SettingsViews.strings b/xdrip/Storyboards/el.lproj/SettingsViews.strings index 4175db70..54eb2340 100644 --- a/xdrip/Storyboards/el.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/el.lproj/SettingsViews.strings @@ -4,7 +4,7 @@ "settingsviews_translateOnlineHelp" = "Αυτόματη μετάφραση;"; "settingsviews_showHelpIcon" = "Εμφάνιση εικονιδίου βοήθειας;"; "settingsviews_restartNeeded" = "(Απαιτείται επανεκκίνηση)"; -"settingsviews_sectiontitlegeneral" = "Γενικά"; +"settingsviews_sectiontitleNotifications" = "Γενικά"; "settingsviews_selectbgunit" = "Μονάδες μέτρησης γλυκόζης"; "settingsviews_masterorfollower" = "Κύριος ή ακόλουθος;"; "settingsviews_master" = "Κύριος"; diff --git a/xdrip/Storyboards/en.lproj/SettingsViews.strings b/xdrip/Storyboards/en.lproj/SettingsViews.strings index 56f99fca..189813c2 100644 --- a/xdrip/Storyboards/en.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/en.lproj/SettingsViews.strings @@ -5,7 +5,7 @@ "settingsviews_translateOnlineHelp" = "Translate Automatically"; "settingsviews_showHelpIcon" = "Show Help Icon"; "settingsviews_restartNeeded" = "(Restart required)"; -"settingsviews_sectiontitlegeneral" = "General"; +"settingsviews_sectiontitleNotifications" = "Notifications"; "settingsviews_selectbgunit" = "Blood Glucose Units"; "settingsviews_masterorfollower" = "Master or Follower"; "settingsviews_master" = "Master"; diff --git a/xdrip/Storyboards/es.lproj/SettingsViews.strings b/xdrip/Storyboards/es.lproj/SettingsViews.strings index 83016045..193468b7 100644 --- a/xdrip/Storyboards/es.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/es.lproj/SettingsViews.strings @@ -1,6 +1,6 @@ "settingsviews_settingstitle" = "Configuración"; "settingsviews_valueIsRequired" = "⚠️ Requerido"; -"settingsviews_sectiontitlegeneral" = "General"; +"settingsviews_sectiontitleNotifications" = "Notifications"; "settingsviews_selectbgunit" = "Seleccionar Unidades"; "settingsviews_masterorfollower" = "Master o Seguidor"; "settingsviews_master" = "Master"; diff --git a/xdrip/Storyboards/fi.lproj/SettingsViews.strings b/xdrip/Storyboards/fi.lproj/SettingsViews.strings index 2dd389c9..0d57e307 100644 --- a/xdrip/Storyboards/fi.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/fi.lproj/SettingsViews.strings @@ -1,5 +1,5 @@ "settingsviews_settingstitle" = "Asetukset"; -"settingsviews_sectiontitlegeneral" = "Yleinen"; +"settingsviews_sectiontitleNotifications" = "Yleinen"; "settingsviews_sectiontitlehomescreen" = "Kotinäyttö"; "settingsviews_selectbgunit" = "Verensokeriyksikkö"; "settingsviews_urgentLowValue" = "Erittäin matala lukema"; diff --git a/xdrip/Storyboards/fr.lproj/SettingsViews.strings b/xdrip/Storyboards/fr.lproj/SettingsViews.strings index 9d3bbdf5..66f6c159 100644 --- a/xdrip/Storyboards/fr.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/fr.lproj/SettingsViews.strings @@ -1,5 +1,5 @@ "settingsviews_settingstitle" = "Réglages"; -"settingsviews_sectiontitlegeneral" = "Géneral"; +"settingsviews_sectiontitleNotifications" = "Géneral"; "settingsviews_selectbgunit" = "Unité glycémie"; "settingsviews_lowValue" = "Valeur faible"; "settingsviews_highValue" = "Valeur forte"; diff --git a/xdrip/Storyboards/it.lproj/SettingsViews.strings b/xdrip/Storyboards/it.lproj/SettingsViews.strings index f67b77f2..da9068ba 100644 --- a/xdrip/Storyboards/it.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/it.lproj/SettingsViews.strings @@ -157,7 +157,7 @@ "m5stack_settingsviews_textColor" = "Text Color"; /// general settings, section title -"settingsviews_sectiontitlegeneral" = "General"; +"settingsviews_sectiontitleNotifications" = "Notifications"; /// in Settings, row title to send settings "sendTraceFile" = "Send Issue Report"; diff --git a/xdrip/Storyboards/nl.lproj/SettingsViews.strings b/xdrip/Storyboards/nl.lproj/SettingsViews.strings index f804a3de..e847a455 100644 --- a/xdrip/Storyboards/nl.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/nl.lproj/SettingsViews.strings @@ -3,7 +3,7 @@ "settingsviews_transmitterid_text_for_button" = "Zender-ID"; "settingsviews_settingstitle" = "Instellingen"; -"settingsviews_sectiontitlegeneral" = "Algemeen"; +"settingsviews_sectiontitleNotifications" = "Algemeen"; "settingsviews_selectbgunit" = "Bloedglucose Eenheden"; "settingsviews_masterorfollower" = "Master of Volger"; "settingsviews_master" = "Master"; diff --git a/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings b/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings index f67b77f2..da9068ba 100644 --- a/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings @@ -157,7 +157,7 @@ "m5stack_settingsviews_textColor" = "Text Color"; /// general settings, section title -"settingsviews_sectiontitlegeneral" = "General"; +"settingsviews_sectiontitleNotifications" = "Notifications"; /// in Settings, row title to send settings "sendTraceFile" = "Send Issue Report"; diff --git a/xdrip/Storyboards/pt.lproj/SettingsViews.strings b/xdrip/Storyboards/pt.lproj/SettingsViews.strings index 807f3663..6a3ecfaf 100644 --- a/xdrip/Storyboards/pt.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/pt.lproj/SettingsViews.strings @@ -4,7 +4,7 @@ "settingsviews_translateOnlineHelp" = "Traduzir Automaticamente"; "settingsviews_showHelpIcon" = "Mostrar Ícone de Ajuda"; "settingsviews_restartNeeded" = "(É necessário reiniciar)"; -"settingsviews_sectiontitlegeneral" = "Geral"; +"settingsviews_sectiontitleNotifications" = "Geral"; "settingsviews_selectbgunit" = "Unidades de Glicose"; "settingsviews_masterorfollower" = "Mestre ou Seguidor"; "settingsviews_master" = "Mestre"; diff --git a/xdrip/Storyboards/ru.lproj/SettingsViews.strings b/xdrip/Storyboards/ru.lproj/SettingsViews.strings index d313ecf9..b791201a 100644 --- a/xdrip/Storyboards/ru.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/ru.lproj/SettingsViews.strings @@ -4,7 +4,7 @@ "settingsviews_translateOnlineHelp" = "Переводить автоматически"; "settingsviews_showHelpIcon" = "Показывать иконку помощи"; "settingsviews_restartNeeded" = "(Требуется перезагрузка)"; -"settingsviews_sectiontitlegeneral" = "Общие"; +"settingsviews_sectiontitleNotifications" = "Общие"; "settingsviews_selectbgunit" = "Единицы измерения глюкозы"; "settingsviews_masterorfollower" = "Главный или подписчик"; "settingsviews_master" = "Главный"; diff --git a/xdrip/Storyboards/sl.lproj/SettingsViews.strings b/xdrip/Storyboards/sl.lproj/SettingsViews.strings index fc31decf..adb867c6 100644 --- a/xdrip/Storyboards/sl.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/sl.lproj/SettingsViews.strings @@ -157,7 +157,7 @@ "m5stack_settingsviews_textColor" = "Text Color"; /// general settings, section title -"settingsviews_sectiontitlegeneral" = "General"; +"settingsviews_sectiontitleNotifications" = "Notifications"; /// in Settings, row title to send settings "sendTraceFile" = "Send Issue Report"; diff --git a/xdrip/Storyboards/sv.lproj/SettingsViews.strings b/xdrip/Storyboards/sv.lproj/SettingsViews.strings index 4a85eb93..f0782a84 100644 --- a/xdrip/Storyboards/sv.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/sv.lproj/SettingsViews.strings @@ -1,5 +1,5 @@ "settingsviews_settingstitle" = "Inställningar"; -"settingsviews_sectiontitlegeneral" = "Allmänt"; +"settingsviews_sectiontitleNotifications" = "Allmänt"; "settingsviews_selectbgunit" = "Blodglukosenheter"; "settingsviews_masterorfollower" = "Användare eller Följare"; "settingsviews_master" = "Användare"; diff --git a/xdrip/Storyboards/tr.lproj/SettingsViews.strings b/xdrip/Storyboards/tr.lproj/SettingsViews.strings index 7f0316cf..b7502a6a 100644 --- a/xdrip/Storyboards/tr.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/tr.lproj/SettingsViews.strings @@ -4,7 +4,7 @@ "settingsviews_translateOnlineHelp" = "Otomatik Olarak Çevir"; "settingsviews_showHelpIcon" = "Yardım Simgesini Göster"; "settingsviews_restartNeeded" = "(Yeniden başlatmak gerekli)"; -"settingsviews_sectiontitlegeneral" = "Ana ayarlar"; +"settingsviews_sectiontitleNotifications" = "Ana ayarlar"; "settingsviews_selectbgunit" = "Kan Şekeri Birimleri"; "settingsviews_masterorfollower" = "Sahip veya Takipci"; "settingsviews_master" = "Sahip"; diff --git a/xdrip/Storyboards/uk.lproj/SettingsViews.strings b/xdrip/Storyboards/uk.lproj/SettingsViews.strings index cf105e3b..0797a028 100644 --- a/xdrip/Storyboards/uk.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/uk.lproj/SettingsViews.strings @@ -334,7 +334,7 @@ "appleWatchSectionTitle" = "Apple Watch"; /// general settings, section title -"settingsviews_sectiontitlegeneral" = "General"; +"settingsviews_sectiontitleNotifications" = "Notifications"; /// dexcom share settings, pop up that asks user to enter dexcom share account name "settingsviews_giveDexcomShareAccountName" = "Enter Dexcom Share Account Name"; diff --git a/xdrip/Storyboards/zh.lproj/SettingsViews.strings b/xdrip/Storyboards/zh.lproj/SettingsViews.strings index 87fadebe..0c978615 100644 --- a/xdrip/Storyboards/zh.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/zh.lproj/SettingsViews.strings @@ -1,5 +1,5 @@ "settingsviews_settingstitle" = "设置"; -"settingsviews_sectiontitlegeneral" = "常规"; +"settingsviews_sectiontitleNotifications" = "常规"; "settingsviews_selectbgunit" = "选择单位"; "settingsviews_lowValue" = "血糖低值"; "settingsviews_highValue" = "血糖高值"; diff --git a/xdrip/Texts/TextsSettingsView.swift b/xdrip/Texts/TextsSettingsView.swift index 0a12a7d2..fa6e7635 100644 --- a/xdrip/Texts/TextsSettingsView.swift +++ b/xdrip/Texts/TextsSettingsView.swift @@ -38,6 +38,46 @@ class Texts_SettingsView { return NSLocalizedString("settingsviews_restartNeeded", tableName: filename, bundle: Bundle.main, value: "(Restart required)", comment: "help settings, restart needed") }() + + // MARK: - Notifications + + static let labelLiveActivityType: String = { + return NSLocalizedString("settingsviews_labelLiveActivityType", tableName: filename, bundle: Bundle.main, value: "Live Activities", comment: "notification settings, type of live activities that should be enabled") + }() + + static let liveActivityTypeDisabled: String = { + return NSLocalizedString("settingsviews_liveActivityTypeDisabled", tableName: filename, bundle: Bundle.main, value: "Disabled", comment: "notification settings, disable live activities") + }() + + static let liveActivityTypeAlways: String = { + return NSLocalizedString("settingsviews_liveActivityTypeAlways", tableName: filename, bundle: Bundle.main, value: "Always", comment: "notification settings, always show live activities") + }() + + static let liveActivityTypeUrgentLow: String = { + return NSLocalizedString("settingsviews_liveActivityTypeUrgentLow", tableName: filename, bundle: Bundle.main, value: "Only Urgent Low", comment: "notification settings, show live activities urgent low") + }() + + static let liveActivityTypeLow: String = { + return NSLocalizedString("settingsviews_liveActivityTypeLow", tableName: filename, bundle: Bundle.main, value: "Only when Low", comment: "notification settings, show live activities when low") + }() + + static let liveActivityTypeLowHigh: String = { + return NSLocalizedString("settingsviews_liveActivityTypeLowHigh", tableName: filename, bundle: Bundle.main, value: "When Low or High", comment: "notification settings, show live activities when low or high") + }() + + static let liveActivityTypeUrgentLowHigh: String = { + return NSLocalizedString("settingsviews_liveActivityTypeUrgentLowHigh", tableName: filename, bundle: Bundle.main, value: "Only Urgent Low/High", comment: "notification settings, show live activities when urgent high or urgent low") + }() + + static let liveActivityDisabledInFollowerMode: String = { + return NSLocalizedString("settingsviews_liveActivityDisabledInFollowerMode", tableName: filename, bundle: Bundle.main, value: "Disabled in follower mode", comment: "notification settings, live activities are not available in follower mode") + }() + + static let liveActivityDisabledInFollowerModeMessage: String = { + return NSLocalizedString("settingsviews_liveActivityDisabledInFollowerModeMessage", tableName: filename, bundle: Bundle.main, value: "\nLive activities can only be used in Master mode.\n\nThey are disabled for Follower mode.", comment: "notification settings, live activities are not available in follower mode") + }() + + // MARK: - Section Data Source static let sectionTitleDataSource: String = { @@ -120,10 +160,10 @@ class Texts_SettingsView { return NSLocalizedString("settingsviews_libreLinkUpNoActiveSensor", tableName: filename, bundle: Bundle.main, value: "No active sensor", comment: "libre link up follower settings, no active sensor") }() - // MARK: - Section General + // MARK: - Section Notifications - static let sectionTitleGeneral: String = { - return NSLocalizedString("settingsviews_sectiontitlegeneral", tableName: filename, bundle: Bundle.main, value: "General", comment: "general settings, section title") + static let sectionTitleNotifications: String = { + return NSLocalizedString("settingsviews_sectiontitleNotifications", tableName: filename, bundle: Bundle.main, value: "Notifications", comment: "general settings, section title") }() static let labelSelectBgUnit:String = { diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index 1ac0bf77..5934a7fe 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -55,17 +55,24 @@ final class RootViewController: UIViewController, ObservableObject { } + @IBOutlet weak var liveActivityToolbarButtonOutlet: UIBarButtonItem! + + @IBAction func liveActivityToolbarButtonAction(_ sender: UIBarButtonItem) { + + updateLiveActivity() + + } + @IBOutlet weak var helpToolbarButtonOutlet: UIBarButtonItem! @IBAction func helpToolbarButtonAction(_ sender: UIBarButtonItem) { // get the 2 character language code for the App Locale (i.e. "en", "es", "nl", "fr") - let languageCode = NSLocale.current.languageCode - + // get the 2 character language code for the App Locale (i.e. "en", "es", "nl", "fr") // if the user has the app in a language other than English and they have the "auto translate" option selected, then load the help pages through Google Translate // important to check the the URLs actually exist in ConstansHomeView before trying to open them - if let languageCode = languageCode, languageCode != ConstantsHomeView.onlineHelpBaseLocale && UserDefaults.standard.translateOnlineHelp { + if let languageCode = NSLocale.current.language.languageCode?.identifier, languageCode != ConstantsHomeView.onlineHelpBaseLocale && UserDefaults.standard.translateOnlineHelp { guard let url = URL(string: ConstantsHomeView.onlineHelpURLTranslated1 + languageCode + ConstantsHomeView.onlineHelpURLTranslated2) else { return } @@ -839,6 +846,8 @@ final class RootViewController: UIViewController, ObservableObject { // launch Nightscout sync UserDefaults.standard.nightScoutSyncTreatmentsRequired = true + self.updateLiveActivity() + }) // Setup View @@ -868,7 +877,8 @@ final class RootViewController: UIViewController, ObservableObject { UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.bloodGlucoseUnitIsMgDl.rawValue, options: .new, context: nil) // update show clock value for the screen lock function UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.showClockWhenScreenIsLocked.rawValue, options: .new, context: nil) - + // if live action type is updated + UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.liveActivityType.rawValue, options: .new, context: nil) // high mark , low mark , urgent high mark, urgent low mark. change requires redraw of chart UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.urgentLowMarkValue.rawValue, options: .new, context: nil) @@ -946,8 +956,15 @@ final class RootViewController: UIViewController, ObservableObject { // add tracing when app comes to foreground ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground(key: applicationManagerKeyTraceAppGoesToForeground, closure: {trace("Application will enter foreground", log: self.log, category: ConstantsLog.categoryRootView, type: .info)}) - // add tracing when app will terminaten - this only works for non-suspended apps, probably (not tested) also works for apps that crash in the background - ApplicationManager.shared.addClosureToRunWhenAppWillTerminate(key: applicationManagerKeyTraceAppWillTerminate, closure: {trace("Application will terminate", log: self.log, category: ConstantsLog.categoryRootView, type: .info)}) + // add tracing when app will terminate - this only works for non-suspended apps, probably (not tested) also works for apps that crash in the background + ApplicationManager.shared.addClosureToRunWhenAppWillTerminate(key: applicationManagerKeyTraceAppWillTerminate, closure: { + + // force the live activity to end if it exists to prevent it becoming "orphaned" and unclosable by the app + LiveActivityManager.shared.endAllActivities() + + trace("Application will terminate - it has probably been force-closed by the user", log: self.log, category: ConstantsLog.categoryRootView, type: .info) + + }) ApplicationManager.shared.addClosureToRunWhenAppDidEnterBackground(key: applicationManagerKeyCleanMemoryGlucoseChartManager, closure: { @@ -973,6 +990,10 @@ final class RootViewController: UIViewController, ObservableObject { // update statistics related outlets self.updateStatistics(animate: true) + // check and see if we need to restart the live activity in case the user dismissed it from the lock screen + // the app cannot restart the activity from the background so let's check it now + self.updateLiveActivity() + } }) @@ -1450,6 +1471,8 @@ final class RootViewController: UIViewController, ObservableObject { } updateWatchApp() + + updateLiveActivity() } } @@ -1519,12 +1542,15 @@ final class RootViewController: UIViewController, ObservableObject { switch keyPathEnum { - case UserDefaults.Key.isMaster : + case UserDefaults.Key.isMaster: changeButtonsStatusTo(enabled: UserDefaults.standard.isMaster) guard let cgmTransmitter = self.bluetoothPeripheralManager?.getCGMTransmitter() else {break} + // need to check this in order to disable live activities in follower mode + updateLiveActivity() + // no sensor needed in follower mode, stop it stopSensor(cGMTransmitter: cgmTransmitter, sendToTransmitter: false) @@ -1563,6 +1589,13 @@ final class RootViewController: UIViewController, ObservableObject { // this will trigger update of app badge, will also create notification, but as app is most likely in foreground, this won't show up createBgReadingNotificationAndSetAppBadge(overrideShowReadingInNotification: true) + updateLiveActivity() + + case UserDefaults.Key.liveActivityType: + + // check and configure the live activity if applicable + updateLiveActivity() + case UserDefaults.Key.urgentLowMarkValue, UserDefaults.Key.lowMarkValue, UserDefaults.Key.highMarkValue, UserDefaults.Key.urgentHighMarkValue, UserDefaults.Key.nightScoutTreatmentsUpdateCounter: // redraw chart is necessary @@ -1574,6 +1607,8 @@ final class RootViewController: UIViewController, ObservableObject { // update Watch App with the new objective values updateWatchApp() + updateLiveActivity() + case UserDefaults.Key.offsetCarbTreatmentsOnChart: // redraw chart is necessary @@ -2163,6 +2198,7 @@ final class RootViewController: UIViewController, ObservableObject { /// - forceReset : if true, then force the update to be done even if the main chart is panned back in time (used for the double tap gesture) @objc private func updateLabelsAndChart(overrideApplicationState: Bool = false, forceReset: Bool = false) { + // TODO: Still crashing here... DispatchQueue.main.async { UserDefaults.standard.nightScoutSyncTreatmentsRequired = true } @@ -2848,7 +2884,7 @@ final class RootViewController: UIViewController, ObservableObject { if nightMode { - screenLockToolbarButtonOutlet.image = UIImage(systemName: "lock.fill") + screenLockToolbarButtonOutlet.image = UIImage(systemName: "lock.circle.fill") // set the value label font size to big valueLabelOutlet.font = ConstantsUI.valueLabelFontSizeScreenLock @@ -2919,7 +2955,7 @@ final class RootViewController: UIViewController, ObservableObject { screenLockToolbarButtonOutlet.tintColor = nil // set the lock icon back to the standard SF Symbol - screenLockToolbarButtonOutlet.image = UIImage(systemName: "lock") + screenLockToolbarButtonOutlet.image = UIImage(systemName: "lock.circle") valueLabelOutlet.font = ConstantsUI.valueLabelFontSizeNormal @@ -3456,6 +3492,76 @@ final class RootViewController: UIViewController, ObservableObject { } + + /// check if the conditions are correct to start a live activity, update it, or end it + private func updateLiveActivity() { + + // check the live activity type requested by the user + let liveActivityType = UserDefaults.standard.liveActivityType + + // TODO: remove the comments below for production + // if the user is in follower mode then don't show live activities as they will not get updated in the background + // also take advantage to just skip the rest of the function if they have live activities disabled + var showLiveActivity: Bool = true //!(!UserDefaults.standard.isMaster || UserDefaults.standard.liveActivityType == .disabled) + + if let bgReadingsAccessor = bgReadingsAccessor, showLiveActivity { + + // get 2 last Readings, with a calculatedValue + let lastReading = bgReadingsAccessor.get2LatestBgReadings(minimumTimeIntervalInMinutes: 0) + + // there should be at least one reading + guard lastReading.count > 0 else { + print("no recent BG readings returned") + LiveActivityManager.shared.endAllActivities() + return + } + + let bgValueInMgDl = lastReading[0].calculatedValue + + // now that we've got the current BG value, let's refine the check to see if we should run/show the live activity + switch liveActivityType { + case .always: + showLiveActivity = true + case .disabled: + showLiveActivity = false + case .low: + showLiveActivity = (bgValueInMgDl <= UserDefaults.standard.lowMarkValue) ? true : false + case .urgentLow: + showLiveActivity = (bgValueInMgDl <= UserDefaults.standard.urgentLowMarkValue) ? true : false + case .lowHigh: + showLiveActivity = ((bgValueInMgDl <= UserDefaults.standard.lowMarkValue) || (bgValueInMgDl >= UserDefaults.standard.highMarkValue)) ? true : false + case .urgentLowHigh: + showLiveActivity = ((bgValueInMgDl <= UserDefaults.standard.urgentLowMarkValue) || (bgValueInMgDl >= UserDefaults.standard.urgentHighMarkValue)) ? true : false + } + + // if we should still show it, then let's continue processing the lastReading array to create a valid contentState + if showLiveActivity { + let bgReadingDate = lastReading[0].timeStamp + let slopeOrdinal: Int = lastReading[0].slopeOrdinal() //? "" : lastReading[0].slopeArrow() + + var deltaChangeInMgDl: Double? + + // add delta if needed + if lastReading.count > 1 { + + deltaChangeInMgDl = lastReading[0].currentSlope(previousBgReading: lastReading[1]) * lastReading[0].timeStamp.timeIntervalSince(lastReading[1].timeStamp) * 1000; + } + + // create the contentState that will update the dynamic attributes of the Live Activity Widget + let contentState = XDripWidgetAttributes.ContentState(bgValueInMgDl: bgValueInMgDl, isMgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl, slopeOrdinal: slopeOrdinal, deltaChangeInMgDl: deltaChangeInMgDl, urgentLowLimitInMgDl: UserDefaults.standard.urgentLowMarkValue, lowLimitInMgDl: UserDefaults.standard.lowMarkValue, highLimitInMgDl: UserDefaults.standard.highMarkValue, urgentHighLimitInMgDl: UserDefaults.standard.urgentHighMarkValue, bgReadingDate: bgReadingDate, updatedDate: Date()) + + LiveActivityManager.shared.runActivity(contentState: contentState) + + } + } + + // try to end the activity if needed + if !showLiveActivity { + LiveActivityManager.shared.endAllActivities() + } + + } + } @@ -3781,6 +3887,8 @@ extension RootViewController: FollowerDelegate { updateWatchApp() + updateLiveActivity() + } } } diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/M5StackSettingsViewController/M5StackSettingsViewModels/SettingsViewM5StackGeneralSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/M5StackSettingsViewController/M5StackSettingsViewModels/SettingsViewM5StackGeneralSettingsViewModel.swift index f1039b2c..40bd385b 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/M5StackSettingsViewController/M5StackSettingsViewModels/SettingsViewM5StackGeneralSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/M5StackSettingsViewController/M5StackSettingsViewModels/SettingsViewM5StackGeneralSettingsViewModel.swift @@ -18,7 +18,7 @@ struct SettingsViewM5StackGeneralSettingsViewModel: SettingsViewModelProtocol { } func sectionTitle() -> String? { - return Texts_SettingsView.sectionTitleGeneral + return Texts_BgReadings.generalSectionHeader } func settingsRowText(index: Int) -> String { diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift index e7e97caf..7d6c24df 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift @@ -85,7 +85,7 @@ final class SettingsViewController: UIViewController { case .dataSource: return SettingsViewDataSourceSettingsViewModel(coreDataManager: coreDataManager) case .general: - return SettingsViewGeneralSettingsViewModel() + return SettingsViewNotificationsSettingsViewModel() case .homescreen: return SettingsViewHomeScreenSettingsViewModel() case .treatments: diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDataSourceSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDataSourceSettingsViewModel.swift index f77fe668..13cb16da 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDataSourceSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDataSourceSettingsViewModel.swift @@ -11,35 +11,38 @@ import os fileprivate enum Setting: Int, CaseIterable { + /// blood glucose unit + case bloodGlucoseUnit = 0 + /// choose between master and follower - case masterFollower = 0 + case masterFollower = 1 /// if follower, what should be the data source - case followerDataSourceType = 1 + case followerDataSourceType = 2 /// if follower, should we try and keep the app alive in the background - case followerKeepAliveType = 2 + case followerKeepAliveType = 3 /// patient name/alias (optional) - useful for users who follow various people - case followerPatientName = 3 + case followerPatientName = 4 /// if follower data source is not Nightscout, should we upload the BG values to Nightscout? - case followerUploadDataToNightscout = 4 + case followerUploadDataToNightscout = 5 /// web follower username - case followerUserName = 5 + case followerUserName = 6 /// web follower username - case followerPassword = 6 + case followerPassword = 7 /// web follower sensor serial number (will not always be available) - case followerSensorSerialNumber = 7 + case followerSensorSerialNumber = 8 /// web follower sensor start date (will not always be available) - case followerSensorStartDate = 8 + case followerSensorStartDate = 9 /// web follower server region - case followerRegion = 9 + case followerRegion = 10 } @@ -92,7 +95,8 @@ class SettingsViewDataSourceSettingsViewModel: NSObject, SettingsViewModelProtoc func completeSettingsViewRefreshNeeded(index: Int) -> Bool { // changing follower to master or master to follower requires changing ui for nightscout settings and transmitter type settings - if (index == Setting.masterFollower.rawValue || index == Setting.followerDataSourceType.rawValue) {return true} + // the same applies when changing bloodGlucoseUnit, because off the seperate section with bgObjectives + if (index == Setting.bloodGlucoseUnit.rawValue || index == Setting.masterFollower.rawValue || index == Setting.followerDataSourceType.rawValue) {return true} return false } @@ -105,6 +109,13 @@ class SettingsViewDataSourceSettingsViewModel: NSObject, SettingsViewModelProtoc guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } switch setting { + + case .bloodGlucoseUnit: + return SettingsSelectedRowAction.callFunction(function: { + + UserDefaults.standard.bloodGlucoseUnitIsMgDl ? (UserDefaults.standard.bloodGlucoseUnitIsMgDl = false) : (UserDefaults.standard.bloodGlucoseUnitIsMgDl = true) + + }) case .masterFollower: @@ -341,7 +352,7 @@ class SettingsViewDataSourceSettingsViewModel: NSObject, SettingsViewModelProtoc // if master is selected then just show this row and hide the rest if UserDefaults.standard.isMaster { - return 1 + return 2 } else { @@ -351,7 +362,7 @@ class SettingsViewDataSourceSettingsViewModel: NSObject, SettingsViewModelProtoc case .nightscout: // no need to show any extra rows/settings (beyond patient name) as all Nightscout required parameters are set in the Nightscout section - return 4 + return 5 case .libreLinkUp: // show all sections @@ -368,6 +379,9 @@ class SettingsViewDataSourceSettingsViewModel: NSObject, SettingsViewModelProtoc switch setting { + case .bloodGlucoseUnit: + return Texts_SettingsView.labelSelectBgUnit + case .masterFollower: return Texts_SettingsView.labelMasterOrFollower @@ -407,7 +421,7 @@ class SettingsViewDataSourceSettingsViewModel: NSObject, SettingsViewModelProtoc switch setting { - case .masterFollower, .followerUploadDataToNightscout, .followerSensorSerialNumber, .followerRegion: + case .bloodGlucoseUnit, .masterFollower, .followerUploadDataToNightscout, .followerSensorSerialNumber, .followerRegion: return UITableViewCell.AccessoryType.none case .followerDataSourceType, .followerKeepAliveType, .followerPatientName, .followerUserName, .followerPassword: @@ -425,6 +439,9 @@ class SettingsViewDataSourceSettingsViewModel: NSObject, SettingsViewModelProtoc switch setting { + case .bloodGlucoseUnit: + return UserDefaults.standard.bloodGlucoseUnitIsMgDl ? Texts_Common.mgdl:Texts_Common.mmol + case .masterFollower: return UserDefaults.standard.isMaster ? Texts_SettingsView.master : Texts_SettingsView.follower @@ -546,7 +563,7 @@ class SettingsViewDataSourceSettingsViewModel: NSObject, SettingsViewModelProtoc case .followerUploadDataToNightscout: return UserDefaults.standard.nightScoutEnabled ? UISwitch(isOn: UserDefaults.standard.followerUploadDataToNightscout, action: {(isOn:Bool) in UserDefaults.standard.followerUploadDataToNightscout = isOn}) : nil - case .masterFollower, .followerDataSourceType, .followerKeepAliveType, .followerPatientName, .followerUserName, .followerPassword, .followerSensorSerialNumber, .followerSensorStartDate, .followerRegion: + case .bloodGlucoseUnit, .masterFollower, .followerDataSourceType, .followerKeepAliveType, .followerPatientName, .followerUserName, .followerPassword, .followerSensorSerialNumber, .followerSensorStartDate, .followerRegion: return nil } diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHelpSettingModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHelpSettingModel.swift index 85405747..1e851bb5 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHelpSettingModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHelpSettingModel.swift @@ -50,13 +50,11 @@ struct SettingsViewHelpSettingsViewModel:SettingsViewModelProtocol { case .showOnlineHelp: // get the 2 character language code for the App Locale (i.e. "en", "es", "nl", "fr") - let languageCode = NSLocale.current.languageCode - // if the user has the app in a language other than English and they have the "auto translate" option selected, then load the help pages through Google Translate // important to check the the URLs actually exist in ConstansHomeView before trying to open them - if languageCode != ConstantsHomeView.onlineHelpBaseLocale && UserDefaults.standard.translateOnlineHelp { + if let languageCode = NSLocale.current.language.languageCode?.identifier, languageCode != ConstantsHomeView.onlineHelpBaseLocale && UserDefaults.standard.translateOnlineHelp { - guard let url = URL(string: ConstantsHomeView.onlineHelpURLTranslated1 + languageCode! + ConstantsHomeView.onlineHelpURLTranslated2) else { return .nothing} + guard let url = URL(string: ConstantsHomeView.onlineHelpURLTranslated1 + languageCode + ConstantsHomeView.onlineHelpURLTranslated2) else { return .nothing } UIApplication.shared.open(url) diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewGeneralSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewNotificationsSettingsViewModel.swift similarity index 59% rename from xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewGeneralSettingsViewModel.swift rename to xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewNotificationsSettingsViewModel.swift index 53c47793..31199a59 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewGeneralSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewNotificationsSettingsViewModel.swift @@ -1,16 +1,18 @@ import UIKit +import OSLog +import ActivityKit fileprivate enum Setting:Int, CaseIterable { - /// blood glucose unit - case bloodGlucoseUnit = 0 - /// should reading be shown in notification - case showReadingInNotification = 1 + case showReadingInNotification = 0 /// - minimum time between two readings, for which notification should be created (in minutes) /// - except if there's been a disconnect, in that case this value is not taken into account - case notificationInterval = 2 + case notificationInterval = 1 + + /// show live activities + case liveActivityType = 2 /// show reading in app badge case showReadingInAppBadge = 3 @@ -21,7 +23,10 @@ fileprivate enum Setting:Int, CaseIterable { } /// conforms to SettingsViewModelProtocol for all general settings in the first sections screen -class SettingsViewGeneralSettingsViewModel: SettingsViewModelProtocol { +class SettingsViewNotificationsSettingsViewModel: SettingsViewModelProtocol { + + /// for trace + private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categorySettingsViewDataSourceSettingsViewModel) func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {} @@ -33,10 +38,6 @@ class SettingsViewGeneralSettingsViewModel: SettingsViewModelProtocol { func completeSettingsViewRefreshNeeded(index: Int) -> Bool { - // changing follower to master or master to follower requires changing ui for nightscout settings and transmitter type settings - // the same applies when changing bloodGlucoseUnit, because off the seperate section with bgObjectives - if (index == Setting.bloodGlucoseUnit.rawValue) {return true} - return false } @@ -49,25 +50,68 @@ class SettingsViewGeneralSettingsViewModel: SettingsViewModelProtocol { switch setting { - case .bloodGlucoseUnit: - return SettingsSelectedRowAction.callFunction(function: { - - UserDefaults.standard.bloodGlucoseUnitIsMgDl ? (UserDefaults.standard.bloodGlucoseUnitIsMgDl = false) : (UserDefaults.standard.bloodGlucoseUnitIsMgDl = true) - - }) - case .showReadingInNotification, .showReadingInAppBadge, .multipleAppBadgeValueWith10: return SettingsSelectedRowAction.nothing case .notificationInterval: return SettingsSelectedRowAction.askText(title: Texts_SettingsView.settingsviews_IntervalTitle, message: Texts_SettingsView.settingsviews_IntervalMessage, keyboardType: .numberPad, text: UserDefaults.standard.notificationInterval.description, placeHolder: "0", actionTitle: nil, cancelTitle: nil, actionHandler: {(interval:String) in if let interval = Int(interval) {UserDefaults.standard.notificationInterval = Int(interval)}}, cancelHandler: nil, inputValidator: nil) + + case .liveActivityType: + // live activities can only be used in master mode as follower mode + // will not allow updates whilst the app is in the background + if UserDefaults.standard.isMaster { + + // data to be displayed in list from which user needs to pick a live activity type + var data = [String]() + + var selectedRow: Int? + + var index = 0 + + let currentLiveActivityType = UserDefaults.standard.liveActivityType + + // get all data source types and add the description to data. Search for the type that matches the FollowerDataSourceType that is currently stored in userdefaults. + for liveActivityType in LiveActivityType.allCases { + + data.append(liveActivityType.description) + + if liveActivityType == currentLiveActivityType { + selectedRow = index + } + + index += 1 + + } + + return SettingsSelectedRowAction.selectFromList(title: Texts_SettingsView.labelLiveActivityType, data: data, selectedRow: selectedRow, actionTitle: nil, cancelTitle: nil, actionHandler: {(index:Int) in + + // we'll set this here so that we can use it in the else statement for logging + let oldLiveActivityType = UserDefaults.standard.liveActivityType + + if index != selectedRow { + + UserDefaults.standard.liveActivityType = LiveActivityType(rawValue: index) ?? .disabled + + let newLiveActivityType = UserDefaults.standard.liveActivityType + + trace("Live activity type was changed from '%{public}@' to '%{public}@'", log: self.log, category: ConstantsLog.categorySettingsViewNotificationsSettingsViewModel, type: .info, oldLiveActivityType.description, newLiveActivityType.description) + + } + + }, cancelHandler: nil, didSelectRowHandler: nil) + + } else { + + return .showInfoText(title: Texts_SettingsView.labelLiveActivityType, message: Texts_SettingsView.liveActivityDisabledInFollowerModeMessage, actionHandler: {}) + + } } } func sectionTitle() -> String? { - return Texts_SettingsView.sectionTitleGeneral + return Texts_SettingsView.sectionTitleNotifications } func numberOfRows() -> Int { @@ -87,15 +131,15 @@ class SettingsViewGeneralSettingsViewModel: SettingsViewModelProtocol { switch setting { - case .bloodGlucoseUnit: - return Texts_SettingsView.labelSelectBgUnit - case .showReadingInNotification: return Texts_SettingsView.showReadingInNotification case .notificationInterval: return Texts_SettingsView.settingsviews_IntervalTitle + case .liveActivityType: + return Texts_SettingsView.labelLiveActivityType + case .showReadingInAppBadge: return Texts_SettingsView.labelShowReadingInAppBadge @@ -110,14 +154,14 @@ class SettingsViewGeneralSettingsViewModel: SettingsViewModelProtocol { switch setting { - case .bloodGlucoseUnit: - return UITableViewCell.AccessoryType.none - case .showReadingInNotification, .showReadingInAppBadge, .multipleAppBadgeValueWith10: - return UITableViewCell.AccessoryType.none + return .none case .notificationInterval: - return UITableViewCell.AccessoryType.disclosureIndicator + return .disclosureIndicator + + case .liveActivityType: + return UserDefaults.standard.isMaster ? .disclosureIndicator : .none } } @@ -127,14 +171,15 @@ class SettingsViewGeneralSettingsViewModel: SettingsViewModelProtocol { switch setting { - case .bloodGlucoseUnit: - return UserDefaults.standard.bloodGlucoseUnitIsMgDl ? Texts_Common.mgdl:Texts_Common.mmol - case .showReadingInNotification, .showReadingInAppBadge, .multipleAppBadgeValueWith10: return nil case .notificationInterval: return UserDefaults.standard.notificationInterval.description + + case .liveActivityType: + return UserDefaults.standard.isMaster ? UserDefaults.standard.liveActivityType.description : Texts_SettingsView.liveActivityDisabledInFollowerMode + } } @@ -156,10 +201,7 @@ class SettingsViewGeneralSettingsViewModel: SettingsViewModelProtocol { return UISwitch(isOn: UserDefaults.standard.multipleAppBadgeValueWith10, action: {(isOn:Bool) in UserDefaults.standard.multipleAppBadgeValueWith10 = isOn}) - case .bloodGlucoseUnit: - return nil - - case .notificationInterval: + case .notificationInterval, .liveActivityType: return nil } From 55ca10a95053c26225c4c31b9d7ac9c91eb1aab1 Mon Sep 17 00:00:00 2001 From: Paul Plant Date: Wed, 3 Jan 2024 17:03:24 +0100 Subject: [PATCH 06/89] revert deployment target back down to iOS15 and add availability checks also added fallback for live activity settings ui to show as disabled when iOS16.2 is not available --- xDripWidget/XDripWidgetBundle.swift | 1 + xDripWidget/XDripWidgetLiveActivity.swift | 4 +- xdrip.xcodeproj/project.pbxproj | 8 +- .../LiveActivity/LiveActivityManager.swift | 4 +- .../RootViewController.swift | 174 ++++++++++-------- .../SettingsViewHelpSettingModel.swift | 42 +++-- ...gsViewNotificationsSettingsViewModel.swift | 99 +++++----- 7 files changed, 195 insertions(+), 137 deletions(-) diff --git a/xDripWidget/XDripWidgetBundle.swift b/xDripWidget/XDripWidgetBundle.swift index 867230f2..966a0210 100644 --- a/xDripWidget/XDripWidgetBundle.swift +++ b/xDripWidget/XDripWidgetBundle.swift @@ -9,6 +9,7 @@ import WidgetKit import SwiftUI +@available(iOSApplicationExtension 16.2, *) @main struct XDripWidgetBundle: WidgetBundle { var body: some Widget { diff --git a/xDripWidget/XDripWidgetLiveActivity.swift b/xDripWidget/XDripWidgetLiveActivity.swift index bf0596f4..d8a5f0a2 100644 --- a/xDripWidget/XDripWidgetLiveActivity.swift +++ b/xDripWidget/XDripWidgetLiveActivity.swift @@ -10,6 +10,7 @@ import ActivityKit import WidgetKit import SwiftUI +@available(iOSApplicationExtension 16.2, *) struct XDripWidgetLiveActivity: Widget { var body: some WidgetConfiguration { @@ -75,6 +76,7 @@ struct XDripWidgetLiveActivity: Widget { } +@available(iOSApplicationExtension 16.2, *) struct LiveActivityView: View { let state: XDripWidgetAttributes.ContentState @@ -116,7 +118,7 @@ struct LiveActivityView: View { } } -//@available(iOS 16.2, *) +@available(iOS 16.2, *) struct XDripWidgetLiveActivity_Previews: PreviewProvider { static let attributes = XDripWidgetAttributes(eventStartDate: Date().addingTimeInterval(-1000)) static let contentState = XDripWidgetAttributes.ContentState(bgValueInMgDl: 252, isMgDl: true, slopeOrdinal:5, deltaChangeInMgDl: -2, urgentLowLimitInMgDl: 70, lowLimitInMgDl: 80, highLimitInMgDl: 140, urgentHighLimitInMgDl: 180, bgReadingDate: Date().addingTimeInterval(-180), updatedDate: Date()) diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 64fc01c9..81f3e3d9 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -4686,7 +4686,7 @@ INFOPLIST_FILE = xDripWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = xDripWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Johan Degraeve. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4720,7 +4720,7 @@ INFOPLIST_FILE = xDripWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = xDripWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Johan Degraeve. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4879,7 +4879,7 @@ CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; DEVELOPMENT_TEAM = "$(XDRIP_DEVELOPMENT_TEAM)"; INFOPLIST_FILE = "$(SRCROOT)/xdrip/Supporting Files/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4908,7 +4908,7 @@ CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; DEVELOPMENT_TEAM = "$(XDRIP_DEVELOPMENT_TEAM)"; INFOPLIST_FILE = "$(SRCROOT)/xdrip/Supporting Files/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/xdrip/Managers/LiveActivity/LiveActivityManager.swift b/xdrip/Managers/LiveActivity/LiveActivityManager.swift index f7988edd..3da23aaa 100644 --- a/xdrip/Managers/LiveActivity/LiveActivityManager.swift +++ b/xdrip/Managers/LiveActivity/LiveActivityManager.swift @@ -10,7 +10,7 @@ import Foundation import ActivityKit import OSLog -//@available(iOS 16.2, *) +@available(iOS 16.2, *) public final class LiveActivityManager { /// for trace @@ -27,7 +27,7 @@ public final class LiveActivityManager { } // MARK: - Helpers -//@available(iOS 16.2, *) +@available(iOS 16.2, *) extension LiveActivityManager { /// start or update the live activity based upon whether it currently exists or not diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index 5934a7fe..cd13319d 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -72,23 +72,45 @@ final class RootViewController: UIViewController, ObservableObject { // get the 2 character language code for the App Locale (i.e. "en", "es", "nl", "fr") // if the user has the app in a language other than English and they have the "auto translate" option selected, then load the help pages through Google Translate // important to check the the URLs actually exist in ConstansHomeView before trying to open them - if let languageCode = NSLocale.current.language.languageCode?.identifier, languageCode != ConstantsHomeView.onlineHelpBaseLocale && UserDefaults.standard.translateOnlineHelp { - - guard let url = URL(string: ConstantsHomeView.onlineHelpURLTranslated1 + languageCode + ConstantsHomeView.onlineHelpURLTranslated2) else { return } - - UIApplication.shared.open(url) - + if #available(iOS 16, *) { + if let languageCode = NSLocale.current.language.languageCode?.identifier, languageCode != ConstantsHomeView.onlineHelpBaseLocale && UserDefaults.standard.translateOnlineHelp { + + guard let url = URL(string: ConstantsHomeView.onlineHelpURLTranslated1 + languageCode + ConstantsHomeView.onlineHelpURLTranslated2) else { return } + + UIApplication.shared.open(url) + + } else { + + // so the user is running the app in English + // or + // NSLocale.current.languageCode returned a nil value + // or + // they don't want to translate so let's just load it directly + guard let url = URL(string: ConstantsHomeView.onlineHelpURL) else { return } + + UIApplication.shared.open(url) + + } } else { - - // so the user is running the app in English - // or - // NSLocale.current.languageCode returned a nil value - // or - // they don't want to translate so let's just load it directly - guard let url = URL(string: ConstantsHomeView.onlineHelpURL) else { return } - - UIApplication.shared.open(url) - + // Fallback on earlier versions + if let languageCode = NSLocale.current.languageCode, languageCode != ConstantsHomeView.onlineHelpBaseLocale && UserDefaults.standard.translateOnlineHelp { + + guard let url = URL(string: ConstantsHomeView.onlineHelpURLTranslated1 + languageCode + ConstantsHomeView.onlineHelpURLTranslated2) else { return } + + UIApplication.shared.open(url) + + } else { + + // so the user is running the app in English + // or + // NSLocale.current.languageCode returned a nil value + // or + // they don't want to translate so let's just load it directly + guard let url = URL(string: ConstantsHomeView.onlineHelpURL) else { return } + + UIApplication.shared.open(url) + + } } } @@ -959,8 +981,10 @@ final class RootViewController: UIViewController, ObservableObject { // add tracing when app will terminate - this only works for non-suspended apps, probably (not tested) also works for apps that crash in the background ApplicationManager.shared.addClosureToRunWhenAppWillTerminate(key: applicationManagerKeyTraceAppWillTerminate, closure: { - // force the live activity to end if it exists to prevent it becoming "orphaned" and unclosable by the app - LiveActivityManager.shared.endAllActivities() + if #available(iOS 16.2, *) { + // force the live activity to end if it exists to prevent it becoming "orphaned" and unclosable by the app + LiveActivityManager.shared.endAllActivities() + } trace("Application will terminate - it has probably been force-closed by the user", log: self.log, category: ConstantsLog.categoryRootView, type: .info) @@ -3496,70 +3520,72 @@ final class RootViewController: UIViewController, ObservableObject { /// check if the conditions are correct to start a live activity, update it, or end it private func updateLiveActivity() { - // check the live activity type requested by the user - let liveActivityType = UserDefaults.standard.liveActivityType - - // TODO: remove the comments below for production - // if the user is in follower mode then don't show live activities as they will not get updated in the background - // also take advantage to just skip the rest of the function if they have live activities disabled - var showLiveActivity: Bool = true //!(!UserDefaults.standard.isMaster || UserDefaults.standard.liveActivityType == .disabled) + if #available(iOS 16.2, *) { + // check the live activity type requested by the user + let liveActivityType = UserDefaults.standard.liveActivityType + + // TODO: remove the comments below for production + // if the user is in follower mode then don't show live activities as they will not get updated in the background + // also take advantage to just skip the rest of the function if they have live activities disabled + var showLiveActivity: Bool = true //!(!UserDefaults.standard.isMaster || UserDefaults.standard.liveActivityType == .disabled) + + if let bgReadingsAccessor = bgReadingsAccessor, showLiveActivity { - if let bgReadingsAccessor = bgReadingsAccessor, showLiveActivity { - - // get 2 last Readings, with a calculatedValue - let lastReading = bgReadingsAccessor.get2LatestBgReadings(minimumTimeIntervalInMinutes: 0) - - // there should be at least one reading - guard lastReading.count > 0 else { - print("no recent BG readings returned") - LiveActivityManager.shared.endAllActivities() - return - } - - let bgValueInMgDl = lastReading[0].calculatedValue - - // now that we've got the current BG value, let's refine the check to see if we should run/show the live activity - switch liveActivityType { - case .always: - showLiveActivity = true - case .disabled: - showLiveActivity = false - case .low: - showLiveActivity = (bgValueInMgDl <= UserDefaults.standard.lowMarkValue) ? true : false - case .urgentLow: - showLiveActivity = (bgValueInMgDl <= UserDefaults.standard.urgentLowMarkValue) ? true : false - case .lowHigh: - showLiveActivity = ((bgValueInMgDl <= UserDefaults.standard.lowMarkValue) || (bgValueInMgDl >= UserDefaults.standard.highMarkValue)) ? true : false - case .urgentLowHigh: - showLiveActivity = ((bgValueInMgDl <= UserDefaults.standard.urgentLowMarkValue) || (bgValueInMgDl >= UserDefaults.standard.urgentHighMarkValue)) ? true : false - } - - // if we should still show it, then let's continue processing the lastReading array to create a valid contentState - if showLiveActivity { - let bgReadingDate = lastReading[0].timeStamp - let slopeOrdinal: Int = lastReading[0].slopeOrdinal() //? "" : lastReading[0].slopeArrow() + // get 2 last Readings, with a calculatedValue + let lastReading = bgReadingsAccessor.get2LatestBgReadings(minimumTimeIntervalInMinutes: 0) - var deltaChangeInMgDl: Double? - - // add delta if needed - if lastReading.count > 1 { - - deltaChangeInMgDl = lastReading[0].currentSlope(previousBgReading: lastReading[1]) * lastReading[0].timeStamp.timeIntervalSince(lastReading[1].timeStamp) * 1000; + // there should be at least one reading + guard lastReading.count > 0 else { + print("no recent BG readings returned") + LiveActivityManager.shared.endAllActivities() + return } - // create the contentState that will update the dynamic attributes of the Live Activity Widget - let contentState = XDripWidgetAttributes.ContentState(bgValueInMgDl: bgValueInMgDl, isMgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl, slopeOrdinal: slopeOrdinal, deltaChangeInMgDl: deltaChangeInMgDl, urgentLowLimitInMgDl: UserDefaults.standard.urgentLowMarkValue, lowLimitInMgDl: UserDefaults.standard.lowMarkValue, highLimitInMgDl: UserDefaults.standard.highMarkValue, urgentHighLimitInMgDl: UserDefaults.standard.urgentHighMarkValue, bgReadingDate: bgReadingDate, updatedDate: Date()) + let bgValueInMgDl = lastReading[0].calculatedValue - LiveActivityManager.shared.runActivity(contentState: contentState) + // now that we've got the current BG value, let's refine the check to see if we should run/show the live activity + switch liveActivityType { + case .always: + showLiveActivity = true + case .disabled: + showLiveActivity = false + case .low: + showLiveActivity = (bgValueInMgDl <= UserDefaults.standard.lowMarkValue) ? true : false + case .urgentLow: + showLiveActivity = (bgValueInMgDl <= UserDefaults.standard.urgentLowMarkValue) ? true : false + case .lowHigh: + showLiveActivity = ((bgValueInMgDl <= UserDefaults.standard.lowMarkValue) || (bgValueInMgDl >= UserDefaults.standard.highMarkValue)) ? true : false + case .urgentLowHigh: + showLiveActivity = ((bgValueInMgDl <= UserDefaults.standard.urgentLowMarkValue) || (bgValueInMgDl >= UserDefaults.standard.urgentHighMarkValue)) ? true : false + } + // if we should still show it, then let's continue processing the lastReading array to create a valid contentState + if showLiveActivity { + let bgReadingDate = lastReading[0].timeStamp + let slopeOrdinal: Int = lastReading[0].slopeOrdinal() //? "" : lastReading[0].slopeArrow() + + var deltaChangeInMgDl: Double? + + // add delta if needed + if lastReading.count > 1 { + + deltaChangeInMgDl = lastReading[0].currentSlope(previousBgReading: lastReading[1]) * lastReading[0].timeStamp.timeIntervalSince(lastReading[1].timeStamp) * 1000; + } + + // create the contentState that will update the dynamic attributes of the Live Activity Widget + let contentState = XDripWidgetAttributes.ContentState(bgValueInMgDl: bgValueInMgDl, isMgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl, slopeOrdinal: slopeOrdinal, deltaChangeInMgDl: deltaChangeInMgDl, urgentLowLimitInMgDl: UserDefaults.standard.urgentLowMarkValue, lowLimitInMgDl: UserDefaults.standard.lowMarkValue, highLimitInMgDl: UserDefaults.standard.highMarkValue, urgentHighLimitInMgDl: UserDefaults.standard.urgentHighMarkValue, bgReadingDate: bgReadingDate, updatedDate: Date()) + + LiveActivityManager.shared.runActivity(contentState: contentState) + + } } - } - - // try to end the activity if needed - if !showLiveActivity { - LiveActivityManager.shared.endAllActivities() + + // try to end the activity if needed + if !showLiveActivity { + LiveActivityManager.shared.endAllActivities() + } + } - } } diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHelpSettingModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHelpSettingModel.swift index 1e851bb5..6aca7e05 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHelpSettingModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHelpSettingModel.swift @@ -52,19 +52,37 @@ struct SettingsViewHelpSettingsViewModel:SettingsViewModelProtocol { // get the 2 character language code for the App Locale (i.e. "en", "es", "nl", "fr") // if the user has the app in a language other than English and they have the "auto translate" option selected, then load the help pages through Google Translate // important to check the the URLs actually exist in ConstansHomeView before trying to open them - if let languageCode = NSLocale.current.language.languageCode?.identifier, languageCode != ConstantsHomeView.onlineHelpBaseLocale && UserDefaults.standard.translateOnlineHelp { - - guard let url = URL(string: ConstantsHomeView.onlineHelpURLTranslated1 + languageCode + ConstantsHomeView.onlineHelpURLTranslated2) else { return .nothing } - - UIApplication.shared.open(url) - + if #available(iOS 16, *) { + if let languageCode = NSLocale.current.language.languageCode?.identifier, languageCode != ConstantsHomeView.onlineHelpBaseLocale && UserDefaults.standard.translateOnlineHelp { + + guard let url = URL(string: ConstantsHomeView.onlineHelpURLTranslated1 + languageCode + ConstantsHomeView.onlineHelpURLTranslated2) else { return .nothing } + + UIApplication.shared.open(url) + + } else { + + // so the user is running the app in English or they don't want to translate so let's just load it directly + guard let url = URL(string: ConstantsHomeView.onlineHelpURL) else { return .nothing} + + UIApplication.shared.open(url) + + } } else { - - // so the user is running the app in English or they don't want to translate so let's just load it directly - guard let url = URL(string: ConstantsHomeView.onlineHelpURL) else { return .nothing} - - UIApplication.shared.open(url) - + // Fallback on earlier versions + if let languageCode = NSLocale.current.languageCode, languageCode != ConstantsHomeView.onlineHelpBaseLocale && UserDefaults.standard.translateOnlineHelp { + + guard let url = URL(string: ConstantsHomeView.onlineHelpURLTranslated1 + languageCode + ConstantsHomeView.onlineHelpURLTranslated2) else { return .nothing } + + UIApplication.shared.open(url) + + } else { + + // so the user is running the app in English or they don't want to translate so let's just load it directly + guard let url = URL(string: ConstantsHomeView.onlineHelpURL) else { return .nothing} + + UIApplication.shared.open(url) + + } } return .nothing diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewNotificationsSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewNotificationsSettingsViewModel.swift index 31199a59..ee11f9a5 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewNotificationsSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewNotificationsSettingsViewModel.swift @@ -51,7 +51,7 @@ class SettingsViewNotificationsSettingsViewModel: SettingsViewModelProtocol { switch setting { case .showReadingInNotification, .showReadingInAppBadge, .multipleAppBadgeValueWith10: - return SettingsSelectedRowAction.nothing + return .nothing case .notificationInterval: @@ -59,53 +59,57 @@ class SettingsViewNotificationsSettingsViewModel: SettingsViewModelProtocol { case .liveActivityType: - // live activities can only be used in master mode as follower mode - // will not allow updates whilst the app is in the background - if UserDefaults.standard.isMaster { - - // data to be displayed in list from which user needs to pick a live activity type - var data = [String]() - - var selectedRow: Int? - - var index = 0 - - let currentLiveActivityType = UserDefaults.standard.liveActivityType - - // get all data source types and add the description to data. Search for the type that matches the FollowerDataSourceType that is currently stored in userdefaults. - for liveActivityType in LiveActivityType.allCases { + if #available(iOS 16.2, *) { + // live activities can only be used in master mode as follower mode + // will not allow updates whilst the app is in the background + if UserDefaults.standard.isMaster { - data.append(liveActivityType.description) + // data to be displayed in list from which user needs to pick a live activity type + var data = [String]() - if liveActivityType == currentLiveActivityType { - selectedRow = index + var selectedRow: Int? + + var index = 0 + + let currentLiveActivityType = UserDefaults.standard.liveActivityType + + // get all data source types and add the description to data. Search for the type that matches the FollowerDataSourceType that is currently stored in userdefaults. + for liveActivityType in LiveActivityType.allCases { + + data.append(liveActivityType.description) + + if liveActivityType == currentLiveActivityType { + selectedRow = index + } + + index += 1 + } - index += 1 + return SettingsSelectedRowAction.selectFromList(title: Texts_SettingsView.labelLiveActivityType, data: data, selectedRow: selectedRow, actionTitle: nil, cancelTitle: nil, actionHandler: {(index:Int) in + + // we'll set this here so that we can use it in the else statement for logging + let oldLiveActivityType = UserDefaults.standard.liveActivityType + + if index != selectedRow { + + UserDefaults.standard.liveActivityType = LiveActivityType(rawValue: index) ?? .disabled + + let newLiveActivityType = UserDefaults.standard.liveActivityType + + trace("Live activity type was changed from '%{public}@' to '%{public}@'", log: self.log, category: ConstantsLog.categorySettingsViewNotificationsSettingsViewModel, type: .info, oldLiveActivityType.description, newLiveActivityType.description) + + } + + }, cancelHandler: nil, didSelectRowHandler: nil) + + } else { + + return .showInfoText(title: Texts_SettingsView.labelLiveActivityType, message: Texts_SettingsView.liveActivityDisabledInFollowerModeMessage, actionHandler: {}) } - - return SettingsSelectedRowAction.selectFromList(title: Texts_SettingsView.labelLiveActivityType, data: data, selectedRow: selectedRow, actionTitle: nil, cancelTitle: nil, actionHandler: {(index:Int) in - - // we'll set this here so that we can use it in the else statement for logging - let oldLiveActivityType = UserDefaults.standard.liveActivityType - - if index != selectedRow { - - UserDefaults.standard.liveActivityType = LiveActivityType(rawValue: index) ?? .disabled - - let newLiveActivityType = UserDefaults.standard.liveActivityType - - trace("Live activity type was changed from '%{public}@' to '%{public}@'", log: self.log, category: ConstantsLog.categorySettingsViewNotificationsSettingsViewModel, type: .info, oldLiveActivityType.description, newLiveActivityType.description) - - } - - }, cancelHandler: nil, didSelectRowHandler: nil) - } else { - - return .showInfoText(title: Texts_SettingsView.labelLiveActivityType, message: Texts_SettingsView.liveActivityDisabledInFollowerModeMessage, actionHandler: {}) - + return .nothing } } } @@ -161,7 +165,11 @@ class SettingsViewNotificationsSettingsViewModel: SettingsViewModelProtocol { return .disclosureIndicator case .liveActivityType: - return UserDefaults.standard.isMaster ? .disclosureIndicator : .none + if #available(iOS 16.2, *) { + return UserDefaults.standard.isMaster ? .disclosureIndicator : .none + } else { + return .none + } } } @@ -178,8 +186,11 @@ class SettingsViewNotificationsSettingsViewModel: SettingsViewModelProtocol { return UserDefaults.standard.notificationInterval.description case .liveActivityType: - return UserDefaults.standard.isMaster ? UserDefaults.standard.liveActivityType.description : Texts_SettingsView.liveActivityDisabledInFollowerMode - + if #available(iOS 16.2, *) { + return UserDefaults.standard.isMaster ? UserDefaults.standard.liveActivityType.description : Texts_SettingsView.liveActivityDisabledInFollowerMode + } else { + return "Minimum iOS 16.2 needed" + } } } From e795fcece63a3df374ef5eda458c2b3bc1001513 Mon Sep 17 00:00:00 2001 From: Paul Plant Date: Sun, 21 Jan 2024 11:54:04 +0100 Subject: [PATCH 07/89] update --- xDripWidget/Constants/ConstantsWidget.swift | 47 +++++ xDripWidget/Extensions/Date.swift | 7 +- xDripWidget/GlucoseChartView.swift | 146 +++++++++++++++ xDripWidget/Models/Glucose+GlucoseKit.swift | 9 - .../Models/GlucoseChartWidgetType.swift | 138 ++++++++++++++ .../Models/XDripWidgetAttributes.swift | 49 +++-- xDripWidget/Texts/TextsWidget.swift | 8 +- xDripWidget/XDripWidgetLiveActivity.swift | 173 ++++++++++++------ xdrip.xcodeproj/project.pbxproj | 22 ++- xdrip/Extensions/UserDefaults.swift | 14 ++ .../LiveActivity/LiveActivityManager.swift | 63 +++++-- .../LiveActivityNotificationSizeType.swift | 51 ++++++ xdrip/Storyboards/Base.lproj/Main.storyboard | 27 +-- xdrip/Texts/TextsSettingsView.swift | 4 + .../RootViewController.swift | 58 +++--- ...gsViewNotificationsSettingsViewModel.swift | 90 +++++++-- 16 files changed, 746 insertions(+), 160 deletions(-) create mode 100644 xDripWidget/GlucoseChartView.swift delete mode 100644 xDripWidget/Models/Glucose+GlucoseKit.swift create mode 100644 xDripWidget/Models/GlucoseChartWidgetType.swift create mode 100644 xdrip/Managers/LiveActivity/LiveActivityNotificationSizeType.swift diff --git a/xDripWidget/Constants/ConstantsWidget.swift b/xDripWidget/Constants/ConstantsWidget.swift index 23a03143..2263ac81 100644 --- a/xDripWidget/Constants/ConstantsWidget.swift +++ b/xDripWidget/Constants/ConstantsWidget.swift @@ -6,8 +6,55 @@ // Copyright © 2023 Johan Degraeve. All rights reserved. // +import SwiftUI + enum ConstantsWidget { + static let mmollToMgdl = 18.01801801801802 static let mgDlToMmoll = 0.0555 + /// application name, appears in licenseInfo as title + static let applicationName: String = { + + guard let dictionary = Bundle.main.infoDictionary else {return "unknown"} + + guard let version = dictionary["CFBundleDisplayName"] as? String else {return "unknown"} + + return version + + }() + + // live activity notification widget + static let viewWidthLiveActivityNotificationNormal: CGFloat = 200 + static let viewHeightLiveActivityNotificationNormal: CGFloat = 80 // 100 for normal + static let hoursToShowLiveActivityNotificationNormal: Double = 3 + static let intervalBetweenXAxisValuesLiveActivityNotificationNormal: Int = 1 + + static let viewWidthLiveActivityNotificationLarge: CGFloat = 220 + static let viewHeightLiveActivityNotificationLarge: CGFloat = 140 // 150 seems to be max size without clipping + static let hoursToShowLiveActivityNotificationLarge: Double = 5 + static let intervalBetweenXAxisValuesLiveActivityNotificationLarge: Int = 1 + + static let viewBackgroundColorLiveActivityNotification = Color.black + static let lowHighLineColorLiveActivityNotification = Color(white: 0.7) + static let urgentLowHighLineLiveActivityNotification = Color(white: 0.5) + static let xAxisGridLineColorLiveActivityNotification = Color(white: 0.4) + static let glucoseCircleDiameterLiveActivityNotification: Double = 36 + static let relativeYAxisLineSizeLiveActivityNotification: Double = 1 + static let xAxisLabelOffsetLiveActivityNotification: Double = -10 + + // dynamic island bottom (expanded) + static let viewWidthDynamicIsland: CGFloat = 320 + static let viewHeightDynamicIsland: CGFloat = 70 + + static let viewBackgroundColorDynamicIsland = Color.black + static let lowHighLineColorDynamicIsland = Color(white: 0.7) + static let urgentLowHighLineColorDynamicIsland = Color(white: 0.5) + static let xAxisGridLineColorDynamicIsland = Color(white: 0.5) + static let hoursToShowDynamicIsland: Double = 12 + static let intervalBetweenXAxisValuesDynamicIsland: Int = 2 + static let glucoseCircleDiameterDynamicIsland: Double = 14 + static let relativeYAxisLineSizeDynamicIsland: Double = 0.8 + static let xAxisLabelOffsetDynamicIsland: Double = -10 + } diff --git a/xDripWidget/Extensions/Date.swift b/xDripWidget/Extensions/Date.swift index f82b73ec..a6df1fc7 100644 --- a/xDripWidget/Extensions/Date.swift +++ b/xDripWidget/Extensions/Date.swift @@ -5,5 +5,10 @@ extension Date { func toMillisecondsAsDouble() -> Double { return Double(self.timeIntervalSince1970 * 1000) } - + + /// creates a new date, rounded to lower hour, eg if date = 26 10 2019 23:23:35, returnvalue is date 26 10 2019 23:00:00 + func toLowerHour() -> Date { + return Date(timeIntervalSinceReferenceDate: + (timeIntervalSinceReferenceDate / 3600.0).rounded(.down) * 3600.0) + } } diff --git a/xDripWidget/GlucoseChartView.swift b/xDripWidget/GlucoseChartView.swift new file mode 100644 index 00000000..b6c40e41 --- /dev/null +++ b/xDripWidget/GlucoseChartView.swift @@ -0,0 +1,146 @@ +// +// GlucoseChartView.swift +// xdrip +// +// Created by Paul Plant on 13/01/2024. +// Copyright © 2023 Johan Degraeve. All rights reserved. +// + +import Charts +import SwiftUI +import Foundation + +@available(iOS 16, *) +struct GlucoseChartView: View { + + var bgReadingValues: [Double] + var bgReadingDates: [Date] + let glucoseChartWidgetType: GlucoseChartWidgetType + let isMgDl: Bool + let urgentLowLimitInMgDl: Double + let lowLimitInMgDl: Double + let highLimitInMgDl: Double + let urgentHighLimitInMgDl: Double + let liveActivityNotificationSizeType: LiveActivityNotificationSizeType + + init(bgReadingValues: [Double], bgReadingDates: [Date], glucoseChartWidgetType: GlucoseChartWidgetType, isMgDl: Bool, urgentLowLimitInMgDl: Double, lowLimitInMgDl: Double, highLimitInMgDl: Double, urgentHighLimitInMgDl: Double, liveActivityNotificationSizeType: LiveActivityNotificationSizeType) { + + // as all widget instances are passed 12 hours of bg values, we must initialize this instance to use only the amount of hours of value required by the glucoseChartWidgetType passed + self.bgReadingValues = [] + self.bgReadingDates = [] + + var index = 0 + + for _ in bgReadingValues { + if bgReadingDates[index] > Date().addingTimeInterval(-glucoseChartWidgetType.hoursToShow(liveActivityNotificationSizeType: liveActivityNotificationSizeType) * 60 * 60) { + self.bgReadingValues.append(bgReadingValues[index]) + self.bgReadingDates.append(bgReadingDates[index]) + } + index += 1 + } + + self.glucoseChartWidgetType = glucoseChartWidgetType + self.isMgDl = isMgDl + self.urgentLowLimitInMgDl = urgentLowLimitInMgDl + self.lowLimitInMgDl = lowLimitInMgDl + self.highLimitInMgDl = highLimitInMgDl + self.urgentHighLimitInMgDl = urgentHighLimitInMgDl + self.liveActivityNotificationSizeType = liveActivityNotificationSizeType + } + + /// Blood glucose color dependant on the user defined limit values + /// - Returns: a Color object either red, yellow or green + func bgColor(bgValueInMgDl: Double) -> Color { + if bgValueInMgDl >= urgentHighLimitInMgDl || bgValueInMgDl <= urgentLowLimitInMgDl { + return .red + } else if bgValueInMgDl >= highLimitInMgDl || bgValueInMgDl <= lowLimitInMgDl { + return .yellow + } else { + return .green + } + } + + func xAxisValues() -> [Date] { + + // adapted from generateXAxisValues() from GlucoseChartManager.swift in xDrip target + + let startDate: Date = bgReadingDates.last ?? Date().addingTimeInterval(-glucoseChartWidgetType.hoursToShow(liveActivityNotificationSizeType: liveActivityNotificationSizeType) * 3600) + let endDate: Date = Date() + + /// how many full hours between startdate and enddate + let amountOfFullHours = Int(ceil(endDate.timeIntervalSince(startDate) / 3600)) + + /// create array that goes from 1 to number of full hours, as helper to map to array of ChartAxisValueDate - array will go from 1 to 6 + let mappingArray = Array(1...amountOfFullHours) + + /// set the stride count interval to make sure we don't add too many labels to the x-axis if the user wants to view >6 hours + let intervalBetweenAxisValues: Int = glucoseChartWidgetType.intervalBetweenAxisValues(liveActivityNotificationSizeType: liveActivityNotificationSizeType) + + /// first, for each int in mappingArray, we create a Date, starting with the lower hour + 1 hour - we will create 5 in this example, starting with hour 08 (7 + 3600 seconds) + let startDateLower = startDate.toLowerHour() + + let xAxisValues: [Date] = stride(from: 1, to: mappingArray.count + 1, by: intervalBetweenAxisValues).map { + startDateLower.addingTimeInterval(Double($0)*3600) + } + + return xAxisValues + + } + + + var body: some View { + let domain = 40 ... max(bgReadingValues.max() ?? 400, urgentHighLimitInMgDl) + + Chart { + if domain.contains(urgentLowLimitInMgDl) { + RuleMark(y: .value("", urgentLowLimitInMgDl)) + .lineStyle(StrokeStyle(lineWidth: 1 * glucoseChartWidgetType.relativeYAxisLineSize, dash: [2 * glucoseChartWidgetType.relativeYAxisLineSize, 6 * glucoseChartWidgetType.relativeYAxisLineSize])) + .foregroundStyle(glucoseChartWidgetType.urgentLowHighLineColor) + } + + if domain.contains(urgentHighLimitInMgDl) { + RuleMark(y: .value("", urgentHighLimitInMgDl)) + .lineStyle(StrokeStyle(lineWidth: 1 * glucoseChartWidgetType.relativeYAxisLineSize, dash: [2 * glucoseChartWidgetType.relativeYAxisLineSize, 6 * glucoseChartWidgetType.relativeYAxisLineSize])) + .foregroundStyle(glucoseChartWidgetType.urgentLowHighLineColor) + } + + if domain.contains(lowLimitInMgDl) { + RuleMark(y: .value("", lowLimitInMgDl)) + .lineStyle(StrokeStyle(lineWidth: 1 * glucoseChartWidgetType.relativeYAxisLineSize, dash: [4 * glucoseChartWidgetType.relativeYAxisLineSize, 3 * glucoseChartWidgetType.relativeYAxisLineSize])) + .foregroundStyle(glucoseChartWidgetType.lowHighLineColor) + } + + if domain.contains(highLimitInMgDl) { + RuleMark(y: .value("", highLimitInMgDl)) + .lineStyle(StrokeStyle(lineWidth: 1 * glucoseChartWidgetType.relativeYAxisLineSize, dash: [4 * glucoseChartWidgetType.relativeYAxisLineSize, 3 * glucoseChartWidgetType.relativeYAxisLineSize])) + .foregroundStyle(glucoseChartWidgetType.lowHighLineColor) + } + + ForEach(bgReadingValues.indices, id: \.self) { index in + PointMark(x: .value("Time", bgReadingDates[index]), + y: .value("BG", bgReadingValues[index])) + .symbol(Circle()) + .symbolSize(glucoseChartWidgetType.glucoseCircleDiameter) + .foregroundStyle(bgColor(bgValueInMgDl: bgReadingValues[index])) + } + } + .chartXAxis { + // https://developer.apple.com/documentation/charts/customizing-axes-in-swift-charts + AxisMarks(values: xAxisValues()) { + + if let v = $0.as(Date.self) { + AxisValueLabel { + Text(v.formatted(.dateTime.hour())) + .foregroundStyle(Color.white) + } + .offset(x: glucoseChartWidgetType.xAxisLabelOffset) + AxisGridLine() + .foregroundStyle(glucoseChartWidgetType.xAxisGridLineColor) + } + } + } + .chartYAxis(.hidden) + .chartYScale(domain: domain) + .frame(width: glucoseChartWidgetType.viewSize(liveActivityNotificationSizeType: liveActivityNotificationSizeType).width, height: glucoseChartWidgetType.viewSize(liveActivityNotificationSizeType: liveActivityNotificationSizeType).height) + } +} diff --git a/xDripWidget/Models/Glucose+GlucoseKit.swift b/xDripWidget/Models/Glucose+GlucoseKit.swift deleted file mode 100644 index 9cececa5..00000000 --- a/xDripWidget/Models/Glucose+GlucoseKit.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -public struct Glucose { - public let glucose: UInt16 - public let trend: UInt8 - public let timestamp: Date - public let collector: String? -} - diff --git a/xDripWidget/Models/GlucoseChartWidgetType.swift b/xDripWidget/Models/GlucoseChartWidgetType.swift new file mode 100644 index 00000000..dadde6b9 --- /dev/null +++ b/xDripWidget/Models/GlucoseChartWidgetType.swift @@ -0,0 +1,138 @@ +// +// GlucoseChartWidgetType.swift +// xdrip +// +// Created by Paul Plant on 5/1/24. +// Copyright © 2024 Johan Degraeve. All rights reserved. +// + +import Foundation +import UIKit +import SwiftUI + +/// holds and returns the different parameters used for creating the images for different widget types +public enum GlucoseChartWidgetType: Int, CaseIterable { + + // when adding GlucoseChartWidgetType, add new cases at the end (ie 3, ...) + // if this is done in the middle then a database migration would be required, because the rawvalue is stored as Int16 in the coredata + // the order of the data source types will in the uiview is determined by the initializer init(forRowAt row: Int) + + case liveActivityNotification = 0 + case dynamicIsland = 1 + + var description: String { + switch self { + case .liveActivityNotification: + return "Live Activity Notification Widget" + case .dynamicIsland: + return "Dynamic Island (Expanded) Widget" + } + } + + + func viewSize(liveActivityNotificationSizeType: LiveActivityNotificationSizeType) -> (width: CGFloat, height: CGFloat) { + switch self { + case .liveActivityNotification: + switch liveActivityNotificationSizeType { + case .large: + return (ConstantsWidget.viewWidthLiveActivityNotificationLarge, ConstantsWidget.viewHeightLiveActivityNotificationLarge) + default: + return (ConstantsWidget.viewWidthLiveActivityNotificationNormal, ConstantsWidget.viewHeightLiveActivityNotificationNormal) + } + case .dynamicIsland: + return (ConstantsWidget.viewWidthDynamicIsland, ConstantsWidget.viewHeightDynamicIsland) + } + } + + func hoursToShow(liveActivityNotificationSizeType: LiveActivityNotificationSizeType) -> Double { + switch self { + case .liveActivityNotification: + switch liveActivityNotificationSizeType { + case .large: + return ConstantsWidget.hoursToShowLiveActivityNotificationLarge + default: + return ConstantsWidget.hoursToShowLiveActivityNotificationNormal + } + case .dynamicIsland: + return ConstantsWidget.hoursToShowDynamicIsland + } + } + + func intervalBetweenAxisValues(liveActivityNotificationSizeType: LiveActivityNotificationSizeType) -> Int { + switch self { + case .liveActivityNotification: + switch liveActivityNotificationSizeType { + case .large: + return ConstantsWidget.intervalBetweenXAxisValuesLiveActivityNotificationLarge + default: + return ConstantsWidget.intervalBetweenXAxisValuesLiveActivityNotificationNormal + } + case .dynamicIsland: + return ConstantsWidget.intervalBetweenXAxisValuesDynamicIsland + } + } + + var backgroundColor: Color { + switch self { + case .liveActivityNotification: + return ConstantsWidget.viewBackgroundColorLiveActivityNotification + case .dynamicIsland: + return ConstantsWidget.viewBackgroundColorDynamicIsland + } + } + + var glucoseCircleDiameter: Double { + switch self { + case .liveActivityNotification: + return ConstantsWidget.glucoseCircleDiameterLiveActivityNotification + case .dynamicIsland: + return ConstantsWidget.glucoseCircleDiameterDynamicIsland + } + } + + var lowHighLineColor: Color { + switch self { + case .liveActivityNotification: + return ConstantsWidget.lowHighLineColorLiveActivityNotification + case .dynamicIsland: + return ConstantsWidget.lowHighLineColorDynamicIsland + } + } + + var urgentLowHighLineColor: Color { + switch self { + case .liveActivityNotification: + return ConstantsWidget.urgentLowHighLineLiveActivityNotification + case .dynamicIsland: + return ConstantsWidget.urgentLowHighLineColorDynamicIsland + } + } + + var relativeYAxisLineSize: Double { + switch self { + case .liveActivityNotification: + return ConstantsWidget.relativeYAxisLineSizeLiveActivityNotification + case .dynamicIsland: + return ConstantsWidget.relativeYAxisLineSizeDynamicIsland + } + } + + var xAxisLabelOffset: Double { + switch self { + case .liveActivityNotification: + return ConstantsWidget.xAxisLabelOffsetLiveActivityNotification + case .dynamicIsland: + return ConstantsWidget.xAxisLabelOffsetDynamicIsland + } + } + + var xAxisGridLineColor: Color { + switch self { + case .liveActivityNotification: + return ConstantsWidget.xAxisGridLineColorLiveActivityNotification + case .dynamicIsland: + return ConstantsWidget.xAxisGridLineColorDynamicIsland + } + } + +} diff --git a/xDripWidget/Models/XDripWidgetAttributes.swift b/xDripWidget/Models/XDripWidgetAttributes.swift index 0deb2af5..d6e84cca 100644 --- a/xDripWidget/Models/XDripWidgetAttributes.swift +++ b/xDripWidget/Models/XDripWidgetAttributes.swift @@ -18,43 +18,49 @@ struct XDripWidgetAttributes: ActivityAttributes { //var previousActivityID: String //var activityID: String + // Dynamic stateful properties about your activity go here! - var bgValueInMgDl: Double + var bgReadingValues: [Double] + var bgReadingDates: [Date] var isMgDl: Bool -// var trendArrow: String var slopeOrdinal: Int var deltaChangeInMgDl: Double? var urgentLowLimitInMgDl: Double var lowLimitInMgDl: Double var highLimitInMgDl: Double var urgentHighLimitInMgDl: Double - var bgReadingDate: Date var updatedDate: Date + var liveActivityNotificationSizeTypeAsInt: Int - var bgValueStringInUserChosenUnit: String + + var bgValueInMgDl: Double + var bgReadingDate: Date var bgUnitString: String -// var deltaChangeStringInUserChosenUnit: String + var bgValueStringInUserChosenUnit: String - init(bgValueInMgDl: Double, isMgDl: Bool, slopeOrdinal: Int, deltaChangeInMgDl: Double?, urgentLowLimitInMgDl: Double, lowLimitInMgDl: Double, highLimitInMgDl: Double, urgentHighLimitInMgDl: Double, bgReadingDate: Date, updatedDate: Date) { + //var bgReadings: [BgReading] + + init(bgReadingValues: [Double], bgReadingDates: [Date], isMgDl: Bool, slopeOrdinal: Int, deltaChangeInMgDl: Double?, urgentLowLimitInMgDl: Double, lowLimitInMgDl: Double, highLimitInMgDl: Double, urgentHighLimitInMgDl: Double, updatedDate: Date, liveActivityNotificationSizeTypeAsInt: Int) { // these are the "passed in" stateful values used to initialize - self.bgValueInMgDl = bgValueInMgDl self.isMgDl = isMgDl -// self.trendArrow = trendArrow self.slopeOrdinal = slopeOrdinal self.deltaChangeInMgDl = deltaChangeInMgDl// ?? nil self.urgentLowLimitInMgDl = urgentLowLimitInMgDl self.lowLimitInMgDl = lowLimitInMgDl self.highLimitInMgDl = highLimitInMgDl self.urgentHighLimitInMgDl = urgentHighLimitInMgDl - self.bgReadingDate = bgReadingDate self.updatedDate = updatedDate + self.liveActivityNotificationSizeTypeAsInt = liveActivityNotificationSizeTypeAsInt + + self.bgReadingValues = bgReadingValues + self.bgReadingDates = bgReadingDates // these are dynamically initialized based on the above - //self.bgValueInUserChosenUnit = bgValueInMgDl.mgdlToMmol(mgdl: isMgDl) + self.bgValueInMgDl = bgReadingValues[0] + self.bgReadingDate = bgReadingDates[0] self.bgUnitString = isMgDl ? Texts_Widget.mgdl : Texts_Widget.mmol - self.bgValueStringInUserChosenUnit = bgValueInMgDl.mgdlToMmolAndToString(mgdl: isMgDl) - //self.deltaChangeStringInUserChosenUnit = deltaChangeInMgDl.mgdlToMmolAndToString(mgdl: isMgDl) + self.bgValueStringInUserChosenUnit = bgReadingValues[0].mgdlToMmolAndToString(mgdl: isMgDl) } @@ -73,8 +79,7 @@ struct XDripWidgetAttributes: ActivityAttributes { /// Show the bg event title if relevant /// - Returns: a localized string such as "HIGH" or "LOW" as required - func getBgTitle() -> String { - + func getBgTitle() -> String? { if bgValueInMgDl >= urgentHighLimitInMgDl { return Texts_Widget.urgentHigh } else if bgValueInMgDl >= highLimitInMgDl { @@ -84,7 +89,7 @@ struct XDripWidgetAttributes: ActivityAttributes { } else if bgValueInMgDl <= urgentLowLimitInMgDl { return Texts_Widget.urgentLow } else { - return "" + return nil } } @@ -122,7 +127,6 @@ struct XDripWidgetAttributes: ActivityAttributes { /// returns a string holding the trend arrow /// - Returns: trend arrow string (i.e. "↑") func trendArrow() -> String { - switch slopeOrdinal { case 7: return "\u{2193}\u{2193}" // ↓↓ @@ -140,11 +144,20 @@ struct XDripWidgetAttributes: ActivityAttributes { return "\u{2191}\u{2191}" // ↑↑ default: return "n/a" - } - } + func deltaChangeFormatted(font: Font) -> some View { + HStack(spacing: 4) { + Text(getDeltaChangeStringInUserChosenUnit()) + .font(font).bold() + .foregroundStyle(Color(white: 0.9)) + Text(bgUnitString) + .font(font) + .foregroundStyle(Color(white: 0.5)) + } + } + } // when was the live activity event started? We check this on each update cycle and dismiss/recreate it before the 8-hour limit. diff --git a/xDripWidget/Texts/TextsWidget.swift b/xDripWidget/Texts/TextsWidget.swift index c300d622..034b376e 100644 --- a/xDripWidget/Texts/TextsWidget.swift +++ b/xDripWidget/Texts/TextsWidget.swift @@ -13,19 +13,19 @@ class Texts_Widget { }() static let high = { - return NSLocalizedString("widget_high", tableName: filename, bundle: Bundle.main, value: "High", comment: "the word HIGH, in capitals") + return NSLocalizedString("widget_high", tableName: filename, bundle: Bundle.main, value: "HIGH", comment: "the word HIGH, in capitals") }() static let low = { - return NSLocalizedString("widget_low", tableName: filename, bundle: Bundle.main, value: "Low", comment: "the word LOW, in capitals") + return NSLocalizedString("widget_low", tableName: filename, bundle: Bundle.main, value: "LOW", comment: "the word LOW, in capitals") }() static let urgentHigh = { - return NSLocalizedString("widget_urgentHigh", tableName: filename, bundle: Bundle.main, value: "Urgent High", comment: "the words urgent HIGH, in capitals") + return NSLocalizedString("widget_urgentHigh", tableName: filename, bundle: Bundle.main, value: "URGENT\nHIGH", comment: "the words URGENT HIGH, in capitals") }() static let urgentLow = { - return NSLocalizedString("widget_urgentLow", tableName: filename, bundle: Bundle.main, value: "Urgent Low", comment: "the words urgent LOW, in capitals") + return NSLocalizedString("widget_urgentLow", tableName: filename, bundle: Bundle.main, value: "URGENT\nLOW", comment: "the words URGENT LOW, in capitals") }() static let mgdl: String = { diff --git a/xDripWidget/XDripWidgetLiveActivity.swift b/xDripWidget/XDripWidgetLiveActivity.swift index d8a5f0a2..4c204513 100644 --- a/xDripWidget/XDripWidgetLiveActivity.swift +++ b/xDripWidget/XDripWidgetLiveActivity.swift @@ -12,6 +12,9 @@ import SwiftUI @available(iOSApplicationExtension 16.2, *) struct XDripWidgetLiveActivity: Widget { + + let glucoseChartWidgetType: GlucoseChartWidgetType = .dynamicIsland + var body: some WidgetConfiguration { ActivityConfiguration(for: XDripWidgetAttributes.self) { context in @@ -21,43 +24,19 @@ struct XDripWidgetLiveActivity: Widget { } dynamicIsland: { context in DynamicIsland { - DynamicIslandExpandedRegion(.leading) { - Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow())") - .font(.largeTitle) + DynamicIslandExpandedRegion(.leading) { Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow())") + .font(.largeTitle).bold() .foregroundStyle(context.state.getBgColor()) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) } DynamicIslandExpandedRegion(.trailing) { - Text(context.state.getBgTitle()) - .font(.largeTitle) - .foregroundStyle(context.state.getBgColor()) + context.state.deltaChangeFormatted(font: .title2) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) } - // DynamicIslandExpandedRegion(.center) { - // EmptyView() - // } DynamicIslandExpandedRegion(.bottom) { - VStack { - HStack { - Text("\(context.state.getDeltaChangeStringInUserChosenUnit()) \(context.state.bgUnitString)") - .font(.title3) - .foregroundStyle(Color(white: 0.8)) - .bold() - Spacer() - VStack { - Text("Reading \(context.state.bgReadingDate.formatted(date: .omitted, time: .shortened))") - .font(.subheadline) - .foregroundStyle(Color(white: 0.6)) - .bold() - .frame(maxWidth: .infinity, alignment: .trailing) - - Text("Updated \(context.state.updatedDate.formatted(date: .omitted, time: .shortened))") - .font(.subheadline) - .foregroundStyle(Color(white: 0.6)) - .bold() - .frame(maxWidth: .infinity, alignment: .trailing) - - } - } - } + + GlucoseChartView(bgReadingValues: context.state.bgReadingValues, bgReadingDates: context.state.bgReadingDates, glucoseChartWidgetType: glucoseChartWidgetType, isMgDl: context.state.isMgDl, urgentLowLimitInMgDl: context.state.urgentLowLimitInMgDl, lowLimitInMgDl: context.state.lowLimitInMgDl, highLimitInMgDl: context.state.highLimitInMgDl, urgentHighLimitInMgDl: context.state.urgentHighLimitInMgDl, liveActivityNotificationSizeType: LiveActivityNotificationSizeType(rawValue: context.state.liveActivityNotificationSizeTypeAsInt) ?? .normal) + } } compactLeading: { Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow())") @@ -80,48 +59,128 @@ struct XDripWidgetLiveActivity: Widget { struct LiveActivityView: View { let state: XDripWidgetAttributes.ContentState + let glucoseChartWidgetType: GlucoseChartWidgetType = .liveActivityNotification var body: some View { // Lock screen/banner UI goes here - VStack { + + if state.liveActivityNotificationSizeTypeAsInt == 0 { + // 0 = normal size chart + + HStack(spacing: 20) { + VStack(spacing: 10) { + Text("\(state.bgValueStringInUserChosenUnit)\(state.trendArrow())") + .font(.largeTitle).bold() + .foregroundStyle(state.getBgColor()) + + state.deltaChangeFormatted(font: .title2) + } + + GlucoseChartView(bgReadingValues: state.bgReadingValues, bgReadingDates: state.bgReadingDates, glucoseChartWidgetType: glucoseChartWidgetType, isMgDl: state.isMgDl, urgentLowLimitInMgDl: state.urgentLowLimitInMgDl, lowLimitInMgDl: state.lowLimitInMgDl, highLimitInMgDl: state.highLimitInMgDl, urgentHighLimitInMgDl: state.urgentHighLimitInMgDl, liveActivityNotificationSizeType: LiveActivityNotificationSizeType(rawValue: state.liveActivityNotificationSizeTypeAsInt) ?? .normal) + + } + .activityBackgroundTint(.black) + .padding(10) + + } else if state.liveActivityNotificationSizeTypeAsInt == 1 { + // 1 = minimal widget with no chart + HStack { + Text("\(state.bgValueStringInUserChosenUnit)\(state.trendArrow())") .font(.largeTitle).bold() .foregroundStyle(state.getBgColor()) + Spacer() - Text(state.getBgTitle()) - .font(.title).bold() - .foregroundStyle(state.getBgColor()) - } - - HStack { - Text("\(state.getDeltaChangeStringInUserChosenUnit()) \(state.bgUnitString)") - .font(.title3) - .foregroundStyle(Color(white: 0.8)) - .bold() - Spacer() - VStack { - Text("Reading \(state.bgReadingDate.formatted(date: .omitted, time: .shortened))") - .font(.subheadline) - .foregroundStyle(Color(white: 0.6)) - .frame(maxWidth: .infinity, alignment: .trailing) - .bold() - Text("Updated \(state.updatedDate.formatted(date: .omitted, time: .shortened))") - .font(.subheadline) - .foregroundStyle(Color(white: 0.6)) - .frame(maxWidth: .infinity, alignment: .trailing) - .bold() + + if let bgTitle = state.getBgTitle() { + Text(bgTitle) + .foregroundStyle(state.getBgColor()) + .font(.title2).bold() + .multilineTextAlignment(.center) } + + Spacer() + + state.deltaChangeFormatted(font: .title2) } + .activityBackgroundTint(.black) + .padding(15) + + } else { + // 2 = large widget with no chart + + HStack(spacing: 10) { + VStack(spacing: 10) { + Text("\(state.bgValueStringInUserChosenUnit)\(state.trendArrow())") + .font(.title).bold() + .foregroundStyle(state.getBgColor()) + + state.deltaChangeFormatted(font: .title3) + + if let bgTitle = state.getBgTitle() { + Text(bgTitle) + .foregroundStyle(state.getBgColor()) + .font(.subheadline).bold() + .multilineTextAlignment(.center) + } + } + + GlucoseChartView(bgReadingValues: state.bgReadingValues, bgReadingDates: state.bgReadingDates, glucoseChartWidgetType: glucoseChartWidgetType, isMgDl: state.isMgDl, urgentLowLimitInMgDl: state.urgentLowLimitInMgDl, lowLimitInMgDl: state.lowLimitInMgDl, highLimitInMgDl: state.highLimitInMgDl, urgentHighLimitInMgDl: state.urgentHighLimitInMgDl, liveActivityNotificationSizeType: LiveActivityNotificationSizeType(rawValue: state.liveActivityNotificationSizeTypeAsInt) ?? .normal) + + } + .activityBackgroundTint(.black) + .padding(10) } - .padding(15) } } @available(iOS 16.2, *) struct XDripWidgetLiveActivity_Previews: PreviewProvider { + + static func bgDateArray() -> [Date] { + + let endDate = Date() + let startDate = endDate.addingTimeInterval(-3600 * 12) + var currentDate = startDate + + var dateArray: [Date] = [] + + while currentDate < endDate { + dateArray.append(currentDate) + currentDate = currentDate.addingTimeInterval(60 * 5) + } + + return dateArray + } + + static func bgValueArray() -> [Double] { + + var bgValueArray:[Double] = Array(repeating: 0, count: 144) + var currentValue: Double = 100 + var increaseValues: Bool = true + + for index in bgValueArray.indices { + + if currentValue < 70 { + increaseValues = true + } else if currentValue > 180 { + increaseValues = false + } + + let randomValue = Double(Int.random(in: 0..<20)) + bgValueArray[index] = currentValue + (increaseValues ? randomValue : -randomValue) + currentValue = bgValueArray[index] + } + + return bgValueArray + + } + + static let attributes = XDripWidgetAttributes(eventStartDate: Date().addingTimeInterval(-1000)) - static let contentState = XDripWidgetAttributes.ContentState(bgValueInMgDl: 252, isMgDl: true, slopeOrdinal:5, deltaChangeInMgDl: -2, urgentLowLimitInMgDl: 70, lowLimitInMgDl: 80, highLimitInMgDl: 140, urgentHighLimitInMgDl: 180, bgReadingDate: Date().addingTimeInterval(-180), updatedDate: Date()) + + static let contentState = XDripWidgetAttributes.ContentState(bgReadingValues: bgValueArray(), bgReadingDates: bgDateArray(), isMgDl: true, slopeOrdinal:5, deltaChangeInMgDl: -2, urgentLowLimitInMgDl: 70, lowLimitInMgDl: 80, highLimitInMgDl: 140, urgentHighLimitInMgDl: 180, updatedDate: Date(), liveActivityNotificationSizeTypeAsInt: 2) static var previews: some View { attributes diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 81f3e3d9..671287fd 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -52,9 +52,16 @@ 47AB72F327105EF4005E7CAB /* SettingsViewHelpSettingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47AB72F227105EF4005E7CAB /* SettingsViewHelpSettingModel.swift */; }; 47ADD2DF27FAF8630025E2F4 /* ChartPointsScatterDownTrianglesLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47ADD2DE27FAF8630025E2F4 /* ChartPointsScatterDownTrianglesLayer.swift */; }; 47ADD2E127FB05EB0025E2F4 /* ChartPointsScatterDownTrianglesWithDropdownLineLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47ADD2E027FB05EB0025E2F4 /* ChartPointsScatterDownTrianglesWithDropdownLineLayer.swift */; }; + 47B2F77B2B53DBA60038E0B6 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E53FBC2517F96800052CE5 /* Date.swift */; }; 47B60F3726F389E2003198D3 /* LandscapeChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B60F3626F389E2003198D3 /* LandscapeChartViewController.swift */; }; 47B7FC722B00CF4B004C872B /* FollowerBackgroundKeepAliveType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B7FC712B00CF4B004C872B /* FollowerBackgroundKeepAliveType.swift */; }; + 47C210F02B52A05B00005711 /* GlucoseChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C210EF2B52A05B00005711 /* GlucoseChartView.swift */; }; 47CF18B22B37689A00FA6160 /* TimeInRangeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CF18B12B37689A00FA6160 /* TimeInRangeType.swift */; }; + 47D08D5A2B54373600B0BEA7 /* GlucoseChartWidgetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C210ED2B5298EB00005711 /* GlucoseChartWidgetType.swift */; }; + 47D08D5B2B5437F500B0BEA7 /* ConstantsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716A50C2B416EE100419052 /* ConstantsWidget.swift */; }; + 47D08D5C2B54386600B0BEA7 /* GlucoseChartWidgetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C210ED2B5298EB00005711 /* GlucoseChartWidgetType.swift */; }; + 47D08D5E2B54390B00B0BEA7 /* LiveActivityNotificationSizeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47D08D5D2B54390B00B0BEA7 /* LiveActivityNotificationSizeType.swift */; }; + 47D08D5F2B54390B00B0BEA7 /* LiveActivityNotificationSizeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47D08D5D2B54390B00B0BEA7 /* LiveActivityNotificationSizeType.swift */; }; 47D2DB3B2B14F6D000C8EE6B /* ScreenLockDimmingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47D2DB3A2B14F6D000C8EE6B /* ScreenLockDimmingType.swift */; }; 47D9BC952A78498500AB85B2 /* BgReadingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47D9BC942A78498500AB85B2 /* BgReadingsDetailView.swift */; }; 47DB06C22A6FC02200267BE3 /* SettingsViewDataSourceSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DB06C12A6FC02200267BE3 /* SettingsViewDataSourceSettingsViewModel.swift */; }; @@ -756,7 +763,10 @@ 47ADD2E027FB05EB0025E2F4 /* ChartPointsScatterDownTrianglesWithDropdownLineLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartPointsScatterDownTrianglesWithDropdownLineLayer.swift; sourceTree = ""; }; 47B60F3626F389E2003198D3 /* LandscapeChartViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandscapeChartViewController.swift; sourceTree = ""; }; 47B7FC712B00CF4B004C872B /* FollowerBackgroundKeepAliveType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowerBackgroundKeepAliveType.swift; sourceTree = ""; }; + 47C210ED2B5298EB00005711 /* GlucoseChartWidgetType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseChartWidgetType.swift; sourceTree = ""; }; + 47C210EF2B52A05B00005711 /* GlucoseChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseChartView.swift; sourceTree = ""; }; 47CF18B12B37689A00FA6160 /* TimeInRangeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInRangeType.swift; sourceTree = ""; }; + 47D08D5D2B54390B00B0BEA7 /* LiveActivityNotificationSizeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityNotificationSizeType.swift; sourceTree = ""; }; 47D2DB3A2B14F6D000C8EE6B /* ScreenLockDimmingType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockDimmingType.swift; sourceTree = ""; }; 47D9BC942A78498500AB85B2 /* BgReadingsDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BgReadingsDetailView.swift; sourceTree = ""; }; 47DB06C02A6FB3CC00267BE3 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/BgReadings.strings; sourceTree = ""; }; @@ -1224,7 +1234,6 @@ F86A3C79247718C800EE7E46 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/LaunchScreen.strings; sourceTree = ""; }; F86A3C7A247718C800EE7E46 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Main.strings; sourceTree = ""; }; F870D3D225126A49008967B0 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; - F870D3E925129C43008967B0 /* Glucose+GlucoseKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Glucose+GlucoseKit.swift"; sourceTree = ""; }; F8797CE9255B43960033956B /* GlucoseData+Smoothable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GlucoseData+Smoothable.swift"; sourceTree = ""; }; F87F5EFD2560686C00FFA395 /* xdrip v15.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "xdrip v15.xcdatamodel"; sourceTree = ""; }; F889CB70236D84AC00A81068 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/M5StackView.strings; sourceTree = ""; }; @@ -1653,6 +1662,7 @@ isa = PBXGroup; children = ( 4716A4FB2B406C3F00419052 /* Info.plist */, + 47C210EF2B52A05B00005711 /* GlucoseChartView.swift */, 4716A4F72B406C3D00419052 /* XDripWidget.swift */, 4716A4F32B406C3D00419052 /* XDripWidgetBundle.swift */, 4716A4F52B406C3D00419052 /* XDripWidgetLiveActivity.swift */, @@ -1677,7 +1687,7 @@ 4716A5032B40704000419052 /* Models */ = { isa = PBXGroup; children = ( - F870D3E925129C43008967B0 /* Glucose+GlucoseKit.swift */, + 47C210ED2B5298EB00005711 /* GlucoseChartWidgetType.swift */, 4716A5042B40709E00419052 /* XDripWidgetAttributes.swift */, ); path = Models; @@ -1696,6 +1706,7 @@ children = ( 4716A5132B41CAD000419052 /* LiveActivityManager.swift */, 477B2C7D2B432775002F64A4 /* LiveActivityType.swift */, + 47D08D5D2B54390B00B0BEA7 /* LiveActivityNotificationSizeType.swift */, ); path = LiveActivity; sourceTree = ""; @@ -3574,12 +3585,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 47C210F02B52A05B00005711 /* GlucoseChartView.swift in Sources */, 4716A5052B40709E00419052 /* XDripWidgetAttributes.swift in Sources */, 4716A4F62B406C3D00419052 /* XDripWidgetLiveActivity.swift in Sources */, + 47D08D5C2B54386600B0BEA7 /* GlucoseChartWidgetType.swift in Sources */, 4716A4F42B406C3D00419052 /* XDripWidgetBundle.swift in Sources */, 4716A50A2B416E6500419052 /* Double.swift in Sources */, + 47D08D5F2B54390B00B0BEA7 /* LiveActivityNotificationSizeType.swift in Sources */, 4716A4F82B406C3D00419052 /* XDripWidget.swift in Sources */, 4716A50D2B416EE100419052 /* ConstantsWidget.swift in Sources */, + 47B2F77B2B53DBA60038E0B6 /* Date.swift in Sources */, 4716A50E2B41707D00419052 /* TextsWidget.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3781,6 +3796,7 @@ F804870D2336D90200EBDDB7 /* M5Stack+CoreDataProperties.swift in Sources */, F85FF3CD252F9FD7004E6FF1 /* SnoozeParameters+CoreDataProperties.swift in Sources */, F8F9722F23A5915900C3F17D /* M5StackPacket.swift in Sources */, + 47D08D5A2B54373600B0BEA7 /* GlucoseChartWidgetType.swift in Sources */, F821CF8E22AB090C005C1E43 /* DatePickerViewController.swift in Sources */, F8AF11F324B1279500AE5BA2 /* TextsLibreErrors.swift in Sources */, F8691888239CEEFA0065B607 /* BluetoothPeripheralViewModel.swift in Sources */, @@ -3812,6 +3828,7 @@ F8E3A2A923D906C200E5E98A /* WatchManager.swift in Sources */, F8B3A80A227A3D11004BA588 /* TextsAlertTypeSettings.swift in Sources */, F85FF39125288870004E6FF1 /* HouseKeeper.swift in Sources */, + 47D08D5E2B54390B00B0BEA7 /* LiveActivityNotificationSizeType.swift in Sources */, F8CB59C42739D1CD00BA199E /* DexcomBackfillTxMessage.swift in Sources */, F8F9721823A5915900C3F17D /* CGMBlueReaderTransmitter.swift in Sources */, F8252867243E50FE0067AF77 /* ConstantsLibre.swift in Sources */, @@ -3819,6 +3836,7 @@ F8CB59D3274D94AF00BA199E /* DexcomSessionStartTxMessage.swift in Sources */, F80ED2ED236F68F90005C035 /* SettingsViewM5StackGeneralSettingsViewModel.swift in Sources */, F816E10A2437E7B8009EE65B /* CGMBlueReaderTransmitterDelegate.swift in Sources */, + 47D08D5B2B5437F500B0BEA7 /* ConstantsWidget.swift in Sources */, F8B3A850227F26F8004BA588 /* AlertTypesSettingsViewController.swift in Sources */, D484BC292774F783008490E9 /* TreatmentsInsertViewController.swift in Sources */, F816E0FE24367338009EE65B /* GNSEntry+BluetoothPeripheral.swift in Sources */, diff --git a/xdrip/Extensions/UserDefaults.swift b/xdrip/Extensions/UserDefaults.swift index 266e83e1..9cc5ec90 100644 --- a/xdrip/Extensions/UserDefaults.swift +++ b/xdrip/Extensions/UserDefaults.swift @@ -73,6 +73,8 @@ extension UserDefaults { case notificationInterval = "notificationInterval" /// which type of live activities should be shown? case liveActivityType = "liveActivityType" + /// which size should the live activities be shown? + case liveActivityNotificationSizeType = "liveActivityNotificationSizeType" // Home Screen and main chart settings @@ -633,6 +635,18 @@ extension UserDefaults { } } + /// holds the enum integer of the type of live activity to be shown + /// default to 0 (normal) + var liveActivityNotificationSizeType: LiveActivityNotificationSizeType { + get { + let liveActivityNotificationSizeTypeAsInt = integer(forKey: Key.liveActivityNotificationSizeType.rawValue) + return LiveActivityNotificationSizeType(rawValue: liveActivityNotificationSizeTypeAsInt) ?? .normal + } + set { + set(newValue.rawValue, forKey: Key.liveActivityNotificationSizeType.rawValue) + } + } + // MARK: Home Screen Settings diff --git a/xdrip/Managers/LiveActivity/LiveActivityManager.swift b/xdrip/Managers/LiveActivity/LiveActivityManager.swift index 3da23aaa..4d359fc1 100644 --- a/xdrip/Managers/LiveActivity/LiveActivityManager.swift +++ b/xdrip/Managers/LiveActivity/LiveActivityManager.swift @@ -32,7 +32,8 @@ extension LiveActivityManager { /// start or update the live activity based upon whether it currently exists or not /// - Parameter contentState: the contentState to show - func runActivity(contentState: XDripWidgetAttributes.ContentState) { + /// - Parameter forceRestart: will force the current live activity to end and a new one will be started + func runActivity(contentState: XDripWidgetAttributes.ContentState, forceRestart: Bool) { trace("In runActivity", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) @@ -42,8 +43,17 @@ extension LiveActivityManager { trace(" eventActivity == nil, trying to start new activity", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) startActivity(contentState: contentState) + } else if forceRestart { + print("ending current activity and starting a new one") + trace(" eventActivity != nil, will attempt to end the current activity and start a new one", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) + + Task { + endActivity() + + startActivity(contentState: contentState) + } } else { - trace(" eventActivity != nil, trying to update existing", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) + trace(" eventActivity != nil, trying to update existing activity", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) Task { await updateActivity(to: contentState) @@ -66,7 +76,8 @@ extension LiveActivityManager { content: content, pushType: nil ) - print("New activity started: \(String(describing: eventActivity?.id))") + + print("New live activity started: \(String(describing: eventActivity?.id))") let idString = "\(String(describing: eventActivity?.id))" trace("New live activity started: %{public}@", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info, idString) @@ -85,7 +96,7 @@ extension LiveActivityManager { Task { print("Live activity state is \(String(describing: eventActivity?.activityState))") - trace("Previous live activity was dismissed so will end it and try to start a new one.", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) + trace("Previous live activity was dismissed by the user so it will be ended and will try to start a new one.", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) endAllActivities() @@ -94,12 +105,26 @@ extension LiveActivityManager { return } else { - await eventActivity?.update( - ActivityContent( - state: contentState, - staleDate: Date().addingTimeInterval(10) + + // check if the activity is not about to be orphaned by iOS (after 8 hours) and left on the screen with the initial state. If it is then end it. + if eventAttributes.eventStartDate > Date().addingTimeInterval(-3600 * 8) { + + await eventActivity?.update( + ActivityContent( + state: contentState, + staleDate: Date().addingTimeInterval(10) + ) ) - ) + + } else { + + trace("Live activity is 8 hours old so will no longer be able to be updated. Ending activity ", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info) + + endActivity() + + startActivity(contentState: contentState) + + } } } @@ -108,13 +133,15 @@ extension LiveActivityManager { if eventActivity != nil { Task { - print("Ending live activity: \(String(describing: eventActivity?.id))") - - let idString = "\(String(describing: eventActivity?.id))" - trace("Ending live activity: %{public}@", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info, idString) - - await eventActivity?.end(nil, dismissalPolicy: .immediate) - eventActivity = nil + for activity in Activity.activities + { + + let idString = "\(String(describing: eventActivity?.id))" + trace("Ending live activity: %{public}@", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info, idString) + + await activity.end(nil, dismissalPolicy: .immediate) + eventActivity = nil + } } } } @@ -130,10 +157,10 @@ extension LiveActivityManager { { for activity in Activity.activities { - print("Force-close detected. Ending Live Activity: \(activity.id)") + print("Ending live activity: \(activity.id)") let idString = "\(String(describing: eventActivity?.id))" - trace("Force-close detected. Ending live activity: %{public}@", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info, idString) + trace("Ending live activity: %{public}@", log: self.log, category: ConstantsLog.categoryLiveActivityManager, type: .info, idString) await activity.end(nil, dismissalPolicy: .immediate) eventActivity = nil diff --git a/xdrip/Managers/LiveActivity/LiveActivityNotificationSizeType.swift b/xdrip/Managers/LiveActivity/LiveActivityNotificationSizeType.swift new file mode 100644 index 00000000..27a866a8 --- /dev/null +++ b/xdrip/Managers/LiveActivity/LiveActivityNotificationSizeType.swift @@ -0,0 +1,51 @@ +// +// LiveActivityNotificationSizeType.swift +// xdrip +// +// Created by Paul Plant on 14/1/24. +// Copyright © 2024 Johan Degraeve. All rights reserved. +// + +import Foundation + +/// holds and returns the different parameters used for creating the images for different widget types +public enum LiveActivityNotificationSizeType: Int, CaseIterable { + + // when adding LiveActivityNotificationSizeType, add new cases at the end (ie 3, ...) + // if this is done in the middle then a database migration would be required, because the rawvalue is stored as Int16 in the coredata + // the order of the data source types will in the uiview is determined by the initializer init(forRowAt row: Int) + + case normal = 0 + case minimal = 1 + case large = 2 + + var description: String { + switch self { + case .normal: + return "Normal" + case .minimal: + return "Minimal" + case .large: + return "Large" + } + } + + /// gives the raw value of the LiveActivityNotificationSizeType for a specific section in a uitableview, is the opposite of the initializer + static func LiveActivityNotificationSizeTypeRawValue(rawValue: Int) -> Int { + + switch rawValue { + + case 0:// normal + return 0 + case 1:// minimal + return 1 + case 2:// large + return 2 + default: + fatalError("in LiveActivityNotificationSizeType, unknown case") + + } + + } + +} diff --git a/xdrip/Storyboards/Base.lproj/Main.storyboard b/xdrip/Storyboards/Base.lproj/Main.storyboard index 670b147e..18dc64c7 100644 --- a/xdrip/Storyboards/Base.lproj/Main.storyboard +++ b/xdrip/Storyboards/Base.lproj/Main.storyboard @@ -423,12 +423,6 @@ - - - - - - @@ -445,14 +439,14 @@ -