Skip to content

Commit e2e063b

Browse files
authored
[LOOP-4438] Mute alerts UI (#540)
* added mute icon loop settings view * added how mute alerts work view * updated alert management view * added alert mute UI to status table * reload banner if already displayed to ensure the state is correct * clean up
1 parent 5719c63 commit e2e063b

File tree

7 files changed

+264
-51
lines changed

7 files changed

+264
-51
lines changed

Loop.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@
399399
B40D07C7251A89D500C1C6D7 /* GlucoseDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D07C6251A89D500C1C6D7 /* GlucoseDisplay.swift */; };
400400
B42C951424A3C76000857C73 /* CGMStatusHUDViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B42C951324A3C76000857C73 /* CGMStatusHUDViewModel.swift */; };
401401
B42D124328D371C400E43D22 /* AlertMuter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B42D124228D371C400E43D22 /* AlertMuter.swift */; };
402+
B43CF07E29434EC4008A520B /* HowMuteAlertWorkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43CF07D29434EC4008A520B /* HowMuteAlertWorkView.swift */; };
402403
B43DA44124D9C12100CAFF4E /* DismissibleHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43DA44024D9C12100CAFF4E /* DismissibleHostingController.swift */; };
403404
B44251B3252350CE00605937 /* ChartAxisValuesStaticGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44251B2252350CE00605937 /* ChartAxisValuesStaticGeneratorTests.swift */; };
404405
B44251B62523578300605937 /* PredictedGlucoseChartTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44251B52523578300605937 /* PredictedGlucoseChartTests.swift */; };
@@ -1352,6 +1353,7 @@
13521353
B40D07C6251A89D500C1C6D7 /* GlucoseDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseDisplay.swift; sourceTree = "<group>"; };
13531354
B42C951324A3C76000857C73 /* CGMStatusHUDViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMStatusHUDViewModel.swift; sourceTree = "<group>"; };
13541355
B42D124228D371C400E43D22 /* AlertMuter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertMuter.swift; sourceTree = "<group>"; };
1356+
B43CF07D29434EC4008A520B /* HowMuteAlertWorkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowMuteAlertWorkView.swift; sourceTree = "<group>"; };
13551357
B43DA44024D9C12100CAFF4E /* DismissibleHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissibleHostingController.swift; sourceTree = "<group>"; };
13561358
B44251B2252350CE00605937 /* ChartAxisValuesStaticGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartAxisValuesStaticGeneratorTests.swift; sourceTree = "<group>"; };
13571359
B44251B52523578300605937 /* PredictedGlucoseChartTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PredictedGlucoseChartTests.swift; sourceTree = "<group>"; };
@@ -2108,6 +2110,7 @@
21082110
A9A056B224B93C62007CF06D /* CriticalEventLogExportView.swift */,
21092111
C191D2A025B3ACAA00C26C0B /* DosingStrategySelectionView.swift */,
21102112
43D381611EBD9759007F8C8F /* HeaderValuesTableViewCell.swift */,
2113+
B43CF07D29434EC4008A520B /* HowMuteAlertWorkView.swift */,
21112114
430D85881F44037000AF2D4F /* HUDViewTableViewCell.swift */,
21122115
A91D2A3E26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift */,
21132116
C1742331259BEADC00399C9D /* ManualEntryDoseView.swift */,
@@ -3511,6 +3514,7 @@
35113514
4FC8C8011DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift in Sources */,
35123515
43E93FB61E469A4000EAB8DB /* NumberFormatter.swift in Sources */,
35133516
C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */,
3517+
B43CF07E29434EC4008A520B /* HowMuteAlertWorkView.swift in Sources */,
35143518
1D6B1B6726866D89009AC446 /* AlertPermissionsChecker.swift in Sources */,
35153519
4F08DE8F1E7BB871006741EA /* CollectionType+Loop.swift in Sources */,
35163520
A9F703772489D8AA00C98AD8 /* PersistentDeviceLog+SimulatedCoreData.swift in Sources */,

