diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 93619d31..57f3329d 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 47ADD2E127FB05EB0025E2F4 /* ChartPointsScatterDownTrianglesWithDropdownLineLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47ADD2E027FB05EB0025E2F4 /* ChartPointsScatterDownTrianglesWithDropdownLineLayer.swift */; }; 47B60F3726F389E2003198D3 /* LandscapeChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B60F3626F389E2003198D3 /* LandscapeChartViewController.swift */; }; 47B7FC722B00CF4B004C872B /* FollowerBackgroundKeepAliveType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B7FC712B00CF4B004C872B /* FollowerBackgroundKeepAliveType.swift */; }; + 47CF18B22B37689A00FA6160 /* TimeInRangeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47CF18B12B37689A00FA6160 /* TimeInRangeType.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 */; }; @@ -742,6 +743,7 @@ 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 = ""; }; + 47CF18B12B37689A00FA6160 /* TimeInRangeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInRangeType.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 = ""; }; @@ -1670,6 +1672,7 @@ isa = PBXGroup; children = ( 47FB28072636B04200042FFB /* StatisticsManager.swift */, + 47CF18B12B37689A00FA6160 /* TimeInRangeType.swift */, ); path = Statistics; sourceTree = ""; @@ -3862,6 +3865,7 @@ F8F9722D23A5915900C3F17D /* M5StackBluetoothTransmitter.swift in Sources */, F808D2C8240323CA0084B5DB /* BubbleBluetoothPeripheralViewModel.swift in Sources */, F8E6C79024CEC22A007C1199 /* TextsSnooze.swift in Sources */, + 47CF18B22B37689A00FA6160 /* TimeInRangeType.swift in Sources */, F8F9722C23A5915900C3F17D /* GlucoseData.swift in Sources */, F8A389E7232ECE7E0010F405 /* SettingsViewUtilities.swift in Sources */, F8A2BC3125DB0D6D001D1E78 /* BluetoothPeripheralManager+M5StackBluetoothTransmitterDelegate.swift in Sources */, diff --git a/xdrip/Extensions/UserDefaults.swift b/xdrip/Extensions/UserDefaults.swift index dd099645..11960ced 100644 --- a/xdrip/Extensions/UserDefaults.swift +++ b/xdrip/Extensions/UserDefaults.swift @@ -125,6 +125,8 @@ extension UserDefaults { case daysToUseStatistics = "daysToUseStatistics" /// use IFCC way to show A1C? case useIFCCA1C = "useIFCCA1C" + /// which type of TIR calculation is selected? + case timeInRangeType = "timeInRangeType" /// no longer used, but will leave it here to prevent compiler coredata warnings case useStandardStatisticsRange = "useStandardStatisticsRange" /// use the newer TITR of 70-140mg/dL to calculate the statistics? If false, we will use the conventional TIR of 70-180mg/dL @@ -1171,25 +1173,15 @@ extension UserDefaults { } } - /// no longer used, but will leave it here to prevent compiler coredata warnings - @objc dynamic var useStandardStatisticsRange: Bool { - // default value for bool in userdefaults is false, by default we want the statistics view to calculate using the older-style, standardised TIR values (false) + /// holds the enum integer of the time in range calculation type + /// it will default to 0 which is standard + var timeInRangeType: TimeInRangeType { get { - return bool(forKey: Key.useTITRStatisticsRange.rawValue) + let timeInRangeTypeAsInt = integer(forKey: Key.timeInRangeType.rawValue) + return TimeInRangeType(rawValue: timeInRangeTypeAsInt) ?? .standardRange } set { - set(newValue, forKey: Key.useTITRStatisticsRange.rawValue) - } - } - - /// use the newer TITR range of 70-140mg/dL to calculate the statistics? If false, we will use the conventional TIR range of 70-180mg/dL - @objc dynamic var useTITRStatisticsRange: Bool { - // default value for bool in userdefaults is false, by default we want the statistics view to calculate using the older-style, standardised TIR values (false) - get { - return bool(forKey: Key.useTITRStatisticsRange.rawValue) - } - set { - set(newValue, forKey: Key.useTITRStatisticsRange.rawValue) + set(newValue.rawValue, forKey: Key.timeInRangeType.rawValue) } } diff --git a/xdrip/Managers/Statistics/StatisticsManager.swift b/xdrip/Managers/Statistics/StatisticsManager.swift index d0795b14..c9768668 100644 --- a/xdrip/Managers/Statistics/StatisticsManager.swift +++ b/xdrip/Managers/Statistics/StatisticsManager.swift @@ -121,7 +121,7 @@ public final class StatisticsManager { } } - + /* // let's set up the which values will be used to calculate TIR. It can be either the standardised "Time in Range" values or the newer "Time in Tight Range" values. let useTITR: Bool = UserDefaults.standard.useTITRStatisticsRange @@ -132,6 +132,9 @@ public final class StatisticsManager { lowLimitForTIR = useTITR ? ConstantsStatistics.standardisedLowValueForTITRInMmol : ConstantsStatistics.standardisedLowValueForTIRInMmol highLimitForTIR = useTITR ? ConstantsStatistics.standardisedHighValueForTITRInMmol : ConstantsStatistics.standardisedHighValueForTIRInMmol } + */ + lowLimitForTIR = UserDefaults.standard.timeInRangeType.lowerLimit + highLimitForTIR = UserDefaults.standard.timeInRangeType.higherLimit // make sure that there exist elements in the glucoseValue array before trying to process statistics calculations or we could get a fatal divide by zero error/crash if glucoseValues.count > 0 { diff --git a/xdrip/Managers/Statistics/TimeInRangeType.swift b/xdrip/Managers/Statistics/TimeInRangeType.swift new file mode 100644 index 00000000..2c7e412c --- /dev/null +++ b/xdrip/Managers/Statistics/TimeInRangeType.swift @@ -0,0 +1,81 @@ +// +// TimeInRangeType.swift +// xdrip +// +// Created by Paul Plant on 23/12/23. +// Copyright © 2023 Johan Degraeve. All rights reserved. +// + +import Foundation + + +/// types of background keep-alive +public enum TimeInRangeType: Int, CaseIterable { + + // when adding to TimeInRangeType, 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 standardRange = 0 + case tightRange = 1 + case userDefinedRange = 2 + + var description: String { + switch self { + case .standardRange: + return Texts_SettingsView.timeInRangeTypeStandardRange + case .tightRange: + return Texts_SettingsView.timeInRangeTypeTightRange + case .userDefinedRange: + return Texts_SettingsView.timeInRangeTypeUserDefinedRange + } + } + + var title: String { + switch self { + case .standardRange: + return Texts_Common.inRangeStatistics + case .tightRange: + return Texts_Common.inTightRangeStatistics + case .userDefinedRange: + return Texts_Common.userRangeStatistics + } + } + + var lowerLimit: Double { + + let isMgDl = UserDefaults.standard.bloodGlucoseUnitIsMgDl + + switch self { + case .standardRange: + return isMgDl ? ConstantsStatistics.standardisedLowValueForTIRInMgDl : ConstantsStatistics.standardisedLowValueForTIRInMmol + case .tightRange: + return isMgDl ? ConstantsStatistics.standardisedLowValueForTITRInMgDl : ConstantsStatistics.standardisedLowValueForTITRInMmol + case .userDefinedRange: + return UserDefaults.standard.lowMarkValueInUserChosenUnit + } + } + + var higherLimit: Double { + + let isMgDl = UserDefaults.standard.bloodGlucoseUnitIsMgDl + + switch self { + case .standardRange: + return isMgDl ? ConstantsStatistics.standardisedHighValueForTIRInMgDl : ConstantsStatistics.standardisedHighValueForTIRInMmol + case .tightRange: + return isMgDl ? ConstantsStatistics.standardisedHighValueForTITRInMgDl : ConstantsStatistics.standardisedHighValueForTITRInMmol + case .userDefinedRange: + return UserDefaults.standard.highMarkValueInUserChosenUnit + } + } + + func rangeString() -> String { + + let isMgDl = UserDefaults.standard.bloodGlucoseUnitIsMgDl + + return " (" + self.lowerLimit.bgValuetoString(mgdl: isMgDl) + "-" + self.higherLimit.bgValuetoString(mgdl: isMgDl) + ")" + + } + +} diff --git a/xdrip/Storyboards/da.lproj/SettingsViews.strings b/xdrip/Storyboards/da.lproj/SettingsViews.strings index ee8b40a4..aa571531 100644 --- a/xdrip/Storyboards/da.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/da.lproj/SettingsViews.strings @@ -30,7 +30,6 @@ "settingsviews_showStatistics" = "Show Statistics"; "settingsviews_daysToUseStatisticsTitle" = "Days to Calculate"; "settingsviews_daysToUseStatisticsMessage" = "Maximum days we should try to use to calculate the statistics?\n\n(Enter 0 to calculate today since midnight)"; -"settingsviews_useTITRStatisticsRange" = "Use Time in Tight Range"; "settingsviews_useIFCCA1C" = "Show HbA1c in mmols/mol"; "settingsviews_sectiontitletransmitter" = "Transmitter"; "settingsviews_transmittertype" = "Transmitter Type"; diff --git a/xdrip/Storyboards/en.lproj/Common.strings b/xdrip/Storyboards/en.lproj/Common.strings index 04db60a8..5c7795ae 100644 --- a/xdrip/Storyboards/en.lproj/Common.strings +++ b/xdrip/Storyboards/en.lproj/Common.strings @@ -42,6 +42,7 @@ "common_statistics_low" = "Low"; "common_statistics_inRange" = "In Range"; "common_statistics_inTightRange" = "Tight Range"; +"common_statistics_userRange" = "User Range"; "common_statistics_high" = "High"; "common_statistics_average" = "Average"; "common_statistics_a1c" = "HbA1c"; diff --git a/xdrip/Storyboards/en.lproj/SettingsViews.strings b/xdrip/Storyboards/en.lproj/SettingsViews.strings index afa23208..56f99fca 100644 --- a/xdrip/Storyboards/en.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/en.lproj/SettingsViews.strings @@ -60,7 +60,10 @@ "settingsviews_showStatistics" = "Show Statistics"; "settingsviews_daysToUseStatisticsTitle" = "Days to Calculate"; "settingsviews_daysToUseStatisticsMessage" = "Maximum days we should try to use to calculate the statistics?\n\n(Enter 0 to calculate today since midnight)"; -"settingsviews_useTITRStatisticsRange" = "Use Time in Tight Range"; +"settingsviews_labelTimeInRangeType" = "Time In Range Type"; +"settingsviews_timeInRangeTypeStandardRange" = "Standard Range"; +"settingsviews_timeInRangeTypeTightRange" = "Tight Range"; +"settingsviews_timeInRangeTypeUserDefinedRange" = "User Range"; "settingsviews_useIFCCA1C" = "Show HbA1c in mmols/mol"; "settingsviews_sectiontitletransmitter" = "Transmitter"; "settingsviews_transmittertype" = "Transmitter Type"; diff --git a/xdrip/Storyboards/es.lproj/Common.strings b/xdrip/Storyboards/es.lproj/Common.strings index a00af25b..73fbc013 100644 --- a/xdrip/Storyboards/es.lproj/Common.strings +++ b/xdrip/Storyboards/es.lproj/Common.strings @@ -42,6 +42,7 @@ "common_statistics_low" = "Baja"; "common_statistics_inRange" = "En Rango"; "common_statistics_inTightRange" = "Estrecho"; +"common_statistics_userRange" = "Usuario"; "common_statistics_high" = "Alta"; "common_statistics_average" = "Promedio"; "common_statistics_a1c" = "HbA1c"; diff --git a/xdrip/Storyboards/es.lproj/SettingsViews.strings b/xdrip/Storyboards/es.lproj/SettingsViews.strings index c0f29f56..83016045 100644 --- a/xdrip/Storyboards/es.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/es.lproj/SettingsViews.strings @@ -55,7 +55,10 @@ "settingsviews_showStatistics" = "Mostrar Estadísticas"; "settingsviews_daysToUseStatisticsTitle" = "Días a Calcular"; "settingsviews_daysToUseStatisticsMessage" = "Máximos días a intentar usar para calcular las estadísticas?\n\n(Entrar 0 para calcular el día de hoy desde las 00:00hrs)"; -"settingsviews_useTITRStatisticsRange" = "Usar Tiempo en Rango Estrecho"; +"settingsviews_labelTimeInRangeType" = "Tipo de Tiempo en Rango"; +"settingsviews_timeInRangeTypeStandardRange" = "Rango Estándar"; +"settingsviews_timeInRangeTypeTightRange" = "Rango Estrecho"; +"settingsviews_timeInRangeTypeUserDefinedRange" = "Rango Usuario"; "settingsviews_useIFCCA1C" = "Ver HbA1c en mmols/mol"; "settingsviews_sectiontitletransmitter" = "Transmisor"; "settingsviews_transmittertype" = "Tipo de Transmisor"; diff --git a/xdrip/Storyboards/it.lproj/SettingsViews.strings b/xdrip/Storyboards/it.lproj/SettingsViews.strings index 8d21b80b..f67b77f2 100644 --- a/xdrip/Storyboards/it.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/it.lproj/SettingsViews.strings @@ -307,9 +307,6 @@ /// statistics settings, section title "settingsviews_sectiontitlestatistics" = "Statistics"; -/// statistics settings, use standard range? -"settingsviews_useTITRStatisticsRange" = "Use Time in Tight Range"; - /// statistics settings, use IFCC method for HbA1c? "settingsviews_useIFCCA1C" = "Show HbA1c in mmols/mol"; diff --git a/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings b/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings index 8d21b80b..f67b77f2 100644 --- a/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings @@ -307,9 +307,6 @@ /// statistics settings, section title "settingsviews_sectiontitlestatistics" = "Statistics"; -/// statistics settings, use standard range? -"settingsviews_useTITRStatisticsRange" = "Use Time in Tight Range"; - /// statistics settings, use IFCC method for HbA1c? "settingsviews_useIFCCA1C" = "Show HbA1c in mmols/mol"; diff --git a/xdrip/Storyboards/sl.lproj/SettingsViews.strings b/xdrip/Storyboards/sl.lproj/SettingsViews.strings index 60be3228..fc31decf 100644 --- a/xdrip/Storyboards/sl.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/sl.lproj/SettingsViews.strings @@ -307,9 +307,6 @@ /// statistics settings, section title "settingsviews_sectiontitlestatistics" = "Statistics"; -/// statistics settings, use standard range? -"settingsviews_useTITRStatisticsRange" = "Use Time in Tight Range"; - /// statistics settings, use IFCC method for HbA1c? "settingsviews_useIFCCA1C" = "Show HbA1c in mmols/mol"; diff --git a/xdrip/Storyboards/uk.lproj/SettingsViews.strings b/xdrip/Storyboards/uk.lproj/SettingsViews.strings index e82dfec4..cf105e3b 100644 --- a/xdrip/Storyboards/uk.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/uk.lproj/SettingsViews.strings @@ -270,9 +270,6 @@ /// transmitter settings, this is for the button, when clicked then user will be requested to give transmitter id. The only difference with settingsviews_transmitterid is that ':' is not added "settingsviews_transmitterid_text_for_button" = "Transmitter ID"; -/// statistics settings, use standard range? -"settingsviews_useTITRStatisticsRange" = "Use Time in Tight Range"; - /// dexcom share settings, where user can select if readings should be uploaded to dexcom share yes or no "settingsviews_uploadReadingstoDexcomShare" = "Upload to Dexcom Share"; diff --git a/xdrip/Storyboards/zh.lproj/SettingsViews.strings b/xdrip/Storyboards/zh.lproj/SettingsViews.strings index db251cbf..87fadebe 100644 --- a/xdrip/Storyboards/zh.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/zh.lproj/SettingsViews.strings @@ -231,9 +231,6 @@ /// statistics settings, section title "settingsviews_sectiontitlestatistics" = "Statistics"; -/// statistics settings, use standard range? -"settingsviews_useTITRStatisticsRange" = "Use Time in Tight Range"; - /// statistics settings, use IFCC method for HbA1c? "settingsviews_useIFCCA1C" = "Show HbA1c in mmols/mol"; diff --git a/xdrip/Texts/TextsCommon.swift b/xdrip/Texts/TextsCommon.swift index 2c99928b..e1c2a844 100644 --- a/xdrip/Texts/TextsCommon.swift +++ b/xdrip/Texts/TextsCommon.swift @@ -190,6 +190,10 @@ class Texts_Common { return NSLocalizedString("common_statistics_inTightRange", tableName: filename, bundle: Bundle.main, value: "Tight Range", comment: "the words in tight range") }() + static let userRangeStatistics = { + return NSLocalizedString("common_statistics_userRange", tableName: filename, bundle: Bundle.main, value: "User Range", comment: "the words in user range") + }() + static let highStatistics = { return NSLocalizedString("common_statistics_high", tableName: filename, bundle: Bundle.main, value: "High", comment: "the word high") }() diff --git a/xdrip/Texts/TextsSettingsView.swift b/xdrip/Texts/TextsSettingsView.swift index 157ce6dc..0a12a7d2 100644 --- a/xdrip/Texts/TextsSettingsView.swift +++ b/xdrip/Texts/TextsSettingsView.swift @@ -272,8 +272,20 @@ class Texts_SettingsView { return NSLocalizedString("settingsviews_daysToUseStatisticsMessage", tableName: filename, bundle: Bundle.main, value: "How many days should we use to calculate the statistics? (Enter 0 to calculate today since midnight)", comment: "statistics settings, how many days to use for calculations") }() - static let labelUseTITRStatisticsRange: String = { - return NSLocalizedString("settingsviews_useTITRStatisticsRange", tableName: filename, bundle: Bundle.main, value: "Use Time in Tight Range", comment: "statistics settings, prefer time in tight range") + static let labelTimeInRangeType: String = { + return NSLocalizedString("settingsviews_labelTimeInRangeType", tableName: filename, bundle: Bundle.main, value: "Time In Range Type", comment: "statistics settings, the type of time in range selected") + }() + + static let timeInRangeTypeStandardRange: String = { + return NSLocalizedString("settingsviews_timeInRangeTypeStandardRange", tableName: filename, bundle: Bundle.main, value: "Standard Range", comment: "statistics settings, prefer standard time in range") + }() + + static let timeInRangeTypeTightRange: String = { + return NSLocalizedString("settingsviews_timeInRangeTypeTightRange", tableName: filename, bundle: Bundle.main, value: "Tight Range", comment: "statistics settings, prefer time in tight range") + }() + + static let timeInRangeTypeUserDefinedRange: String = { + return NSLocalizedString("settingsviews_timeInRangeTypeUserDefinedRange", tableName: filename, bundle: Bundle.main, value: "User Range", comment: "statistics settings, prefer user-defined range") }() static let labelUseIFFCA1C: String = { diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index 875fd02f..fcd63c6f 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -375,47 +375,61 @@ final class RootViewController: UIViewController, ObservableObject { @IBOutlet var miniChartDoubleTapGestureRecognizer: UITapGestureRecognizer! - /// can be used as a shortcut to switch between TIR and TITR calculation methods. The user will be notified of the change via UI transitions to show what has changed in the calculation limits + /// can be used as a shortcut to switch between different TIR calculation methods. The user will be notified of the change via UI transitions to show what has changed in the calculation limits @IBAction func statisticsViewDoubleTapGestureRecognizer(_ sender: UITapGestureRecognizer) { - // the userdefault will be changed due to the double tap - let useTITR = !UserDefaults.standard.useTITRStatisticsRange + let previousTimeInRangeType = UserDefaults.standard.timeInRangeType - UserDefaults.standard.useTITRStatisticsRange = useTITR + var newTimeInRangeType: TimeInRangeType = previousTimeInRangeType + + // if we're at the last index in the enum, then go to the first one + // otherwise, just set to the next index + if previousTimeInRangeType == TimeInRangeType.allCases.last { + + newTimeInRangeType = .standardRange + + } else { + + newTimeInRangeType = TimeInRangeType(rawValue: previousTimeInRangeType.rawValue + 1) ?? .tightRange + + } + + // write the new index back to userdefaults (this will also trigger the observers to update the UI) + UserDefaults.standard.timeInRangeType = newTimeInRangeType updateStatistics(animate: false) let normalTitleColor: UIColor = lowTitleLabelOutlet.textColor - let normalLimitValueColor: UIColor = lowLabelOutlet.textColor inRangeTitleLabelOutlet.textColor = ConstantsStatistics.highlightColorTitles - if ConstantsStatistics.standardisedLowValueForTIRInMgDl != ConstantsStatistics.standardisedLowValueForTITRInMgDl { + if previousTimeInRangeType.lowerLimit != newTimeInRangeType.lowerLimit { self.lowLabelOutlet.textColor = ConstantsStatistics.labelLowColor + } - if ConstantsStatistics.standardisedHighValueForTIRInMgDl != ConstantsStatistics.standardisedHighValueForTITRInMgDl { + if previousTimeInRangeType.higherLimit != newTimeInRangeType.higherLimit { self.highLabelOutlet.textColor = ConstantsStatistics.labelHighColor } // wait a short while and then fade the labels back out - // even if the label colours weren't changed, it's easier to just fade them all every time. + // even if some of the label colours weren't changed, it's easier to just fade them all every time. DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { - UIView.transition(with: self.inRangeTitleLabelOutlet, duration: 2, options: .transitionCrossDissolve, animations: { + UIView.transition(with: self.inRangeTitleLabelOutlet, duration: 1, options: .transitionCrossDissolve, animations: { self.inRangeTitleLabelOutlet.textColor = normalTitleColor }) - UIView.transition(with: self.lowLabelOutlet, duration: 2, options: .transitionCrossDissolve, animations: { - self.lowLabelOutlet.textColor = normalLimitValueColor + UIView.transition(with: self.lowLabelOutlet, duration: 1, options: .transitionCrossDissolve, animations: { + self.lowLabelOutlet.textColor = .lightGray }) - UIView.transition(with: self.highLabelOutlet, duration: 2, options: .transitionCrossDissolve, animations: { - self.highLabelOutlet.textColor = normalLimitValueColor + UIView.transition(with: self.highLabelOutlet, duration: 1, options: .transitionCrossDissolve, animations: { + self.highLabelOutlet.textColor = .lightGray }) } @@ -2609,7 +2623,7 @@ final class RootViewController: UIViewController, ObservableObject { // set the title labels to their correct localization self.lowTitleLabelOutlet.text = Texts_Common.lowStatistics - self.inRangeTitleLabelOutlet.text = UserDefaults.standard.useTITRStatisticsRange ? Texts_Common.inTightRangeStatistics : Texts_Common.inRangeStatistics + self.inRangeTitleLabelOutlet.text = UserDefaults.standard.timeInRangeType.title self.highTitleLabelOutlet.text = Texts_Common.highStatistics self.averageTitleLabelOutlet.text = Texts_Common.averageStatistics self.a1cTitleLabelOutlet.text = Texts_Common.a1cStatistics diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewStatisticsSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewStatisticsSettingsViewModel.swift index c3d84592..f08782f3 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewStatisticsSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewStatisticsSettingsViewModel.swift @@ -15,8 +15,8 @@ fileprivate enum Setting:Int, CaseIterable { //show the statistics on the home screen? case showStatistics = 0 - //should we use the standard range for TIR, or the newer Time in Tighter Range values? - case useTITRStatisticsRange = 1 + //which TIR type should be used? + case timeInRangeType = 1 //urgent low value case useIFCCA1C = 2 @@ -24,7 +24,15 @@ fileprivate enum Setting:Int, CaseIterable { } /// conforms to SettingsViewModelProtocol for all general settings in the first sections screen -struct SettingsViewStatisticsSettingsViewModel:SettingsViewModelProtocol { +class SettingsViewStatisticsSettingsViewModel: NSObject, SettingsViewModelProtocol { + + override init() { + + super.init() + + addObservers() + + } func uiView(index: Int) -> UIView? { @@ -35,8 +43,8 @@ struct SettingsViewStatisticsSettingsViewModel:SettingsViewModelProtocol { case .showStatistics: return UISwitch(isOn: UserDefaults.standard.showStatistics, action: {(isOn:Bool) in UserDefaults.standard.showStatistics = isOn}) - case .useTITRStatisticsRange : - return UISwitch(isOn: UserDefaults.standard.useTITRStatisticsRange, action: {(isOn:Bool) in UserDefaults.standard.useTITRStatisticsRange = isOn}) + case .timeInRangeType: + return nil case .useIFCCA1C : return UISwitch(isOn: UserDefaults.standard.useIFCCA1C, action: {(isOn:Bool) in UserDefaults.standard.useIFCCA1C = isOn}) @@ -45,12 +53,21 @@ struct SettingsViewStatisticsSettingsViewModel:SettingsViewModelProtocol { } 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.timeInRangeType.rawValue) {return true} + return false } + var sectionReloadClosure: (() -> Void)? func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {} + func storeSectionReloadClosure(sectionReloadClosure: @escaping (() -> Void)) { + self.sectionReloadClosure = sectionReloadClosure + } + func storeUIViewController(uIViewController: UIViewController) {} func storeMessageHandler(messageHandler: ((String, String) -> Void)) { @@ -65,34 +82,59 @@ struct SettingsViewStatisticsSettingsViewModel:SettingsViewModelProtocol { guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } switch setting { + + case .showStatistics: + return SettingsSelectedRowAction.callFunction(function: { + if UserDefaults.standard.showStatistics { + UserDefaults.standard.showStatistics = false + } else { + UserDefaults.standard.showStatistics = true + } + }) + + case .timeInRangeType: + + // data to be displayed in list from which user needs to pick a screen dimming type + var data = [String]() + + var selectedRow: Int? + + var index = 0 + + let currentTimeInRangeType = UserDefaults.standard.timeInRangeType + + // get all data source types and add the description to data. Search for the type that matches the ScreenLockDimmingType that is currently stored in userdefaults. + for timeInRangeType in TimeInRangeType.allCases { - case .showStatistics: - return SettingsSelectedRowAction.callFunction(function: { - if UserDefaults.standard.showStatistics { - UserDefaults.standard.showStatistics = false - } else { - UserDefaults.standard.showStatistics = true - } - }) + data.append(timeInRangeType.description + timeInRangeType.rangeString()) + + if timeInRangeType == currentTimeInRangeType { + selectedRow = index + } + + index += 1 + + } + + return SettingsSelectedRowAction.selectFromList(title: Texts_SettingsView.labelTimeInRangeType, data: data, selectedRow: selectedRow, actionTitle: nil, cancelTitle: nil, actionHandler: {(index:Int) in + + if index != selectedRow { - case .useTITRStatisticsRange: - return SettingsSelectedRowAction.callFunction(function: { - if UserDefaults.standard.useTITRStatisticsRange { - UserDefaults.standard.useTITRStatisticsRange = false - } else { - UserDefaults.standard.useTITRStatisticsRange = true - } - }) - - case .useIFCCA1C: - return SettingsSelectedRowAction.callFunction(function: { - if UserDefaults.standard.useIFCCA1C { - UserDefaults.standard.useIFCCA1C = false - } else { - UserDefaults.standard.useIFCCA1C = true - } - }) + UserDefaults.standard.timeInRangeType = TimeInRangeType(rawValue: index) ?? .standardRange + + } + }, cancelHandler: nil, didSelectRowHandler: nil) + + case .useIFCCA1C: + return SettingsSelectedRowAction.callFunction(function: { + if UserDefaults.standard.useIFCCA1C { + UserDefaults.standard.useIFCCA1C = false + } else { + UserDefaults.standard.useIFCCA1C = true + } + }) + } } @@ -118,8 +160,8 @@ struct SettingsViewStatisticsSettingsViewModel:SettingsViewModelProtocol { case .showStatistics: return Texts_SettingsView.labelShowStatistics - case .useTITRStatisticsRange: - return Texts_SettingsView.labelUseTITRStatisticsRange + case .timeInRangeType: + return Texts_SettingsView.labelTimeInRangeType case .useIFCCA1C: return Texts_SettingsView.labelUseIFFCA1C @@ -132,9 +174,12 @@ struct SettingsViewStatisticsSettingsViewModel:SettingsViewModelProtocol { switch setting { - case .showStatistics, .useTITRStatisticsRange, .useIFCCA1C: + case .showStatistics, .useIFCCA1C: return UITableViewCell.AccessoryType.none + case .timeInRangeType: + return UITableViewCell.AccessoryType.disclosureIndicator + } } @@ -143,9 +188,42 @@ struct SettingsViewStatisticsSettingsViewModel:SettingsViewModelProtocol { switch setting { - case .showStatistics, .useTITRStatisticsRange, .useIFCCA1C: + case .showStatistics, .useIFCCA1C: return nil + case .timeInRangeType: + return UserDefaults.standard.timeInRangeType.description + + } + } + + + // MARK: - observe functions + + private func addObservers() { + + // Listen for changes in the timeInRangeType to trigger the UI to be updated + UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.timeInRangeType.rawValue, options: .new, context: nil) + + } + + override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + + guard let keyPath = keyPath, + let keyPathEnum = UserDefaults.Key(rawValue: keyPath) + else { return } + + switch keyPathEnum { + case UserDefaults.Key.timeInRangeType: + + // we have to run this in the main thread to avoid access errors + DispatchQueue.main.async { + self.sectionReloadClosure?() + } + + default: + break + } }