-
Notifications
You must be signed in to change notification settings - Fork 354
/
Copy pathRootViewController.swift
4189 lines (2967 loc) · 231 KB
/
RootViewController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import UIKit
import CoreData
import os
import CoreBluetooth
import UserNotifications
import SwiftCharts
import HealthKitUI
import AVFoundation
import PieCharts
import WatchConnectivity
import SwiftUI
import WidgetKit
#if canImport(AppIntents)
import AppIntents
#endif
/// viewcontroller for the home screen
final class RootViewController: UIViewController, ObservableObject {
// MARK: - Properties - Outlets and Actions for buttons and labels in home screen
// *******************
// ***** Toolbar *****
// *******************
@IBOutlet weak var toolbarOutlet: UIToolbar!
@IBOutlet weak var preSnoozeToolbarButtonOutlet: UIBarButtonItem!
@IBAction func preSnoozeToolbarButtonAction(_ sender: UIBarButtonItem) {
// opens the SnoozeViewController, see storyboard
}
@IBOutlet weak var bgReadingsToolbarButtonOutlet: UIBarButtonItem!
@IBAction func bgReadingsToolbarButtonAction(_ sender: UIBarButtonItem) {
showBgReadingsView()
}
@IBOutlet weak var sensorToolbarButtonOutlet: UIBarButtonItem!
@IBAction func sensorToolbarButtonAction(_ sender: UIBarButtonItem) {
createAndPresentSensorButtonActionSheet()
}
@IBOutlet weak var calibrateToolbarButtonOutlet: UIBarButtonItem!
@IBAction func calibrateToolbarButtonAction(_ sender: UIBarButtonItem) {
// if this is a transmitter that does not require and is not allowed to be calibrated, then give warning message
if let cgmTransmitter = self.bluetoothPeripheralManager?.getCGMTransmitter(), (cgmTransmitter.isWebOOPEnabled() && !cgmTransmitter.overruleIsWebOOPEnabled()) {
let alert = UIAlertController(title: Texts_Common.warning, message: Texts_HomeView.calibrationNotNecessary, actionHandler: nil)
self.present(alert, animated: true, completion: nil)
} else {
trace("calibration : user clicked the calibrate button", log: self.log, category: ConstantsLog.categoryRootView, type: .info)
requestCalibration(userRequested: true)
}
}
@IBOutlet weak var showHideItemsToolbarButtonOutlet: UIBarButtonItem!
@IBAction func showHideItemsToolbarButtonAction(_ sender: UIBarButtonItem) {
}
/// outlet for the lock button - it will change text based upon whether they screen is locked or not
@IBOutlet weak var screenLockToolbarButtonOutlet: UIBarButtonItem!
/// call the screen lock alert when the button is pressed
@IBAction func screenLockToolbarButtonAction(_ sender: UIBarButtonItem) {
screenLockAlert(nightMode: true)
}
// **************************
// ***** Pump Data View *****
// **************************
/// create a view outlet (with the statistics day control inside) so that we can show/hide it as necessary
@IBOutlet weak var pumpViewOutlet: UIView!
@IBOutlet weak var pumpBasalLabelOutlet: UILabel!
@IBOutlet weak var pumpBasalValueOutlet: UILabel!
@IBOutlet weak var pumpReservoirLabelOutlet: UILabel!
@IBOutlet weak var pumpReservoirValueOutlet: UILabel!
@IBOutlet weak var pumpBatteryLabelOutlet: UILabel!
@IBOutlet weak var pumpBatteryValueOutlet: UILabel!
@IBOutlet weak var pumpCAGELabelOutlet: UILabel!
@IBOutlet weak var pumpCAGEValueOutlet: UILabel!
// *******************************
// ***** Treatments/AID View *****
// *******************************
@IBOutlet weak var infoViewOutlet: UIView!
@IBOutlet weak var infoIOBLabelOutlet: UILabel!
@IBOutlet weak var infoIOBValueOutlet: UILabel!
@IBOutlet weak var infoCOBLabelOutlet: UILabel!
@IBOutlet weak var infoCOBValueOutlet: UILabel!
@IBOutlet weak var infoUploaderBatteryOutlet: UIImageView!
@IBOutlet weak var infoStatusActivityIndicatorOutlet: UIActivityIndicatorView!
@IBOutlet weak var infoStatusIconOutlet: UIImageView!
@IBOutlet weak var infoStatusTimeAgoOutlet: UILabel!
@IBOutlet weak var infoStatusButtonOutlet: UIButton!
@IBAction func infoStatusButtonAction(_ sender: UIButton) {
showAIDStatusView()
}
// *****************************
// ***** BG and Delta View *****
// *****************************
/// outlet for label that shows how many minutes ago and so on
@IBOutlet weak var minutesLabelOutlet: UILabel!
@IBOutlet weak var minutesAgoLabelOutlet: UILabel!
/// outlet for label that shows difference with previous reading
@IBOutlet weak var diffLabelOutlet: UILabel!
@IBOutlet weak var diffLabelUnitOutlet: UILabel!
/// outlet for label that shows the current reading
@IBOutlet weak var valueLabelOutlet: UILabel!
@IBAction func valueLabelLongPressGestureRecognizerAction(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
// call the UIAlert but assume that the user wants a simple screen lock, not the full lock mode
screenLockAlert(overrideScreenIsLocked: true, nightMode: false)
}
}
/// action to show/hide the AID status windows if AID follow is enabled
@IBAction func valueLabelTapGestureRecognizerAction(_ sender: UITapGestureRecognizer) {
if UserDefaults.standard.nightscoutFollowType != .none {
UserDefaults.standard.nightscoutFollowShowExpandedInfo.toggle()
updatePumpAndAIDStatusViews()
}
}
// ***************************
// ***** Main Chart View *****
// ***************************
/// outlet for optional patient name to show who is being followed
@IBOutlet weak var followerPatientNameLabelOutlet: UILabel!
/// outlet for chart
@IBOutlet weak var chartOutlet: BloodGlucoseChartView!
// ***************************
// ***** Mini Chart View *****
// ***************************
/// 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
@IBOutlet weak var segmentedControlChartHours: UISegmentedControl!
/// update the chart period in hours
@IBAction func chartHoursChanged(_ sender: Any) {
switch segmentedControlChartHours.selectedSegmentIndex {
case 0:
UserDefaults.standard.chartWidthInHours = 3
case 1:
UserDefaults.standard.chartWidthInHours = 5
case 2:
UserDefaults.standard.chartWidthInHours = 8
case 3:
UserDefaults.standard.chartWidthInHours = 12
default:
break
}
}
/// create a view outlet (with the statistics day control inside) so that we can show/hide it as necessary
@IBOutlet weak var segmentedControlStatisticsDaysView: UIView!
@IBOutlet weak var segmentedControlStatisticsDays: UISegmentedControl!
/// update the days to use for statistics calculations
@IBAction func statisticsDaysChanged(_ sender: Any) {
switch segmentedControlStatisticsDays.selectedSegmentIndex {
case 0:
UserDefaults.standard.daysToUseStatistics = 0
case 1:
UserDefaults.standard.daysToUseStatistics = 1
case 2:
UserDefaults.standard.daysToUseStatistics = 7
case 3:
UserDefaults.standard.daysToUseStatistics = 30
case 4:
UserDefaults.standard.daysToUseStatistics = 90
default:
break
}
}
// ***************************
// ***** Statistics View *****
// ***************************
/// outlets for statistics view
@IBOutlet weak var statisticsView: UIView!
@IBOutlet weak var pieChartOutlet: PieChart!
@IBOutlet weak var lowStatisticLabelOutlet: UILabel!
@IBOutlet weak var inRangeStatisticLabelOutlet: UILabel!
@IBOutlet weak var highStatisticLabelOutlet: UILabel!
@IBOutlet weak var averageStatisticLabelOutlet: UILabel!
@IBOutlet weak var a1CStatisticLabelOutlet: UILabel!
@IBOutlet weak var cVStatisticLabelOutlet: UILabel!
@IBOutlet weak var lowTitleLabelOutlet: UILabel!
@IBOutlet weak var inRangeTitleLabelOutlet: UILabel!
@IBOutlet weak var highTitleLabelOutlet: UILabel!
@IBOutlet weak var averageTitleLabelOutlet: UILabel!
@IBOutlet weak var a1cTitleLabelOutlet: UILabel!
@IBOutlet weak var cvTitleLabelOutlet: UILabel!
@IBOutlet weak var lowLabelOutlet: UILabel!
@IBOutlet weak var highLabelOutlet: UILabel!
@IBOutlet weak var pieChartLabelOutlet: UILabel!
@IBOutlet weak var timePeriodLabelOutlet: UILabel!
@IBOutlet weak var activityMonitorOutlet: UIActivityIndicatorView!
// **********************
// ***** Clock View *****
// **********************
/// clock view
@IBOutlet weak var clockView: UIView!
@IBOutlet weak var clockLabelOutlet: UILabel!
// ********************************
// ***** Sensor Progress View *****
// ********************************
/// sensor progress view - progress view
@IBOutlet weak var sensorProgressViewOutlet: UIView!
@IBOutlet weak var sensorProgressOutlet: UIProgressView!
// ****************************
// ***** Data Source View *****
// ****************************
/// data source info view - general info
@IBOutlet weak var dataSourceViewOutlet: UIView!
@IBOutlet weak var dataSourceConnectionStatusImage: UIImageView!
@IBOutlet weak var dataSourceLabelOutlet: UILabel!
@IBOutlet weak var dataSourceKeepAliveImageOutlet: UIImageView!
@IBOutlet weak var dataSourceSensorCurrentAgeOutlet: UILabel!
@IBOutlet weak var dataSourceSensorMaxAgeOutlet: UILabel!
/// used to temporarily hide the Nightscout URL from the data source info view
/// this is to allow a user to make screenshots etc without any personal information
@IBAction func urlDoubleTapGestureRecognizerAction(_ sender: UITapGestureRecognizer) {
// make sure we only act on the gesture if we're in Nightscout follower mode (i.e. with the URL visible)
if !UserDefaults.standard.isMaster && UserDefaults.standard.nightscoutEnabled && UserDefaults.standard.followerDataSourceType == .nightscout && UserDefaults.standard.nightscoutUrl != nil {
dataSourceSensorMaxAgeOutlet.textColor = .systemRed
dataSourceSensorMaxAgeOutlet.text = Texts_HomeView.hidingUrlForXSeconds
// wait and then fade out the text
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
// make a animated transition with the label. Fade it out
UIView.transition(with: self.miniChartHoursLabelOutlet, duration: 1, options: .transitionCrossDissolve, animations: {
self.dataSourceSensorMaxAgeOutlet.alpha = 0
}, completion: { _ in
// wait for a some time and then put the URL back as it was
DispatchQueue.main.asyncAfter(deadline: .now() + Double(ConstantsHomeView.hideUrlDuringTimeInSeconds)) {
// just copied directly from updateDataSourceInfo()
var nightscoutUrlString: String = UserDefaults.standard.nightscoutUrl ?? ""
if nightscoutUrlString.count > 30 {
nightscoutUrlString = nightscoutUrlString.replacingOccurrences(of: nightscoutUrlString.dropFirst(27), with: "...")
}
self.dataSourceSensorMaxAgeOutlet.alpha = 1
self.dataSourceSensorMaxAgeOutlet.textColor = .systemGray
self.dataSourceSensorMaxAgeOutlet.text = nightscoutUrlString
}
})
}
}
}
// ******************************************
// ***** Main Chart Gesture Recognizers *****
// ******************************************
@IBOutlet var chartPanGestureRecognizerOutlet: UIPanGestureRecognizer!
@IBAction func chartPanGestureRecognizerAction(_ sender: UIPanGestureRecognizer) {
guard let glucoseChartManager = glucoseChartManager else {return}
glucoseChartManager.handleUIGestureRecognizer(recognizer: sender, chartOutlet: chartOutlet, completionHandler: {
// user has been panning, if chart is panned backward, then need to set valueLabel to value of latest chartPoint shown in the chart, and minutesAgo text to timeStamp of latestChartPoint
if glucoseChartManager.chartIsPannedBackward {
if let lastChartPointEarlierThanEndDate = glucoseChartManager.lastChartPointEarlierThanEndDate, let chartAxisValueDate = lastChartPointEarlierThanEndDate.x as? ChartAxisValueDate {
// valueLabel text should not be strikethrough (might still be strikethrough in case latest reading is older than 10 minutes
self.valueLabelOutlet.attributedText = nil
// set value to value of latest chartPoint
self.valueLabelOutlet.text = lastChartPointEarlierThanEndDate.y.scalar.bgValueToString(mgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl)
// set timestamp to timestamp of latest chartPoint, in red so user can notice this is an old value
self.minutesLabelOutlet.text = self.dateTimeFormatterForMinutesLabelWhenPanning.string(from: chartAxisValueDate.date)
self.minutesLabelOutlet.textColor = UIColor.red
self.minutesAgoLabelOutlet.text = ""
self.valueLabelOutlet.textColor = UIColor.lightGray
// apply strikethrough to the BG value text format
let attributedString = NSMutableAttributedString(string: self.valueLabelOutlet.text!)
attributedString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 1, range: NSMakeRange(0, attributedString.length))
self.valueLabelOutlet.attributedText = attributedString
// don't show anything in diff outlet
self.diffLabelOutlet.text = ""
self.diffLabelUnitOutlet.text = ""
} else {
// this would only be the case if there's no readings withing the shown timeframe
self.updateLabelsAndChart(overrideApplicationState: false)
}
} else {
// chart is not panned, update labels is necessary
self.updateLabelsAndChart(overrideApplicationState: false)
}
})
}
@IBOutlet var chartLongPressGestureRecognizerOutlet: UILongPressGestureRecognizer!
@IBAction func chartLongPressGestureRecognizerAction(_ sender: UILongPressGestureRecognizer) {
// this one needs trigger in case user has panned, chart is decelerating, user clicks to stop the decleration, call to handleUIGestureRecognizer will stop the deceleration
// there's no completionhandler needed because the call in chartPanGestureRecognizerAction to handleUIGestureRecognizer already includes a completionhandler
glucoseChartManager?.handleUIGestureRecognizer(recognizer: sender, chartOutlet: chartOutlet, completionHandler: nil)
}
@IBAction func chartDoubleTapGestureRecognizer(_ sender: UITapGestureRecognizer) {
// if the main chart is double-tapped then force a reset to return to the current date/time, refresh the chart and also all labels
updateLabelsAndChart(forceReset: true)
}
// ******************************************
// ***** Mini Chart Gesture Recognizers *****
// ******************************************
@IBAction func miniChartDoubleTapGestureRecognizer(_ sender: UITapGestureRecognizer) {
// move the days range to the next one (or back to the first one) and also set the text. We'll use "24 hours" for the first range (to make it clear it's not a full day, but the last 24 hours), but to keep the UI simpler, we'll use "x days" for the rest.
switch UserDefaults.standard.miniChartHoursToShow {
case ConstantsGlucoseChart.miniChartHoursToShow1:
UserDefaults.standard.miniChartHoursToShow = ConstantsGlucoseChart.miniChartHoursToShow2
miniChartHoursLabelOutlet.text = Int(UserDefaults.standard.miniChartHoursToShow / 24).description + " " + Texts_Common.days
case ConstantsGlucoseChart.miniChartHoursToShow2:
UserDefaults.standard.miniChartHoursToShow = ConstantsGlucoseChart.miniChartHoursToShow3
miniChartHoursLabelOutlet.text = Int(UserDefaults.standard.miniChartHoursToShow / 24).description + " " + Texts_Common.days
case ConstantsGlucoseChart.miniChartHoursToShow3:
UserDefaults.standard.miniChartHoursToShow = ConstantsGlucoseChart.miniChartHoursToShow4
miniChartHoursLabelOutlet.text = Int(UserDefaults.standard.miniChartHoursToShow / 24).description + " " + Texts_Common.days
case ConstantsGlucoseChart.miniChartHoursToShow4:
// we're already on the last range, so roll back to the first range
UserDefaults.standard.miniChartHoursToShow = ConstantsGlucoseChart.miniChartHoursToShow1
miniChartHoursLabelOutlet.text = Int(UserDefaults.standard.miniChartHoursToShow).description + " " + Texts_Common.hours
default: // the default will never get resolved as there is always an expected value assigned, but we need to include it to keep the compiler happy
UserDefaults.standard.miniChartHoursToShow = ConstantsGlucoseChart.miniChartHoursToShow1
miniChartHoursLabelOutlet.text = Int(UserDefaults.standard.miniChartHoursToShow).description + " " + Texts_Common.hours
}
// increase alpha to fully brighten the label temporarily
miniChartHoursLabelOutlet.alpha = 1.0
// wait for a second and then fade the label back 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 = ConstantsGlucoseChart.miniChartHoursToShowLabelAlpha
})
}
}
@IBOutlet var miniChartDoubleTapGestureRecognizer: UITapGestureRecognizer!
/// can be used as a shortcut to switch between different TIR calculation methods. The user will be notified of the change via UI transitions to show what has changed in the calculation limits
@IBAction func statisticsViewDoubleTapGestureRecognizer(_ sender: UITapGestureRecognizer) {
let previousTimeInRangeType = UserDefaults.standard.timeInRangeType
var newTimeInRangeType: TimeInRangeType = previousTimeInRangeType
// if we're at the last index in the enum, then go to the first one
// otherwise, just set to the next index
if previousTimeInRangeType == TimeInRangeType.allCases.last {
newTimeInRangeType = .standardRange
} else {
newTimeInRangeType = TimeInRangeType(rawValue: previousTimeInRangeType.rawValue + 1) ?? .tightRange
}
// write the new index back to userdefaults (this will also trigger the observers to update the UI)
UserDefaults.standard.timeInRangeType = newTimeInRangeType
updateStatistics(animate: false)
let normalTitleColor: UIColor = lowTitleLabelOutlet.textColor
inRangeTitleLabelOutlet.textColor = ConstantsStatistics.highlightColorTitles
if previousTimeInRangeType.lowerLimit != newTimeInRangeType.lowerLimit {
self.lowLabelOutlet.textColor = ConstantsStatistics.labelLowColor
}
if previousTimeInRangeType.higherLimit != newTimeInRangeType.higherLimit {
self.highLabelOutlet.textColor = ConstantsStatistics.labelHighColor
}
// wait a short while and then fade the labels back out
// even if some of the label colours weren't changed, it's easier to just fade them all every time.
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
UIView.transition(with: self.inRangeTitleLabelOutlet, duration: 1, options: .transitionCrossDissolve, animations: {
self.inRangeTitleLabelOutlet.textColor = normalTitleColor
})
UIView.transition(with: self.lowLabelOutlet, duration: 1, options: .transitionCrossDissolve, animations: {
self.lowLabelOutlet.textColor = .lightGray
})
UIView.transition(with: self.highLabelOutlet, duration: 1, options: .transitionCrossDissolve, animations: {
self.highLabelOutlet.textColor = .lightGray
})
}
}
// **********************************************************
// ***** Clock (dimming cover view) Gesture Recognizers *****
// **********************************************************
/// function which is triggered when hideCoverView is called
@objc func handleTapCoverView(_ sender: UITapGestureRecognizer) {
screenLockUpdate(enabled: false)
}
// MARK: - Actions for SwiftUI Hosting Controller integration
@IBSegueAction func segueToBgReadingsView(_ coder: NSCoder) -> UIViewController? {
return UIHostingController(coder: coder, rootView: BgReadingsView().environmentObject(bgReadingsAccessor!).environmentObject(nightscoutSyncManager!))
}
@IBSegueAction func segueToShowHideItemsView(_ coder: NSCoder) -> UIViewController? {
return UIHostingController(coder: coder, rootView: ShowHideItemsView())
}
@IBSegueAction func segueToAIDStatusView(_ coder: NSCoder) -> UIViewController? {
return UIHostingController(coder: coder, rootView: AIDStatusView().environmentObject(nightscoutSyncManager!))
}
// MARK: - Constants for ApplicationManager usage
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - create updateLabelsAndChartTimer
private let applicationManagerKeyCreateupdateLabelsAndChartTimer = "RootViewController-CreateupdateLabelsAndChartTimer"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppDidEnterBackground
private let applicationManagerKeyInvalidateupdateLabelsAndChartTimerAndCloseSnoozeViewController = "RootViewController-InvalidateupdateLabelsAndChartTimerAndCloseSnoozeViewController"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - initial calibration
private let applicationManagerKeyInitialCalibration = "RootViewController-InitialCalibration"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppDidEnterBackground - isIdleTimerDisabled
private let applicationManagerKeyIsIdleTimerDisabled = "RootViewController-isIdleTimerDisabled"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppDidEnterBackground - trace that app goes to background
private let applicationManagerKeyTraceAppGoesToBackGround = "applicationManagerKeyTraceAppGoesToBackGround"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - trace that app goes to background
private let applicationManagerKeyTraceAppGoesToForeground = "applicationManagerKeyTraceAppGoesToForeground"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillTerminate - trace that app goes to background
private let applicationManagerKeyTraceAppWillTerminate = "applicationManagerKeyTraceAppWillTerminate"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppDidEnterBackground - to clean GlucoseChartManager memory
private let applicationManagerKeyCleanMemoryGlucoseChartManager = "applicationManagerKeyCleanMemoryGlucoseChartManager"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - to initialize the glucoseChartManager and update labels and chart
private let applicationManagerKeyUpdateLabelsAndChart = "applicationManagerKeyUpdateLabelsAndChart"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - to dismiss screenLockAlertController
private let applicationManagerKeyDismissScreenLockAlertController = "applicationManagerKeyDismissScreenLockAlertController"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - to do a Nightscout Treatment sync
private let applicationManagerKeyStartNightscoutTreatmentSync = "applicationManagerKeyStartNightscoutTreatmentSync"
// MARK: - Properties - other private properties
/// for logging
private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryRootView)
/// CoreDataManager to be used throughout the project
private var coreDataManager: CoreDataManager?
/// to solve problem that sometemes UserDefaults key value changes is triggered twice for just one change
private let keyValueObserverTimeKeeper: KeyValueObserverTimeKeeper = KeyValueObserverTimeKeeper()
/// Calibrator to be used for calibration, value will depend on transmitter type
private var calibrator: Calibrator?
/// BgReadingsAccessor instance
private var bgReadingsAccessor: BgReadingsAccessor?
/// CalibrationsAccessor instance
private var calibrationsAccessor: CalibrationsAccessor?
/// TreatmentEntryAccessor instance
private var treatmentEntryAccessor: TreatmentEntryAccessor?
/// NightscoutSyncManager instance
private var nightscoutSyncManager: NightscoutSyncManager?
/// AlertManager instance
private var alertManager: AlertManager?
/// LoopManager instance
private var loopManager: LoopManager?
/// SoundPlayer instance
private var soundPlayer: SoundPlayer?
/// NightscoutFollowManager instance
private var nightscoutFollowManager: NightscoutFollowManager?
/// LibreLinkUpFollowManager instance
private var libreLinkUpFollowManager: LibreLinkUpFollowManager?
/// LoopFollowManager instance
private var loopFollowManager: LoopFollowManager?
/// DexcomShareUploadManager instance
private var dexcomShareUploadManager: DexcomShareUploadManager?
/// CalendarManager instance
private var calendarManager: CalendarManager?
/// ContactImageManager instance
private var contactImageManager: ContactImageManager?
/// HealthKit manager instance
private var healthKitManager:HealthKitManager?
/// reference to activeSensor
private var activeSensor:Sensor?
/// reference to bgReadingSpeaker
private var bgReadingSpeaker:BGReadingSpeaker?
/// manages bluetoothPeripherals that this app knows
private var bluetoothPeripheralManager: BluetoothPeripheralManager?
/// - manage glucose chart
/// - 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 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?
/// watchManager instance
private var watchManager: WatchManager?
/// dateformatter for minutesLabelOutlet, when user is panning the chart
private let dateTimeFormatterForMinutesLabelWhenPanning: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.amSymbol = ConstantsUI.timeFormatAM
dateFormatter.pmSymbol = ConstantsUI.timeFormatPM
dateFormatter.setLocalizedDateFormatFromTemplate(ConstantsGlucoseChart.dateFormatLatestChartPointWhenPanning)
return dateFormatter
}()
/// housekeeper instance
private var houseKeeper: HouseKeeper?
/// current value of webOPEnabled, if nil then it means no cgmTransmitter connected yet , false is used as value
/// - used to detect changes in the value
///
/// in fact it will never be used with a nil value, except when connecting to a cgm transmitter for the first time
private var webOOPEnabled: Bool?
/// current value of nonFixedSlopeEnabled, if nil then it means no cgmTransmitter connected yet , false is used as value
/// - used to detect changes in the value
///
/// in fact it will never be used with a nil value, except when connecting to a cgm transmitter for the first time
private var nonFixedSlopeEnabled: Bool?
/// when was the last notification created with bgreading, setting to 1 1 1970 initially to avoid having to unwrap it
private var timeStampLastBGNotification = Date(timeIntervalSince1970: 0)
/// to hold the current state of the screen keep-alive
private var screenIsLocked: Bool = false
/// date formatter for the clock view
private var clockDateFormatter = DateFormatter()
/// initiate a Timer object that we will use later to keep the clock view updated if the user activates the screen lock
private var clockTimer: Timer?
/// initiate a Timer object that we will use keep the follower connection status updated every 30 seconds or so
private var followerConnectionTimer: Timer?
/// UIAlertController to use when user chooses to lock the screen. Defined here so we can dismiss it when app goes to the background
private var screenLockAlertController: UIAlertController?
/// create the chart landscape view
private var landscapeChartViewController: LandscapeChartViewController?
/// create the value landscape view
private var landscapeValueViewController: LandscapeValueViewController?
/// uiview to be used for the night-mode overlay to darken the app screen
private var overlayView: UIView?
// MARK: - overriden functions
// set the status bar content colour to light to match new darker theme
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// never seen it triggered, copied that from Loop
glucoseChartManager?.cleanUpMemory()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// check if allowed to rotate to landscape view
updateScreenRotationSettings()
// 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)
updatePumpAndAIDStatusViews()
// show the mini-chart and other info as required
miniChartOutlet.isHidden = !UserDefaults.standard.showMiniChart
statisticsView.isHidden = !UserDefaults.standard.showStatistics
segmentedControlStatisticsDaysView.isHidden = !UserDefaults.standard.showStatistics
if inRangeStatisticLabelOutlet.text == "-" {
activityMonitorOutlet.isHidden = true
} else {
activityMonitorOutlet.isHidden = false
}
// display the data source info view if applicable
updateDataSourceInfo()
// update statistics related outlets
updateStatistics(animate: true, overrideApplicationState: true)
}
override func viewDidAppear(_ animated: Bool) {
// remove titles from tabbar items
self.tabBarController?.cleanTitles()
watchManager?.updateWatchApp(forceComplicationUpdate: false)
// let's run the data source info and chart update 1 second after the root view appears. This should give time for the follower modes to download and populate the info needed.
// no animation is needed as in most cases, we're just refreshing and displaying what is already shown on screen so we want to keep this refresh invisible.
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// if the user locks the screen before the update is called, then don't run the update
if !self.screenIsLocked {
self.updateDataSourceInfo()
}
self.updateLabelsAndChart(overrideApplicationState: true)
self.updatePumpAndAIDStatusViews()
}
self.updateSnoozeStatus()
IntentDonationManager.shared.donate(intent: GlucoseIntent())
}
override func viewDidLoad() {
super.viewDidLoad()
// from 5.2.0 the showTarget userdefault will be deprecated
// showTarget will not be checked by the app any more, it will use targetMarkValue
// targetMarkValue == 0 for disabled (hide) or targetMarkValue > 0 for enabled (show)
if !UserDefaults.standard.showTarget {
UserDefaults.standard.targetMarkValueInUserChosenUnit = 0
}
// set up the clock view
clockDateFormatter.dateStyle = .none
clockDateFormatter.timeStyle = .short
clockDateFormatter.dateFormat = "HH:mm"
clockLabelOutlet.font = ConstantsUI.clockLabelFontSize
clockLabelOutlet.textColor = ConstantsUI.clockLabelColor
// ensure the screen layout
screenLockUpdate(enabled: false)
// this is to force update of userdefaults that are also stored in the shared user defaults
// these are used by the today widget. After a year or so (september 2021) this can all be deleted
UserDefaults.standard.urgentLowMarkValueInUserChosenUnit = UserDefaults.standard.urgentLowMarkValueInUserChosenUnit
UserDefaults.standard.urgentHighMarkValueInUserChosenUnit = UserDefaults.standard.urgentHighMarkValueInUserChosenUnit
UserDefaults.standard.lowMarkValueInUserChosenUnit = UserDefaults.standard.lowMarkValueInUserChosenUnit
UserDefaults.standard.highMarkValueInUserChosenUnit = UserDefaults.standard.highMarkValueInUserChosenUnit
UserDefaults.standard.bloodGlucoseUnitIsMgDl = UserDefaults.standard.bloodGlucoseUnitIsMgDl
// on 10Jan2025 the chart width options were changed from 3/5/12/24 to 3/5/8/12
// this is just a quick check to catch any users that had 24 selected when they updated
// NOTE: The UI showed 3/6/12/24, but it was actually using 3/5/12/24 for 4 years and nobody noticed the missing hour :)
if UserDefaults.standard.chartWidthInHours == 24 {
UserDefaults.standard.chartWidthInHours = 12
}
// set the localized text of the segmented controls
segmentedControlChartHours.setTitle("3" + Texts_Common.hourshort, forSegmentAt: 0)
segmentedControlChartHours.setTitle("5" + Texts_Common.hourshort, forSegmentAt: 1)
segmentedControlChartHours.setTitle("8" + Texts_Common.hourshort, forSegmentAt: 2)
segmentedControlChartHours.setTitle("12" + Texts_Common.hourshort, forSegmentAt:3)
segmentedControlStatisticsDays.setTitle(Texts_Common.todayshort, forSegmentAt: 0)
segmentedControlStatisticsDays.setTitle("24" + Texts_Common.hourshort, forSegmentAt: 1)
segmentedControlStatisticsDays.setTitle("7" + Texts_Common.dayshort, forSegmentAt: 2)
segmentedControlStatisticsDays.setTitle("30" + Texts_Common.dayshort, forSegmentAt:3)
segmentedControlStatisticsDays.setTitle("90" + Texts_Common.dayshort, forSegmentAt:4)
// update the segmented control of the chart hours
switch UserDefaults.standard.chartWidthInHours
{
case 3:
segmentedControlChartHours.selectedSegmentIndex = 0
case 5:
segmentedControlChartHours.selectedSegmentIndex = 1
case 8:
segmentedControlChartHours.selectedSegmentIndex = 2
case 12:
segmentedControlChartHours.selectedSegmentIndex = 3
default:
break
}
// update the segmented control of the statistics days
switch UserDefaults.standard.daysToUseStatistics
{
case 0:
segmentedControlStatisticsDays.selectedSegmentIndex = 0
case 1:
segmentedControlStatisticsDays.selectedSegmentIndex = 1
case 7:
segmentedControlStatisticsDays.selectedSegmentIndex = 2
case 30:
segmentedControlStatisticsDays.selectedSegmentIndex = 3
case 90:
segmentedControlStatisticsDays.selectedSegmentIndex = 4
default:
break
}
// format the segmented control of the chart hours. We basically want it to dissapear into the background
segmentedControlChartHours.backgroundColor = ConstantsUI.segmentedControlBackgroundColor
segmentedControlChartHours.tintColor = ConstantsUI.segmentedControlBackgroundColor
segmentedControlChartHours.layer.borderWidth = ConstantsUI.segmentedControlBorderWidth
segmentedControlChartHours.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: ConstantsUI.segmentedControlNormalTextColor, NSAttributedString.Key.font: ConstantsUI.segmentedControlFont], for:.normal)
segmentedControlChartHours.selectedSegmentTintColor = ConstantsUI.segmentedControlSelectedTintColor
segmentedControlChartHours.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: ConstantsUI.segmentedControlSelectedTextColor, NSAttributedString.Key.font: ConstantsUI.segmentedControlFont], for:.selected)
// set the basic formatting of the chart days
segmentedControlStatisticsDays.backgroundColor = ConstantsUI.segmentedControlBackgroundColor
segmentedControlStatisticsDays.tintColor = ConstantsUI.segmentedControlBackgroundColor
segmentedControlStatisticsDays.layer.borderWidth = ConstantsUI.segmentedControlBorderWidth
segmentedControlStatisticsDays.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: ConstantsUI.segmentedControlNormalTextColor, NSAttributedString.Key.font: ConstantsUI.segmentedControlFont], for:.normal)
segmentedControlStatisticsDays.selectedSegmentTintColor = ConstantsUI.segmentedControlSelectedTintColor
segmentedControlStatisticsDays.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: ConstantsUI.segmentedControlSelectedTextColor, NSAttributedString.Key.font: ConstantsUI.segmentedControlFont], for:.selected)
// if a RTL localization is in use (such as arabic), then correctly align the low (<x) and high (>x) label outlets towards the centre of the (now reversed) horizontal stack views
if UIView.userInterfaceLayoutDirection(for: view.semanticContentAttribute) == UIUserInterfaceLayoutDirection.rightToLeft {
lowLabelOutlet.textAlignment = .right
lowTitleLabelOutlet.textAlignment = .left
highLabelOutlet.textAlignment = .right
highTitleLabelOutlet.textAlignment = .left
} else {
lowLabelOutlet.textAlignment = .left
lowTitleLabelOutlet.textAlignment = .right
highLabelOutlet.textAlignment = .left
highTitleLabelOutlet.textAlignment = .right
}
// enable or disable the buttons 'sensor' and 'calibrate' on top, depending on master or follower
changeButtonsStatusTo(enabled: UserDefaults.standard.isMaster)
// nillify the active sensor start date on start-up
UserDefaults.standard.activeSensorStartDate = nil
// Setup Core Data Manager - setting up coreDataManager happens asynchronously
// completion handler is called when finished. This gives the app time to already continue setup which is independent of coredata, like initializing the views
coreDataManager = CoreDataManager(modelName: ConstantsCoreData.modelName, completion: {
self.setupApplicationData()
// housekeeper should be non nil here, kall housekeeper
self.houseKeeper?.doAppStartUpHouseKeeping()
// update label texts, minutes ago, diff and value
self.updateLabelsAndChart(overrideApplicationState: true)
// update the mini-chart
self.updateMiniChart()
// update data source info
self.updateDataSourceInfo()
// update statistics related outlets
self.updateStatistics(animate: true, overrideApplicationState: true)
// create badge counter
self.createBgReadingNotificationAndSetAppBadge(overrideShowReadingInNotification: true)
// if licenseinfo not yet accepted, show license info with only ok button
if !UserDefaults.standard.licenseInfoAccepted {
let alert = UIAlertController(title: ConstantsHomeView.applicationName, message: Texts_HomeView.licenseInfo + ConstantsHomeView.infoEmailAddress, actionHandler: {
// set licenseInfoAccepted to true
UserDefaults.standard.licenseInfoAccepted = true
// create info screen about transmitters
let infoScreenAlert = UIAlertController(title: Texts_HomeView.info, message: Texts_HomeView.transmitterInfo, actionHandler: nil)
self.present(infoScreenAlert, animated: true, completion: nil)
})
self.present(alert, animated: true, completion: nil)
}
// launch Nightscout sync
self.setNightscoutSyncRequiredToTrue(forceNow: true)
self.updateLiveActivityAndWidgets(forceRestart: false)
})
// Setup View
setupView()
// observe setting changes
// changing from follower to master or vice versa
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.isMaster.rawValue, options: .new, context: nil)
// 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)
// showing or hiding the statistics view
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.showStatistics.rawValue, options: .new, context: nil)
// showing or hiding the treatments on the chart
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.showTreatmentsOnChart.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)
// bg reading notification and badge, and multiplication factor
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.showReadingInNotification.rawValue, options: .new, context: nil)
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.showReadingInAppBadge.rawValue, options: .new, context: nil)
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.multipleAppBadgeValueWith10.rawValue, options: .new, context: nil)
// also update of unit requires update of badge
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.bloodGlucoseUnitIsMgDl.rawValue, options: .new, context: nil)
// update show clock value for the screen lock function
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.showClockWhenScreenIsLocked.rawValue, options: .new, context: nil)
// if live action type is updated
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.liveActivityType.rawValue, options: .new, context: nil)
// high mark , low mark , urgent high mark, urgent low mark. change requires redraw of chart
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.urgentLowMarkValue.rawValue, options: .new, context: nil)
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.lowMarkValue.rawValue, options: .new, context: nil)
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.highMarkValue.rawValue, options: .new, context: nil)
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.urgentHighMarkValue.rawValue, options: .new, context: nil)
// add observer for nightscoutTreatmentsUpdateCounter, to reload the chart whenever a treatment is added or updated or deleted changes
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.nightscoutTreatmentsUpdateCounter.rawValue, options: .new, context: nil)
// add observer for stopActiveSensor, this will reset the active sensor to nil when the user disconnects an intergrated transmitter/sensor (e.g. Libre 2 Direct). This will help ensure that the data source info is updated/disabled until a new sensor is started.
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.stopActiveSensor.rawValue, options: .new, context: nil)
// add observer for followerKeepAliveType, to reset the app badge notification if in follower mode and keep-alive is set to disabled
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.followerBackgroundKeepAliveType.rawValue, options: .new, context: nil)
// add observer for the last heartbeat timestamp in order to update the UI
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.timeStampOfLastHeartBeat.rawValue, options: .new, context: nil)
// if the user agrees to enable (or disable) Watch complications data
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.showDataInWatchComplications.rawValue, options: .new, context: nil)
// force a manual complication update
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.forceComplicationUpdate.rawValue, options: .new, context: nil)
// force the snooze icon status to be updated
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.updateSnoozeStatus.rawValue, options: .new, context: nil)
// if the snooze all until data changes, update the UI
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.snoozeAllAlertsUntilDate.rawValue, options: .new, context: nil)
// if the Nightscout Follower type changes, update the UI
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.nightscoutFollowType.rawValue, options: .new, context: nil)
// if the Nightscout device status changes, update the UI
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.nightscoutDeviceStatus.rawValue, options: .new, context: nil)
// if the widget standby options change, update the widget data
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.allowStandByHighContrast.rawValue, options: .new, context: nil)
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.forceStandByBigNumbers.rawValue, options: .new, context: nil)
// if the snooze all until data changes, update the UI
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.snoozeAllAlertsUntilDate.rawValue, options: .new, context: nil)
// setup delegate for UNUserNotificationCenter
UNUserNotificationCenter.current().delegate = self
// check if app is allowed to send local notification and if not ask it
UNUserNotificationCenter.current().getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .notDetermined, .denied:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (success, error) in
if let error = error {
trace("Request Notification Authorization Failed : %{public}@", log: self.log, category: ConstantsLog.categoryRootView, type: .error, error.localizedDescription)
}
}
default:
break
}
}
// setup self as delegate for tabbarcontroller
self.tabBarController?.delegate = self
// setup the timer logic for updating the view regularly
setupUpdateLabelsAndChartTimer()
// setup AVAudioSession
setupAVAudioSession()
// user may have activated the screen lock function so that the screen stays open, when going back to background, set isIdleTimerDisabled back to false and update the UI so that it's ready to come to foreground when required.
ApplicationManager.shared.addClosureToRunWhenAppDidEnterBackground(key: applicationManagerKeyIsIdleTimerDisabled, closure: {
UIApplication.shared.isIdleTimerDisabled = false
self.screenLockUpdate(enabled: false)
})
// add tracing when app goes from foreground to background
ApplicationManager.shared.addClosureToRunWhenAppDidEnterBackground(key: applicationManagerKeyTraceAppGoesToBackGround, closure: {
trace("Application did enter background", log: self.log, category: ConstantsLog.categoryRootView, type: .info)
if !UserDefaults.standard.isMaster {
trace(" follower background keep-alive type: %{public}@", log: self.log, category: ConstantsLog.categoryRootView, type: .info, UserDefaults.standard.followerBackgroundKeepAliveType.description)
self.followerConnectionTimer?.invalidate()
self.followerConnectionTimer = nil
}
if self.screenIsLocked {
self.screenLockUpdate(enabled: false)
}
// dim active values when the app goes to the background
self.valueLabelOutlet.textColor = UIColor(resource: .colorTertiary)
self.minutesLabelOutlet.textColor = UIColor(resource: .colorSecondary) // don't overdim minsAgo or delta as it will look strange
self.diffLabelOutlet.textColor = UIColor(resource: .colorSecondary)
self.pumpBasalValueOutlet.textColor = UIColor(resource: .colorTertiary)
self.pumpBatteryValueOutlet.textColor = UIColor(resource: .colorTertiary)
self.pumpReservoirValueOutlet.textColor = UIColor(resource: .colorTertiary)
self.pumpCAGEValueOutlet.textColor = UIColor(resource: .colorTertiary)
self.infoIOBValueOutlet.textColor = UIColor(resource: .colorTertiary)
self.infoCOBValueOutlet.textColor = UIColor(resource: .colorTertiary)
self.infoStatusActivityIndicatorOutlet.isHidden = true
self.infoUploaderBatteryOutlet.isHidden = true
self.infoStatusIconOutlet.tintColor = UIColor(resource: .colorTertiary)
self.infoStatusButtonOutlet.setTitleColor(UIColor(resource: .colorTertiary), for: .normal)
self.infoStatusTimeAgoOutlet.textColor = UIColor(resource: .colorTertiary)
self.lowStatisticLabelOutlet.textColor = UIColor(resource: .colorTertiary)
self.highStatisticLabelOutlet.textColor = UIColor(resource: .colorTertiary)
self.inRangeStatisticLabelOutlet.textColor = UIColor(resource: .colorTertiary)
self.averageStatisticLabelOutlet.textColor = UIColor(resource: .colorTertiary)