Loop/Managers/AlertMuter.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,16 @@ public class AlertMuter: ObservableObject {
125125
return shouldMuteAlert(scheduledAt: triggerInterval)
126126
}
127127
}
128+
129+
func unmuteAlerts() {
130+
configuration.startTime = nil
131+
}
132+
133+
var formattedEndTime: String {
134+
guard let endTime = configuration.mutingEndTime else { return NSLocalizedString("Unknown", comment: "result when time cannot be formatted") }
135+
let formatter = DateFormatter()
136+
formatter.timeStyle = .short
137+
formatter.dateStyle = .none
138+
return formatter.string(from: endTime)
139+
}
128140
}

Loop/View Controllers/StatusTableViewController.swift

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
4949

5050
tableView.register(BolusProgressTableViewCell.nib(), forCellReuseIdentifier: BolusProgressTableViewCell.className)
5151
tableView.register(AlertPermissionsDisabledWarningCell.self, forCellReuseIdentifier: AlertPermissionsDisabledWarningCell.className)
52+
tableView.register(MuteAlertsWarningCell.self, forCellReuseIdentifier: MuteAlertsWarningCell.className)
5253

5354
if FeatureFlags.predictedGlucoseChartClampEnabled {
5455
statusCharts.glucose.glucoseDisplayRange = LoopConstants.glucoseChartDefaultDisplayBoundClamped
@@ -633,7 +634,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
633634
}
634635

