HomeRootView.swift 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. import SpriteKit
  2. import SwiftDate
  3. import SwiftUI
  4. import Swinject
  5. extension Home {
  6. struct RootView: BaseView {
  7. let resolver: Resolver
  8. @StateObject var state = StateModel()
  9. @State var isStatusPopupPresented = false
  10. @State var selectedState: durationState
  11. @State var hba1c_all: String
  12. @State var average_: String
  13. @State var median_: String
  14. @State var tir_low: String
  15. @State var tir_high: String
  16. @State var tir_: String
  17. @State var hba1c_: String
  18. @State var sd_: String
  19. @State var cv_: String
  20. private var numberFormatter: NumberFormatter {
  21. let formatter = NumberFormatter()
  22. formatter.numberStyle = .decimal
  23. formatter.maximumFractionDigits = 2
  24. return formatter
  25. }
  26. private var targetFormatter: NumberFormatter {
  27. let formatter = NumberFormatter()
  28. formatter.numberStyle = .decimal
  29. formatter.maximumFractionDigits = 1
  30. return formatter
  31. }
  32. private var tirFormatter: NumberFormatter {
  33. let formatter = NumberFormatter()
  34. formatter.numberStyle = .decimal
  35. formatter.maximumFractionDigits = 0
  36. return formatter
  37. }
  38. private var dateFormatter: DateFormatter {
  39. let dateFormatter = DateFormatter()
  40. dateFormatter.timeStyle = .short
  41. return dateFormatter
  42. }
  43. private var spriteScene: SKScene {
  44. let scene = SnowScene()
  45. scene.scaleMode = .resizeFill
  46. scene.backgroundColor = .clear
  47. return scene
  48. }
  49. @ViewBuilder func header(_ geo: GeometryProxy) -> some View {
  50. HStack(alignment: .bottom) {
  51. Spacer()
  52. cobIobView
  53. Spacer()
  54. glucoseView
  55. Spacer()
  56. pumpView
  57. Spacer()
  58. loopView
  59. Spacer()
  60. }
  61. .frame(maxWidth: .infinity)
  62. .frame(maxHeight: 70)
  63. .padding(.top, geo.safeAreaInsets.top)
  64. .background(Color.gray.opacity(0.2))
  65. }
  66. var cobIobView: some View {
  67. VStack(alignment: .leading, spacing: 12) {
  68. HStack {
  69. Text("IOB").font(.caption2).foregroundColor(.secondary)
  70. Text(
  71. (numberFormatter.string(from: (state.suggestion?.iob ?? 0) as NSNumber) ?? "0") +
  72. NSLocalizedString(" U", comment: "Insulin unit")
  73. )
  74. .font(.system(size: 12, weight: .bold))
  75. }
  76. HStack {
  77. Text("COB").font(.caption2).foregroundColor(.secondary)
  78. Text(
  79. (numberFormatter.string(from: (state.suggestion?.cob ?? 0) as NSNumber) ?? "0") +
  80. NSLocalizedString(" g", comment: "gram of carbs")
  81. )
  82. .font(.system(size: 12, weight: .bold))
  83. }
  84. }
  85. }
  86. var glucoseView: some View {
  87. CurrentGlucoseView(
  88. recentGlucose: $state.recentGlucose,
  89. delta: $state.glucoseDelta,
  90. units: $state.units,
  91. alarm: $state.alarm
  92. )
  93. .onTapGesture {
  94. if state.alarm == nil {
  95. state.openCGM()
  96. } else {
  97. state.showModal(for: .snooze)
  98. }
  99. }
  100. .onLongPressGesture {
  101. let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
  102. impactHeavy.impactOccurred()
  103. if state.alarm == nil {
  104. state.showModal(for: .snooze)
  105. } else {
  106. state.openCGM()
  107. }
  108. }
  109. }
  110. var pumpView: some View {
  111. PumpView(
  112. reservoir: $state.reservoir,
  113. battery: $state.battery,
  114. name: $state.pumpName,
  115. expiresAtDate: $state.pumpExpiresAtDate,
  116. timerDate: $state.timerDate
  117. )
  118. .onTapGesture {
  119. if state.pumpDisplayState != nil {
  120. state.setupPump = true
  121. }
  122. }
  123. }
  124. var loopView: some View {
  125. LoopView(
  126. suggestion: $state.suggestion,
  127. enactedSuggestion: $state.enactedSuggestion,
  128. closedLoop: $state.closedLoop,
  129. timerDate: $state.timerDate,
  130. isLooping: $state.isLooping,
  131. lastLoopDate: $state.lastLoopDate,
  132. manualTempBasal: $state.manualTempBasal
  133. ).onTapGesture {
  134. isStatusPopupPresented = true
  135. }.onLongPressGesture {
  136. let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
  137. impactHeavy.impactOccurred()
  138. state.runLoop()
  139. }
  140. }
  141. var infoPanel: some View {
  142. HStack(alignment: .center) {
  143. if state.pumpSuspended {
  144. Text("Pump suspended")
  145. .font(.system(size: 12, weight: .bold)).foregroundColor(.loopGray)
  146. .padding(.leading, 8)
  147. } else if let tempRate = state.tempRate {
  148. if state.apsManager.isManualTempBasal {
  149. Text(
  150. (numberFormatter.string(from: tempRate as NSNumber) ?? "0") +
  151. NSLocalizedString(" U/hr", comment: "Unit per hour with space") +
  152. NSLocalizedString(" - Manual Basal ⚠️", comment: "Manual Temp basal")
  153. )
  154. .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
  155. .padding(.leading, 8)
  156. } else {
  157. Text(
  158. (numberFormatter.string(from: tempRate as NSNumber) ?? "0") +
  159. NSLocalizedString(" U/hr", comment: "Unit per hour with space")
  160. )
  161. .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
  162. .padding(.leading, 8)
  163. }
  164. }
  165. if let tempTarget = state.tempTarget {
  166. Text(tempTarget.displayName).font(.caption).foregroundColor(.secondary)
  167. if state.units == .mmolL {
  168. Text(
  169. targetFormatter
  170. .string(from: (tempTarget.targetBottom?.asMmolL ?? 0) as NSNumber)!
  171. )
  172. .font(.caption)
  173. .foregroundColor(.secondary)
  174. if tempTarget.targetBottom != tempTarget.targetTop {
  175. Text("-").font(.caption)
  176. .foregroundColor(.secondary)
  177. Text(
  178. targetFormatter
  179. .string(from: (tempTarget.targetTop?.asMmolL ?? 0) as NSNumber)! +
  180. " \(state.units.rawValue)"
  181. )
  182. .font(.caption)
  183. .foregroundColor(.secondary)
  184. } else {
  185. Text(state.units.rawValue).font(.caption)
  186. .foregroundColor(.secondary)
  187. }
  188. } else {
  189. Text(targetFormatter.string(from: (tempTarget.targetBottom ?? 0) as NSNumber)!)
  190. .font(.caption)
  191. .foregroundColor(.secondary)
  192. if tempTarget.targetBottom != tempTarget.targetTop {
  193. Text("-").font(.caption)
  194. .foregroundColor(.secondary)
  195. Text(
  196. targetFormatter
  197. .string(from: (tempTarget.targetTop ?? 0) as NSNumber)! + " \(state.units.rawValue)"
  198. )
  199. .font(.caption)
  200. .foregroundColor(.secondary)
  201. } else {
  202. Text(state.units.rawValue).font(.caption)
  203. .foregroundColor(.secondary)
  204. }
  205. }
  206. }
  207. Spacer()
  208. if let progress = state.bolusProgress {
  209. Text("Bolusing")
  210. .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
  211. ProgressView(value: Double(progress))
  212. .progressViewStyle(BolusProgressViewStyle())
  213. .padding(.trailing, 8)
  214. .onTapGesture {
  215. state.cancelBolus()
  216. }
  217. }
  218. }
  219. .frame(maxWidth: .infinity, maxHeight: 30)
  220. }
  221. @ViewBuilder private func statPanel() -> some View {
  222. if state.settingsManager.settings.displayStatistics {
  223. VStack(alignment: .center, spacing: 6) {
  224. HStack {
  225. Group {
  226. durationButton(states: durationState.allCases, selectedState: $selectedState)
  227. updateStats()
  228. Text("Updated").font(.caption2)
  229. .foregroundColor(.secondary)
  230. Text(dateFormatter.string(from: state.statistics?.created_at ?? Date())).font(.system(size: 12))
  231. }
  232. }
  233. HStack {
  234. Group {
  235. Text(NSLocalizedString("Average", comment: "")).font(.caption2).foregroundColor(.secondary)
  236. Text(average_).font(.system(size: 12))
  237. Text("Median")
  238. .font(.caption2).foregroundColor(.secondary)
  239. Text(median_).font(.system(size: 12))
  240. }
  241. }
  242. HStack {
  243. Group {
  244. Text(
  245. NSLocalizedString("Low (<", comment: " ") +
  246. getString(state.settingsManager.preferences.low, false) + ")"
  247. ).font(.caption2).foregroundColor(.secondary)
  248. Text(tir_low + " %").font(.system(size: 12)).foregroundColor(.loopRed)
  249. Text("Normal").font(.caption2).foregroundColor(.secondary)
  250. Text(tir_ + " %").font(.system(size: 12)).foregroundColor(.loopGreen)
  251. Text(
  252. NSLocalizedString("High (>", comment: " ") +
  253. getString(state.settingsManager.preferences.high, false) +
  254. ")"
  255. ).font(.caption2).foregroundColor(.secondary)
  256. Text(tir_high + " %").font(.system(size: 12)).foregroundColor(.loopYellow)
  257. }
  258. }
  259. HStack {
  260. Group {
  261. Text("HbA1c").font(.caption2).foregroundColor(.secondary)
  262. Text(hba1c_).font(.system(size: 12))
  263. if !state.settingsManager.preferences.displaySD {
  264. Text(
  265. NSLocalizedString("CV", comment: "CV")
  266. ).font(.caption2).foregroundColor(.secondary)
  267. Text(cv_).font(.system(size: 12))
  268. } else {
  269. Text(
  270. NSLocalizedString("SD", comment: "SD")
  271. ).font(.caption2).foregroundColor(.secondary)
  272. Text(sd_).font(.system(size: 12))
  273. }
  274. Text(
  275. NSLocalizedString("All ", comment: "") + getString(state.statistics?.GlucoseStorage_Days, false) +
  276. NSLocalizedString(" days", comment: "")
  277. ).font(.caption2).foregroundColor(.secondary)
  278. Text(hba1c_all).font(.system(size: 12))
  279. }
  280. }
  281. if state.settingsManager.preferences.displayLoops {
  282. HStack {
  283. Group {
  284. Text("Loops").font(.caption2)
  285. .foregroundColor(.secondary)
  286. Text(
  287. tirFormatter
  288. .string(from: (state.statistics?.Statistics.LoopCycles.loops ?? 0) as NSNumber) ?? ""
  289. )
  290. Text("Average Interval").font(.caption2)
  291. .foregroundColor(.secondary)
  292. Text(
  293. targetFormatter
  294. .string(from: (state.statistics?.Statistics.LoopCycles.avg_interval ?? 0) as NSNumber) ??
  295. ""
  296. )
  297. Text("Median Duration").font(.caption2)
  298. .foregroundColor(.secondary)
  299. Text(
  300. numberFormatter
  301. .string(
  302. from: (state.statistics?.Statistics.LoopCycles.median_duration ?? 0) as NSNumber
  303. ) ?? ""
  304. )
  305. }
  306. }
  307. }
  308. }
  309. .frame(maxWidth: .infinity, maxHeight: 120, alignment: .center)
  310. }
  311. }
  312. var legendPanel: some View {
  313. ZStack {
  314. HStack(alignment: .center) {
  315. Group {
  316. Circle().fill(Color.loopGreen).frame(width: 8, height: 8)
  317. Text("BG")
  318. .font(.system(size: 12, weight: .bold)).foregroundColor(.loopGreen)
  319. }
  320. Group {
  321. Circle().fill(Color.insulin).frame(width: 8, height: 8)
  322. .padding(.leading, 8)
  323. Text("IOB")
  324. .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
  325. }
  326. Group {
  327. Circle().fill(Color.zt).frame(width: 8, height: 8)
  328. .padding(.leading, 8)
  329. Text("ZT")
  330. .font(.system(size: 12, weight: .bold)).foregroundColor(.zt)
  331. }
  332. Group {
  333. Circle().fill(Color.loopYellow).frame(width: 8, height: 8)
  334. .padding(.leading, 8)
  335. Text("COB")
  336. .font(.system(size: 12, weight: .bold)).foregroundColor(.loopYellow)
  337. }
  338. Group {
  339. Circle().fill(Color.uam).frame(width: 8, height: 8)
  340. .padding(.leading, 8)
  341. Text("UAM")
  342. .font(.system(size: 12, weight: .bold)).foregroundColor(.uam)
  343. }
  344. if let eventualBG = state.eventualBG {
  345. Text(
  346. "⇢ " + numberFormatter.string(
  347. from: (state.units == .mmolL ? eventualBG.asMmolL : Decimal(eventualBG)) as NSNumber
  348. )!
  349. )
  350. .font(.system(size: 12, weight: .bold)).foregroundColor(.secondary)
  351. }
  352. }
  353. .frame(maxWidth: .infinity)
  354. }
  355. }
  356. var mainChart: some View {
  357. ZStack {
  358. if state.animatedBackground {
  359. SpriteView(scene: spriteScene, options: [.allowsTransparency])
  360. .ignoresSafeArea()
  361. .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
  362. }
  363. MainChartView(
  364. glucose: $state.glucose,
  365. suggestion: $state.suggestion,
  366. statistcs: $state.statistics,
  367. tempBasals: $state.tempBasals,
  368. boluses: $state.boluses,
  369. suspensions: $state.suspensions,
  370. hours: .constant(state.filteredHours),
  371. maxBasal: $state.maxBasal,
  372. autotunedBasalProfile: $state.autotunedBasalProfile,
  373. basalProfile: $state.basalProfile,
  374. tempTargets: $state.tempTargets,
  375. carbs: $state.carbs,
  376. timerDate: $state.timerDate,
  377. units: $state.units
  378. )
  379. }
  380. // .padding(.bottom)
  381. .modal(for: .dataTable, from: self)
  382. }
  383. @ViewBuilder private func bottomPanel(_ geo: GeometryProxy) -> some View {
  384. ZStack {
  385. Rectangle().fill(Color.gray.opacity(0.2)).frame(height: 50 + geo.safeAreaInsets.bottom)
  386. HStack {
  387. Button { state.showModal(for: .addCarbs) }
  388. label: {
  389. ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
  390. Image("carbs")
  391. .renderingMode(.template)
  392. .resizable()
  393. .frame(width: 24, height: 24)
  394. .foregroundColor(.loopGreen)
  395. .padding(8)
  396. if let carbsReq = state.carbsRequired {
  397. Text(numberFormatter.string(from: carbsReq as NSNumber)!)
  398. .font(.caption)
  399. .foregroundColor(.white)
  400. .padding(4)
  401. .background(Capsule().fill(Color.red))
  402. }
  403. }
  404. }
  405. Spacer()
  406. Button { state.showModal(for: .addTempTarget) }
  407. label: {
  408. Image("target")
  409. .renderingMode(.template)
  410. .resizable()
  411. .frame(width: 24, height: 24)
  412. .padding(8)
  413. }.foregroundColor(.loopYellow)
  414. Spacer()
  415. Button { state.showModal(for: .bolus(waitForSuggestion: false)) }
  416. label: {
  417. Image("bolus")
  418. .renderingMode(.template)
  419. .resizable()
  420. .frame(width: 24, height: 24)
  421. .padding(8)
  422. }.foregroundColor(.insulin)
  423. Spacer()
  424. if state.allowManualTemp {
  425. Button { state.showModal(for: .manualTempBasal) }
  426. label: {
  427. Image("bolus1")
  428. .renderingMode(.template)
  429. .resizable()
  430. .frame(width: 24, height: 24)
  431. .padding(8)
  432. }.foregroundColor(.insulin)
  433. Spacer()
  434. }
  435. Button { state.showModal(for: .settings) }
  436. label: {
  437. Image("settings1")
  438. .renderingMode(.template)
  439. .resizable()
  440. .frame(width: 24, height: 24)
  441. .padding(8)
  442. }.foregroundColor(.loopGray)
  443. }
  444. .padding(.horizontal, 24)
  445. .padding(.bottom, geo.safeAreaInsets.bottom)
  446. }
  447. }
  448. var body: some View {
  449. GeometryReader { geo in
  450. VStack(spacing: 0) {
  451. header(geo)
  452. infoPanel
  453. mainChart
  454. legendPanel
  455. statPanel()
  456. bottomPanel(geo)
  457. }
  458. .edgesIgnoringSafeArea(.vertical)
  459. }
  460. .onAppear(perform: configureView)
  461. .navigationTitle("Home")
  462. .navigationBarHidden(true)
  463. .ignoresSafeArea(.keyboard)
  464. .popup(isPresented: isStatusPopupPresented, alignment: .top, direction: .top) {
  465. popup
  466. .padding()
  467. .background(
  468. RoundedRectangle(cornerRadius: 8, style: .continuous)
  469. .fill(Color(UIColor.darkGray))
  470. )
  471. .onTapGesture {
  472. isStatusPopupPresented = false
  473. }
  474. .gesture(
  475. DragGesture(minimumDistance: 10, coordinateSpace: .local)
  476. .onEnded { value in
  477. if value.translation.height < 0 {
  478. isStatusPopupPresented = false
  479. }
  480. }
  481. )
  482. }
  483. }
  484. private var popup: some View {
  485. VStack(alignment: .leading, spacing: 4) {
  486. Text(state.statusTitle).font(.headline).foregroundColor(.white)
  487. .padding(.bottom, 4)
  488. if let suggestion = state.suggestion {
  489. TagCloudView(tags: suggestion.reasonParts).animation(.none, value: false)
  490. Text(suggestion.reasonConclusion.capitalizingFirstLetter()).font(.caption).foregroundColor(.white)
  491. } else {
  492. Text("No sugestion found").font(.body).foregroundColor(.white)
  493. }
  494. if let errorMessage = state.errorMessage, let date = state.errorDate {
  495. Text("Error at \(dateFormatter.string(from: date))")
  496. .foregroundColor(.white)
  497. .font(.headline)
  498. .padding(.bottom, 4)
  499. .padding(.top, 8)
  500. Text(errorMessage).font(.caption).foregroundColor(.loopRed)
  501. }
  502. }
  503. }
  504. private func colorOfGlucose(_ glucose: Decimal) -> Color {
  505. switch glucose {
  506. case 4 ... 8,
  507. 30 ... 46,
  508. 72 ... 144:
  509. return .loopGreen
  510. case 0 ... 4,
  511. 20 ... 71:
  512. return .loopRed
  513. default:
  514. return .loopYellow
  515. }
  516. }
  517. private func getString(_ stat: Decimal?, _ test: Bool) -> String {
  518. var string: String = targetFormatter.string(from: (stat ?? 0) as NSNumber) ?? ""
  519. if state.units != .mmolL, test == true {
  520. string = tirFormatter.string(from: (stat ?? 0) as NSNumber) ?? ""
  521. }
  522. return string
  523. }
  524. private func updateStats() {
  525. hba1c_all = getString(state.statistics?.Statistics.HbA1c.total, false)
  526. switch selectedState {
  527. case .day:
  528. average_ = getString(state.statistics?.Statistics.Glucose.Average.day, true)
  529. median_ = getString(state.statistics?.Statistics.Glucose.Median.day, true)
  530. tir_low = getString(state.statistics?.Statistics.Distribution.Hypos.day, false)
  531. tir_high = getString(state.statistics?.Statistics.Distribution.Hypers.day, false)
  532. tir_ = getString(state.statistics?.Statistics.Distribution.TIR.day, false)
  533. hba1c_ = getString(state.statistics?.Statistics.HbA1c.day, false)
  534. sd_ = getString(state.statistics?.Statistics.Variance.SD.day, true)
  535. cv_ = getString(state.statistics?.Statistics.Variance.CV.day, false)
  536. case .week:
  537. average_ = getString(state.statistics?.Statistics.Glucose.Average.week, true)
  538. median_ = getString(state.statistics?.Statistics.Glucose.Median.week, true)
  539. tir_low = getString(state.statistics?.Statistics.Distribution.Hypos.week, false)
  540. tir_high = getString(state.statistics?.Statistics.Distribution.Hypers.week, false)
  541. tir_ = getString(state.statistics?.Statistics.Distribution.TIR.week, false)
  542. hba1c_ = getString(state.statistics?.Statistics.HbA1c.week, false)
  543. sd_ = getString(state.statistics?.Statistics.Variance.SD.week, true)
  544. cv_ = getString(state.statistics?.Statistics.Variance.CV.week, false)
  545. case .month:
  546. average_ = getString(state.statistics?.Statistics.Glucose.Average.month, true)
  547. median_ = getString(state.statistics?.Statistics.Glucose.Median.month, true)
  548. tir_low = getString(state.statistics?.Statistics.Distribution.Hypos.month, false)
  549. tir_high = getString(state.statistics?.Statistics.Distribution.Hypers.month, false)
  550. tir_ = getString(state.statistics?.Statistics.Distribution.TIR.month, false)
  551. hba1c_ = getString(state.statistics?.Statistics.HbA1c.month, false)
  552. sd_ = getString(state.statistics?.Statistics.Variance.SD.month, true)
  553. cv_ = getString(state.statistics?.Statistics.Variance.CV.month, false)
  554. case .ninetyDays:
  555. average_ = getString(state.statistics?.Statistics.Glucose.Average.ninetyDays, true)
  556. median_ = getString(state.statistics?.Statistics.Glucose.Median.ninetyDays, true)
  557. tir_low = getString(state.statistics?.Statistics.Distribution.Hypos.ninetyDays, false)
  558. tir_high = getString(state.statistics?.Statistics.Distribution.Hypers.ninetyDays, false)
  559. tir_ = getString(state.statistics?.Statistics.Distribution.TIR.ninetyDays, false)
  560. hba1c_ = getString(state.statistics?.Statistics.HbA1c.ninetyDays, false)
  561. sd_ = getString(state.statistics?.Statistics.Variance.SD.ninetyDays, true)
  562. cv_ = getString(state.statistics?.Statistics.Variance.CV.ninetyDays, false)
  563. case .total:
  564. average_ = getString(state.statistics?.Statistics.Glucose.Average.total, true)
  565. median_ = getString(state.statistics?.Statistics.Glucose.Median.total, true)
  566. tir_low = getString(state.statistics?.Statistics.Distribution.Hypos.total, false)
  567. tir_high = getString(state.statistics?.Statistics.Distribution.Hypers.total, false)
  568. tir_ = getString(state.statistics?.Statistics.Distribution.TIR.total, false)
  569. hba1c_ = getString(state.statistics?.Statistics.HbA1c.total, false)
  570. sd_ = getString(state.statistics?.Statistics.Variance.SD.total, true)
  571. cv_ = getString(state.statistics?.Statistics.Variance.CV.total, false)
  572. }
  573. }
  574. }
  575. }