RileyLinkMinimedDeviceTableViewController.swift 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. //
  2. // RileyLinkMinimedDeviceTableViewController.swift
  3. // Naterade
  4. //
  5. // Created by Nathan Racklyeft on 3/5/16.
  6. // Copyright © 2016 Nathan Racklyeft. All rights reserved.
  7. //
  8. import UIKit
  9. import CoreBluetooth
  10. import LoopKitUI
  11. import MinimedKit
  12. import RileyLinkBLEKit
  13. import RileyLinkKit
  14. import RileyLinkKitUI
  15. let CellIdentifier = "Cell"
  16. public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
  17. public let device: RileyLinkDevice
  18. private let ops: PumpOps
  19. private var pumpState: PumpState? {
  20. didSet {
  21. // Update the UI if its visible
  22. guard rssiFetchTimer != nil else { return }
  23. if let cell = cellForRow(.awake) {
  24. cell.setAwakeUntil(pumpState?.awakeUntil, formatter: dateFormatter)
  25. }
  26. if let cell = cellForRow(.model) {
  27. cell.setPumpModel(pumpState?.pumpModel)
  28. }
  29. if let cell = cellForRow(.tune) {
  30. cell.setTuneInfo(lastValidFrequency: pumpState?.lastValidFrequency, lastTuned: pumpState?.lastTuned, measurementFormatter: measurementFormatter, dateFormatter: dateFormatter)
  31. }
  32. }
  33. }
  34. private var bleRSSI: Int?
  35. private var firmwareVersion: String? {
  36. didSet {
  37. guard isViewLoaded else {
  38. return
  39. }
  40. cellForRow(.version)?.detailTextLabel?.text = firmwareVersion
  41. }
  42. }
  43. private var fw_hw: String? {
  44. didSet {
  45. guard isViewLoaded else {
  46. return
  47. }
  48. cellForRow(.orl)?.detailTextLabel?.text = fw_hw
  49. }
  50. }
  51. private var disconnectLed: Bool = false
  52. private var disconnectVibration: Bool = false
  53. private var connectLed: Bool = false
  54. private var connectVibration: Bool = false
  55. private var uptime: TimeInterval? {
  56. didSet {
  57. guard isViewLoaded else {
  58. return
  59. }
  60. cellForRow(.uptime)?.setDetailAge(uptime)
  61. }
  62. }
  63. private var battery: String? {
  64. didSet {
  65. guard isViewLoaded else {
  66. return
  67. }
  68. cellForRow(.battery)?.setDetailBatteryLevel(battery)
  69. }
  70. }
  71. private var lastIdle: Date? {
  72. didSet {
  73. guard isViewLoaded else {
  74. return
  75. }
  76. cellForRow(.idleStatus)?.setDetailDate(lastIdle, formatter: dateFormatter)
  77. }
  78. }
  79. private var rssiFetchTimer: Timer? {
  80. willSet {
  81. rssiFetchTimer?.invalidate()
  82. }
  83. }
  84. private var appeared = false
  85. public init(device: RileyLinkDevice, pumpOps: PumpOps) {
  86. self.device = device
  87. self.ops = pumpOps
  88. self.pumpState = pumpOps.pumpState.value
  89. super.init(style: .grouped)
  90. updateDeviceStatus()
  91. }
  92. required public init?(coder aDecoder: NSCoder) {
  93. fatalError("init(coder:) has not been implemented")
  94. }
  95. public override func viewDidLoad() {
  96. super.viewDidLoad()
  97. title = device.name
  98. self.observe()
  99. }
  100. @objc func updateRSSI() {
  101. device.readRSSI()
  102. }
  103. func updateUptime() {
  104. device.runSession(withName: "Get stats for uptime") { (session) in
  105. do {
  106. let statistics = try session.getRileyLinkStatistics()
  107. DispatchQueue.main.async {
  108. self.uptime = statistics.uptime
  109. }
  110. } catch { }
  111. }
  112. }
  113. func updateBatteryLevel() {
  114. device.runSession(withName: "Get battery level") { (session) in
  115. let batteryLevel = self.device.getBatterylevel()
  116. DispatchQueue.main.async {
  117. self.battery = batteryLevel
  118. }
  119. }
  120. }
  121. func orangeClose() {
  122. device.runSession(withName: "Orange Action Close") { (session) in
  123. self.device.orangeClose()
  124. }
  125. }
  126. func orangeReadSet() {
  127. device.runSession(withName: "orange Read Set") { (session) in
  128. self.device.orangeReadSet()
  129. }
  130. }
  131. func writePSW() {
  132. device.runSession(withName: "Orange Action PSW") { (session) in
  133. self.device.orangeWritePwd()
  134. }
  135. }
  136. func orangeAction(index: Int) {
  137. device.runSession(withName: "Orange Action \(index)") { (session) in
  138. self.device.orangeAction(mode: index)
  139. }
  140. }
  141. func orangeAction(index: Int, open: Bool) {
  142. device.runSession(withName: "Orange Set Action \(index)") { (session) in
  143. self.device.orangeSetAction(index: index, open: open)
  144. }
  145. }
  146. private func updateDeviceStatus() {
  147. device.getStatus { (status) in
  148. DispatchQueue.main.async {
  149. self.firmwareVersion = status.firmwareDescription
  150. self.fw_hw = status.fw_hw
  151. self.connectLed = status.ledOn
  152. self.connectVibration = status.vibrationOn
  153. self.disconnectLed = status.ledOn
  154. self.disconnectVibration = status.vibrationOn
  155. self.tableView.reloadData()
  156. }
  157. }
  158. }
  159. // References to registered notification center observers
  160. private var notificationObservers: [Any] = []
  161. deinit {
  162. for observer in notificationObservers {
  163. NotificationCenter.default.removeObserver(observer)
  164. }
  165. }
  166. private func observe() {
  167. let center = NotificationCenter.default
  168. let mainQueue = OperationQueue.main
  169. notificationObservers = [
  170. center.addObserver(forName: .DeviceNameDidChange, object: device, queue: mainQueue) { [weak self] (note) -> Void in
  171. if let cell = self?.cellForRow(.customName) {
  172. cell.detailTextLabel?.text = self?.device.name
  173. }
  174. self?.title = self?.device.name
  175. },
  176. center.addObserver(forName: .DeviceConnectionStateDidChange, object: device, queue: mainQueue) { [weak self] (note) -> Void in
  177. if let cell = self?.cellForRow(.connection) {
  178. cell.detailTextLabel?.text = self?.device.peripheralState.description
  179. }
  180. },
  181. center.addObserver(forName: .DeviceRSSIDidChange, object: device, queue: mainQueue) { [weak self] (note) -> Void in
  182. self?.bleRSSI = note.userInfo?[RileyLinkDevice.notificationRSSIKey] as? Int
  183. if let cell = self?.cellForRow(.rssi), let formatter = self?.integerFormatter {
  184. cell.setDetailRSSI(self?.bleRSSI, formatter: formatter)
  185. }
  186. },
  187. center.addObserver(forName: .DeviceDidStartIdle, object: device, queue: mainQueue) { [weak self] (note) in
  188. self?.updateDeviceStatus()
  189. },
  190. center.addObserver(forName: .PumpOpsStateDidChange, object: ops, queue: mainQueue) { [weak self] (note) in
  191. if let state = note.userInfo?[PumpOps.notificationPumpStateKey] as? PumpState {
  192. self?.pumpState = state
  193. }
  194. },
  195. center.addObserver(forName: .DeviceFW_HWChange, object: device, queue: mainQueue) { [weak self] (note) in
  196. self?.updateDeviceStatus()
  197. },
  198. ]
  199. }
  200. public override func viewWillAppear(_ animated: Bool) {
  201. super.viewWillAppear(animated)
  202. if appeared {
  203. tableView.reloadData()
  204. }
  205. rssiFetchTimer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(updateRSSI), userInfo: nil, repeats: true)
  206. appeared = true
  207. updateRSSI()
  208. updateUptime()
  209. updateBatteryLevel()
  210. writePSW()
  211. orangeAction(index: 9)
  212. orangeReadSet()
  213. }
  214. public override func viewDidDisappear(_ animated: Bool) {
  215. super.viewDidDisappear(animated)
  216. orangeClose()
  217. }
  218. public override func viewWillDisappear(_ animated: Bool) {
  219. super.viewWillDisappear(animated)
  220. rssiFetchTimer = nil
  221. }
  222. // MARK: - Formatters
  223. private lazy var dateFormatter: DateFormatter = {
  224. let dateFormatter = DateFormatter()
  225. dateFormatter.dateStyle = .none
  226. dateFormatter.timeStyle = .medium
  227. return dateFormatter
  228. }()
  229. private lazy var integerFormatter = NumberFormatter()
  230. private lazy var measurementFormatter: MeasurementFormatter = {
  231. let formatter = MeasurementFormatter()
  232. formatter.numberFormatter = decimalFormatter
  233. return formatter
  234. }()
  235. private lazy var decimalFormatter: NumberFormatter = {
  236. let decimalFormatter = NumberFormatter()
  237. decimalFormatter.numberStyle = .decimal
  238. decimalFormatter.minimumSignificantDigits = 5
  239. return decimalFormatter
  240. }()
  241. // MARK: - Table view data source
  242. private enum Section: Int, CaseCountable {
  243. case device
  244. case pump
  245. case commands
  246. }
  247. private enum DeviceRow: Int, CaseCountable {
  248. case customName
  249. case version
  250. case rssi
  251. case connection
  252. case uptime
  253. case idleStatus
  254. case battery
  255. case orl
  256. }
  257. private enum PumpRow: Int, CaseCountable {
  258. case id
  259. case model
  260. case awake
  261. }
  262. private enum CommandRow: Int, CaseCountable {
  263. case tune
  264. case changeTime
  265. case mySentryPair
  266. case dumpHistory
  267. case fetchGlucose
  268. case getPumpModel
  269. case pressDownButton
  270. case readPumpStatus
  271. case readBasalSchedule
  272. case enableLED
  273. case discoverCommands
  274. case getStatistics
  275. case yellow
  276. case red
  277. case off
  278. case shake
  279. case shakeOff
  280. case disconnectLed
  281. case disconnectVibration
  282. case connectLed
  283. case connectVibration
  284. }
  285. @objc
  286. func switchAction(sender: RileyLinkSwitch) {
  287. switch CommandRow(rawValue: sender.index)! {
  288. case .connectLed:
  289. orangeAction(index: 4, open: sender.isOn)
  290. case .connectVibration:
  291. orangeAction(index: 5, open: sender.isOn)
  292. case .disconnectLed:
  293. orangeAction(index: 2, open: sender.isOn)
  294. case .disconnectVibration:
  295. orangeAction(index: 3, open: sender.isOn)
  296. default:
  297. break
  298. }
  299. }
  300. private func cellForRow(_ row: DeviceRow) -> UITableViewCell? {
  301. return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: Section.device.rawValue))
  302. }
  303. private func cellForRow(_ row: PumpRow) -> UITableViewCell? {
  304. return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: Section.pump.rawValue))
  305. }
  306. private func cellForRow(_ row: CommandRow) -> UITableViewCell? {
  307. return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: Section.commands.rawValue))
  308. }
  309. public override func numberOfSections(in tableView: UITableView) -> Int {
  310. return Section.count
  311. }
  312. public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  313. switch Section(rawValue: section)! {
  314. case .device:
  315. return DeviceRow.count
  316. case .pump:
  317. return PumpRow.count
  318. case .commands:
  319. return CommandRow.count
  320. }
  321. }
  322. public override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  323. return 45
  324. }
  325. public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  326. let cell: UITableViewCell
  327. if let reusableCell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier) {
  328. cell = reusableCell
  329. } else {
  330. cell = UITableViewCell(style: .value1, reuseIdentifier: CellIdentifier)
  331. let switchView = RileyLinkSwitch()
  332. switchView.tag = 10000
  333. switchView.addTarget(self, action: #selector(switchAction(sender:)), for: .valueChanged)
  334. switchView.frame = CGRect(x: tableView.frame.width - 51 - 20, y: 7, width: 51, height: 31)
  335. cell.contentView.addSubview(switchView)
  336. }
  337. let switchView = cell.contentView.viewWithTag(10000) as? RileyLinkSwitch
  338. switchView?.isHidden = true
  339. switchView?.index = indexPath.row
  340. cell.accessoryType = .none
  341. switch Section(rawValue: indexPath.section)! {
  342. case .device:
  343. switch DeviceRow(rawValue: indexPath.row)! {
  344. case .customName:
  345. cell.textLabel?.text = LocalizedString("Name", comment: "The title of the cell showing device name")
  346. cell.detailTextLabel?.text = device.name
  347. cell.accessoryType = .disclosureIndicator
  348. case .version:
  349. cell.textLabel?.text = LocalizedString("Firmware", comment: "The title of the cell showing firmware version")
  350. cell.detailTextLabel?.text = firmwareVersion
  351. case .connection:
  352. cell.textLabel?.text = LocalizedString("Connection State", comment: "The title of the cell showing BLE connection state")
  353. cell.detailTextLabel?.text = device.peripheralState.description
  354. case .rssi:
  355. cell.textLabel?.text = LocalizedString("Signal Strength", comment: "The title of the cell showing BLE signal strength (RSSI)")
  356. cell.setDetailRSSI(bleRSSI, formatter: integerFormatter)
  357. case .uptime:
  358. cell.textLabel?.text = LocalizedString("Uptime", comment: "The title of the cell showing uptime")
  359. cell.setDetailAge(uptime)
  360. case .idleStatus:
  361. cell.textLabel?.text = LocalizedString("On Idle", comment: "The title of the cell showing the last idle")
  362. cell.setDetailDate(lastIdle, formatter: dateFormatter)
  363. case .battery:
  364. cell.textLabel?.text = NSLocalizedString("Battery Level", comment: "The title of the cell showing battery level")
  365. cell.setDetailBatteryLevel(battery)
  366. case .orl:
  367. cell.textLabel?.text = NSLocalizedString("ORL", comment: "The title of the cell showing ORL")
  368. cell.detailTextLabel?.text = fw_hw
  369. }
  370. case .pump:
  371. switch PumpRow(rawValue: indexPath.row)! {
  372. case .id:
  373. cell.textLabel?.text = LocalizedString("Pump ID", comment: "The title of the cell showing pump ID")
  374. cell.detailTextLabel?.text = ops.pumpSettings.pumpID
  375. case .model:
  376. cell.textLabel?.text = LocalizedString("Pump Model", comment: "The title of the cell showing the pump model number")
  377. cell.setPumpModel(pumpState?.pumpModel)
  378. case .awake:
  379. cell.setAwakeUntil(pumpState?.awakeUntil, formatter: dateFormatter)
  380. }
  381. case .commands:
  382. cell.accessoryType = .disclosureIndicator
  383. cell.detailTextLabel?.text = nil
  384. switch CommandRow(rawValue: indexPath.row)! {
  385. case .tune:
  386. cell.setTuneInfo(lastValidFrequency: pumpState?.lastValidFrequency, lastTuned: pumpState?.lastTuned, measurementFormatter: measurementFormatter, dateFormatter: dateFormatter)
  387. case .changeTime:
  388. cell.textLabel?.text = LocalizedString("Change Time", comment: "The title of the command to change pump time")
  389. let localTimeZone = TimeZone.current
  390. let localTimeZoneName = localTimeZone.abbreviation() ?? localTimeZone.identifier
  391. if let pumpTimeZone = pumpState?.timeZone {
  392. let timeZoneDiff = TimeInterval(pumpTimeZone.secondsFromGMT() - localTimeZone.secondsFromGMT())
  393. let formatter = DateComponentsFormatter()
  394. formatter.allowedUnits = [.hour, .minute]
  395. let diffString = timeZoneDiff != 0 ? formatter.string(from: abs(timeZoneDiff)) ?? String(abs(timeZoneDiff)) : ""
  396. 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)
  397. } else {
  398. cell.detailTextLabel?.text = localTimeZoneName
  399. }
  400. case .mySentryPair:
  401. cell.textLabel?.text = LocalizedString("MySentry Pair", comment: "The title of the command to pair with mysentry")
  402. case .dumpHistory:
  403. cell.textLabel?.text = LocalizedString("Fetch Recent History", comment: "The title of the command to fetch recent history")
  404. case .fetchGlucose:
  405. cell.textLabel?.text = LocalizedString("Fetch Enlite Glucose", comment: "The title of the command to fetch recent glucose")
  406. case .getPumpModel:
  407. cell.textLabel?.text = LocalizedString("Get Pump Model", comment: "The title of the command to get pump model")
  408. case .pressDownButton:
  409. cell.textLabel?.text = LocalizedString("Send Button Press", comment: "The title of the command to send a button press")
  410. case .readPumpStatus:
  411. cell.textLabel?.text = LocalizedString("Read Pump Status", comment: "The title of the command to read pump status")
  412. case .readBasalSchedule:
  413. cell.textLabel?.text = LocalizedString("Read Basal Schedule", comment: "The title of the command to read basal schedule")
  414. case .enableLED:
  415. cell.textLabel?.text = LocalizedString("Enable Diagnostic LEDs", comment: "The title of the command to enable diagnostic LEDs")
  416. case .discoverCommands:
  417. cell.textLabel?.text = LocalizedString("Discover Commands", comment: "The title of the command to discover commands")
  418. case .getStatistics:
  419. cell.textLabel?.text = LocalizedString("RileyLink Statistics", comment: "The title of the command to fetch RileyLink statistics")
  420. case .yellow:
  421. cell.textLabel?.text = NSLocalizedString("Lighten Yellow LED", comment: "The title of the cell showing Lighten Yellow LED")
  422. case .red:
  423. cell.textLabel?.text = NSLocalizedString("Lighten Red LED", comment: "The title of the cell showing Lighten Red LED")
  424. case .off:
  425. cell.textLabel?.text = NSLocalizedString("Turn Off LED", comment: "The title of the cell showing Turn Off LED")
  426. case .shake:
  427. cell.textLabel?.text = NSLocalizedString("Test Vibrator", comment: "The title of the cell showing Test Vibrator")
  428. case .shakeOff:
  429. cell.textLabel?.text = NSLocalizedString("Stop Vibrator", comment: "The title of the cell showing Stop Vibrator")
  430. case .disconnectLed:
  431. switchView?.isHidden = false
  432. switchView?.isOn = disconnectLed
  433. cell.accessoryType = .none
  434. cell.textLabel?.text = NSLocalizedString("Disconnect Led", comment: "The title of the cell showing Stop Vibrator")
  435. case .disconnectVibration:
  436. switchView?.isHidden = false
  437. switchView?.isOn = disconnectVibration
  438. cell.accessoryType = .none
  439. cell.textLabel?.text = NSLocalizedString("Disconnect Vibrator", comment: "The title of the cell showing Stop Vibrator")
  440. case .connectLed:
  441. switchView?.isHidden = false
  442. switchView?.isOn = connectLed
  443. cell.accessoryType = .none
  444. cell.textLabel?.text = NSLocalizedString("Connect Led", comment: "The title of the cell showing Stop Vibrator")
  445. case .connectVibration:
  446. switchView?.isHidden = false
  447. switchView?.isOn = connectVibration
  448. cell.accessoryType = .none
  449. cell.textLabel?.text = NSLocalizedString("Connect Vibrator", comment: "The title of the cell showing Stop Vibrator")
  450. }
  451. }
  452. return cell
  453. }
  454. public override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  455. switch Section(rawValue: section)! {
  456. case .device:
  457. return LocalizedString("Device", comment: "The title of the section describing the device")
  458. case .pump:
  459. return LocalizedString("Pump", comment: "The title of the section describing the pump")
  460. case .commands:
  461. return LocalizedString("Commands", comment: "The title of the section describing commands")
  462. }
  463. }
  464. // MARK: - UITableViewDelegate
  465. public override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
  466. switch Section(rawValue: indexPath.section)! {
  467. case .device:
  468. switch DeviceRow(rawValue: indexPath.row)! {
  469. case .customName:
  470. return true
  471. default:
  472. return false
  473. }
  474. case .pump:
  475. return false
  476. case .commands:
  477. return device.peripheralState == .connected
  478. }
  479. }
  480. public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  481. switch Section(rawValue: indexPath.section)! {
  482. case .device:
  483. switch DeviceRow(rawValue: indexPath.row)! {
  484. case .customName:
  485. let vc = TextFieldTableViewController()
  486. if let cell = tableView.cellForRow(at: indexPath) {
  487. vc.title = cell.textLabel?.text
  488. vc.value = device.name
  489. vc.delegate = self
  490. vc.keyboardType = .default
  491. }
  492. show(vc, sender: indexPath)
  493. default:
  494. break
  495. }
  496. case .commands:
  497. var vc: CommandResponseViewController?
  498. switch CommandRow(rawValue: indexPath.row)! {
  499. case .tune:
  500. vc = .tuneRadio(ops: ops, device: device, measurementFormatter: measurementFormatter)
  501. case .changeTime:
  502. vc = .changeTime(ops: ops, device: device)
  503. case .mySentryPair:
  504. vc = .mySentryPair(ops: ops, device: device)
  505. case .dumpHistory:
  506. vc = .dumpHistory(ops: ops, device: device)
  507. case .fetchGlucose:
  508. vc = .fetchGlucose(ops: ops, device: device)
  509. case .getPumpModel:
  510. vc = .getPumpModel(ops: ops, device: device)
  511. case .pressDownButton:
  512. vc = .pressDownButton(ops: ops, device: device)
  513. case .readPumpStatus:
  514. vc = .readPumpStatus(ops: ops, device: device, measurementFormatter: measurementFormatter)
  515. case .readBasalSchedule:
  516. vc = .readBasalSchedule(ops: ops, device: device, integerFormatter: integerFormatter)
  517. case .enableLED:
  518. vc = .enableLEDs(ops: ops, device: device)
  519. case .discoverCommands:
  520. vc = .discoverCommands(ops: ops, device: device)
  521. case .getStatistics:
  522. vc = .getStatistics(ops: ops, device: device)
  523. case .yellow: orangeAction(index: 1)
  524. case .red: orangeAction(index: 2)
  525. case .off: orangeAction(index: 3)
  526. case .shake: orangeAction(index: 4)
  527. case .shakeOff: orangeAction(index: 5)
  528. default:
  529. break
  530. }
  531. if let cell = tableView.cellForRow(at: indexPath) {
  532. vc?.title = cell.textLabel?.text
  533. }
  534. if let vc = vc {
  535. show(vc, sender: indexPath)
  536. }
  537. case .pump:
  538. break
  539. }
  540. }
  541. }
  542. extension RileyLinkMinimedDeviceTableViewController: TextFieldTableViewControllerDelegate {
  543. public func textFieldTableViewControllerDidReturn(_ controller: TextFieldTableViewController) {
  544. _ = navigationController?.popViewController(animated: true)
  545. }
  546. public func textFieldTableViewControllerDidEndEditing(_ controller: TextFieldTableViewController) {
  547. if let indexPath = tableView.indexPathForSelectedRow {
  548. switch Section(rawValue: indexPath.section)! {
  549. case .device:
  550. switch DeviceRow(rawValue: indexPath.row)! {
  551. case .customName:
  552. device.setCustomName(controller.value!)
  553. default:
  554. break
  555. }
  556. default:
  557. break
  558. }
  559. }
  560. }
  561. }
  562. private extension TimeInterval {
  563. func format(using units: NSCalendar.Unit) -> String? {
  564. let formatter = DateComponentsFormatter()
  565. formatter.allowedUnits = units
  566. formatter.unitsStyle = .full
  567. formatter.zeroFormattingBehavior = .dropLeading
  568. formatter.maximumUnitCount = 2
  569. return formatter.string(from: self)
  570. }
  571. }
  572. private extension UITableViewCell {
  573. func setDetailBatteryLevel(_ batteryLevel: String?) {
  574. if let unwrappedBatteryLevel = batteryLevel {
  575. detailTextLabel?.text = unwrappedBatteryLevel + " %"
  576. } else {
  577. detailTextLabel?.text = ""
  578. }
  579. }
  580. func setDetailDate(_ date: Date?, formatter: DateFormatter) {
  581. if let date = date {
  582. detailTextLabel?.text = formatter.string(from: date)
  583. } else {
  584. detailTextLabel?.text = "-"
  585. }
  586. }
  587. func setDetailRSSI(_ decibles: Int?, formatter: NumberFormatter) {
  588. detailTextLabel?.text = formatter.decibleString(from: decibles) ?? "-"
  589. }
  590. func setDetailAge(_ age: TimeInterval?) {
  591. if let age = age {
  592. detailTextLabel?.text = age.format(using: [.day, .hour, .minute])
  593. } else {
  594. detailTextLabel?.text = ""
  595. }
  596. }
  597. func setAwakeUntil(_ awakeUntil: Date?, formatter: DateFormatter) {
  598. switch awakeUntil {
  599. case let until? where until.timeIntervalSinceNow < 0:
  600. textLabel?.text = LocalizedString("Last Awake", comment: "The title of the cell describing an awake radio")
  601. setDetailDate(until, formatter: formatter)
  602. case let until?:
  603. textLabel?.text = LocalizedString("Awake Until", comment: "The title of the cell describing an awake radio")
  604. setDetailDate(until, formatter: formatter)
  605. default:
  606. textLabel?.text = LocalizedString("Listening Off", comment: "The title of the cell describing no radio awake data")
  607. detailTextLabel?.text = nil
  608. }
  609. }
  610. func setPumpModel(_ pumpModel: PumpModel?) {
  611. if let pumpModel = pumpModel {
  612. detailTextLabel?.text = String(describing: pumpModel)
  613. } else {
  614. detailTextLabel?.text = LocalizedString("Unknown", comment: "The detail text for an unknown pump model")
  615. }
  616. }
  617. func setTuneInfo(lastValidFrequency: Measurement<UnitFrequency>?, lastTuned: Date?, measurementFormatter: MeasurementFormatter, dateFormatter: DateFormatter) {
  618. if let frequency = lastValidFrequency, let date = lastTuned {
  619. textLabel?.text = measurementFormatter.string(from: frequency)
  620. setDetailDate(date, formatter: dateFormatter)
  621. } else {
  622. textLabel?.text = LocalizedString("Tune Radio Frequency", comment: "The title of the command to re-tune the radio")
  623. }
  624. }
  625. }