| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052 |
- //
- // OmnipodSettingsViewController.swift
- // OmniKitUI
- //
- // Created by Pete Schwamb on 8/5/18.
- // Copyright © 2018 Pete Schwamb. All rights reserved.
- //
- import UIKit
- import RileyLinkKitUI
- import LoopKit
- import OmniKit
- import LoopKitUI
- public class ConfirmationBeepsTableViewCell: TextButtonTableViewCell {
- public func updateTextLabel(enabled: Bool) {
- if enabled {
- self.textLabel?.text = LocalizedString("Disable Confirmation Beeps", comment: "Title text for button to disable confirmation beeps")
- } else {
- self.textLabel?.text = LocalizedString("Enable Confirmation Beeps", comment: "Title text for button to enable confirmation beeps")
- }
- }
-
- override public func loadingStatusChanged() {
- self.isEnabled = !isLoading
- }
- }
- public class AutoBolusBeepsTableViewCell: TextButtonTableViewCell {
- public func updateTextLabel(enabled: Bool) {
- if enabled {
- self.textLabel?.text = LocalizedString("Disable Automatic Bolus Beeps", comment: "Title text for button to disable automatic bolus beeps")
- } else {
- self.textLabel?.text = LocalizedString("Enable Automatic Bolus Beeps", comment: "Title text for button to enable automatic bolus beeps")
- }
- }
- }
- class OmnipodSettingsViewController: RileyLinkSettingsViewController {
- let pumpManager: OmnipodPumpManager
-
- var statusError: Error?
-
- var podState: PodState? {
- didSet {
- refreshButton.isHidden = !refreshAvailable
- }
- }
-
- var pumpManagerStatus: PumpManagerStatus?
-
- var refreshAvailable: Bool {
- return podState != nil
- }
-
- private var bolusProgressTimer: Timer?
-
- init(pumpManager: OmnipodPumpManager) {
- self.pumpManager = pumpManager
- podState = pumpManager.state.podState
- pumpManagerStatus = pumpManager.status
-
- let devicesSectionIndex = OmnipodSettingsViewController.sectionList(podState).firstIndex(of: .rileyLinks)!
- super.init(rileyLinkPumpManager: pumpManager, devicesSectionIndex: devicesSectionIndex, style: .grouped)
-
- pumpManager.addStatusObserver(self, queue: .main)
- pumpManager.addPodStateObserver(self, queue: .main)
- }
-
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- lazy var suspendResumeTableViewCell: SuspendResumeTableViewCell = {
- let cell = SuspendResumeTableViewCell(style: .default, reuseIdentifier: nil)
- cell.basalDeliveryState = pumpManager.status.basalDeliveryState
- return cell
- }()
- lazy var confirmationBeepsTableViewCell: ConfirmationBeepsTableViewCell = {
- let cell = ConfirmationBeepsTableViewCell(style: .default, reuseIdentifier: nil)
- cell.updateTextLabel(enabled: pumpManager.confirmationBeeps)
- return cell
- }()
-
- lazy var autoBolusBeepsTableViewCell: AutoBolusBeepsTableViewCell = {
- let cell = AutoBolusBeepsTableViewCell(style: .default, reuseIdentifier: nil)
- cell.updateTextLabel(enabled: pumpManager.automaticBolusBeeps)
- cell.isEnabled = self.pumpManager.confirmationBeeps
- return cell
- }()
- var activityIndicator: UIActivityIndicatorView!
- var refreshButton: UIButton!
- override func viewDidLoad() {
- super.viewDidLoad()
-
- title = LocalizedString("Pod Settings", comment: "Title of the pod settings view controller")
-
- tableView.rowHeight = UITableView.automaticDimension
- tableView.estimatedRowHeight = 44
-
- tableView.sectionHeaderHeight = UITableView.automaticDimension
- tableView.estimatedSectionHeaderHeight = 55
-
- tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.className)
- tableView.register(TextButtonTableViewCell.self, forCellReuseIdentifier: TextButtonTableViewCell.className)
- tableView.register(AlarmsTableViewCell.self, forCellReuseIdentifier: AlarmsTableViewCell.className)
- tableView.register(ExpirationReminderDateTableViewCell.nib(), forCellReuseIdentifier: ExpirationReminderDateTableViewCell.className)
-
- let podImage = UIImage(named: "PodLarge", in: Bundle(for: OmnipodSettingsViewController.self), compatibleWith: nil)!
- let imageView = UIImageView(image: podImage)
- imageView.contentMode = .center
- imageView.frame.size.height += 18 // feels right
-
- let activityIndicatorStyle: UIActivityIndicatorView.Style
- if #available(iOSApplicationExtension 13.0, *) {
- activityIndicatorStyle = .medium
- } else {
- activityIndicatorStyle = .white
- }
- activityIndicator = UIActivityIndicatorView(style: activityIndicatorStyle)
- activityIndicator.hidesWhenStopped = true
- imageView.addSubview(activityIndicator)
-
- refreshButton = UIButton(type: .custom)
- if #available(iOSApplicationExtension 13.0, *) {
- let medConfig = UIImage.SymbolConfiguration(pointSize: 21, weight: .bold, scale: .medium)
- refreshButton.setImage(UIImage(systemName: "arrow.clockwise", withConfiguration: medConfig), for: .normal)
- refreshButton.tintColor = .systemFill
- }
- refreshButton.addTarget(self, action: #selector(refreshTapped(_:)), for: .touchUpInside)
- imageView.isUserInteractionEnabled = true
- imageView.addSubview(refreshButton)
-
- let margin: CGFloat = 15
- activityIndicator.translatesAutoresizingMaskIntoConstraints = false
- refreshButton.translatesAutoresizingMaskIntoConstraints = false
- let parent = imageView.layoutMarginsGuide
- NSLayoutConstraint.activate([
- activityIndicator.trailingAnchor.constraint(equalTo: parent.trailingAnchor, constant: -margin),
- activityIndicator.bottomAnchor.constraint(equalTo: parent.bottomAnchor, constant: -margin),
- refreshButton.centerYAnchor.constraint(equalTo: activityIndicator.centerYAnchor),
- refreshButton.centerXAnchor.constraint(equalTo: activityIndicator.centerXAnchor),
- ])
-
- tableView.tableHeaderView = imageView
-
- if #available(iOSApplicationExtension 13.0, *) {
- tableView.tableHeaderView?.backgroundColor = .systemBackground
- } else {
- tableView.tableHeaderView?.backgroundColor = UIColor.white
- }
- let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped(_:)))
- self.navigationItem.setRightBarButton(button, animated: false)
-
- if self.podState != nil {
- refreshPodStatus(emitConfirmationBeep: false)
- } else {
- refreshButton.isHidden = true
- }
- }
- @objc func doneTapped(_ sender: Any) {
- done()
- }
-
- @objc func refreshTapped(_ sender: Any) {
- refreshPodStatus(emitConfirmationBeep: true)
- }
-
- private func refreshPodStatus(emitConfirmationBeep: Bool) {
- refreshButton.alpha = 0
- activityIndicator.startAnimating()
- pumpManager.refreshStatus(emitConfirmationBeep: emitConfirmationBeep) { (_) in
- DispatchQueue.main.async {
- self.refreshButton.alpha = 1
- self.activityIndicator.stopAnimating()
- }
- }
- }
- private func done() {
- if let nav = navigationController as? SettingsNavigationViewController {
- nav.notifyComplete()
- }
- if let nav = navigationController as? OmnipodPumpManagerSetupViewController {
- nav.finishedSettingsDisplay()
- }
- }
-
- override func viewWillAppear(_ animated: Bool) {
- if clearsSelectionOnViewWillAppear {
- // Manually invoke the delegate for rows deselecting on appear
- for indexPath in tableView.indexPathsForSelectedRows ?? [] {
- _ = tableView(tableView, willDeselectRowAt: indexPath)
- }
- }
- if let configSectionIdx = self.sections.firstIndex(of: .configuration),
- let replacePodRowIdx = self.configurationRows.firstIndex(of: .replacePod)
- {
- self.tableView.reloadRows(at: [IndexPath(row: replacePodRowIdx, section: configSectionIdx)], with: .none)
- }
-
- super.viewWillAppear(animated)
- }
-
- // MARK: - Formatters
-
- private lazy var dateFormatter: DateFormatter = {
- let dateFormatter = DateFormatter()
- dateFormatter.timeStyle = .short
- dateFormatter.dateStyle = .medium
- dateFormatter.doesRelativeDateFormatting = true
- //dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "EEEE 'at' hm", options: 0, locale: nil)
- return dateFormatter
- }()
-
- // MARK: - Data Source
-
- private enum Section: Int, CaseIterable {
- case status = 0
- case configuration
- case rileyLinks
- case diagnostics
- case podDetails
- case deletePumpManager
- }
-
- private class func sectionList(_ podState: PodState?) -> [Section] {
- if let podState = podState {
- if podState.unfinishedSetup {
- return [.configuration, .rileyLinks]
- } else {
- return [.status, .configuration, .rileyLinks, .diagnostics, .podDetails]
- }
- } else {
- return [.configuration, .rileyLinks, .deletePumpManager]
- }
- }
-
- private var sections: [Section] {
- return OmnipodSettingsViewController.sectionList(podState)
- }
-
- private enum PodDetailsRow: Int, CaseIterable {
- case podAddress = 0
- case podLot
- case podTid
- case piVersion
- case pmVersion
- }
-
- private enum Diagnostics: Int, CaseIterable {
- case readPodStatus = 0
- case playTestBeeps
- case readPulseLog
- case testCommand
- }
-
- private var configurationRows: [ConfigurationRow] {
- if podState == nil || podState?.unfinishedSetup == true {
- return [.replacePod]
- } else {
- return ConfigurationRow.allCases
- }
- }
-
- private enum ConfigurationRow: Int, CaseIterable {
- case suspendResume = 0
- case enableDisableConfirmationBeeps
- case enableDisableAutoBolusBeeps
- case reminder
- case timeZoneOffset
- case insulinType
- case replacePod
- }
-
- fileprivate enum StatusRow: Int, CaseIterable {
- case expiresAt = 0
- case podActiveClock
- case bolus
- case basal
- case alarms
- case reservoirLevel
- case deliveredInsulin
- }
-
- // MARK: UITableViewDataSource
-
- override func numberOfSections(in tableView: UITableView) -> Int {
- return sections.count
- }
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- switch sections[section] {
- case .podDetails:
- return PodDetailsRow.allCases.count
- case .diagnostics:
- return Diagnostics.allCases.count
- case .configuration:
- return configurationRows.count
- case .status:
- return StatusRow.allCases.count
- case .rileyLinks:
- return super.tableView(tableView, numberOfRowsInSection: section)
- case .deletePumpManager:
- return 1
- }
- }
-
- override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
- switch sections[section] {
- case .status:
- return nil // No title, appears below a pod picture
- case .configuration:
- return LocalizedString("Configuration", comment: "The title of the configuration section in settings")
- case .rileyLinks:
- return super.tableView(tableView, titleForHeaderInSection: section)
- case .diagnostics:
- return LocalizedString("Diagnostics", comment: "The title of the diagnostics section in settings")
- case .podDetails:
- return LocalizedString("Pod Details", comment: "The title of the pod details section in settings")
- case .deletePumpManager:
- return " " // Use an empty string for more dramatic spacing
- }
- }
-
- override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
- switch sections[section] {
- case .rileyLinks:
- return super.tableView(tableView, viewForHeaderInSection: section)
- case .podDetails, .diagnostics, .configuration, .status, .deletePumpManager:
- return nil
- }
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- switch sections[indexPath.section] {
- case .status:
- let podState = self.podState!
- let statusRow = StatusRow(rawValue: indexPath.row)!
- if statusRow == .alarms {
- let cell = tableView.dequeueReusableCell(withIdentifier: AlarmsTableViewCell.className, for: indexPath) as! AlarmsTableViewCell
- cell.textLabel?.text = LocalizedString("Alarms", comment: "The title of the cell showing alarm status")
- cell.alerts = podState.activeAlerts
- return cell
- }
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- switch statusRow {
- case .expiresAt:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- if let expiresAt = podState.expiresAt {
- if expiresAt.timeIntervalSinceNow > 0 {
- cell.textLabel?.text = LocalizedString("Expires", comment: "The title of the cell showing the pod expiration")
- } else {
- cell.textLabel?.text = LocalizedString("Expired", comment: "The title of the cell showing the pod expiration after expiry")
- }
- }
- cell.setDetailDate(podState.expiresAt, formatter: dateFormatter)
- return cell
- case .podActiveClock:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("Pod Active Clock", comment: "The title of the cell showing the pod active clock")
- cell.setDetailAge(podState.expiresAt?.addingTimeInterval(-Pod.nominalPodLife).timeIntervalSinceNow)
- return cell
- case .bolus:
- cell.textLabel?.text = LocalizedString("Bolus Delivery", comment: "The title of the cell showing pod bolus status")
- let deliveredUnits: Double?
- if let dose = podState.unfinalizedBolus {
- deliveredUnits = pumpManager.roundToSupportedBolusVolume(units: dose.progress * dose.units)
- } else {
- deliveredUnits = nil
- }
- cell.setDetailBolus(suspended: podState.isSuspended, dose: podState.unfinalizedBolus, deliveredUnits: deliveredUnits)
- // TODO: This timer is in the wrong context; should be part of a custom bolus progress cell
- // if bolusProgressTimer == nil {
- // bolusProgressTimer = Timer.scheduledTimer(withTimeInterval: .seconds(2), repeats: true) { [weak self] (_) in
- // self?.tableView.reloadRows(at: [indexPath], with: .none)
- // }
- // }
- case .basal:
- cell.textLabel?.text = LocalizedString("Basal Delivery", comment: "The title of the cell showing pod basal status")
- cell.setDetailBasal(suspended: podState.isSuspended, dose: podState.unfinalizedTempBasal)
- case .reservoirLevel:
- cell.textLabel?.text = LocalizedString("Reservoir", comment: "The title of the cell showing reservoir status")
- cell.setReservoirDetail(podState.lastInsulinMeasurements)
- case .deliveredInsulin:
- cell.textLabel?.text = LocalizedString("Insulin Delivered", comment: "The title of the cell showing delivered insulin")
- cell.setDeliveredInsulinDetail(podState.lastInsulinMeasurements)
- default:
- break
- }
- return cell
- case .configuration:
- switch configurationRows[indexPath.row] {
- case .suspendResume:
- return suspendResumeTableViewCell
- case .enableDisableConfirmationBeeps:
- return confirmationBeepsTableViewCell
- case .enableDisableAutoBolusBeeps:
- return autoBolusBeepsTableViewCell
- case .reminder:
- let cell = tableView.dequeueReusableCell(withIdentifier: ExpirationReminderDateTableViewCell.className, for: indexPath) as! ExpirationReminderDateTableViewCell
- if let podState = podState, let reminderDate = pumpManager.expirationReminderDate {
- cell.titleLabel.text = LocalizedString("Expiration Reminder", comment: "The title of the cell showing the pod expiration reminder date")
- cell.date = reminderDate
- cell.datePicker.datePickerMode = .dateAndTime
- #if swift(>=5.2)
- if #available(iOS 14.0, *) {
- cell.datePicker.preferredDatePickerStyle = .wheels
- }
- #endif
- cell.datePicker.maximumDate = podState.expiresAt?.addingTimeInterval(-Pod.expirationReminderAlertMinTimeBeforeExpiration)
- cell.datePicker.minimumDate = podState.expiresAt?.addingTimeInterval(-Pod.expirationReminderAlertMaxTimeBeforeExpiration)
- cell.datePicker.minuteInterval = 1
- cell.delegate = self
- }
- return cell
- case .timeZoneOffset:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("Change Time Zone", comment: "The title of the command to change pump time zone")
-
- let localTimeZone = TimeZone.current
- let localTimeZoneName = localTimeZone.abbreviation() ?? localTimeZone.identifier
-
- if let timeZone = pumpManagerStatus?.timeZone {
- let timeZoneDiff = TimeInterval(timeZone.secondsFromGMT() - localTimeZone.secondsFromGMT())
- let formatter = DateComponentsFormatter()
- formatter.allowedUnits = [.hour, .minute]
- let diffString = timeZoneDiff != 0 ? formatter.string(from: abs(timeZoneDiff)) ?? String(abs(timeZoneDiff)) : ""
-
- cell.detailTextLabel?.text = String(format: LocalizedString("%1$@%2$@%3$@", comment: "The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00)"), localTimeZoneName, timeZoneDiff != 0 ? (timeZoneDiff < 0 ? "-" : "+") : "", diffString)
- }
- cell.accessoryType = .disclosureIndicator
- return cell
- case .insulinType:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.prepareForReuse()
- cell.textLabel?.text = LocalizedString("Insulin Type", comment: "The title of the Insulin Type")
- cell.detailTextLabel?.text = pumpManager.insulinType?.brandName
- cell.accessoryType = .disclosureIndicator
- return cell
- case .replacePod:
- let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell
- if podState == nil {
- cell.textLabel?.text = LocalizedString("Pair New Pod", comment: "The title of the command to pair new pod")
- } else if let podState = podState, podState.isFaulted {
- cell.textLabel?.text = LocalizedString("Replace Pod Now", comment: "The title of the command to replace pod when there is a pod fault")
- } else if let podState = podState, podState.unfinishedSetup {
- cell.textLabel?.text = LocalizedString("Finish pod setup", comment: "The title of the command to finish pod setup")
- } else {
- cell.textLabel?.text = LocalizedString("Replace Pod", comment: "The title of the command to replace pod")
- cell.tintColor = .deleteColor
- }
- cell.isEnabled = true
- return cell
- }
-
- case .rileyLinks:
- return super.tableView(tableView, cellForRowAt: indexPath)
- case .diagnostics:
- switch Diagnostics(rawValue: indexPath.row)! {
- case .readPodStatus:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("Read Pod Status", comment: "The title of the command to read the pod status")
- cell.accessoryType = .disclosureIndicator
- return cell
- case .playTestBeeps:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("Play Test Beeps", comment: "The title of the command to play test beeps")
- cell.accessoryType = .disclosureIndicator
- return cell
- case .readPulseLog:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("Read Pulse Log", comment: "The title of the command to read the pulse log")
- cell.accessoryType = .disclosureIndicator
- return cell
- case .testCommand:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("Test Command", comment: "The title of the command to run the test command")
- cell.accessoryType = .disclosureIndicator
- return cell
- }
- case .podDetails:
- let podState = self.podState!
- switch PodDetailsRow(rawValue: indexPath.row)! {
- case .podAddress:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("Assigned Address", comment: "The title text for the address assigned to the pod")
- cell.detailTextLabel?.text = String(format:"%04X", podState.address)
- return cell
- case .podLot:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("Lot", comment: "The title of the cell showing the pod lot id")
- cell.detailTextLabel?.text = String(format:"L%d", podState.lot)
- return cell
- case .podTid:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("TID", comment: "The title of the cell showing the pod TID")
- cell.detailTextLabel?.text = String(format:"%07d", podState.tid)
- return cell
- case .piVersion:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("PI Version", comment: "The title of the cell showing the pod pi version")
- cell.detailTextLabel?.text = podState.piVersion
- return cell
- case .pmVersion:
- let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath)
- cell.textLabel?.text = LocalizedString("PM Version", comment: "The title of the cell showing the pod pm version")
- cell.detailTextLabel?.text = podState.pmVersion
- return cell
- }
- case .deletePumpManager:
- let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell
-
- cell.textLabel?.text = LocalizedString("Switch from Omnipod Pumps", comment: "Title text for the button to delete Omnipod PumpManager")
- cell.textLabel?.textAlignment = .center
- cell.tintColor = .deleteColor
- cell.isEnabled = true
- return cell
- }
- }
-
- override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
- switch sections[indexPath.section] {
- case .status:
- switch StatusRow(rawValue: indexPath.row)! {
- case .alarms:
- return true
- default:
- return false
- }
- case .configuration, .rileyLinks, .diagnostics, .deletePumpManager:
- return true
- case .podDetails:
- return false
- }
- }
- override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
- if indexPath == IndexPath(row: ConfigurationRow.reminder.rawValue, section: Section.configuration.rawValue) {
- tableView.beginUpdates()
- }
- return indexPath
- }
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- let sender = tableView.cellForRow(at: indexPath)
-
- switch sections[indexPath.section] {
- case .status:
- switch StatusRow(rawValue: indexPath.row)! {
- case .alarms:
- if let cell = tableView.cellForRow(at: indexPath) as? AlarmsTableViewCell {
- let activeSlots = AlertSet(slots: Array(cell.alerts.keys))
- if activeSlots.count > 0 {
- cell.isLoading = true
- cell.isEnabled = false
- pumpManager.acknowledgeAlerts(activeSlots) { (updatedAlerts) in
- DispatchQueue.main.async {
- cell.isLoading = false
- cell.isEnabled = true
- if let updatedAlerts = updatedAlerts {
- cell.alerts = updatedAlerts
- }
- }
- }
- }
- tableView.deselectRow(at: indexPath, animated: true)
- }
- default:
- break
- }
- case .configuration:
- switch configurationRows[indexPath.row] {
- case .suspendResume:
- suspendResumeTapped()
- tableView.deselectRow(at: indexPath, animated: true)
- case .enableDisableConfirmationBeeps:
- confirmationBeepsTapped()
- tableView.deselectRow(at: indexPath, animated: true)
- case .enableDisableAutoBolusBeeps:
- autoBolusBeepsTapped()
- tableView.deselectRow(at: indexPath, animated: true)
- case .reminder:
- tableView.deselectRow(at: indexPath, animated: true)
- tableView.endUpdates()
- case .timeZoneOffset:
- let vc = CommandResponseViewController.changeTime(pumpManager: pumpManager)
- vc.title = sender?.textLabel?.text
- show(vc, sender: indexPath)
- case .insulinType:
- let view = InsulinTypeSetting(initialValue: pumpManager.insulinType ?? .novolog, supportedInsulinTypes: InsulinType.allCases) { (newType) in
- self.pumpManager.insulinType = newType
- }
- let vc = DismissibleHostingController(rootView: view)
- vc.title = LocalizedString("Insulin Type", comment: "Controller title for insulin type selection screen")
- show(vc, sender: sender)
- case .replacePod:
- let vc: UIViewController
- if let podState = podState, podState.isFaulted {
- vc = PodReplacementNavigationController.instantiatePodReplacementFlow(pumpManager)
- } else if podState == nil || podState!.setupProgress.primingNeeded {
- vc = PodReplacementNavigationController.instantiateNewPodFlow(pumpManager)
- } else if let podState = podState, podState.unfinishedSetup {
- vc = PodReplacementNavigationController.instantiateInsertCannulaFlow(pumpManager)
- } else {
- vc = PodReplacementNavigationController.instantiatePodReplacementFlow(pumpManager)
- }
- if var completionNotifying = vc as? CompletionNotifying {
- completionNotifying.completionDelegate = self
- }
- self.navigationController?.present(vc, animated: true, completion: nil)
- }
- case .rileyLinks:
- let device = devicesDataSource.devices[indexPath.row]
-
- guard device.hardwareType != nil else {
- tableView.deselectRow(at: indexPath, animated: true)
- return
- }
- let vc = RileyLinkDeviceTableViewController(
- device: device,
- batteryAlertLevel: pumpManager.rileyLinkBatteryAlertLevel,
- batteryAlertLevelChanged: { [weak self] value in
- self?.pumpManager.rileyLinkBatteryAlertLevel = value
- }
- )
-
- self.show(vc, sender: sender)
- case .diagnostics:
- switch Diagnostics(rawValue: indexPath.row)! {
- case .readPodStatus:
- let vc = CommandResponseViewController.readPodStatus(pumpManager: pumpManager)
- vc.title = sender?.textLabel?.text
- show(vc, sender: indexPath)
- case .playTestBeeps:
- let vc = CommandResponseViewController.playTestBeeps(pumpManager: pumpManager)
- vc.title = sender?.textLabel?.text
- show(vc, sender: indexPath)
- case .readPulseLog:
- let vc = CommandResponseViewController.readPulseLog(pumpManager: pumpManager)
- vc.title = sender?.textLabel?.text
- show(vc, sender: indexPath)
- case .testCommand:
- let vc = CommandResponseViewController.testingCommands(pumpManager: pumpManager)
- vc.title = sender?.textLabel?.text
- show(vc, sender: indexPath)
- }
- case .podDetails:
- break
- case .deletePumpManager:
- let confirmVC = UIAlertController(pumpManagerDeletionHandler: {
- self.pumpManager.notifyDelegateOfDeactivation {
- DispatchQueue.main.async {
- self.done()
- }
- }
- })
-
- present(confirmVC, animated: true) {
- tableView.deselectRow(at: indexPath, animated: true)
- }
- }
- }
-
- override func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? {
- switch sections[indexPath.section] {
- case .status:
- break
- case .configuration:
- switch configurationRows[indexPath.row] {
- case .suspendResume, .enableDisableConfirmationBeeps, .enableDisableAutoBolusBeeps, .reminder:
- break
- case .timeZoneOffset, .replacePod, .insulinType:
- tableView.reloadRows(at: [indexPath], with: .fade)
- }
- case .rileyLinks:
- break
- case .diagnostics:
- tableView.reloadRows(at: [indexPath], with: .fade)
- case .podDetails, .deletePumpManager:
- break
- }
-
- return indexPath
- }
- private func suspendResumeTapped() {
- switch suspendResumeTableViewCell.shownAction {
- case .resume:
- pumpManager.resumeDelivery { (error) in
- if let error = error {
- DispatchQueue.main.async {
- let title = LocalizedString("Error Resuming", comment: "The alert title for a resume error")
- self.present(UIAlertController(with: error, title: title), animated: true)
- }
- }
- }
- case .suspend:
- pumpManager.suspendDelivery { (error) in
- if let error = error {
- DispatchQueue.main.async {
- let title = LocalizedString("Error Suspending", comment: "The alert title for a suspend error")
- self.present(UIAlertController(with: error, title: title), animated: true)
- }
- }
- }
- default:
- break
- }
- }
- private func setConfirmationBeeps(confirmationBeeps: Bool) {
- func done() {
- DispatchQueue.main.async { [weak self] in
- if let self = self {
- self.confirmationBeepsTableViewCell.updateTextLabel(enabled: self.pumpManager.confirmationBeeps)
- self.confirmationBeepsTableViewCell.isLoading = false
- self.autoBolusBeepsTableViewCell.isEnabled = self.pumpManager.confirmationBeeps
- }
- }
- }
- confirmationBeepsTableViewCell.isLoading = true
- pumpManager.setConfirmationBeeps(enabled: confirmationBeeps, completion: { (error) in
- if let error = error {
- DispatchQueue.main.async {
- let title: String
- if confirmationBeeps {
- title = LocalizedString("Error enabling confirmation beeps", comment: "The alert title for enable confirmation beeps error")
- } else {
- title = LocalizedString("Error disabling confirmation beeps", comment: "The alert title for disable confirmation beeps error")
- }
- self.present(UIAlertController(with: error, title: title), animated: true)
- }
- }
- done()
- })
- }
- private func confirmationBeepsTapped() {
- setConfirmationBeeps(confirmationBeeps: !pumpManager.confirmationBeeps)
- }
- private func autoBolusBeepsTapped() {
- let newValue = !pumpManager.automaticBolusBeeps
- pumpManager.automaticBolusBeeps = newValue
- func done() {
- DispatchQueue.main.async { [weak self] in
- if let self = self {
- self.autoBolusBeepsTableViewCell.updateTextLabel(enabled: newValue)
- self.autoBolusBeepsTableViewCell.isLoading = false
- }
- }
- }
- // Beep if confirmation beeps are enabled else just update the value displayed
- if pumpManager.confirmationBeeps {
- self.autoBolusBeepsTableViewCell.isLoading = true
- pumpManager.setConfirmationBeeps(enabled: true, completion: { (error) in
- done() // no worries if confirmation beep fails for any reason
- })
- } else {
- self.autoBolusBeepsTableViewCell.updateTextLabel(enabled: newValue)
- }
- }
- }
- extension OmnipodSettingsViewController: CompletionDelegate {
- func completionNotifyingDidComplete(_ object: CompletionNotifying) {
- if let vc = object as? UIViewController, vc === presentedViewController {
- dismiss(animated: true, completion: nil)
- }
- }
- }
- extension OmnipodSettingsViewController: RadioSelectionTableViewControllerDelegate {
- func radioSelectionTableViewControllerDidChangeSelectedIndex(_ controller: RadioSelectionTableViewController) {
- guard let indexPath = self.tableView.indexPathForSelectedRow else {
- return
- }
-
- switch sections[indexPath.section] {
- case .configuration:
- switch configurationRows[indexPath.row] {
- default:
- assertionFailure()
- }
- default:
- assertionFailure()
- }
-
- tableView.reloadRows(at: [indexPath], with: .none)
- }
- }
- extension OmnipodSettingsViewController: PodStateObserver {
- func podStateDidUpdate(_ state: PodState?) {
- let newSections = OmnipodSettingsViewController.sectionList(state)
- let sectionsChanged = OmnipodSettingsViewController.sectionList(self.podState) != newSections
- let oldConfigurationRowsCount = self.configurationRows.count
- let oldState = self.podState
- self.podState = state
- if sectionsChanged {
- self.devicesDataSource.devicesSectionIndex = self.sections.firstIndex(of: .rileyLinks)!
- self.tableView.reloadData()
- } else {
- if oldConfigurationRowsCount != self.configurationRows.count, let idx = newSections.firstIndex(of: .configuration) {
- self.tableView.reloadSections([idx], with: .fade)
- }
- }
- guard let statusIdx = newSections.firstIndex(of: .status) else {
- return
- }
- let reloadRows: [StatusRow] = [.podActiveClock, .bolus, .basal, .reservoirLevel, .deliveredInsulin]
- self.tableView.reloadRows(at: reloadRows.map({ IndexPath(row: $0.rawValue, section: statusIdx) }), with: .none)
- if oldState?.activeAlerts != state?.activeAlerts,
- let alerts = state?.activeAlerts,
- let alertCell = self.tableView.cellForRow(at: IndexPath(row: StatusRow.alarms.rawValue, section: statusIdx)) as? AlarmsTableViewCell
- {
- alertCell.alerts = alerts
- }
- }
- }
- extension OmnipodSettingsViewController: PumpManagerStatusObserver {
- func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus) {
- self.pumpManagerStatus = status
- self.suspendResumeTableViewCell.basalDeliveryState = status.basalDeliveryState
- if let statusSectionIdx = self.sections.firstIndex(of: .status) {
- self.tableView.reloadSections([statusSectionIdx], with: .none)
- }
- }
- }
- extension OmnipodSettingsViewController: DatePickerTableViewCellDelegate {
- func datePickerTableViewCellDidUpdateDate(_ cell: DatePickerTableViewCell) {
- pumpManager.expirationReminderDate = cell.date
- }
- }
- private extension UIAlertController {
- convenience init(pumpManagerDeletionHandler handler: @escaping () -> Void) {
- self.init(
- title: nil,
- message: LocalizedString("Are you sure you want to stop using Omnipod?", comment: "Confirmation message for removing Omnipod PumpManager"),
- preferredStyle: .actionSheet
- )
-
- addAction(UIAlertAction(
- title: LocalizedString("Delete Omnipod", comment: "Button title to delete Omnipod PumpManager"),
- style: .destructive,
- handler: { (_) in
- handler()
- }
- ))
-
- let cancel = LocalizedString("Cancel", comment: "The title of the cancel action in an action sheet")
- addAction(UIAlertAction(title: cancel, style: .cancel, handler: nil))
- }
- }
- private extension TimeInterval {
- func format(using units: NSCalendar.Unit) -> String? {
- let formatter = DateComponentsFormatter()
- formatter.allowedUnits = units
- formatter.unitsStyle = .short
- formatter.zeroFormattingBehavior = .dropLeading
- formatter.maximumUnitCount = 2
- return formatter.string(from: self)
- }
- }
- class AlarmsTableViewCell: LoadingTableViewCell {
-
- private var defaultDetailColor: UIColor?
- override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: .value1, reuseIdentifier: reuseIdentifier)
- detailTextLabel?.tintAdjustmentMode = .automatic
- defaultDetailColor = detailTextLabel?.textColor
- }
-
- required public init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- }
-
- private func updateColor() {
- if alerts.count == 0 {
- detailTextLabel?.textColor = defaultDetailColor
- } else {
- detailTextLabel?.textColor = tintColor
- }
- }
-
- public var isEnabled = true {
- didSet {
- selectionStyle = isEnabled ? .default : .none
- }
- }
-
- override public func loadingStatusChanged() {
- self.detailTextLabel?.isHidden = isLoading
- }
-
- var alerts = [AlertSlot: PodAlert]() {
- didSet {
- updateColor()
- if alerts.isEmpty {
- detailTextLabel?.text = LocalizedString("None", comment: "Alerts detail when no alerts unacknowledged")
- } else {
- detailTextLabel?.text = alerts.map { slot, alert in String.init(describing: alert) }.joined(separator: ", ")
- }
- }
- }
-
- open override func tintColorDidChange() {
- super.tintColorDidChange()
- updateColor()
- }
-
- open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
- super.traitCollectionDidChange(previousTraitCollection)
- updateColor()
- }
- }
- private extension UITableViewCell {
-
- private var insulinFormatter: NumberFormatter {
- let formatter = NumberFormatter()
- formatter.numberStyle = .decimal
- formatter.maximumFractionDigits = 3
- return formatter
- }
-
- private var percentFormatter: NumberFormatter {
- let formatter = NumberFormatter()
- formatter.numberStyle = .decimal
- formatter.maximumFractionDigits = 0
- return formatter
- }
- func setDetailDate(_ date: Date?, formatter: DateFormatter) {
- if let date = date {
- detailTextLabel?.text = formatter.string(from: date)
- } else {
- detailTextLabel?.text = "-"
- }
- }
-
- func setDetailAge(_ age: TimeInterval?) {
- if let age = age {
- detailTextLabel?.text = fabs(age).format(using: [.hour, .minute])
- } else {
- detailTextLabel?.text = ""
- }
- }
-
- func setDetailBasal(suspended: Bool, dose: UnfinalizedDose?) {
- if suspended {
- detailTextLabel?.text = LocalizedString("Suspended", comment: "The detail text of the basal row when pod is suspended")
- } else if let dose = dose {
- if let rate = insulinFormatter.string(from: dose.rate) {
- detailTextLabel?.text = String(format: LocalizedString("%@ U/hour", comment: "Format string for temp basal rate. (1: The localized amount)"), rate)
- }
- } else {
- detailTextLabel?.text = LocalizedString("Schedule", comment: "The detail text of the basal row when pod is running scheduled basal")
- }
- }
-
- func setDetailBolus(suspended: Bool, dose: UnfinalizedDose?, deliveredUnits: Double?) {
- guard let dose = dose, let delivered = deliveredUnits, !suspended else {
- detailTextLabel?.text = LocalizedString("None", comment: "The detail text for bolus delivery when no bolus is being delivered")
- return
- }
-
- let progress = dose.progress
- if let units = self.insulinFormatter.string(from: dose.units), let deliveredUnits = self.insulinFormatter.string(from: delivered) {
- if progress >= 1 {
- self.detailTextLabel?.text = String(format: LocalizedString("%@ U (Finished)", comment: "Format string for bolus progress when finished. (1: The localized amount)"), units)
- } else {
- let progressFormatted = percentFormatter.string(from: progress * 100.0) ?? ""
- let progressStr = String(format: LocalizedString("%@%%", comment: "Format string for bolus percent progress. (1: Percent progress)"), progressFormatted)
- self.detailTextLabel?.text = String(format: LocalizedString("%@ U of %@ U (%@)", comment: "Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress)"), deliveredUnits, units, progressStr)
- }
- }
- }
-
- func setDeliveredInsulinDetail(_ measurements: PodInsulinMeasurements?) {
- guard let measurements = measurements else {
- detailTextLabel?.text = LocalizedString("Unknown", comment: "The detail text for delivered insulin when no measurement is available")
- return
- }
- if let units = insulinFormatter.string(from: measurements.delivered) {
- detailTextLabel?.text = String(format: LocalizedString("%@ U", comment: "Format string for delivered insulin. (1: The localized amount)"), units)
- }
- }
- func setReservoirDetail(_ measurements: PodInsulinMeasurements?) {
- guard let measurements = measurements else {
- detailTextLabel?.text = LocalizedString("Unknown", comment: "The detail text for delivered insulin when no measurement is available")
- return
- }
- if measurements.reservoirLevel == nil {
- if let units = insulinFormatter.string(from: Pod.maximumReservoirReading) {
- detailTextLabel?.text = String(format: LocalizedString("%@+ U", comment: "Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount)"), units)
- }
- } else {
- if let reservoirValue = measurements.reservoirLevel,
- let units = insulinFormatter.string(from: reservoirValue)
- {
- detailTextLabel?.text = String(format: LocalizedString("%@ U", comment: "Format string for insulin remaining in reservoir. (1: The localized amount)"), units)
- }
- }
- }
- }
|