| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- // LoopFollow
- // AlarmSelectionView.swift
- import SwiftUI
- struct AlarmSelectionView: View {
- @ObservedObject private var allAlarms = Storage.shared.alarms
- @State private var selectedAlarmIds: Set<UUID> = []
- @State private var showingCharacterLimitAlert = false
- let exportedAlarmIds: Set<UUID>
- let onConfirm: ([Alarm]) -> Void
- let onCancel: () -> Void
- // QR Code character limits (calibrated for alarm exports)
- private let maxQRCharacters = 2000
- private let maxRecommendedAlarms = 5
- // Computed property for actual character count (used internally)
- private var actualCharacterCount: Int {
- let selectedAlarms = allAlarms.value.filter { selectedAlarmIds.contains($0.id) }
- let testExport = AlarmSettingsExport(
- version: AppVersionManager().version(),
- alarms: selectedAlarms,
- alarmConfiguration: Storage.shared.alarmConfiguration.value
- )
- if let jsonString = testExport.encodeToJSON() {
- return jsonString.count
- }
- return 0
- }
- private var exceedsCharacterLimit: Bool {
- return actualCharacterCount > maxQRCharacters
- }
- var body: some View {
- NavigationView {
- VStack {
- // Character count indicator
- characterCountView
- List {
- ForEach(allAlarms.value) { alarm in
- AlarmSelectionRow(
- alarm: alarm,
- isSelected: selectedAlarmIds.contains(alarm.id),
- isExported: exportedAlarmIds.contains(alarm.id),
- isDisabled: !canSelectAlarm(alarm),
- onToggle: { toggleAlarm(alarm) }
- )
- }
- }
- }
- .navigationTitle("Select Alarms to Export")
- .navigationBarTitleDisplayMode(.inline)
- .navigationBarItems(
- leading: Button("Cancel") {
- onCancel()
- },
- trailing: Button("Export") {
- let selectedAlarms = allAlarms.value.filter { selectedAlarmIds.contains($0.id) }
- onConfirm(selectedAlarms)
- }
- .disabled(selectedAlarmIds.isEmpty)
- )
- }
- .alert("Character Limit Reached", isPresented: $showingCharacterLimitAlert) {
- Button("OK") {}
- } message: {
- Text("Adding this alarm would exceed the QR code character limit. Please remove some alarms first.")
- }
- }
- private var characterCountView: some View {
- VStack(spacing: 8) {
- HStack {
- Text("Selected Alarms: \(selectedAlarmIds.count)")
- .font(.headline)
- Spacer()
- Text("Max \(maxRecommendedAlarms) alarms at a time")
- .font(.caption)
- .foregroundColor(.secondary)
- }
- if selectedAlarmIds.count > 0 {
- ProgressView(value: Double(selectedAlarmIds.count), total: Double(maxRecommendedAlarms))
- .progressViewStyle(LinearProgressViewStyle(tint: progressBarColor))
- }
- }
- .padding()
- .background(Color(.systemGray6))
- }
- private var progressBarColor: Color {
- if exceedsCharacterLimit {
- return .red
- } else if selectedAlarmIds.count >= maxRecommendedAlarms {
- return .blue
- } else {
- return .green
- }
- }
- private func canSelectAlarm(_ alarm: Alarm) -> Bool {
- let testSelection = selectedAlarmIds.union([alarm.id])
- let testAlarms = allAlarms.value.filter { testSelection.contains($0.id) }
- // Block if we're at or over the recommended alarm limit
- if testAlarms.count > maxRecommendedAlarms {
- return false
- }
- // Test actual character count to prevent exceeding QR code limit
- let testExport = AlarmSettingsExport(
- version: AppVersionManager().version(),
- alarms: testAlarms,
- alarmConfiguration: Storage.shared.alarmConfiguration.value
- )
- if let jsonString = testExport.encodeToJSON() {
- return jsonString.count <= maxQRCharacters
- }
- return false
- }
- private func toggleAlarm(_ alarm: Alarm) {
- if selectedAlarmIds.contains(alarm.id) {
- selectedAlarmIds.remove(alarm.id)
- } else {
- if canSelectAlarm(alarm) {
- selectedAlarmIds.insert(alarm.id)
- } else {
- showingCharacterLimitAlert = true
- }
- }
- }
- }
- struct AlarmSelectionRow: View {
- let alarm: Alarm
- let isSelected: Bool
- let isExported: Bool
- let isDisabled: Bool
- let onToggle: () -> Void
- var body: some View {
- Button(action: onToggle) {
- HStack {
- VStack(alignment: .leading, spacing: 4) {
- HStack {
- Text(alarm.name)
- .font(.headline)
- .foregroundColor(isDisabled ? .secondary : .primary)
- if isExported {
- Image(systemName: "checkmark.circle.fill")
- .foregroundColor(.green)
- .font(.caption)
- }
- }
- Text(alarmTypeDescription)
- .font(.caption)
- .foregroundColor(.secondary)
- if let aboveBG = alarm.aboveBG {
- Text("Above: \(String(format: "%.0f", aboveBG))")
- .font(.caption2)
- .foregroundColor(.secondary)
- }
- if let belowBG = alarm.belowBG {
- Text("Below: \(String(format: "%.0f", belowBG))")
- .font(.caption2)
- .foregroundColor(.secondary)
- }
- }
- Spacer()
- Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
- .foregroundColor(isSelected ? .blue : (isDisabled ? .secondary : .primary))
- .font(.title2)
- }
- .padding(.vertical, 4)
- }
- .buttonStyle(.plain)
- .disabled(isDisabled && !isSelected)
- }
- private var alarmTypeDescription: String {
- switch alarm.type {
- case .temporary:
- return "Temporary Alert"
- case .iob:
- return "IOB Alert"
- case .cob:
- return "COB Alert"
- case .low:
- return "Low BG Alert"
- case .high:
- return "High BG Alert"
- case .fastDrop:
- return "Fast Drop Alert"
- case .fastRise:
- return "Fast Rise Alert"
- case .missedReading:
- return "Missed Reading Alert"
- case .notLooping:
- return "Not Looping Alert"
- case .missedBolus:
- return "Missed Bolus Alert"
- case .futureCarbs:
- return "Future Carbs Alert"
- case .sensorChange:
- return "Sensor Change Alert"
- case .pumpChange:
- return "Pump Change Alert"
- case .pump:
- return "Pump Insulin Alert"
- case .pumpBattery:
- return "Pump Battery Alert"
- case .battery:
- return "Low Battery"
- case .batteryDrop:
- return "Battery Drop"
- case .recBolus:
- return "Rec. Bolus"
- case .overrideStart:
- return "Override Started"
- case .overrideEnd:
- return "Override Ended"
- case .tempTargetStart:
- return "Temp Target Started"
- case .tempTargetEnd:
- return "Temp Target Ended"
- case .buildExpire:
- return "Looping app expiration"
- case .dbSize:
- return "Nightscout Database Size"
- }
- }
- }
- #Preview {
- AlarmSelectionView(
- exportedAlarmIds: [],
- onConfirm: { _ in },
- onCancel: {}
- )
- }
|