diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index ce5ff20a..1fe49893 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 47503382247420A200D2260B /* BluetoothPeripheralView.strings in Resources */ = {isa = PBXBuildFile; fileRef = 47503384247420A200D2260B /* BluetoothPeripheralView.strings */; }; 4752B400263570DA0081D551 /* ConstantsStatistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4752B3FF263570DA0081D551 /* ConstantsStatistics.swift */; }; 4752B4062635878E0081D551 /* SettingsViewStatisticsSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4752B4052635878E0081D551 /* SettingsViewStatisticsSettingsViewModel.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 */; }; 47ADD2E127FB05EB0025E2F4 /* ChartPointsScatterDownTrianglesWithDropdownLineLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47ADD2E027FB05EB0025E2F4 /* ChartPointsScatterDownTrianglesWithDropdownLineLayer.swift */; }; @@ -673,6 +674,7 @@ 4752B3FF263570DA0081D551 /* ConstantsStatistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsStatistics.swift; sourceTree = ""; }; 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 = ""; }; + 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 = ""; }; @@ -2590,6 +2592,7 @@ 47ADD2DE27FAF8630025E2F4 /* ChartPointsScatterDownTrianglesLayer.swift */, 47ADD2E027FB05EB0025E2F4 /* ChartPointsScatterDownTrianglesWithDropdownLineLayer.swift */, F8BECB04235CE5D80060DAE1 /* GlucoseChartManager.swift */, + 477F45E5285B993100AC8475 /* GlucoseMiniChartManager.swift */, ); path = Charts; sourceTree = ""; @@ -3608,6 +3611,7 @@ F821CF8122A5C814005C1E43 /* RepeatingTimer.swift in Sources */, F80D915C24F06A40006840B5 /* PreLibre2.swift in Sources */, 470F021326DD515300C5D626 /* SettingsViewSensorCountdownSettingsViewModel.swift in Sources */, + 477F45E6285B993200AC8475 /* GlucoseMiniChartManager.swift in Sources */, F64039B5281E91500051EFFE /* TextsQuickActions.swift in Sources */, F8F9722223A5915900C3F17D /* CRC.swift in Sources */, F8CB59C02734976D00BA199E /* DexcomTransmitterTimeTxMessage.swift in Sources */, diff --git a/xdrip/Constants/ConstantsGlucoseChart.swift b/xdrip/Constants/ConstantsGlucoseChart.swift index 2a5eac25..1b8750e8 100644 --- a/xdrip/Constants/ConstantsGlucoseChart.swift +++ b/xdrip/Constants/ConstantsGlucoseChart.swift @@ -239,4 +239,16 @@ enum ConstantsGlucoseChart { /// dateformat for the date label in the 24 hours static landscape chart static let dateFormatLandscapeChart = "EEEE dd/MM/yyyy" + /// the amount of hours of bg readings that the mini-chart should show (first range) + static let miniChartHoursToShow1: Double = 24 + + /// the amount of hours of bg readings that the mini-chart should show (second range) + static let miniChartHoursToShow2: Double = 48 + + /// the size of the glucose circles used in the mini-chart + static let miniChartGlucoseCircleDiameter: CGFloat = 3 + + /// color for high and urgent low lines in the mini-chart + static let guidelineMiniChartHighLowColor = UIColor.white + } diff --git a/xdrip/Extensions/UserDefaults.swift b/xdrip/Extensions/UserDefaults.swift index af8c678b..1b196b5e 100644 --- a/xdrip/Extensions/UserDefaults.swift +++ b/xdrip/Extensions/UserDefaults.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit extension UserDefaults { @@ -43,6 +44,10 @@ extension UserDefaults { // Home Screen and main chart settings + /// should the screen/chart be allowed to rotate? + case showMiniChart = "showMiniChart" + /// hours to show on the mini-chart? + case miniChartHoursToShow = "miniChartHoursToShow" /// should the screen/chart be allowed to rotate? case allowScreenRotation = "allowScreenRotation" /// should the clock view be shown when the screen is locked? @@ -449,6 +454,58 @@ extension UserDefaults { // 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 + @objc dynamic var miniChartHoursToShow: Double { + get { + let returnValue = double(forKey: Key.miniChartHoursToShow.rawValue) + // if 0 set to defaultvalue + if returnValue == 0 { + set(ConstantsGlucoseChart.miniChartHoursToShow1, forKey: Key.miniChartHoursToShow.rawValue) + } + + return returnValue + } + set { + + set(newValue, forKey: Key.miniChartHoursToShow.rawValue) + } + } + + /// should the mini-chart be shown on the home screen? + @objc dynamic var showMiniChart: Bool { + + get { + + // check if the showMiniChart key has already been previously set. If so, then just return it + if let _ = UserDefaults.standard.object(forKey: "showMiniChart") { + + return !bool(forKey: Key.showMiniChart.rawValue) + + } else { + + // this means that this is the first time setting the showMiniChart key. To to avoid crowding the screen we want to only show the mini-chart by default if the user has display zoom disabled + if UIScreen.main.scale < UIScreen.main.nativeScale { + + set(true, forKey: Key.showMiniChart.rawValue) + + } else { + + // if not, then hide it by default + + set(false, forKey: Key.showMiniChart.rawValue) + + } + + return !bool(forKey: Key.showMiniChart.rawValue) + + } + } + set { + + set(!newValue, forKey: Key.showMiniChart.rawValue) + } + } + /// the urgenthighmarkvalue in unit selected by user ie, mgdl or mmol @objc dynamic var urgentHighMarkValueInUserChosenUnit:Double { get { @@ -809,7 +866,30 @@ extension UserDefaults { @objc dynamic var showStatistics: Bool { // default value for bool in userdefaults is false, by default we want the statistics view to show (true) get { - return !bool(forKey: Key.showStatistics.rawValue) + + // check if the showStatistics key has already been previously set. If so, then just return it + if let _ = UserDefaults.standard.object(forKey: "showStatistics") { + + return !bool(forKey: Key.showStatistics.rawValue) + + } else { + + // this means that this is the first time setting the showStatistics key. To to avoid crowding the screen we want to only show the statistics view by default if the user has display zoom disabled + if UIScreen.main.scale < UIScreen.main.nativeScale { + + set(true, forKey: Key.showStatistics.rawValue) + + } else { + + // if not, then hide it by default + + set(false, forKey: Key.showStatistics.rawValue) + + } + + return !bool(forKey: Key.showStatistics.rawValue) + + } } set { set(!newValue, forKey: Key.showStatistics.rawValue) diff --git a/xdrip/Managers/Charts/GlucoseMiniChartManager.swift b/xdrip/Managers/Charts/GlucoseMiniChartManager.swift new file mode 100644 index 00000000..27ae74df --- /dev/null +++ b/xdrip/Managers/Charts/GlucoseMiniChartManager.swift @@ -0,0 +1,491 @@ +// +// GlucoseMiniChartManager.swift +// xdrip +// +// Created by Paul Plant on 16/6/22. +// Copyright © 2022 Johan Degraeve. All rights reserved. +// + +import Foundation +import HealthKit +import SwiftCharts +import os.log +import UIKit +import CoreData + +public class GlucoseMiniChartManager { + + /// to hold range of glucose chartpoints + /// - urgentRange = above urgentHighMarkValue or below urgentLowMarkValue + /// - in range = between lowMarkValue and highMarkValue + /// - notUrgentRange = between highMarkValue and urgentHighMarkValue or between urgentLowMarkValue and lowMarkValue + /// - firstGlucoseChartPoint is the first ChartPoint considering the three arrays together + /// - lastGlucoseChartPoint is the last ChartPoint considering the three arrays together + /// - maximumValueInGlucoseChartPoints = the largest x value (ie the highest Glucose value) considering the three arrays together + typealias GlucoseChartPointsType = (urgentRange: [ChartPoint], inRange: [ChartPoint], notUrgentRange: [ChartPoint], maximumValueInGlucoseChartPoints: Double?) + + // MARK: - private properties + + /// glucoseChartPoints to reuse for each iteration, or for each redrawing of glucose chart + /// + /// Whenever glucoseChartPoints is assigned a new value, glucoseChart is set to nil + private var glucoseChartPoints: GlucoseChartPointsType = ([ChartPoint](), [ChartPoint](), [ChartPoint](), nil) { + didSet { + glucoseChart = nil + } + } + + /// ChartPoints to be shown on chart, procssed only in main thread - urgent Range + private var urgentRangeGlucoseChartPoints = [ChartPoint]() + + /// ChartPoints to be shown on chart, procssed only in main thread - in Range + private var inRangeGlucoseChartPoints = [ChartPoint]() + + /// ChartPoints to be shown on chart, procssed only in main thread - not Urgent Range + private var notUrgentRangeGlucoseChartPoints = [ChartPoint]() + + /// for logging + private var oslog = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryGlucoseChartManager) + + private var chartSettings: ChartSettings? + + private var chartLabelSettings: ChartLabelSettings? + + private var chartLabelSettingsHidden: ChartLabelSettings? + + private var chartGuideLinesLayerSettings: ChartGuideLinesLayerSettings? + + /// The latest date on the X-axis + private(set) var endDate: Date + + /// The earliest date on the X-axis + private var startDate: Date + + /// the (mini) chart with glucose values + private var glucoseChart: Chart? + + /// dateformatter for timestamp in chartpoints + private var chartPointDateFormatter: DateFormatter? + + /// timeformatter for horizontal axis label + private var axisLabelTimeFormatter: DateFormatter? + + /// a BgReadingsAccessor + private var bgReadingsAccessor: BgReadingsAccessor? + + /// a coreDataManager + private var coreDataManager: CoreDataManager + + /// difference in seconds between two pixels (or x values, not sure if it's pixels) +// private var diffInSecondsBetweenTwoPoints: Double { +// endDate.timeIntervalSince(startDate)/Double(innerFrameWidth) +// } + + /// innerFrame width + /// + /// default value 300.0 which is probably not correct but it can't be initiated as long as glusoseChart is not initialized, to avoid having to work with optional, i assign it to 300.0 + private var innerFrameWidth: Double = 300.0 + + /// used for getting bgreadings on a background thread, bgreadings are used to create list of chartPoints + private var operationQueue: OperationQueue? + + /// - the maximum value in glucoseChartPoints array between start and endPoint + /// - the value will never get smaller during the run time of the app + /// - in mgdl + private var maximumValueInGlucoseChartPointsInMgDl: Double = ConstantsGlucoseChart.absoluteMinimumChartValueInMgdl + + + // MARK: - intializer + init(coreDataManager: CoreDataManager) { + + // set coreDataManager and bgReadingsAccessor + self.coreDataManager = coreDataManager + + // now set the start date to the end date minus the amount of hours we want to show + startDate = Date().addingTimeInterval(-UserDefaults.standard.miniChartHoursToShow * 60 * 60) + + // set the end date to the current time + endDate = Date() + + } + + // MARK: - public functions + + /// - updates the chartPoints arrays , and the chartOutlet, and calls completionHandler when finished + /// - if called multiple times after each other there might be calls skipped, + /// - completionhandler will be called when chartOutlet is updated + /// - parameters: + /// - completionHandler : will be called when glucoseChartPoints and chartOutlet are updated + /// - endDate :endDate to apply + /// - coreDataManager : needed to create a private managed object context, which will be used to fetch readings from CoreData + /// + /// update of chartPoints array will be done on background thread. The actual redrawing of the chartoutlet is done on the main thread. Also the completionHandler runs in the main thread. + /// While updating glucoseChartPoints in background thread, the main thread may call again updateChartPoints with a new endDate (because a new value has arrived). A new block will be added in the operation queue and processed later. If there's multiple operations waiting in the queue, only the last one will be executed. + public func updateChartPoints(chartOutlet: BloodGlucoseChartView, completionHandler: (() -> ())?) { + + // create a new operation + let operation = BlockOperation(block: { + + // if there's more than one operation waiting for execution, it makes no sense to execute this one, the next one has a newer endDate to use + guard self.data().operationQueue.operations.count <= 1 else { + return + } + + // set the start date based upon the current time less the number of hours that we want to display + let startDate: Date = Date().addingTimeInterval(-UserDefaults.standard.miniChartHoursToShow * 60 * 60) + + // set the end date to now + let endDate: Date = Date() + + // we're going to check if we have already all chartpoints in the arrays self.glucoseChartPoints for the new start and date time. If not we're going to prepand a arrays and/or append a arrays + + // initialize new list of chartPoints to prepend with empty arrays + var glucoseChartPoints: GlucoseChartPointsType = ([ChartPoint](), [ChartPoint](), [ChartPoint](), nil) + + // get glucosePoints from coredata + glucoseChartPoints = self.getGlucoseChartPoints(startDate: startDate, endDate: endDate, bgReadingsAccessor: self.data().bgReadingsAccessor, on: self.coreDataManager.privateManagedObjectContext) + + self.maximumValueInGlucoseChartPointsInMgDl = self.getNewMaximumValueInGlucoseChartPoints(currentMaximumValueInGlucoseChartPoints: self.maximumValueInGlucoseChartPointsInMgDl, glucoseChartPoints: glucoseChartPoints) + + self.glucoseChartPoints.urgentRange = glucoseChartPoints.urgentRange + self.glucoseChartPoints.inRange = glucoseChartPoints.inRange + self.glucoseChartPoints.notUrgentRange = glucoseChartPoints.notUrgentRange + + DispatchQueue.main.async { + + // so we're in the main thread, now endDate and startDate and glucoseChartPoints can be safely assigned to value that was passed in the call to updateChartPoints + self.endDate = endDate + self.startDate = startDate + + // also assign urgentRangeGlucoseChartPoints, urgentRangeGlucoseChartPoints and urgentRangeGlucoseChartPoints to the corresponding arrays in glucoseChartPoints - can also be safely done because we're in the main thread + self.urgentRangeGlucoseChartPoints = self.glucoseChartPoints.urgentRange + self.inRangeGlucoseChartPoints = self.glucoseChartPoints.inRange + self.notUrgentRangeGlucoseChartPoints = self.glucoseChartPoints.notUrgentRange + + // update the chart outlet + chartOutlet.reloadChart() + + // call completionhandler if not nil + if let completionHandler = completionHandler { + completionHandler() + } + + } + + }) + + // add the operation to the queue and start it. As maxConcurrentOperationCount = 1, it may be kept until a previous operation has finished + data().operationQueue.addOperation { + operation.start() + } + + } + + public func cleanUpMemory() { + + trace("in cleanUpMemory", log: self.oslog, category: ConstantsLog.categoryGlucoseChartManager, type: .info) + + nillifyData() + + } + + public func glucoseChartWithFrame(_ frame: CGRect) -> Chart? { + + if let chart = glucoseChart, chart.frame != frame { + + trace("Glucose chart frame changed to %{public}@", log: self.oslog, category: ConstantsLog.categoryGlucoseChartManager, type: .info, String(describing: frame)) + + self.glucoseChart = nil + } + + if glucoseChart == nil { + glucoseChart = generateGlucoseChartWithFrame(frame) + } + + return glucoseChart + } + + + // MARK: - private functions + + private func generateGlucoseChartWithFrame(_ frame: CGRect) -> Chart? { + + // let's set up the x-axis for the chart. We just want the first and late values - no need for anything in between + var xAxisValues = [ ChartAxisValueDate(date: startDate, formatter: data().axisLabelTimeFormatter, labelSettings: data().chartLabelSettingsHidden) ] + + xAxisValues += [ ChartAxisValueDate(date: endDate, formatter: data().axisLabelTimeFormatter, labelSettings: data().chartLabelSettingsHidden) ] + + // don't show the first and last hour, because this is usually not something like 13 but rather 13:26 + xAxisValues.first?.hidden = true + xAxisValues.last?.hidden = false + + guard xAxisValues.count > 1 else {return nil} + + let xAxisModel = ChartAxisModel(axisValues: xAxisValues) + + // just to save typing + let unitIsMgDl = UserDefaults.standard.bloodGlucoseUnitIsMgDl + + // create yAxisValues, start with 38 mgdl, this is to make sure we show a bit lower than the real lowest value which is usually 40 mgdl, make the label hidden. We must do this with by using a clear color label setting as the hidden property doesn't work (even if we don't know why). + let firstYAxisValue = ChartAxisValueDouble((ConstantsGlucoseChart.absoluteMinimumChartValueInMgdl).mgdlToMmol(mgdl: unitIsMgDl), labelSettings: data().chartLabelSettingsHidden) + + // create now the yAxisValues and add the first + var yAxisValues = [firstYAxisValue as ChartAxisValue] + + yAxisValues += [ChartAxisValueDouble(UserDefaults.standard.highMarkValueInUserChosenUnit.bgValueRounded(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl), labelSettings: data().chartLabelSettingsHidden) as ChartAxisValue] + + if maximumValueInGlucoseChartPointsInMgDl.mgdlToMmol(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) > + UserDefaults.standard.highMarkValueInUserChosenUnit.bgValueRounded(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) { + yAxisValues += [ChartAxisValueDouble((maximumValueInGlucoseChartPointsInMgDl.mgdlToMmol(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl)), labelSettings: data().chartLabelSettingsHidden) as ChartAxisValue] + } + + let yAxisModel = ChartAxisModel(axisValues: yAxisValues, lineColor: ConstantsGlucoseChart.axisLineColor, labelSpaceReservationMode: .fixed(0)) + + // put Y axis on right side + let coordsSpace = ChartCoordsSpaceRightBottomSingleAxis(chartSettings: data().chartSettings, chartFrame: frame, xModel: xAxisModel, yModel: yAxisModel) + + let (xAxisLayer, yAxisLayer, innerFrame) = (coordsSpace.xAxisLayer, coordsSpace.yAxisLayer, coordsSpace.chartInnerFrame) + + // now that we know innerFrame we can set innerFrameWidth + innerFrameWidth = Double(innerFrame.width) + + // Grid lines + let gridLayer = ChartGuideLinesForValuesLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, settings: data().chartGuideLinesLayerSettings, axisValuesX: Array(xAxisValues.dropFirst().dropLast()), axisValuesY: []) + + // Guidelines + let highLowLineLayerSettings = ChartGuideLinesDottedLayerSettings(linesColor: ConstantsGlucoseChart.guidelineMiniChartHighLowColor, linesWidth: UserDefaults.standard.useObjectives ? 0.3 : 0, dotWidth: 3, dotSpacing: 3) + + let highLineLayer = ChartGuideLinesForValuesDottedLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, settings: highLowLineLayerSettings, axisValuesX: [ChartAxisValueDouble(0)], axisValuesY: [ChartAxisValueDouble(UserDefaults.standard.highMarkValueInUserChosenUnit)]) + + let lowLineLayer = ChartGuideLinesForValuesDottedLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, settings: highLowLineLayerSettings, axisValuesX: [ChartAxisValueDouble(0)], axisValuesY: [ChartAxisValueDouble(UserDefaults.standard.lowMarkValueInUserChosenUnit)]) + + // glucose circle diameter for the mini-chart, declared here to save typing + let glucoseCircleDiameter: CGFloat = ConstantsGlucoseChart.miniChartGlucoseCircleDiameter + + // In Range circle layers + let inRangeGlucoseCircles = ChartPointsScatterCirclesLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: inRangeGlucoseChartPoints, displayDelay: 0, itemSize: CGSize(width: glucoseCircleDiameter, height: glucoseCircleDiameter), itemFillColor: ConstantsGlucoseChart.glucoseInRangeColor, optimized: true) + + // urgent Range circle layers + let urgentRangeGlucoseCircles = ChartPointsScatterCirclesLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: urgentRangeGlucoseChartPoints, displayDelay: 0, itemSize: CGSize(width: glucoseCircleDiameter, height: glucoseCircleDiameter), itemFillColor: ConstantsGlucoseChart.glucoseUrgentRangeColor, optimized: true) + + // above target circle layers + let notUrgentRangeGlucoseCircles = ChartPointsScatterCirclesLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: notUrgentRangeGlucoseChartPoints, displayDelay: 0, itemSize: CGSize(width: glucoseCircleDiameter, height: glucoseCircleDiameter), itemFillColor: ConstantsGlucoseChart.glucoseNotUrgentRangeColor, optimized: true) + + let layers: [ChartLayer?] = [ + gridLayer, + // guideline layers + highLineLayer, + lowLineLayer, + // glucosePoint layers + inRangeGlucoseCircles, + notUrgentRangeGlucoseCircles, + urgentRangeGlucoseCircles + ] + + return Chart( + frame: frame, + innerFrame: innerFrame, + settings: data().chartSettings, + layers: layers.compactMap { $0 } + ) + } + + /// - returns: + /// - tuple of three chartpoint arrays, with readings that have calculatedvalue> 0, order ascending, ie first element is the oldest + /// - the three arrays in the tuple according to value compared to lowMarkValue, highMarkValue, urgentHighMarkValue, urgentLowMarkValue stored in UserDefaults + /// - the firstGlucoseChartPoint in the tuple is the oldest ChartPoint in the three arrays + /// - the lastGlucoseChartPoint in the tuple is the most recent ChartPoint in the three arrays + private func getGlucoseChartPoints(startDate: Date, endDate: Date, bgReadingsAccessor: BgReadingsAccessor, on managedObjectContext: NSManagedObjectContext) -> GlucoseChartPointsType { + + // get bgReadings between the two dates + let bgReadings = bgReadingsAccessor.getBgReadings(from: startDate, to: endDate, on: managedObjectContext) + + // intialize the three arrays + var urgentRangeChartPoints = [ChartPoint]() + var inRangeChartPoints = [ChartPoint]() + var notUrgentRangeChartPoints = [ChartPoint]() + + // initiliaze maximumValueInGlucoseChartPoints + var maximumValueInGlucoseChartPoints: Double? + + // bgReadings array has been fetched from coredata using a private mangedObjectContext + // we need to use the same context to perform next piece of code which will use those bgReadings, in order to stay thread-safe + managedObjectContext.performAndWait { + + for reading in bgReadings { + + if reading.calculatedValue > 0.0 { + + let newGlucoseChartPoint = ChartPoint(bgReading: reading, formatter: data().chartPointDateFormatter, unitIsMgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) + + if (reading.calculatedValue < UserDefaults.standard.lowMarkValue && reading.calculatedValue > UserDefaults.standard.urgentLowMarkValue) || (reading.calculatedValue > UserDefaults.standard.highMarkValue && reading.calculatedValue < UserDefaults.standard.urgentHighMarkValue) { + + notUrgentRangeChartPoints.append(newGlucoseChartPoint) + + } else if reading.calculatedValue >= UserDefaults.standard.urgentHighMarkValue || reading.calculatedValue <= UserDefaults.standard.urgentLowMarkValue { + + urgentRangeChartPoints.append(newGlucoseChartPoint) + + } else { + + inRangeChartPoints.append(newGlucoseChartPoint) + + } + + maximumValueInGlucoseChartPoints = (maximumValueInGlucoseChartPoints != nil ? max(maximumValueInGlucoseChartPoints!, reading.calculatedValue) : reading.calculatedValue) + + } + + } + + } + + return (urgentRangeChartPoints, inRangeChartPoints, notUrgentRangeChartPoints, maximumValueInGlucoseChartPoints) + + } + + + /// - set data to nil, will be called eg to clean up memory when going to the background + /// - all needed variables will will be reinitialized as soon as data() is called + private func nillifyData() { + + glucoseChartPoints = ([ChartPoint](), [ChartPoint](), [ChartPoint](), nil) + + chartSettings = nil + + chartPointDateFormatter = nil + + operationQueue = nil + + chartLabelSettings = nil + + chartGuideLinesLayerSettings = nil + + axisLabelTimeFormatter = nil + + bgReadingsAccessor = nil + + urgentRangeGlucoseChartPoints = [] + + inRangeGlucoseChartPoints = [] + + notUrgentRangeGlucoseChartPoints = [] + + chartLabelSettingsHidden = nil + + } + + /// function which gives is variables that are set back to nil when nillifyData is called + private func data() -> (chartSettings: ChartSettings, chartPointDateFormatter: DateFormatter, operationQueue: OperationQueue, chartLabelSettings: ChartLabelSettings, chartLabelSettingsHidden: ChartLabelSettings, chartGuideLinesLayerSettings: ChartGuideLinesLayerSettings, axisLabelTimeFormatter: DateFormatter, bgReadingsAccessor: BgReadingsAccessor){ + + // setup chartSettings + if chartSettings == nil { + + var newChartSettings = ChartSettings() + newChartSettings.top = 10 + newChartSettings.bottom = 15 + newChartSettings.trailing = 10 + newChartSettings.leading = 10 + newChartSettings.axisTitleLabelsToLabelsSpacing = 0 + newChartSettings.labelsToAxisSpacingX = 0 + newChartSettings.spacingBetweenAxesX = 0 + newChartSettings.labelsSpacing = 0 + newChartSettings.labelsToAxisSpacingY = 0 + newChartSettings.spacingBetweenAxesY = 0 + newChartSettings.axisStrokeWidth = 0 + + newChartSettings.clipInnerFrame = false + + chartSettings = newChartSettings + + } + + // setup chartPointDateFormatter + if chartPointDateFormatter == nil { + + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .none + dateFormatter.timeStyle = .none + + chartPointDateFormatter = dateFormatter + + } + + // setup operationqueue + if operationQueue == nil { + // initialize operationQueue + operationQueue = OperationQueue() + + // operationQueue will be queue of blocks that gets readings and updates glucoseChartPoints, startDate and endDate. To avoid race condition, the operations should be one after the other + operationQueue!.maxConcurrentOperationCount = 1 + } + + // intialize chartlabelsettings - this is used for the standard grid labels + if chartLabelSettings == nil { + chartLabelSettings = ChartLabelSettings( + font: .systemFont(ofSize: 14), + fontColor: ConstantsGlucoseChart.axisLabelColor + ) + } + + + // intialize chartlabelsettingsHidden - used to hide the first 38mg/dl value etc + if chartLabelSettingsHidden == nil { + chartLabelSettingsHidden = ChartLabelSettings( + fontColor: ConstantsGlucoseChart.axisLabelColorHidden + ) + } + + // intialize chartGuideLinesLayerSettings + if chartGuideLinesLayerSettings == nil { + chartGuideLinesLayerSettings = ChartGuideLinesLayerSettings(linesColor: UIColor.lightGray, linesWidth: 1) + } + + // intialize axisLabelTimeFormatter + if axisLabelTimeFormatter == nil { + axisLabelTimeFormatter = DateFormatter() + } + + // initialize bgReadingsAccessor + if bgReadingsAccessor == nil { + bgReadingsAccessor = BgReadingsAccessor(coreDataManager: coreDataManager) + } + + return (chartSettings!, chartPointDateFormatter!, operationQueue!, chartLabelSettings!, chartLabelSettingsHidden!, chartGuideLinesLayerSettings!, axisLabelTimeFormatter!, bgReadingsAccessor!) + + } + + /// finds new maximum, either currentMaximumValueInGlucoseChartPoints, or glucoseChartPoints.maximumValueInGlucoseChartPoints + /// - if both input values are nil, then returns constants + private func getNewMaximumValueInGlucoseChartPoints(currentMaximumValueInGlucoseChartPoints: Double?, glucoseChartPoints: GlucoseChartPointsType) -> Double { + + // check if there's already a value for maximumValueInGlucoseChartPoints + if let currentMaximumValueInGlucoseChartPoints = currentMaximumValueInGlucoseChartPoints { + + // check if there's a new value + if let newMaximumValueInGlucoseChartPoints = glucoseChartPoints.maximumValueInGlucoseChartPoints { + + // return the maximum of the two + return max(currentMaximumValueInGlucoseChartPoints, newMaximumValueInGlucoseChartPoints) + + } else { + + return currentMaximumValueInGlucoseChartPoints + + } + + } else { + + // there's no currentMaximumValueInGlucoseChartPoints, if glucoseChartPoints.maximumValueInGlucoseChartPoints not nil, return it + if let maximumValueInGlucoseChartPoints = glucoseChartPoints.maximumValueInGlucoseChartPoints { + return maximumValueInGlucoseChartPoints + } else { + return ConstantsGlucoseChart.absoluteMinimumChartValueInMgdl.mgdlToMmol(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) + } + + } + + } + +} + diff --git a/xdrip/Storyboards/Base.lproj/Main.storyboard b/xdrip/Storyboards/Base.lproj/Main.storyboard index 32319909..0e1bd048 100644 --- a/xdrip/Storyboards/Base.lproj/Main.storyboard +++ b/xdrip/Storyboards/Base.lproj/Main.storyboard @@ -335,7 +335,7 @@ - + - + @@ -389,6 +389,30 @@ + + + + + + + + + + + + + + + + @@ -704,9 +728,6 @@ - - - @@ -743,6 +764,9 @@ + + + @@ -781,6 +805,11 @@ + + + + + diff --git a/xdrip/Storyboards/en.lproj/SettingsViews.strings b/xdrip/Storyboards/en.lproj/SettingsViews.strings index 2f68c757..5f990f0e 100644 --- a/xdrip/Storyboards/en.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/en.lproj/SettingsViews.strings @@ -13,6 +13,7 @@ "settingsviews_labelShowReadingInAppBadge" = "Show BG in the App Badge?"; "settingsviews_multipleAppBadgeValueWith10" = "Multiply App Badge Reading by 10?"; "settingsviews_allowScreenRotation" = "Allow Chart Rotation?"; +"settingsviews_showMiniChart" = "Show the Mini-Chart?"; "settingsviews_showClockWhenScreenIsLocked" = "Show Clock when Locked?"; "settingsviews_urgentHighValue" = "Urgent High Value:"; "settingsviews_highValue" = "High Value:"; diff --git a/xdrip/Texts/TextsSettingsView.swift b/xdrip/Texts/TextsSettingsView.swift index 2338ecea..d70fb40a 100644 --- a/xdrip/Texts/TextsSettingsView.swift +++ b/xdrip/Texts/TextsSettingsView.swift @@ -83,6 +83,10 @@ class Texts_SettingsView { static let allowScreenRotation: String = { return NSLocalizedString("settingsviews_allowScreenRotation", tableName: filename, bundle: Bundle.main, value: "Allow Chart Rotation?", comment: "home screen settings, should the main glucose chart screen be allowed?") }() + + static let showMiniChart: String = { + return NSLocalizedString("settingsviews_showMiniChart", tableName: filename, bundle: Bundle.main, value: "Show the Mini-Chart?", comment: "home screen settings, should the mini-chart be shown?") + }() static let labelUseObjectives: String = { return NSLocalizedString("settingsviews_useobjectives", tableName: filename, bundle: Bundle.main, value: "Show Objectives in Graph?", comment: "home screen settings, use objectives in graph") diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index ea792e64..9b06de49 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -111,6 +111,13 @@ final class RootViewController: UIViewController { /// outlet for chart @IBOutlet weak var chartOutlet: BloodGlucoseChartView! + + /// outlet for mini-chart showing a fixed history of x hours + @IBOutlet weak var miniChartOutlet: BloodGlucoseChartView! + + @IBOutlet weak var miniChartHoursLabelOutlet: UILabel! + + @IBOutlet weak var segmentedControlsView: UIView! /// outlets for chart time period selector @@ -261,6 +268,36 @@ final class RootViewController: UIViewController { @IBOutlet var chartDoubleTapGestureRecognizerOutlet: UITapGestureRecognizer! + @IBAction func miniChartDoubleTapGestureRecognizer(_ sender: UITapGestureRecognizer) { + + // if the mini-chart is double tapped then toggle the hours to show + UserDefaults.standard.miniChartHoursToShow = UserDefaults.standard.miniChartHoursToShow == ConstantsGlucoseChart.miniChartHoursToShow1 ? ConstantsGlucoseChart.miniChartHoursToShow2 : ConstantsGlucoseChart.miniChartHoursToShow1 + + miniChartHoursLabelOutlet.text = " " + Int(UserDefaults.standard.miniChartHoursToShow).description + Texts_Common.hourshort + " " + + // restore the alpha of the label + miniChartHoursLabelOutlet.alpha = 1.0 + + // now show the label + miniChartHoursLabelOutlet.isHidden = false + + // wait for a second (or two) and then fade the label out + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + + // make a animated transition with the label. Fade it out over a couple of seconds. + UIView.transition(with: self.miniChartHoursLabelOutlet, duration: 2, options: .transitionCrossDissolve, animations: { + self.miniChartHoursLabelOutlet.alpha = 0.0 + }) + + // once faded out, just hide it properly so that it doesn't block the tap gesture of the chart in case the user clicks where the label is + self.miniChartHoursLabelOutlet.isHidden = true + + } + } + + @IBOutlet var miniChartDoubleTapGestureRecognizer: UITapGestureRecognizer! + + // MARK: - Constants for ApplicationManager usage /// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - create updateLabelsAndChartTimer @@ -355,6 +392,11 @@ final class RootViewController: UIViewController { /// - will be reinitialized each time the app comes to the foreground private var glucoseChartManager: GlucoseChartManager? + /// - manage the mini glucose chart that shows a fixed amount of data + /// - will be nillified each time the app goes to the background, to avoid unnecessary ram usage (which seems to cause app getting killed) + /// - will be reinitialized each time the app comes to the foreground + private var glucoseMiniChartManager: GlucoseMiniChartManager? + /// statisticsManager instance private var statisticsManager: StatisticsManager? @@ -430,10 +472,14 @@ final class RootViewController: UIViewController { // viewWillAppear when user switches eg from Settings Tab to Home Tab - latest reading value needs to be shown on the view, and also update minutes ago etc. updateLabelsAndChart(overrideApplicationState: true) + // show the mini-chart as required + miniChartOutlet.isHidden = !UserDefaults.standard.showMiniChart + // show the statistics view as required. If not, hide it and show the spacer view to keep segmentedControlChartHours separated a bit more away from the main Tab bar if !screenIsLocked { statisticsView.isHidden = !UserDefaults.standard.showStatistics } + segmentedControlStatisticsDaysView.isHidden = !UserDefaults.standard.showStatistics if inRangeStatisticLabelOutlet.text == "-" { @@ -607,6 +653,9 @@ final class RootViewController: UIViewController { // update label texts, minutes ago, diff and value self.updateLabelsAndChart(overrideApplicationState: true) + // update the mini-chart + self.updateMiniChart() + // update sensor countdown self.updateSensorCountdown() @@ -650,6 +699,12 @@ final class RootViewController: UIViewController { // see if the user has changed the chart x axis timescale UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.KeysCharts.chartWidthInHours.rawValue, options: .new, context: nil) + // have the mini-chart hours been changed? + UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.miniChartHoursToShow.rawValue, options: .new, context: nil) + + // showing or hiding the mini-chart + UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.showMiniChart.rawValue, options: .new, context: nil) + // see if the user has changed the statistic days to use UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.daysToUseStatistics.rawValue, options: .new, context: nil) @@ -727,6 +782,7 @@ final class RootViewController: UIViewController { self.updateLabelsAndChart(overrideApplicationState: true) + self.updateMiniChart() self.updateSensorCountdown() @@ -924,6 +980,9 @@ final class RootViewController: UIViewController { // initialize glucoseChartManager glucoseChartManager = GlucoseChartManager(chartLongPressGestureRecognizer: chartLongPressGestureRecognizerOutlet, coreDataManager: coreDataManager) + // initialize glucoseMiniChartManager + glucoseMiniChartManager = GlucoseMiniChartManager(coreDataManager: coreDataManager) + // initialize statisticsManager statisticsManager = StatisticsManager(coreDataManager: coreDataManager) @@ -932,6 +991,11 @@ final class RootViewController: UIViewController { return self?.glucoseChartManager?.glucoseChartWithFrame(frame)?.view } + // initialize chartGenerator in miniChartOutlet + self.miniChartOutlet.chartGenerator = { [weak self] (frame) in + return self?.glucoseMiniChartManager?.glucoseChartWithFrame(frame)?.view + } + } /// process new glucose data received from transmitter. @@ -1145,6 +1209,9 @@ final class RootViewController: UIViewController { // update all text in first screen updateLabelsAndChart(overrideApplicationState: false) + // update mini-chart + updateMiniChart() + // update statistics related outlets updateStatistics(animatePieChart: false) @@ -1226,7 +1293,7 @@ final class RootViewController: UIViewController { // first check keyValueObserverTimeKeeper switch keyPathEnum { - case UserDefaults.Key.isMaster, UserDefaults.Key.multipleAppBadgeValueWith10, UserDefaults.Key.showReadingInAppBadge, UserDefaults.Key.bloodGlucoseUnitIsMgDl, UserDefaults.Key.daysToUseStatistics : + case UserDefaults.Key.isMaster, UserDefaults.Key.multipleAppBadgeValueWith10, UserDefaults.Key.showReadingInAppBadge, UserDefaults.Key.bloodGlucoseUnitIsMgDl, UserDefaults.Key.daysToUseStatistics, UserDefaults.Key.showMiniChart : // transmittertype change triggered by user, should not be done within 200 ms if !keyValueObserverTimeKeeper.verifyKey(forKey: keyPathEnum.rawValue, withMinimumDelayMilliSeconds: 200) { @@ -1277,8 +1344,21 @@ final class RootViewController: UIViewController { // redraw chart is necessary updateChartWithResetEndDate() + // redraw mini-chart + updateMiniChart() + // update Watch App with the new objective values updateWatchApp() + + case UserDefaults.Key.showMiniChart: + + // show/hide mini-chart view as required + miniChartOutlet.isHidden = !UserDefaults.standard.showMiniChart + + case UserDefaults.Key.miniChartHoursToShow: + + // redraw mini-chart + updateMiniChart() case UserDefaults.Key.daysToUseStatistics: @@ -1352,6 +1432,8 @@ final class RootViewController: UIViewController { // at this moment, coreDataManager is not yet initialized, we're just calling here prerender and reloadChart to show the chart with x and y axis and gridlines, but without readings. The readings will be loaded once coreDataManager is setup, after which updateChart() will be called, which will initiate loading of readings from coredata self.chartOutlet.reloadChart() + self.miniChartOutlet.reloadChart() + } // MARK: - private helper functions @@ -1943,6 +2025,20 @@ final class RootViewController: UIViewController { // update the chart up to now updateChartWithResetEndDate() + self.updateMiniChart() + + } + + /// if the user has chosen to show the mini-chart, then update it. If not, just return without doing anything. + private func updateMiniChart() { + + if UserDefaults.standard.showMiniChart { + + // update the chart + glucoseMiniChartManager?.updateChartPoints(chartOutlet: miniChartOutlet, completionHandler: nil) + + } + } /// when user clicks transmitter button, this will create and present the actionsheet, contents depend on type of transmitter and sensor status @@ -3090,6 +3186,9 @@ extension RootViewController:NightScoutFollowerDelegate { // update all text in first screen updateLabelsAndChart(overrideApplicationState: false) + // update the mini-chart + updateMiniChart() + // update statistics related outlets updateStatistics(animatePieChart: false) diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHomeScreenSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHomeScreenSettingsViewModel.swift index e839bd97..616eca9f 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHomeScreenSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHomeScreenSettingsViewModel.swift @@ -16,26 +16,29 @@ fileprivate enum Setting:Int, CaseIterable { // show a clock at the bottom of the home screen when the screen lock is activated? case showClockWhenScreenIsLocked = 1 + // show a fixed scale mini-chart under the main scrollable chart? + case showMiniChart = 2 + //urgent high value - case urgentHighMarkValue = 2 + case urgentHighMarkValue = 3 //high value - case highMarkValue = 3 + case highMarkValue = 4 //low value - case lowMarkValue = 4 + case lowMarkValue = 5 //urgent low value - case urgentLowMarkValue = 5 + case urgentLowMarkValue = 6 //use objectives in graph? - case useObjectives = 6 + case useObjectives = 7 //show target line? - case showTarget = 7 + case showTarget = 8 //target value - case targetMarkValue = 8 + case targetMarkValue = 9 } @@ -47,16 +50,19 @@ struct SettingsViewHomeScreenSettingsViewModel:SettingsViewModelProtocol { guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } switch setting { - + case .allowScreenRotation: return UISwitch(isOn: UserDefaults.standard.allowScreenRotation, action: {(isOn:Bool) in UserDefaults.standard.allowScreenRotation = isOn}) case .showClockWhenScreenIsLocked: return UISwitch(isOn: UserDefaults.standard.showClockWhenScreenIsLocked, action: {(isOn:Bool) in UserDefaults.standard.showClockWhenScreenIsLocked = isOn}) - + + case .showMiniChart: + return UISwitch(isOn: UserDefaults.standard.showMiniChart, action: {(isOn:Bool) in UserDefaults.standard.showMiniChart = isOn}) + case .useObjectives: return UISwitch(isOn: UserDefaults.standard.useObjectives, action: {(isOn:Bool) in UserDefaults.standard.useObjectives = isOn}) - + case .showTarget : return UISwitch(isOn: UserDefaults.standard.showTarget, action: {(isOn:Bool) in UserDefaults.standard.showTarget = isOn}) @@ -74,7 +80,7 @@ struct SettingsViewHomeScreenSettingsViewModel:SettingsViewModelProtocol { func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {} func storeUIViewController(uIViewController: UIViewController) {} - + func storeMessageHandler(messageHandler: ((String, String) -> Void)) { // this ViewModel does need to send back messages to the viewcontroller asynchronously } @@ -87,57 +93,66 @@ struct SettingsViewHomeScreenSettingsViewModel:SettingsViewModelProtocol { guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } switch setting { - - case .urgentHighMarkValue: - return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelUrgentHighValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.urgentHighMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultUrgentHighMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(urgentHighMarkValue:String) in UserDefaults.standard.urgentHighMarkValueInUserChosenUnitRounded = urgentHighMarkValue}, cancelHandler: nil, inputValidator: nil) - - case .highMarkValue: - return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelHighValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.highMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultHighMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(highMarkValue:String) in UserDefaults.standard.highMarkValueInUserChosenUnitRounded = highMarkValue}, cancelHandler: nil, inputValidator: nil) - case .lowMarkValue: - return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelLowValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.lowMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultLowMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(lowMarkValue:String) in UserDefaults.standard.lowMarkValueInUserChosenUnitRounded = lowMarkValue}, cancelHandler: nil, inputValidator: nil) - - case .urgentLowMarkValue: - return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelUrgentLowValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.urgentLowMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultUrgentLowMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(urgentLowMarkValue:String) in UserDefaults.standard.urgentLowMarkValueInUserChosenUnitRounded = urgentLowMarkValue}, cancelHandler: nil, inputValidator: nil) - - case .allowScreenRotation: - return SettingsSelectedRowAction.callFunction(function: { - if UserDefaults.standard.allowScreenRotation { - UserDefaults.standard.allowScreenRotation = false - } else { - UserDefaults.standard.allowScreenRotation = true - } - }) - - case .showClockWhenScreenIsLocked: - return SettingsSelectedRowAction.callFunction(function: { - if UserDefaults.standard.showClockWhenScreenIsLocked { - UserDefaults.standard.showClockWhenScreenIsLocked = false - } else { - UserDefaults.standard.showClockWhenScreenIsLocked = true - } - }) - - case .useObjectives: - return SettingsSelectedRowAction.callFunction(function: { - if UserDefaults.standard.useObjectives { - UserDefaults.standard.useObjectives = false - } else { - UserDefaults.standard.useObjectives = true - } - }) + case .urgentHighMarkValue: + return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelUrgentHighValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.urgentHighMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultUrgentHighMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(urgentHighMarkValue:String) in UserDefaults.standard.urgentHighMarkValueInUserChosenUnitRounded = urgentHighMarkValue}, cancelHandler: nil, inputValidator: nil) - case .showTarget: - return SettingsSelectedRowAction.callFunction(function: { - if UserDefaults.standard.showTarget { - UserDefaults.standard.showTarget = false - } else { - UserDefaults.standard.showTarget = true - } - }) + case .highMarkValue: + return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelHighValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.highMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultHighMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(highMarkValue:String) in UserDefaults.standard.highMarkValueInUserChosenUnitRounded = highMarkValue}, cancelHandler: nil, inputValidator: nil) - case .targetMarkValue: - return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelTargetValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.targetMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultTargetMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(targetMarkValue:String) in UserDefaults.standard.targetMarkValueInUserChosenUnitRounded = targetMarkValue}, cancelHandler: nil, inputValidator: nil) + case .lowMarkValue: + return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelLowValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.lowMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultLowMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(lowMarkValue:String) in UserDefaults.standard.lowMarkValueInUserChosenUnitRounded = lowMarkValue}, cancelHandler: nil, inputValidator: nil) + + case .urgentLowMarkValue: + return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelUrgentLowValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.urgentLowMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultUrgentLowMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(urgentLowMarkValue:String) in UserDefaults.standard.urgentLowMarkValueInUserChosenUnitRounded = urgentLowMarkValue}, cancelHandler: nil, inputValidator: nil) + + case .allowScreenRotation: + return SettingsSelectedRowAction.callFunction(function: { + if UserDefaults.standard.allowScreenRotation { + UserDefaults.standard.allowScreenRotation = false + } else { + UserDefaults.standard.allowScreenRotation = true + } + }) + + case .showClockWhenScreenIsLocked: + return SettingsSelectedRowAction.callFunction(function: { + if UserDefaults.standard.showClockWhenScreenIsLocked { + UserDefaults.standard.showClockWhenScreenIsLocked = false + } else { + UserDefaults.standard.showClockWhenScreenIsLocked = true + } + }) + + case .showMiniChart: + return SettingsSelectedRowAction.callFunction(function: { + if UserDefaults.standard.showMiniChart { + UserDefaults.standard.showMiniChart = false + } else { + UserDefaults.standard.showMiniChart = true + } + }) + + case .useObjectives: + return SettingsSelectedRowAction.callFunction(function: { + if UserDefaults.standard.useObjectives { + UserDefaults.standard.useObjectives = false + } else { + UserDefaults.standard.useObjectives = true + } + }) + + case .showTarget: + return SettingsSelectedRowAction.callFunction(function: { + if UserDefaults.standard.showTarget { + UserDefaults.standard.showTarget = false + } else { + UserDefaults.standard.showTarget = true + } + }) + + case .targetMarkValue: + return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelTargetValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.targetMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultTargetMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(targetMarkValue:String) in UserDefaults.standard.targetMarkValueInUserChosenUnitRounded = targetMarkValue}, cancelHandler: nil, inputValidator: nil) } } @@ -159,35 +174,38 @@ struct SettingsViewHomeScreenSettingsViewModel:SettingsViewModelProtocol { func settingsRowText(index: Int) -> String { guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } - + switch setting { - - case .urgentHighMarkValue: - return Texts_SettingsView.labelUrgentHighValue - - case .highMarkValue: - return Texts_SettingsView.labelHighValue - - case .lowMarkValue: - return Texts_SettingsView.labelLowValue - - case .urgentLowMarkValue: - return Texts_SettingsView.labelUrgentLowValue - - case .allowScreenRotation: - return Texts_SettingsView.allowScreenRotation - - case .showClockWhenScreenIsLocked: - return Texts_SettingsView.showClockWhenScreenIsLocked - - case .useObjectives: - return Texts_SettingsView.labelUseObjectives - case .showTarget: - return Texts_SettingsView.labelShowTarget - - case .targetMarkValue: - return Texts_SettingsView.labelTargetValue + case .urgentHighMarkValue: + return Texts_SettingsView.labelUrgentHighValue + + case .highMarkValue: + return Texts_SettingsView.labelHighValue + + case .lowMarkValue: + return Texts_SettingsView.labelLowValue + + case .urgentLowMarkValue: + return Texts_SettingsView.labelUrgentLowValue + + case .allowScreenRotation: + return Texts_SettingsView.allowScreenRotation + + case .showClockWhenScreenIsLocked: + return Texts_SettingsView.showClockWhenScreenIsLocked + + case .showMiniChart: + return Texts_SettingsView.showMiniChart + + case .useObjectives: + return Texts_SettingsView.labelUseObjectives + + case .showTarget: + return Texts_SettingsView.labelShowTarget + + case .targetMarkValue: + return Texts_SettingsView.labelTargetValue } } @@ -196,57 +214,36 @@ struct SettingsViewHomeScreenSettingsViewModel:SettingsViewModelProtocol { switch setting { - case .urgentHighMarkValue: + case .urgentHighMarkValue, .highMarkValue, .lowMarkValue, .urgentLowMarkValue, .targetMarkValue: return UITableViewCell.AccessoryType.disclosureIndicator - case .highMarkValue: - return UITableViewCell.AccessoryType.disclosureIndicator - - case .lowMarkValue: - return UITableViewCell.AccessoryType.disclosureIndicator - - case .urgentLowMarkValue: - return UITableViewCell.AccessoryType.disclosureIndicator - - case .allowScreenRotation: + case .allowScreenRotation, .showClockWhenScreenIsLocked, .showMiniChart, .useObjectives, .showTarget: return UITableViewCell.AccessoryType.none - case .showClockWhenScreenIsLocked: - return UITableViewCell.AccessoryType.none - - case .useObjectives: - return UITableViewCell.AccessoryType.none - - case .showTarget: - return UITableViewCell.AccessoryType.none - - case .targetMarkValue: - return UITableViewCell.AccessoryType.disclosureIndicator - } } func detailedText(index: Int) -> String? { guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } - + switch setting { case .urgentHighMarkValue: return UserDefaults.standard.urgentHighMarkValueInUserChosenUnit.bgValuetoString(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) - + case .highMarkValue: return UserDefaults.standard.highMarkValueInUserChosenUnit.bgValuetoString(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) - + case .lowMarkValue: return UserDefaults.standard.lowMarkValueInUserChosenUnit.bgValuetoString(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) - + case .urgentLowMarkValue: return UserDefaults.standard.urgentLowMarkValueInUserChosenUnit.bgValuetoString(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) - + case .targetMarkValue: return UserDefaults.standard.targetMarkValueInUserChosenUnit.bgValuetoString(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) - case .allowScreenRotation, .showClockWhenScreenIsLocked, .useObjectives, .showTarget: + case .allowScreenRotation, .showClockWhenScreenIsLocked, .showMiniChart, .useObjectives, .showTarget: return nil }