mini-chart initial implementation

This uses a new glucoseMiniGlucoseChart object to display a static 24hr or 48hr mini-chart beneath the main chart.
- simplified to display only glucose chart points with all axiis, guidelines and labels hidden.
- it will update every time new glucose readings are processed
- a double tap will toggle between 24hr and 48hr modes (default 24hrs)
- by default the mini-chart will be hidden (upon first ever launch) for users with "Zoomed Display" enabled. Otherwise, the mini-chart will be shown by default.
- as above but same logic also applied to the statistics view for first launch
- home screen settings menu edited to add option
- observer added to hide/show the chart after settings change and also to redraw the chart after hours are toggled by double tap
This commit is contained in:
Paul Plant 2022-06-17 17:48:06 +02:00
parent 48ee34a9bd
commit 34bae76168
9 changed files with 839 additions and 122 deletions

View File

@ -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 = "<group>"; };
4752B4052635878E0081D551 /* SettingsViewStatisticsSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewStatisticsSettingsViewModel.swift; sourceTree = "<group>"; };
475DED96244AF92A00F78473 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Alerts.strings; sourceTree = "<group>"; };
477F45E5285B993100AC8475 /* GlucoseMiniChartManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseMiniChartManager.swift; sourceTree = "<group>"; };
4798BAC727BA6AA8002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/LaunchScreen.strings; sourceTree = "<group>"; };
4798BAC827BA6AA8002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Main.strings; sourceTree = "<group>"; };
4798BAC927BA766A002583BC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/MainInterface.strings; sourceTree = "<group>"; };
@ -2590,6 +2592,7 @@
47ADD2DE27FAF8630025E2F4 /* ChartPointsScatterDownTrianglesLayer.swift */,
47ADD2E027FB05EB0025E2F4 /* ChartPointsScatterDownTrianglesWithDropdownLineLayer.swift */,
F8BECB04235CE5D80060DAE1 /* GlucoseChartManager.swift */,
477F45E5285B993100AC8475 /* GlucoseMiniChartManager.swift */,
);
path = Charts;
sourceTree = "<group>";
@ -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 */,

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}
}
}
}

View File

@ -335,7 +335,7 @@
</items>
</toolbar>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="C16-NQ-F1Y">
<rect key="frame" x="0.0" y="50" width="390" height="389"/>
<rect key="frame" x="0.0" y="50" width="390" height="319"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="x mins ago" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uyn-2k-K74">
<rect key="frame" x="10" y="0.0" width="102" height="26.333333333333332"/>
@ -364,7 +364,7 @@
</connections>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0nE-AX-r0w" customClass="BloodGlucoseChartView" customModule="xdrip" customModuleProvider="target">
<rect key="frame" x="10" y="132.66666666666666" width="380" height="256.33333333333337"/>
<rect key="frame" x="10" y="132.66666666666663" width="380" height="186.33333333333337"/>
<gestureRecognizers/>
<connections>
<outletCollection property="gestureRecognizers" destination="Fi5-iu-Usk" appends="YES" id="Rkv-hK-sLH"/>
@ -389,6 +389,30 @@
<constraint firstItem="We3-bN-ffR" firstAttribute="top" secondItem="C16-NQ-F1Y" secondAttribute="top" constant="25" id="xak-Qv-s0j"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="YJB-T6-p3Y" customClass="BloodGlucoseChartView" customModule="xdrip" customModuleProvider="target">
<rect key="frame" x="0.0" y="369" width="390" height="70"/>
<subviews>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" alpha="0.90000000000000002" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" 24h " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WIO-Oh-Z4T">
<rect key="frame" x="15" y="50" width="32" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="ACV-EP-M2d"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<gestureRecognizers/>
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="WIO-Oh-Z4T" secondAttribute="trailing" symbolic="YES" id="1RU-Mc-elX"/>
<constraint firstAttribute="height" constant="70" id="40A-Zk-fOI"/>
<constraint firstItem="WIO-Oh-Z4T" firstAttribute="leading" secondItem="YJB-T6-p3Y" secondAttribute="leading" constant="15" id="rea-6b-zUM"/>
<constraint firstAttribute="bottom" secondItem="WIO-Oh-Z4T" secondAttribute="bottom" id="uJt-rF-x1l"/>
</constraints>
<connections>
<outletCollection property="gestureRecognizers" destination="AyP-F5-KtO" appends="YES" id="gXU-Ha-MP8"/>
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="x9u-yg-PXE" userLabel="segmentControlsView">
<rect key="frame" x="0.0" y="439" width="390" height="34"/>
<subviews>
@ -704,9 +728,6 @@
</constraints>
</imageView>
</subviews>
<constraints>
<constraint firstItem="x9u-yg-PXE" firstAttribute="top" secondItem="C16-NQ-F1Y" secondAttribute="bottom" id="WZ6-cn-zwk"/>
</constraints>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="PQr-Ze-W5v"/>
@ -743,6 +764,9 @@
<outlet property="lowLabelOutlet" destination="Ncy-6l-nO2" id="11g-UN-f9n"/>
<outlet property="lowStatisticLabelOutlet" destination="4LK-sw-cBS" id="0gk-05-iOl"/>
<outlet property="lowTitleLabelOutlet" destination="B0Q-ux-IHf" id="ATk-vB-TVw"/>
<outlet property="miniChartDoubleTapGestureRecognizer" destination="AyP-F5-KtO" id="5Zi-rx-OgB"/>
<outlet property="miniChartHoursLabelOutlet" destination="WIO-Oh-Z4T" id="Qsu-SO-ClV"/>
<outlet property="miniChartOutlet" destination="YJB-T6-p3Y" id="A7b-PQ-INx"/>
<outlet property="minutesLabelOutlet" destination="uyn-2k-K74" id="XXm-rq-Suy"/>
<outlet property="pieChartLabelOutlet" destination="c1z-wL-Eye" id="tBn-RJ-oiQ"/>
<outlet property="pieChartOutlet" destination="vm5-IB-4CY" id="t8a-2d-t5l"/>
@ -781,6 +805,11 @@
<action selector="chartDoubleTapGestureRecognizer:" destination="9pv-A4-QxB" id="KJ1-MA-2o8"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer numberOfTapsRequired="2" id="AyP-F5-KtO">
<connections>
<action selector="miniChartDoubleTapGestureRecognizer:" destination="9pv-A4-QxB" id="5IT-6c-SZi"/>
</connections>
</tapGestureRecognizer>
</objects>
<point key="canvasLocation" x="744.61538461538464" y="-890.75829383886253"/>
</scene>

View File

@ -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:";

View File

@ -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")

View File

@ -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)

View File

@ -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
}