diff --git a/Loop/Managers/CGM/CGMManager.swift b/Loop/Managers/CGM/CGMManager.swift index 2abc5f04e6..64615f545a 100644 --- a/Loop/Managers/CGM/CGMManager.swift +++ b/Loop/Managers/CGM/CGMManager.swift @@ -43,6 +43,9 @@ protocol CGMManager: CustomDebugStringConvertible { /// Whether the device is capable of waking the app var providesBLEHeartbeat: Bool { get } + /// The length of time to keep samples in HealthKit before removal. Return nil to never remove samples. + var managedDataInterval: TimeInterval? { get } + var sensorState: SensorDisplayable? { get } /// The representation of the device for use in HealthKit diff --git a/Loop/Managers/CGM/DexCGMManager.swift b/Loop/Managers/CGM/DexCGMManager.swift index 8a3a32a7d4..e591d1f276 100644 --- a/Loop/Managers/CGM/DexCGMManager.swift +++ b/Loop/Managers/CGM/DexCGMManager.swift @@ -34,6 +34,10 @@ class DexCGMManager: CGMManager { return shareManager?.sensorState } + var managedDataInterval: TimeInterval? { + return shareManager?.managedDataInterval + } + fileprivate var shareManager: ShareClientManager? = ShareClientManager() var device: HKDevice? { @@ -53,12 +57,14 @@ class DexCGMManager: CGMManager { final class ShareClientManager: CGMManager { weak var delegate: CGMManagerDelegate? - var providesBLEHeartbeat = false + let providesBLEHeartbeat = false var sensorState: SensorDisplayable? { return latestBackfill } + let managedDataInterval: TimeInterval? = nil + private var latestBackfill: ShareGlucose? func fetchNewDataIfNeeded(with deviceManager: DeviceDataManager, _ completion: @escaping (CGMResult) -> Void) { @@ -129,6 +135,14 @@ final class G5CGMManager: DexCGMManager, TransmitterDelegate { return latestReading ?? super.sensorState } + override var managedDataInterval: TimeInterval? { + if let transmitter = transmitter, transmitter.passiveModeEnabled { + return .hours(3) + } + + return super.managedDataInterval + } + private var latestReading: Glucose? { didSet { // Once we have our first reading, disable backfill @@ -202,6 +216,11 @@ final class G4CGMManager: DexCGMManager, ReceiverDelegate { return latestReading ?? super.sensorState } + + override var managedDataInterval: TimeInterval? { + return .hours(3) + } + private var latestReading: GlucoseG4? { didSet { // Once we have our first reading, disable backfill diff --git a/Loop/Managers/CGM/EnliteCGMManager.swift b/Loop/Managers/CGM/EnliteCGMManager.swift index c0a6587413..247137c803 100644 --- a/Loop/Managers/CGM/EnliteCGMManager.swift +++ b/Loop/Managers/CGM/EnliteCGMManager.swift @@ -12,12 +12,14 @@ import MinimedKit final class EnliteCGMManager: CGMManager { - var providesBLEHeartbeat = false + let providesBLEHeartbeat = false weak var delegate: CGMManagerDelegate? var sensorState: SensorDisplayable? + let managedDataInterval: TimeInterval? = nil + func fetchNewDataIfNeeded(with deviceManager: DeviceDataManager, _ completion: @escaping (CGMResult) -> Void) { guard let device = deviceManager.rileyLinkManager.firstConnectedDevice?.ops else { diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index d593091540..3af4b0c9cb 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -473,6 +473,7 @@ final class DeviceDataManager { private func setupCGM() { cgmManager = cgm?.createManager() cgmManager?.delegate = self + loopManager.glucoseStore.managedDataInterval = cgmManager?.managedDataInterval /// Controls the management of the RileyLink timer tick, which is a reliably-changing BLE /// characteristic which can cause the app to wake. For most users, the G5 Transmitter and diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 228d4a457a..a594208f98 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -105,6 +105,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu case enlite = 0 case g4 case g5 + case dexcomShare // only displayed if g4 or g5 switched on case g5TransmitterID // only displayed if g5 switched on } @@ -120,8 +121,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } fileprivate enum ServiceRow: Int, CaseCountable { - case share = 0 - case nightscout + case nightscout = 0 case mLab case loggly case amplitude @@ -151,10 +151,12 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu return PumpRow.count case .cgm: switch dataManager.cgm { + case .g4?: + return CGMRow.count - 1 // No Transmitter ID cell case .g5?: return CGMRow.count default: - return CGMRow.count - 1 + return CGMRow.count - 2 // No Share or Transmitter ID cell } case .configuration: return ConfigurationRow.count @@ -166,8 +168,6 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell: UITableViewCell - switch Section(rawValue: indexPath.section)! { case .loop: switch LoopRow(rawValue: indexPath.row)! { @@ -205,20 +205,28 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu configCell.textLabel?.text = NSLocalizedString("Pump Battery Type", comment: "The title text for the battery type value") configCell.detailTextLabel?.text = String(describing: dataManager.batteryChemistry) } - cell = configCell + return configCell case .cgm: let row = CGMRow(rawValue: indexPath.row)! switch row { + case .dexcomShare: + let configCell = tableView.dequeueReusableCell(withIdentifier: ConfigCellIdentifier, for: indexPath) + let shareService = dataManager.remoteDataManager.shareService + + configCell.textLabel?.text = shareService.title + configCell.detailTextLabel?.text = shareService.username ?? TapToSetString + + return configCell case .g5TransmitterID: let configCell = tableView.dequeueReusableCell(withIdentifier: ConfigCellIdentifier, for: indexPath) - configCell.textLabel?.text = NSLocalizedString("G5 Transmitter ID", comment: "The title text for the Dexcom G5 transmitter ID config value") + configCell.textLabel?.text = NSLocalizedString("Transmitter ID", comment: "The title text for the Dexcom G5 transmitter ID config value") if case .g5(let transmitterID)? = dataManager.cgm { configCell.detailTextLabel?.text = transmitterID ?? TapToSetString } - cell = configCell + return configCell default: let switchCell = tableView.dequeueReusableCell(withIdentifier: SwitchTableViewCell.className, for: indexPath) as! SwitchTableViewCell @@ -240,11 +248,11 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu switchCell.titleLabel.text = NSLocalizedString("G5 Transmitter", comment: "The title text for the G5 Transmitter switch cell") switchCell.switch?.addTarget(self, action: #selector(g5Changed(_:)), for: .valueChanged) - case .g5TransmitterID: + case .dexcomShare, .g5TransmitterID: assertionFailure() } - cell = switchCell + return switchCell } case .configuration: let configCell = tableView.dequeueReusableCell(withIdentifier: ConfigCellIdentifier, for: indexPath) @@ -318,7 +326,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu configCell.detailTextLabel?.text = TapToSetString } case .maxBasal: - configCell.textLabel?.text = NSLocalizedString("Maximum Basal Rate", comment: "The title text for the maximum basal rate value") + configCell.textLabel?.text = NSLocalizedString("Maximum Basal Rate", comment: "The title text for the maximum basal rate value") if let maxBasal = dataManager.loopManager.settings.maximumBasalRatePerHour { configCell.detailTextLabel?.text = "\(valueNumberFormatter.string(from: NSNumber(value: maxBasal))!) U/hour" @@ -335,7 +343,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } } - cell = configCell + return configCell case .devices: let deviceCell = tableView.dequeueReusableCell(withIdentifier: RileyLinkDeviceTableViewCell.className) as! RileyLinkDeviceTableViewCell let device = dataManager.rileyLinkManager.devices[indexPath.row] @@ -347,16 +355,11 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu deviceCell.connectSwitch.addTarget(self, action: #selector(deviceConnectionChanged(_:)), for: .valueChanged) - cell = deviceCell + return deviceCell case .services: let configCell = tableView.dequeueReusableCell(withIdentifier: ConfigCellIdentifier, for: indexPath) switch ServiceRow(rawValue: indexPath.row)! { - case .share: - let shareService = dataManager.remoteDataManager.shareService - - configCell.textLabel?.text = shareService.title - configCell.detailTextLabel?.text = shareService.username ?? TapToSetString case .nightscout: let nightscoutService = dataManager.remoteDataManager.nightscoutService @@ -381,7 +384,6 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu return configCell } - return cell } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { @@ -403,6 +405,22 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu // MARK: - UITableViewDelegate + override func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: IndexPath) -> Int { + switch Section(rawValue: indexPath.section)! { + case .cgm: + switch CGMRow(rawValue: indexPath.row)! { + case .dexcomShare, .g5TransmitterID: + return 1 + default: + break + } + default: + break + } + + return 0 + } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let sender = tableView.cellForRow(at: indexPath) @@ -432,6 +450,16 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } case .cgm: switch CGMRow(rawValue: indexPath.row)! { + case .dexcomShare: + let service = dataManager.remoteDataManager.shareService + let vc = AuthenticationViewController(authentication: service) + vc.authenticationObserver = { [unowned self] (service) in + self.dataManager.remoteDataManager.shareService = service + + self.tableView.reloadRows(at: [indexPath], with: .none) + } + + show(vc, sender: sender) case .g5TransmitterID: let vc: TextFieldTableViewController var value: String? @@ -591,16 +619,6 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } case .services: switch ServiceRow(rawValue: indexPath.row)! { - case .share: - let service = dataManager.remoteDataManager.shareService - let vc = AuthenticationViewController(authentication: service) - vc.authenticationObserver = { [unowned self] (service) in - self.dataManager.remoteDataManager.shareService = service - - self.tableView.reloadRows(at: [indexPath], with: .none) - } - - show(vc, sender: sender) case .nightscout: let service = dataManager.remoteDataManager.nightscoutService let vc = AuthenticationViewController(authentication: service) @@ -687,10 +705,17 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu if sender.isOn { setG4SwitchOff() setEnliteSwitchOff() + let shareRowExists = tableView.numberOfRows(inSection: Section.cgm.rawValue) > CGMRow.dexcomShare.rawValue dataManager.cgm = .g5(transmitterID: g5TransmitterID) - tableView.insertRows(at: [IndexPath(row: CGMRow.g5TransmitterID.rawValue, section:Section.cgm.rawValue)], with: .top) + var indexPaths = [IndexPath(row: CGMRow.g5TransmitterID.rawValue, section:Section.cgm.rawValue)] + if !shareRowExists { + indexPaths.insert(IndexPath(row: CGMRow.dexcomShare.rawValue, section:Section.cgm.rawValue), at: 0) + } + + tableView.insertRows(at: indexPaths, with: .top) } else { + removeDexcomShareRow() removeG5TransmitterIDRow() dataManager.cgm = nil } @@ -702,8 +727,15 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu if sender.isOn { setG5SwitchOff() setEnliteSwitchOff() + removeG5TransmitterIDRow() + let shareRowExists = tableView.numberOfRows(inSection: Section.cgm.rawValue) > CGMRow.dexcomShare.rawValue dataManager.cgm = .g4 + + if !shareRowExists { + tableView.insertRows(at: [IndexPath(row: CGMRow.dexcomShare.rawValue, section:Section.cgm.rawValue)], with: .top) + } } else { + removeDexcomShareRow() dataManager.cgm = nil } tableView.endUpdates() @@ -714,6 +746,8 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu if sender.isOn { setG5SwitchOff() setG4SwitchOff() + removeDexcomShareRow() + removeG5TransmitterIDRow() dataManager.cgm = .enlite } else { dataManager.cgm = nil @@ -723,18 +757,25 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu // MARK: Views + private func removeDexcomShareRow() { + switch dataManager.cgm { + case .g4?, .g5?: + tableView.deleteRows(at: [IndexPath(row: CGMRow.dexcomShare.rawValue, section: Section.cgm.rawValue)], with: .top) + default: + break; + } + } + private func removeG5TransmitterIDRow() { if case .g5(let transmitterID)? = dataManager.cgm { g5TransmitterID = transmitterID - tableView.deleteRows(at: [IndexPath(row: CGMRow.g5TransmitterID.rawValue, section:Section.cgm.rawValue)], with: .top) + tableView.deleteRows(at: [IndexPath(row: CGMRow.g5TransmitterID.rawValue, section: Section.cgm.rawValue)], with: .top) } } private func setG5SwitchOff() { let switchCell = tableView.cellForRow(at: IndexPath(row: CGMRow.g5.rawValue, section: Section.cgm.rawValue)) as! SwitchTableViewCell switchCell.switch?.setOn(false, animated: true) - - removeG5TransmitterIDRow() } private func setG4SwitchOff() { diff --git a/Loop/Views/AuthenticationTableViewCell.swift b/Loop/Views/AuthenticationTableViewCell.swift index d44a0406a6..157ea04780 100644 --- a/Loop/Views/AuthenticationTableViewCell.swift +++ b/Loop/Views/AuthenticationTableViewCell.swift @@ -27,6 +27,7 @@ final class AuthenticationTableViewCell: UITableViewCell, NibLoadable { super.prepareForReuse() textField.delegate = nil + credentialOptionPicker = nil } var credentialOptionPicker: CredentialOptionPicker? {