635636
private enum Section: Int, CaseIterable {
636-
case alertPermissionsDisabledWarning
637+
case alertWarning
637638
case hud
638639
case status
639640
case charts
@@ -679,7 +680,6 @@ final class StatusTableViewController: LoopChartsTableViewController {
679680
case pumpSuspended(resuming: Bool)
680681
case onboardingSuspended
681682
case recommendManualGlucoseEntry
682-
case tempMuteAlerts
683683

684684
var hasRow: Bool {
685685
switch self {
@@ -718,22 +718,25 @@ final class StatusTableViewController: LoopChartsTableViewController {
718718
!premealOverride.hasFinished()
719719
{
720720
statusRowMode = .scheduleOverrideEnabled(premealOverride)
721-
} else if alertMuter.shouldMuteAlert() {
722-
statusRowMode = .tempMuteAlerts
723721
} else {
724722
statusRowMode = .hidden
725723
}
726724

727725
return statusRowMode
728726
}
729-
727+
728+
private var shouldShowBannerWarning: Bool {
729+
alertPermissionsChecker.showWarning || alertMuter.configuration.shouldMute
730+
}
731+
730732
private func updateBannerRow(animated: Bool) {
731-
let warningWasVisible = tableView.numberOfRows(inSection: Section.alertPermissionsDisabledWarning.rawValue) != 0
732-
let showWarning = alertPermissionsChecker.showWarning
733-
if !showWarning && warningWasVisible {
734-
tableView.deleteRows(at: [IndexPath(row: 0, section: Section.alertPermissionsDisabledWarning.rawValue)], with: animated ? .top : .none)
735-
} else if showWarning && !warningWasVisible {
736-
tableView.insertRows(at: [IndexPath(row: 0, section: Section.alertPermissionsDisabledWarning.rawValue)], with: animated ? .top : .none)
733+
let warningWasVisible = tableView.numberOfRows(inSection: Section.alertWarning.rawValue) != 0
734+
if !shouldShowBannerWarning && warningWasVisible {
735+
tableView.deleteRows(at: [IndexPath(row: 0, section: Section.alertWarning.rawValue)], with: animated ? .top : .none)
736+
} else if shouldShowBannerWarning && !warningWasVisible {
737+
tableView.insertRows(at: [IndexPath(row: 0, section: Section.alertWarning.rawValue)], with: animated ? .top : .none)
738+
} else {
739+
tableView.reloadRows(at: [IndexPath(row: 0, section: Section.alertWarning.rawValue)], with: .none)
737740
}
738741
}
739742

@@ -850,8 +853,8 @@ final class StatusTableViewController: LoopChartsTableViewController {
850853

851854
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
852855
switch Section(rawValue: section)! {
853-
case .alertPermissionsDisabledWarning:
854-
return alertPermissionsChecker.showWarning ? 1 : 0
856+
case .alertWarning:
857+
return shouldShowBannerWarning ? 1 : 0
855858
case .hud:
856859
return shouldShowHUD ? 1 : 0
857860
case .charts:
@@ -862,7 +865,6 @@ final class StatusTableViewController: LoopChartsTableViewController {
862865
}
863866

864867
private class AlertPermissionsDisabledWarningCell: UITableViewCell {
865-
866868
override func updateConfiguration(using state: UICellConfigurationState) {
867869
super.updateConfiguration(using: state)
868870

@@ -878,7 +880,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
878880
contentConfig.textProperties.color = .white
879881
contentConfig.textProperties.font = .systemFont(ofSize: adjustViewForNarrowDisplay ? 16 : 18, weight: .bold)
880882
contentConfig.textProperties.adjustsFontSizeToFitWidth = true
881-
contentConfig.secondaryText = "Fix now by turning Notifications, Critical Alerts and Time Sensitive Notifications ON."
883+
contentConfig.secondaryText = NSLocalizedString("Fix now by turning Notifications, Critical Alerts and Time Sensitive Notifications ON.", comment: "instructions on how to fix alert permission warning")
882884
contentConfig.secondaryTextProperties.color = .white
883885
contentConfig.secondaryTextProperties.font = .systemFont(ofSize: adjustViewForNarrowDisplay ? 13 : 15)
884886
contentConfiguration = contentConfig
@@ -897,12 +899,55 @@ final class StatusTableViewController: LoopChartsTableViewController {
897899
contentView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 6, leading: 0, bottom: 13, trailing: 0)
898900
}
899901
}
902+
903+
private class MuteAlertsWarningCell: UITableViewCell {
904+
var formattedAlertMuteEndTime: String = NSLocalizedString("Unknown", comment: "label for when the alert mute end time is unknown")
905+
906+
override func updateConfiguration(using state: UICellConfigurationState) {
907+
super.updateConfiguration(using: state)
908+
909+
let adjustViewForNarrowDisplay = bounds.width < 350
910+
911+
var contentConfig = defaultContentConfiguration().updated(for: state)
912+
let title = NSMutableAttributedString(string: NSLocalizedString("All Alerts Muted", comment: "Warning text for when alerts are muted"))
913+
contentConfig.image = UIImage(systemName: "speaker.slash.fill")
914+
contentConfig.imageProperties.tintColor = .white
915+
contentConfig.attributedText = title
916+
contentConfig.textProperties.color = .white
917+
contentConfig.textProperties.font = .systemFont(ofSize: adjustViewForNarrowDisplay ? 16 : 18, weight: .bold)
918+
contentConfig.textProperties.adjustsFontSizeToFitWidth = true
919+
contentConfig.secondaryText = String(format: NSLocalizedString("Until %1$@", comment: "indication of when alerts will be unmuted (1: time when alerts unmute)"), formattedAlertMuteEndTime)
920+
contentConfig.secondaryTextProperties.color = .white
921+
contentConfig.secondaryTextProperties.font = .systemFont(ofSize: adjustViewForNarrowDisplay ? 13 : 15)
922+
contentConfiguration = contentConfig
923+
924+
var backgroundConfig = backgroundConfiguration?.updated(for: state)
925+
backgroundConfig?.backgroundColor = .warning.withAlphaComponent(0.8)
926+
backgroundConfiguration = backgroundConfig
927+
backgroundConfiguration?.backgroundInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 5, trailing: 10)
928+
backgroundConfiguration?.cornerRadius = 10
929+
930+
let unmuteIndicator = UIImage(systemName: "stop.circle")?.withTintColor(.white)
931+
let imageView = UIImageView(image: unmuteIndicator)
932+
imageView.tintColor = .white
933+
imageView.frame.size = CGSize(width: 30, height: 30)
934+
accessoryView = imageView
935+
936+
contentView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 6, leading: 0, bottom: 13, trailing: 0)
937+
}
938+
}
900939

901940
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
902941
switch Section(rawValue: indexPath.section)! {
903-
case .alertPermissionsDisabledWarning:
904-
let cell = tableView.dequeueReusableCell(withIdentifier: AlertPermissionsDisabledWarningCell.className, for: indexPath) as! AlertPermissionsDisabledWarningCell
905-
return cell
942+
case .alertWarning:
943+
if alertPermissionsChecker.showWarning {
944+
let cell = tableView.dequeueReusableCell(withIdentifier: AlertPermissionsDisabledWarningCell.className, for: indexPath) as! AlertPermissionsDisabledWarningCell
945+
return cell
946+
} else {
947+
let cell = tableView.dequeueReusableCell(withIdentifier: MuteAlertsWarningCell.className, for: indexPath) as! MuteAlertsWarningCell
948+
cell.formattedAlertMuteEndTime = alertMuter.formattedEndTime
949+
return cell
950+
}
906951
case .hud:
907952
let cell = tableView.dequeueReusableCell(withIdentifier: HUDViewTableViewCell.className, for: indexPath) as! HUDViewTableViewCell
908953
hudView = cell.hudView
@@ -958,11 +1003,6 @@ final class StatusTableViewController: LoopChartsTableViewController {
9581003
switch StatusRow(rawValue: indexPath.row)! {
9591004
case .status:
9601005
switch statusRowMode {
961-
case .tempMuteAlerts:
962-
//TODO testing (need design to make the correct status row)
963-
let cell = getTitleSubtitleCell()
964-
cell.titleLabel.text = NSLocalizedString("Temp Mute Alerts", comment: "The title of the cell indicating alerts are temporarily muted")
965-
return cell
9661006
case .hidden:
9671007
let cell = getTitleSubtitleCell()
9681008
return cell
@@ -1101,7 +1141,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
11011141
cell.setSubtitleLabel(label: nil)
11021142
}
11031143
}
1104-
case .hud, .status, .alertPermissionsDisabledWarning:
1144+
case .hud, .status, .alertWarning:
11051145
break
11061146
}
11071147
}
@@ -1122,16 +1162,21 @@ final class StatusTableViewController: LoopChartsTableViewController {
11221162
case .iob, .dose, .cob:
11231163
return max(106, 0.21 * availableSize)
11241164
}
1125-
case .hud, .status, .alertPermissionsDisabledWarning:
1165+
case .hud, .status, .alertWarning:
11261166
return UITableView.automaticDimension
11271167
}
11281168
}
11291169

11301170
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
11311171
switch Section(rawValue: indexPath.section)! {
1132-
case .alertPermissionsDisabledWarning:
1133-
tableView.deselectRow(at: indexPath, animated: true)
1134-
AlertPermissionsChecker.gotoSettings()
1172+
case .alertWarning:
1173+
if alertPermissionsChecker.showWarning {
1174+
tableView.deselectRow(at: indexPath, animated: true)
1175+
AlertPermissionsChecker.gotoSettings()
1176+
} else {
1177+
tableView.deselectRow(at: indexPath, animated: true)
1178+
presentUnmuteAlertConfirmation()
1179+
}
11351180
case .hud:
11361181
break
11371182
case .status:
@@ -1208,6 +1253,20 @@ final class StatusTableViewController: LoopChartsTableViewController {
12081253
}
12091254
}
12101255

1256+
private func presentUnmuteAlertConfirmation() {
1257+
let title = NSLocalizedString("Unmute Alerts?", comment: "The alert title for unmute alert confirmation")
1258+
let body = NSLocalizedString("Tap Unmute to resume sound for your alerts and alarms.", comment: "The alert body for unmute alert confirmation")
1259+
let action = UIAlertAction(
1260+
title: NSLocalizedString("Unmute", comment: "The title of the action used to unmute alerts"),
1261+
style: .default) { _ in
1262+
self.alertMuter.unmuteAlerts()
1263+
}
1264+
let alert = UIAlertController(title: title, message: body, preferredStyle: .alert)
1265+
alert.addAction(action)
1266+
alert.addCancelAction { _ in }
1267+
present(alert, animated: true, completion: nil)
1268+
}
1269+
12111270
private func presentErrorCancelingBolus(_ error: (Error)) {
12121271
log.error("Error Canceling Bolus: %@", error.localizedDescription)
12131272
let title = NSLocalizedString("Error Canceling Bolus", comment: "The alert title for an error while canceling a bolus")

Loop/View Models/SettingsViewModel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ public class SettingsViewModel: ObservableObject {
143143
self?.objectWillChange.send()
144144
}
145145
.store(in: &cancellables)
146+
alertMuter.objectWillChange.sink { [weak self] in
147+
self?.objectWillChange.send()
148+
}
149+
.store(in: &cancellables)
146150
pumpManagerSettingsViewModel.objectWillChange.sink { [weak self] in
147151
self?.objectWillChange.send()
148152
}

0 commit comments

Comments
 (0)