| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- // LoopFollow
- // Localizer.swift
- import Foundation
- import HealthKit
- enum GlucoseDisplayUnit: String, Codable, CaseIterable {
- case mgdL = "mg/dL"
- case mmolL = "mmol/L"
- var fractionDigits: Int {
- switch self {
- case .mgdL:
- return 0
- case .mmolL:
- return 1
- }
- }
- }
- enum TimeInRangeDisplayMode: String, Codable, CaseIterable {
- case tir = "TIR"
- case titr = "TITR"
- case custom = "Custom"
- }
- enum GlycemicMetricMode: String, Codable, CaseIterable {
- case ehba1c = "eHbA1c"
- case gmi = "GMI"
- }
- enum GlycemicOutputUnit: String, Codable, CaseIterable {
- case percent = "%"
- case mmolMol = "mmol/mol"
- }
- enum VariabilityMetricMode: String, Codable, CaseIterable {
- case stdDeviation = "Std Deviation"
- case cv = "CV"
- }
- final class UnitSettingsStore {
- static let shared = UnitSettingsStore()
- private init() {}
- var glucoseUnit: GlucoseDisplayUnit {
- get {
- GlucoseDisplayUnit(rawValue: Storage.shared.units.value) ?? .mgdL
- }
- set {
- Storage.shared.units.value = newValue.rawValue
- }
- }
- var timeInRangeMode: TimeInRangeDisplayMode {
- get {
- TimeInRangeDisplayMode(rawValue: Storage.shared.timeInRangeModeRaw.value) ?? .tir
- }
- set {
- Storage.shared.timeInRangeModeRaw.value = newValue.rawValue
- }
- }
- /// Returns the effective low/high thresholds (in mg/dL) for the current range mode.
- func effectiveThresholds() -> (low: Double, high: Double) {
- switch timeInRangeMode {
- case .tir:
- return (70.0, 180.0)
- case .titr:
- return (70.0, 140.0)
- case .custom:
- return (Storage.shared.lowLine.value, Storage.shared.highLine.value)
- }
- }
- var glycemicMetricMode: GlycemicMetricMode {
- get {
- Storage.shared.showGMI.value ? .gmi : .ehba1c
- }
- set {
- Storage.shared.showGMI.value = (newValue == .gmi)
- }
- }
- var glycemicOutputUnit: GlycemicOutputUnit {
- get {
- Storage.shared.useIFCC.value ? .mmolMol : .percent
- }
- set {
- Storage.shared.useIFCC.value = (newValue == .mmolMol)
- }
- }
- var variabilityMetricMode: VariabilityMetricMode {
- get {
- Storage.shared.showStdDev.value ? .stdDeviation : .cv
- }
- set {
- Storage.shared.showStdDev.value = (newValue == .stdDeviation)
- }
- }
- func convertMgdlToDisplay(_ mgdl: Double) -> Double {
- switch glucoseUnit {
- case .mgdL:
- return mgdl
- case .mmolL:
- return mgdl * GlucoseConversion.mgDlToMmolL
- }
- }
- func convertDisplayToMgdl(_ value: Double) -> Double {
- switch glucoseUnit {
- case .mgdL:
- return value
- case .mmolL:
- return value * GlucoseConversion.mmolToMgDl
- }
- }
- }
- enum GlycemicMetricCalculator {
- static func calculateEhba1c(avgGlucoseInDisplayUnits: Double?) -> Double? {
- guard let avgGlucoseInDisplayUnits else { return nil }
- switch UnitSettingsStore.shared.glycemicOutputUnit {
- case .mmolMol:
- let avgMmolL = UnitSettingsStore.shared.glucoseUnit == .mmolL
- ? avgGlucoseInDisplayUnits
- : avgGlucoseInDisplayUnits * GlucoseConversion.mgDlToMmolL
- return avgMmolL * 6.936514699616532 - 6.628248828291433
- case .percent:
- let avgMgdL = UnitSettingsStore.shared.convertDisplayToMgdl(avgGlucoseInDisplayUnits)
- return (avgMgdL + 46.7) / 28.7
- }
- }
- static func calculateGMI(avgGlucoseInDisplayUnits: Double?) -> Double? {
- guard let avgGlucoseInDisplayUnits else { return nil }
- switch UnitSettingsStore.shared.glycemicOutputUnit {
- case .percent:
- let avgMgdL = UnitSettingsStore.shared.convertDisplayToMgdl(avgGlucoseInDisplayUnits)
- return 3.31 + (0.02392 * avgMgdL)
- case .mmolMol:
- let avgMmolL = UnitSettingsStore.shared.glucoseUnit == .mmolL
- ? avgGlucoseInDisplayUnits
- : avgGlucoseInDisplayUnits * GlucoseConversion.mgDlToMmolL
- return 12.71 + (4.70587 * avgMmolL)
- }
- }
- }
- class Localizer {
- static func getPreferredUnit() -> HKUnit {
- switch UnitSettingsStore.shared.glucoseUnit {
- case .mmolL:
- return .millimolesPerLiter
- case .mgdL:
- return .milligramsPerDeciliter
- }
- }
- static func formatToLocalizedString(_ value: Double, maxFractionDigits: Int = 1, minFractionDigits: Int = 0) -> String {
- let numberFormatter = NumberFormatter()
- numberFormatter.numberStyle = .decimal
- numberFormatter.maximumFractionDigits = maxFractionDigits
- numberFormatter.minimumFractionDigits = minFractionDigits
- numberFormatter.locale = Locale.current
- let numberValue = NSNumber(value: value)
- return numberFormatter.string(from: numberValue) ?? String(value)
- }
- static func formatQuantity(_ quantity: HKQuantity) -> String {
- let unit: HKUnit = getPreferredUnit()
- let value = quantity.doubleValue(for: unit)
- return formatToLocalizedString(value, maxFractionDigits: unit.preferredFractionDigits, minFractionDigits: unit.preferredFractionDigits)
- }
- static func formatQuantity(_ value: Double) -> String {
- formatQuantity(HKQuantity(unit: .milligramsPerDeciliter, doubleValue: value))
- }
- static func formatTimestampToLocalString(_ timestamp: TimeInterval) -> String {
- let date = Date(timeIntervalSince1970: timestamp)
- let dateFormatter = DateFormatter()
- dateFormatter.setLocalizedDateFormatFromTemplate("jms")
- dateFormatter.locale = Locale.current
- dateFormatter.timeZone = TimeZone.current
- return dateFormatter.string(from: date)
- }
- static func formatLocalDouble(_ value: Double, unit: String? = nil) -> String {
- let numberFormatter = NumberFormatter()
- numberFormatter.numberStyle = .decimal
- let units = GlucoseDisplayUnit(rawValue: unit ?? UnitSettingsStore.shared.glucoseUnit.rawValue) ?? .mgdL
- numberFormatter.maximumFractionDigits = units.fractionDigits
- numberFormatter.minimumFractionDigits = units.fractionDigits
- numberFormatter.locale = Locale.current
- let numberValue = NSNumber(value: value)
- return numberFormatter.string(from: numberValue) ?? String(value)
- }
- static func toDisplayUnits(_ value: String) -> String {
- let numberFormatter = NumberFormatter()
- numberFormatter.numberStyle = .decimal
- let units = UnitSettingsStore.shared.glucoseUnit
- numberFormatter.maximumFractionDigits = units.fractionDigits
- numberFormatter.minimumFractionDigits = units.fractionDigits
- numberFormatter.locale = Locale.current
- if let number = Float(value) {
- if units == .mgdL {
- let numberValue = NSNumber(value: number)
- return numberFormatter.string(from: numberValue) ?? value
- } else {
- let mmolValue = Double(number) * GlucoseConversion.mgDlToMmolL // Convert number to Double
- let numberValue = NSNumber(value: mmolValue)
- return numberFormatter.string(from: numberValue) ?? value
- }
- }
- return value
- }
- static func removePeriodAndCommaForBadge(_ value: String) -> String {
- var modifiedValue = value
- modifiedValue = modifiedValue.replacingOccurrences(of: ".", with: "")
- modifiedValue = modifiedValue.replacingOccurrences(of: ",", with: "")
- return modifiedValue
- }
- }
- extension Float {
- // remove the decimal part of the float if it is ".0" and trim whitespaces
- var cleanValue: String {
- return truncatingRemainder(dividingBy: 1) == 0
- ? String(format: "%5.0f", self).trimmingCharacters(in: CharacterSet.whitespaces)
- : String(format: "%5.1f", self).trimmingCharacters(in: CharacterSet.whitespaces)
- }
- var roundTo3f: Float {
- return round(to: 3)
- }
- func round(to places: Int) -> Float {
- let divisor = pow(10.0, Float(places))
- return (divisor * self).rounded() / divisor
- }
- }
|