| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670 |
- //
- // OmnipodSettingsView.swift
- // ViewDev
- //
- // Created by Pete Schwamb on 3/8/20.
- // Copyright © 2020 Pete Schwamb. All rights reserved.
- //
- import SwiftUI
- import LoopKit
- import LoopKitUI
- import HealthKit
- import OmniKit
- import RileyLinkBLEKit
- struct OmnipodSettingsView: View {
- @ObservedObject var viewModel: OmnipodSettingsViewModel
- @ObservedObject var rileyLinkListDataSource: RileyLinkListDataSource
- var handleRileyLinkSelection: (RileyLinkDevice) -> Void
- @State private var showingDeleteConfirmation = false
- @State private var showSuspendOptions = false
- @State private var showManualTempBasalOptions = false
- @State private var showSyncTimeOptions = false
- @State private var sendingTestBeepsCommand = false
- @State private var cancelingTempBasal = false
- var supportedInsulinTypes: [InsulinType]
- @Environment(\.guidanceColors) var guidanceColors
- @Environment(\.insulinTintColor) var insulinTintColor
-
- private var daysRemaining: Int? {
- if case .timeRemaining(let remaining, _) = viewModel.lifeState, remaining > .days(1) {
- return Int(remaining.days)
- }
- return nil
- }
-
- private var hoursRemaining: Int? {
- if case .timeRemaining(let remaining, _) = viewModel.lifeState, remaining > .hours(1) {
- return Int(remaining.hours.truncatingRemainder(dividingBy: 24))
- }
- return nil
- }
-
- private var minutesRemaining: Int? {
- if case .timeRemaining(let remaining, _) = viewModel.lifeState, remaining < .hours(2) {
- return Int(remaining.minutes.truncatingRemainder(dividingBy: 60))
- }
- return nil
- }
-
- func timeComponent(value: Int, units: String) -> some View {
- Group {
- Text(String(value)).font(.system(size: 28)).fontWeight(.heavy)
- .foregroundColor(viewModel.podOk ? .primary : .secondary)
- Text(units).foregroundColor(.secondary)
- }
- }
-
- var lifecycleProgress: some View {
- VStack(spacing: 2) {
- HStack(alignment: .lastTextBaseline, spacing: 3) {
- Text(self.viewModel.lifeState.localizedLabelText)
- .foregroundColor(self.viewModel.lifeState.labelColor(using: guidanceColors))
- Spacer()
- daysRemaining.map { (days) in
- timeComponent(value: days, units: days == 1 ?
- LocalizedString("day", comment: "Unit for singular day in pod life remaining") :
- LocalizedString("days", comment: "Unit for plural days in pod life remaining"))
- }
- hoursRemaining.map { (hours) in
- timeComponent(value: hours, units: hours == 1 ?
- LocalizedString("hour", comment: "Unit for singular hour in pod life remaining") :
- LocalizedString("hours", comment: "Unit for plural hours in pod life remaining"))
- }
- minutesRemaining.map { (minutes) in
- timeComponent(value: minutes, units: minutes == 1 ?
- LocalizedString("minute", comment: "Unit for singular minute in pod life remaining") :
- LocalizedString("minutes", comment: "Unit for plural minutes in pod life remaining"))
- }
- }
- ProgressView(progress: CGFloat(self.viewModel.lifeState.progress)).accentColor(self.viewModel.lifeState.progressColor(guidanceColors: guidanceColors))
- }
- }
-
- func cancelDelete() {
- showingDeleteConfirmation = false
- }
-
-
- var deliverySectionTitle: String {
- if self.viewModel.isScheduledBasal {
- return LocalizedString("Scheduled Basal", comment: "Title of insulin delivery section")
- } else {
- return LocalizedString("Insulin Delivery", comment: "Title of insulin delivery section")
- }
- }
-
- var deliveryStatus: some View {
- VStack(alignment: .leading, spacing: 5) {
- Text(deliverySectionTitle)
- .foregroundColor(Color(UIColor.secondaryLabel))
- if viewModel.podOk, viewModel.isSuspendedOrResuming {
- HStack(alignment: .center) {
- Image(systemName: "pause.circle.fill")
- .font(.system(size: 34))
- .fixedSize()
- .foregroundColor(viewModel.suspendResumeButtonColor(guidanceColors: guidanceColors))
- FrameworkLocalText("Insulin\nSuspended", comment: "Text shown in insulin delivery space when insulin suspended")
- .fontWeight(.bold)
- .fixedSize()
- }
- } else if let basalRate = self.viewModel.basalDeliveryRate {
- HStack(alignment: .center) {
- HStack(alignment: .lastTextBaseline, spacing: 3) {
- Text(self.viewModel.basalRateFormatter.string(from: basalRate) ?? "")
- .font(.system(size: 28))
- .fontWeight(.heavy)
- .fixedSize()
- FrameworkLocalText("U/hr", comment: "Units for showing temp basal rate").foregroundColor(.secondary)
- }
- }
- } else {
- HStack(alignment: .center) {
- Image(systemName: "x.circle.fill")
- .font(.system(size: 34))
- .fixedSize()
- .foregroundColor(guidanceColors.critical)
- FrameworkLocalText("No\nDelivery", comment: "Text shown in insulin remaining space when no pod is paired")
- .fontWeight(.bold)
- .fixedSize()
- }
- }
- }
- }
-
- func reservoir(filledPercent: CGFloat, fillColor: Color) -> some View {
- ZStack(alignment: Alignment(horizontal: .center, vertical: .center)) {
- GeometryReader { geometry in
- let offset = geometry.size.height * 0.05
- let fillHeight = geometry.size.height * 0.81
- Rectangle()
- .fill(fillColor)
- .mask(
- Image(frameworkImage: "pod_reservoir_mask_swiftui")
- .resizable()
- .scaledToFit()
- )
- .mask(
- Rectangle().path(in: CGRect(x: 0, y: offset + fillHeight - fillHeight * filledPercent, width: geometry.size.width, height: fillHeight * filledPercent))
- )
- }
- Image(frameworkImage: "pod_reservoir_swiftui")
- .renderingMode(.template)
- .resizable()
- .foregroundColor(fillColor)
- .scaledToFit()
- }.frame(width: 23, height: 32)
- }
-
-
- var reservoirStatus: some View {
- VStack(alignment: .leading, spacing: 5) {
- Text(LocalizedString("Insulin Remaining", comment: "Header for insulin remaining on pod settings screen"))
- .foregroundColor(Color(UIColor.secondaryLabel))
- HStack {
- if let podError = viewModel.podError {
- Image(systemName: "exclamationmark.circle.fill")
- .font(.system(size: 34))
- .fixedSize()
- .foregroundColor(guidanceColors.critical)
- Text(podError).fontWeight(.bold)
- } else if let reservoirLevel = viewModel.reservoirLevel, let reservoirLevelHighlightState = viewModel.reservoirLevelHighlightState {
- reservoir(filledPercent: CGFloat(reservoirLevel.percentage), fillColor: reservoirColor(for: reservoirLevelHighlightState))
- Text(viewModel.reservoirText(for: reservoirLevel))
- .font(.system(size: 28))
- .fontWeight(.heavy)
- .fixedSize()
- } else {
- Image(systemName: "exclamationmark.circle.fill")
- .font(.system(size: 34))
- .fixedSize()
- .foregroundColor(guidanceColors.warning)
-
- FrameworkLocalText("No Pod", comment: "Text shown in insulin remaining space when no pod is paired").fontWeight(.bold)
- }
-
- }
- }
- }
- var manualTempBasalRow: some View {
- Button(action: {
- self.manualBasalTapped()
- }) {
- FrameworkLocalText("Set Temporary Basal Rate", comment: "Button title to set temporary basal rate")
- }
- .sheet(isPresented: $showManualTempBasalOptions) {
- ManualTempBasalEntryView(
- enactBasal: { rate, duration, completion in
- viewModel.runTemporaryBasalProgram(unitsPerHour: rate, for: duration) { error in
- completion(error)
- if error == nil {
- showManualTempBasalOptions = false
- }
- }
- },
- didCancel: {
- showManualTempBasalOptions = false
- },
- allowedRates: viewModel.allowedTempBasalRates
- )
- }
- }
- func suspendResumeRow() -> some View {
- HStack {
- Button(action: {
- self.suspendResumeTapped()
- }) {
- HStack {
- Image(systemName: "pause.circle.fill")
- .font(.system(size: 22))
- .foregroundColor(viewModel.suspendResumeButtonColor(guidanceColors: guidanceColors))
- Text(viewModel.suspendResumeActionText)
- .foregroundColor(viewModel.suspendResumeActionColor())
- }
- }
- .actionSheet(isPresented: $showSuspendOptions) {
- suspendOptionsActionSheet
- }
- Spacer()
- if viewModel.basalTransitioning {
- ActivityIndicator(isAnimating: .constant(true), style: .medium)
- }
- }
- }
-
- private var doneButton: some View {
- Button(LocalizedString("Done", comment: "Title of done button on OmnipodSettingsView"), action: {
- self.viewModel.doneTapped()
- })
- }
-
- var headerImage: some View {
- VStack(alignment: .center) {
- Image(frameworkImage: "Pod")
- .resizable()
- .aspectRatio(contentMode: ContentMode.fit)
- .frame(height: 100)
- .padding(.horizontal)
- }.frame(maxWidth: .infinity)
- }
-
- var body: some View {
- List {
- Section() {
- VStack(alignment: .trailing) {
- Button(action: {
- sendingTestBeepsCommand = true
- viewModel.playTestBeeps { _ in
- sendingTestBeepsCommand = false
- }
- }) {
- Image(systemName: "speaker.wave.2.circle")
- .imageScale(.large)
- .foregroundColor(viewModel.rileylinkConnected ? .accentColor : .secondary)
- .padding(.top,5)
- }
- .buttonStyle(PlainButtonStyle())
- .disabled(!viewModel.rileylinkConnected || sendingTestBeepsCommand)
- headerImage
- lifecycleProgress
- HStack(alignment: .top) {
- deliveryStatus
- Spacer()
- reservoirStatus
- }
- if let faultAction = viewModel.recoveryText {
- Divider()
- Text(faultAction)
- .font(Font.footnote.weight(.semibold))
- .fixedSize(horizontal: false, vertical: true)
- .frame(maxWidth: .infinity, alignment: .leading)
- }
- }
- if let notice = viewModel.notice {
- VStack(alignment: .leading, spacing: 4) {
- Text(notice.title)
- .font(Font.subheadline.weight(.bold))
- Text(notice.description)
- .font(Font.footnote.weight(.semibold))
- }.padding(.vertical, 8)
- }
- }
- Section(header: SectionHeader(label: LocalizedString("Activity", comment: "Section header for activity section"))) {
- suspendResumeRow()
- .disabled(!self.viewModel.podOk)
- if self.viewModel.podOk, case .suspended(let suspendDate) = self.viewModel.basalDeliveryState {
- HStack {
- FrameworkLocalText("Suspended At", comment: "Label for suspended at time")
- Spacer()
- Text(self.viewModel.timeFormatter.string(from: suspendDate))
- .foregroundColor(Color.secondary)
- }
- }
- }
- Section() {
- if let manualTempRemaining = self.viewModel.manualBasalTimeRemaining, let remainingText = self.viewModel.timeRemainingFormatter.string(from: manualTempRemaining) {
- HStack {
- if cancelingTempBasal {
- ProgressView()
- .padding(.trailing)
- } else {
- Image(systemName: "exclamationmark.circle.fill")
- .font(.system(size: 22))
- .foregroundColor(guidanceColors.warning)
- }
- Button(action: {
- self.cancelManualBasal()
- }) {
- FrameworkLocalText("Cancel Manual Basal", comment: "Button title to cancel manual basal")
- }
- }
- HStack {
- FrameworkLocalText("Remaining", comment: "Label for remaining time of manual basal")
- Spacer()
- Text(remainingText)
- .foregroundColor(.secondary)
- }
- } else {
- manualTempBasalRow
- }
- }
- .disabled(cancelingTempBasal || !self.viewModel.podOk)
- Section(header: HStack {
- FrameworkLocalText("Devices", comment: "Header for devices section of RileyLinkSetupView")
- Spacer()
- ProgressView()
- }) {
- ForEach(rileyLinkListDataSource.devices, id: \.peripheralIdentifier) { device in
- Toggle(isOn: rileyLinkListDataSource.autoconnectBinding(for: device)) {
- HStack {
- Text(device.name ?? "Unknown")
- Spacer()
- if rileyLinkListDataSource.autoconnectBinding(for: device).wrappedValue {
- if device.isConnected {
- Text(formatRSSI(rssi:device.rssi)).foregroundColor(.secondary)
- } else {
- Image(systemName: "wifi.exclamationmark")
- .imageScale(.large)
- .foregroundColor(guidanceColors.warning)
- }
- }
- }
- .contentShape(Rectangle())
- .onTapGesture {
- handleRileyLinkSelection(device)
- }
- }
- }
- }
- .onAppear { rileyLinkListDataSource.isScanningEnabled = true }
- .onDisappear { rileyLinkListDataSource.isScanningEnabled = false }
- Section() {
- HStack {
- FrameworkLocalText("Pod Activated", comment: "Label for pod insertion row")
- Spacer()
- Text(self.viewModel.activatedAtString)
- .foregroundColor(Color.secondary)
- }
- HStack {
- if let expiresAt = viewModel.expiresAt, expiresAt < Date() {
- FrameworkLocalText("Pod Expired", comment: "Label for pod expiration row, past tense")
- } else {
- FrameworkLocalText("Pod Expires", comment: "Label for pod expiration row")
- }
- Spacer()
- Text(self.viewModel.expiresAtString)
- .foregroundColor(Color.secondary)
- }
- if let podDetails = self.viewModel.podDetails {
- NavigationLink(destination: PodDetailsView(podDetails: podDetails, title: LocalizedString("Pod Details", comment: "title for pod details page"))) {
- FrameworkLocalText("Pod Details", comment: "Text for pod details disclosure row")
- .foregroundColor(Color.primary)
- }
- } else {
- HStack {
- FrameworkLocalText("Pod Details", comment: "Text for pod details disclosure row")
- Spacer()
- Text("—")
- .foregroundColor(Color.secondary)
- }
- }
- if let previousPodDetails = viewModel.previousPodDetails {
- NavigationLink(destination: PodDetailsView(podDetails: previousPodDetails, title: LocalizedString("Previous Pod", comment: "title for previous pod page"))) {
- FrameworkLocalText("Previous Pod Details", comment: "Text for previous pod details row")
- .foregroundColor(Color.primary)
- }
- } else {
- HStack {
- FrameworkLocalText("Previous Pod Details", comment: "Text for previous pod details row")
- Spacer()
- Text("—")
- .foregroundColor(Color.secondary)
- }
- }
- }
- Section() {
- Button(action: {
- self.viewModel.navigateTo?(self.viewModel.lifeState.nextPodLifecycleAction)
- }) {
- Text(self.viewModel.lifeState.nextPodLifecycleActionDescription)
- .foregroundColor(self.viewModel.lifeState.nextPodLifecycleActionColor)
- }
- }
- Section(header: SectionHeader(label: LocalizedString("Configuration", comment: "Section header for configuration section")))
- {
- NavigationLink(destination:
- NotificationSettingsView(
- dateFormatter: self.viewModel.dateFormatter,
- expirationReminderDefault: self.$viewModel.expirationReminderDefault,
- scheduledReminderDate: self.viewModel.expirationReminderDate,
- allowedScheduledReminderDates: self.viewModel.allowedScheduledReminderDates,
- lowReservoirReminderValue: self.viewModel.lowReservoirAlertValue,
- onSaveScheduledExpirationReminder: self.viewModel.saveScheduledExpirationReminder,
- onSaveLowReservoirReminder: self.viewModel.saveLowReservoirReminder))
- {
- FrameworkLocalText("Notification Settings", comment: "Text for pod details disclosure row").foregroundColor(Color.primary)
- }
- NavigationLink(destination: BeepPreferenceSelectionView(initialValue: viewModel.beepPreference, onSave: viewModel.setConfirmationBeeps)) {
- HStack {
- FrameworkLocalText("Confidence Reminders", comment: "Text for confidence reminders navigation link")
- .foregroundColor(Color.primary)
- Spacer()
- Text(viewModel.beepPreference.title)
- .foregroundColor(.secondary)
- }
- }
- NavigationLink(destination: SilencePodSelectionView(initialValue: viewModel.silencePodPreference, onSave: viewModel.setSilencePod)) {
- HStack {
- FrameworkLocalText("Silence Pod", comment: "Text for silence pod navigation link")
- .foregroundColor(Color.primary)
- Spacer()
- Text(viewModel.silencePodPreference.title)
- .foregroundColor(.secondary)
- }
- }
- NavigationLink(destination: InsulinTypeSetting(initialValue: viewModel.insulinType, supportedInsulinTypes: supportedInsulinTypes, allowUnsetInsulinType: false, didChange: viewModel.didChangeInsulinType)) {
- HStack {
- FrameworkLocalText("Insulin Type", comment: "Text for insulin type navigation link").foregroundColor(Color.primary)
- if let currentTitle = viewModel.insulinType?.brandName {
- Spacer()
- Text(currentTitle)
- .foregroundColor(.secondary)
- }
- }
- }
- }
- Section() {
- HStack {
- FrameworkLocalText("Pump Time", comment: "The title of the command to change pump time zone")
- Spacer()
- if viewModel.isClockOffset {
- Image(systemName: "clock.fill")
- .foregroundColor(guidanceColors.warning)
- }
- TimeView(timeZone: viewModel.timeZone)
- .foregroundColor( viewModel.isClockOffset ? guidanceColors.warning : nil)
- }
- if viewModel.synchronizingTime {
- HStack {
- FrameworkLocalText("Adjusting Pump Time...", comment: "Text indicating ongoing pump time synchronization")
- .foregroundColor(.secondary)
- Spacer()
- ActivityIndicator(isAnimating: .constant(true), style: .medium)
- }
- } else if self.viewModel.timeZone != TimeZone.currentFixed {
- Button(action: {
- showSyncTimeOptions = true
- }) {
- FrameworkLocalText("Sync to Current Time", comment: "The title of the command to change pump time zone")
- }
- .actionSheet(isPresented: $showSyncTimeOptions) {
- syncPumpTimeActionSheet
- }
- }
- }
- Section() {
- NavigationLink(destination: PodDiagnosticsView(
- title: LocalizedString("Pod Diagnostics", comment: "Title for the pod diagnostic view"),
- viewModel: viewModel))
- {
- FrameworkLocalText("Pod Diagnostics", comment: "Text for pod diagnostics row")
- .foregroundColor(Color.primary)
- }
- }
- if self.viewModel.lifeState.allowsPumpManagerRemoval {
- Section() {
- Button(action: {
- self.showingDeleteConfirmation = true
- }) {
- FrameworkLocalText("Switch to other insulin delivery device", comment: "Label for PumpManager deletion button")
- .foregroundColor(guidanceColors.critical)
- }
- .actionSheet(isPresented: $showingDeleteConfirmation) {
- removePumpManagerActionSheet
- }
- }
- }
- }
- .alert(isPresented: $viewModel.alertIsPresented, content: { alert(for: viewModel.activeAlert!) })
- .insetGroupedListStyle()
- .navigationBarItems(trailing: doneButton)
- .navigationBarTitle(self.viewModel.viewTitle)
- }
- var syncPumpTimeActionSheet: ActionSheet {
- ActionSheet(title: FrameworkLocalText("Time Change Detected", comment: "Title for pod sync time action sheet."), message: FrameworkLocalText("The time on your pump is different from the current time. Do you want to update the time on your pump to the current time?", comment: "Message for pod sync time action sheet"), buttons: [
- .default(FrameworkLocalText("Yes, Sync to Current Time", comment: "Button text to confirm pump time sync")) {
- self.viewModel.changeTimeZoneTapped()
- },
- .cancel(FrameworkLocalText("No, Keep Pump As Is", comment: "Button text to cancel pump time sync"))
- ])
- }
-
- var removePumpManagerActionSheet: ActionSheet {
- ActionSheet(title: FrameworkLocalText("Remove Pump", comment: "Title for Omnipod PumpManager deletion action sheet."), message: FrameworkLocalText("Are you sure you want to stop using Omnipod?", comment: "Message for Omnipod PumpManager deletion action sheet"), buttons: [
- .destructive(FrameworkLocalText("Delete Omnipod", comment: "Button text to confirm Omnipod PumpManager deletion")) {
- self.viewModel.stopUsingOmnipodTapped()
- },
- .cancel()
- ])
- }
-
- var suspendOptionsActionSheet: ActionSheet {
- ActionSheet(
- title: FrameworkLocalText("Suspend Delivery", comment: "Title for suspend duration selection action sheet"),
- message: FrameworkLocalText("Insulin delivery will be stopped until you resume manually. When would you like Loop to remind you to resume delivery?", comment: "Message for suspend duration selection action sheet"),
- buttons: [
- .default(FrameworkLocalText("30 minutes", comment: "Button text for 30 minute suspend duration"), action: { self.viewModel.suspendDelivery(duration: .minutes(30)) }),
- .default(FrameworkLocalText("1 hour", comment: "Button text for 1 hour suspend duration"), action: { self.viewModel.suspendDelivery(duration: .hours(1)) }),
- .default(FrameworkLocalText("1 hour 30 minutes", comment: "Button text for 1 hour 30 minute suspend duration"), action: { self.viewModel.suspendDelivery(duration: .hours(1.5)) }),
- .default(FrameworkLocalText("2 hours", comment: "Button text for 2 hour suspend duration"), action: { self.viewModel.suspendDelivery(duration: .hours(2)) }),
- .cancel()
- ])
- }
- func suspendResumeTapped() {
- switch self.viewModel.basalDeliveryState {
- case .active, .tempBasal:
- showSuspendOptions = true
- case .suspended:
- self.viewModel.resumeDelivery()
- default:
- break
- }
- }
- func manualBasalTapped() {
- showManualTempBasalOptions = true
- }
- func cancelManualBasal() {
- cancelingTempBasal = true
- viewModel.runTemporaryBasalProgram(unitsPerHour: 0, for: 0) { error in
- cancelingTempBasal = false
- if let error = error {
- self.viewModel.activeAlert = .cancelManualBasalError(error)
- }
- }
- }
-
- private func errorText(_ error: Error) -> String {
- if let error = error as? LocalizedError {
- return [error.localizedDescription, error.recoverySuggestion].compactMap{$0}.joined(separator: ". ")
- } else {
- return error.localizedDescription
- }
- }
-
- private func alert(for alert: OmnipodSettingsViewAlert) -> SwiftUI.Alert {
- switch alert {
- case .suspendError(let error):
- return SwiftUI.Alert(
- title: Text("Failed to Suspend Insulin Delivery", comment: "Alert title for suspend error"),
- message: Text(errorText(error))
- )
-
- case .resumeError(let error):
- return SwiftUI.Alert(
- title: Text("Failed to Resume Insulin Delivery", comment: "Alert title for resume error"),
- message: Text(errorText(error))
- )
-
- case .syncTimeError(let error):
- return SwiftUI.Alert(
- title: Text("Failed to Set Pump Time", comment: "Alert title for time sync error"),
- message: Text(errorText(error))
- )
- case .cancelManualBasalError(let error):
- return SwiftUI.Alert(
- title: Text("Failed to Cancel Manual Basal", comment: "Alert title for failing to cancel manual basal error"),
- message: Text(errorText(error))
- )
- }
- }
- func reservoirColor(for reservoirLevelHighlightState: ReservoirLevelHighlightState) -> Color {
- switch reservoirLevelHighlightState {
- case .normal:
- return insulinTintColor
- case .warning:
- return guidanceColors.warning
- case .critical:
- return guidanceColors.critical
- }
- }
- var decimalFormatter: NumberFormatter = {
- let formatter = NumberFormatter()
- formatter.numberStyle = .decimal
- formatter.minimumFractionDigits = 0
- formatter.maximumFractionDigits = 2
- return formatter
- }()
- private func formatRSSI(rssi: Int?) -> String {
- if let rssi = rssi, let rssiStr = decimalFormatter.decibleString(from: rssi) {
- return rssiStr
- } else {
- return ""
- }
- }
- }
|