diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index afea8dc1..90e02e82 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -830,8 +830,12 @@ extension OmniBLEPumpManager { // If we're in the simulator, create a mock PodState let mockFaultDuringPairing = false let mockCommsErrorDuringPairing = false + let mockStartDate = Date() + //let mockStartDate = Date.init(timeIntervalSinceNow: -(37 * 60 * 60) - 134) // Active Pod within 72 hour expiration : 37 hours, 2 mins, 14 secs + //let mockStartDate = Date.init(timeIntervalSinceNow: -(74 * 60 * 60) - 134) // Expired Pod within 8 hour grace period : 74 hours, 2 mins, 14 secs + //let mockStartDate = Date.init(timeIntervalSinceNow: -(80 * 60 * 60) - 134) // Expired Pod beyond 8 hour grace period : 80 hours, 2 mins, 14 secs DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + .seconds(2)) { - self.jumpStartPod(lotNo: 135601809, lotSeq: 0800525, mockFault: mockFaultDuringPairing) + self.jumpStartPod(lotNo: 135601809, lotSeq: 0800525, startDate: mockStartDate, mockFault: mockFaultDuringPairing) let fault: DetailedStatus? = self.setStateWithResult({ (state) in var podState = state.podState podState?.setupProgress = .priming diff --git a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift index 8aca8d42..66287580 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift @@ -46,27 +46,70 @@ class OmniBLESettingsViewModel: ObservableObject { var activatedAtString: String { if let activatedAt = activatedAt { - return dateFormatter.string(from: activatedAt) - } else { - return "—" + if relDateFormatter.string(from: activatedAt) == absDateFormatter.string(from: activatedAt) { + return altRelFormatter.string(from: activatedAt) + } + return relDateAndTimeFormatter.string(from: activatedAt) + } else { + return "—" + } } - } - + var expiresAtString: String { if let expiresAt = expiresAt { - return dateFormatter.string(from: expiresAt) + if relDateFormatter.string(from: expiresAt) == absDateFormatter.string(from: expiresAt) { + return altRelFormatter.string(from: expiresAt) + } + return relDateAndTimeFormatter.string(from: expiresAt) } else { return "—" } } - var serviceTimeRemainingString: String? { - if let serviceTimeRemaining = pumpManager.podServiceTimeRemaining, let serviceTimeRemainingString = timeRemainingFormatter.string(from: serviceTimeRemaining) { - return serviceTimeRemainingString + var deliveryStopsAtString: String { + if let serviceTimeRemaining = pumpManager.podServiceTimeRemaining { + let deliveryStopsAt = Date().addingTimeInterval(serviceTimeRemaining) + if relDateFormatter.string(from: deliveryStopsAt) == absDateFormatter.string(from: deliveryStopsAt) { + return altRelFormatter.string(from: deliveryStopsAt) + } + return relDateAndTimeFormatter.string(from: deliveryStopsAt) } else { - return nil + return "—" } } + + var serviceTimeRemainingTI: TimeInterval { + if let serviceTimeRemaining = pumpManager.podServiceTimeRemaining { + return serviceTimeRemaining + } + return 0 + } + + var serviceTimeRemainingString: String? { + if let serviceTimeRemaining = pumpManager.podServiceTimeRemaining { + if let serviceTimeRemainingString = timeRemainingFormatter.string(from: serviceTimeRemaining) { + return serviceTimeRemainingString + } + } + return nil + } + + var insulinTimeRemainingString: String? { + if let insulinTimeRemaining = pumpManager.podServiceTimeRemaining { + let insulinTimeRemainingString = insulinTimeRemainingFormatter.string(from: insulinTimeRemaining) + return insulinTimeRemainingString + + } + return nil + } + var insulinTimeExpiredString: String? { + if let insulinTimeExpired = pumpManager.podServiceTimeRemaining { + let insulinTimeExpiredString = insulinTimeExpiredFormatter.localizedString(fromTimeInterval: insulinTimeExpired) + return insulinTimeExpiredString + + } + return nil + } // Expiration reminder date for current pod @Published var expirationReminderDate: Date? @@ -151,7 +194,24 @@ class OmniBLESettingsViewModel: ObservableObject { return nil } } - + var podServiceTimeRemainingString: String { + if let serviceTimeRemainingString = serviceTimeRemainingString { + return serviceTimeRemainingString + } + return "-" + } + var insulinServiceTimeRemainingString: String { + if let insulinTimeRemainingString = insulinTimeRemainingString { + return insulinTimeRemainingString + } + return "-" + } + var insulinServiceTimeExpiredString: String { + if let insulinTimeExpiredString = insulinTimeExpiredString { + return insulinTimeExpiredString + } + return "-" + } var notice: DashSettingsNotice? { if pumpManager.isClockOffset { return DashSettingsNotice( @@ -170,19 +230,52 @@ class OmniBLESettingsViewModel: ObservableObject { return false } } - + let timeFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.timeStyle = .short + dateFormatter.dateStyle = .none + return dateFormatter + }() + let dateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.timeStyle = .short dateFormatter.dateStyle = .medium + dateFormatter.locale = Locale.current dateFormatter.doesRelativeDateFormatting = true return dateFormatter }() - let timeFormatter: DateFormatter = { + let relDateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.timeStyle = .none + dateFormatter.dateStyle = .medium + dateFormatter.locale = Locale.current + dateFormatter.doesRelativeDateFormatting = true + return dateFormatter + }() + let absDateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.timeStyle = .none + dateFormatter.dateStyle = .medium + dateFormatter.locale = Locale.current + dateFormatter.doesRelativeDateFormatting = false + return dateFormatter + }() + + let altRelFormatter: DateFormatter = { + let fullDF = DateFormatter() + fullDF.locale = Locale.current + fullDF.setLocalizedDateFormatFromTemplate("E, hh:mm a") + return fullDF + }() + + let relDateAndTimeFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.timeStyle = .short - dateFormatter.dateStyle = .none + dateFormatter.dateStyle = .medium + dateFormatter.locale = Locale.current + dateFormatter.doesRelativeDateFormatting = true return dateFormatter }() @@ -194,6 +287,23 @@ class OmniBLESettingsViewModel: ObservableObject { return dateComponentsFormatter }() + let insulinTimeRemainingFormatter: DateComponentsFormatter = { + let dateComponentsFormatter = DateComponentsFormatter() + dateComponentsFormatter.allowedUnits = [.day, .hour, .minute, .second] + dateComponentsFormatter.maximumUnitCount = 2 + dateComponentsFormatter.unitsStyle = .short + dateComponentsFormatter.zeroFormattingBehavior = .dropAll + return dateComponentsFormatter + }() + + let insulinTimeExpiredFormatter: RelativeDateTimeFormatter = { + let dateComponentsFormatter = RelativeDateTimeFormatter() + dateComponentsFormatter.locale = Locale.current + dateComponentsFormatter.unitsStyle = .short + dateComponentsFormatter.dateTimeStyle = .numeric + return dateComponentsFormatter + }() + let basalRateFormatter: NumberFormatter = { let numberFormatter = NumberFormatter() numberFormatter.numberStyle = .decimal @@ -566,7 +676,7 @@ extension OmniBLEPumpManager { guard let podTimeRemaining = podTimeRemaining else { return nil; } - return max(0, Pod.serviceDuration - Pod.nominalPodLife + podTimeRemaining); + return Pod.serviceDuration - Pod.nominalPodLife + podTimeRemaining; } private func podDetails(fromPodState podState: PodState, andDeviceName deviceName: String?) -> PodDetails { diff --git a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift index 364a322a..33fd3c52 100644 --- a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift +++ b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift @@ -47,7 +47,7 @@ struct OmniBLESettingsView: View { } private var minutesRemaining: Int? { - if case .timeRemaining(let remaining, _) = viewModel.lifeState, remaining < .hours(2) { + if case .timeRemaining(let remaining, _) = viewModel.lifeState, remaining < .days(1) { return Int(remaining.minutes.truncatingRemainder(dividingBy: 60)) } return nil @@ -355,17 +355,69 @@ struct OmniBLESettingsView: View { .foregroundColor(Color.secondary) } - HStack { - if let expiresAt = viewModel.expiresAt, expiresAt < Date() { + if let expiresAt = viewModel.expiresAt, expiresAt < Date() { + HStack { FrameworkLocalText("Pod Expired", comment: "Label for pod expiration row, past tense") - } else { + Spacer() + Text(self.viewModel.expiresAtString) + .foregroundColor(Color.red) + } + } else { + HStack { FrameworkLocalText("Pod Expires", comment: "Label for pod expiration row") + Spacer() + Text(self.viewModel.expiresAtString) + .foregroundColor(Color.secondary) } - Spacer() - Text(self.viewModel.expiresAtString) - .foregroundColor(Color.secondary) } + /* + if let serviceTimeRemainingTI = Optional(viewModel.serviceTimeRemainingTI), serviceTimeRemainingTI < Pod.serviceDuration - Pod.nominalPodLife, serviceTimeRemainingTI > 0 { + HStack { + FrameworkLocalText("Delivery Stoppage Timer", comment: "Label for insulin delivery stoppage timer row") + Spacer() + Text(self.viewModel.podServiceTimeRemainingString) + .foregroundColor(Color.red) + } + } + */ + + if let serviceTimeRemainingTI = Optional(viewModel.serviceTimeRemainingTI), serviceTimeRemainingTI <= 0 { + HStack (alignment: .firstTextBaseline){ + FrameworkLocalText("Insulin Delivery Stopped", comment: "Label for insulin delivery stop time row, past tense") + Spacer() + VStack(alignment: .trailing) { + Text(self.viewModel.deliveryStopsAtString) + .foregroundColor(Color.red) + .frame(alignment: .trailing) + Text(self.viewModel.insulinServiceTimeExpiredString) + .foregroundColor(Color.red) + .frame(alignment: .trailing) + } + .layoutPriority(1) + } + } else { + HStack(alignment: .firstTextBaseline) { + FrameworkLocalText("Insulin Delivery Stops", comment: "Label for insulin delivery stop time row") + Spacer() + VStack(alignment: .trailing) { + if let serviceTimeRemainingTI = Optional(viewModel.serviceTimeRemainingTI), serviceTimeRemainingTI < Pod.serviceDuration - Pod.nominalPodLife { + Text(self.viewModel.deliveryStopsAtString) + .foregroundColor(Color.red) + .frame(alignment: .trailing) + Text(self.viewModel.insulinServiceTimeRemainingString) + .foregroundColor(Color.red) + .frame(alignment: .trailing) + } else { + Text(self.viewModel.deliveryStopsAtString) + .foregroundColor(Color.secondary) + .frame(alignment: .trailing) + } + } + .layoutPriority(1) + } + } + 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")