BolusView.swift 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. //
  2. // BolusView.swift
  3. // LoopFollow
  4. //
  5. // Created by Jonas Björkert on 2024-08-25.
  6. // Copyright © 2024 Jon Fawcett. All rights reserved.
  7. //
  8. import SwiftUI
  9. import HealthKit
  10. import LocalAuthentication
  11. struct BolusView: View {
  12. @State private var bolusAmount = HKQuantity(unit: .internationalUnit(), doubleValue: 0.0)
  13. private let pushNotificationManager = PushNotificationManager()
  14. @ObservedObject private var maxBolus = Storage.shared.maxBolus
  15. @FocusState private var bolusFieldIsFocused: Bool
  16. @State private var showAlert = false
  17. @State private var alertType: AlertType? = nil
  18. @State private var isLoading = false
  19. @State private var statusMessage: String? = nil
  20. enum AlertType {
  21. case confirmBolus
  22. case status
  23. }
  24. var body: some View {
  25. NavigationView {
  26. VStack {
  27. Form {
  28. Section {
  29. HKQuantityInputView(
  30. label: "Bolus Amount",
  31. quantity: $bolusAmount,
  32. unit: .internationalUnit(),
  33. maxLength: 4,
  34. minValue: HKQuantity(unit: .internationalUnit(), doubleValue: 0.05),
  35. maxValue: maxBolus.value,
  36. isFocused: $bolusFieldIsFocused
  37. )
  38. }
  39. LoadingButtonView(
  40. buttonText: "Send Bolus",
  41. progressText: "Sending Bolus...",
  42. isLoading: isLoading,
  43. action: {
  44. bolusFieldIsFocused = false
  45. if bolusAmount.doubleValue(for: HKUnit.internationalUnit()) != 0.0 {
  46. alertType = .confirmBolus
  47. showAlert = true
  48. }
  49. },
  50. isDisabled: isLoading
  51. )
  52. }
  53. .navigationTitle("Bolus")
  54. .navigationBarTitleDisplayMode(.inline)
  55. }
  56. .alert(isPresented: $showAlert) {
  57. switch alertType {
  58. case .confirmBolus:
  59. return Alert(
  60. title: Text("Confirm Bolus"),
  61. message: Text("Are you sure you want to send \(bolusAmount.doubleValue(for: HKUnit.internationalUnit()), specifier: "%.2f") U?"),
  62. primaryButton: .default(Text("Confirm"), action: {
  63. authenticateUser { success in
  64. if success {
  65. sendBolus()
  66. } else {
  67. statusMessage = "Authentication failed. Please try again."
  68. alertType = .status
  69. showAlert = true
  70. }
  71. }
  72. }),
  73. secondaryButton: .cancel()
  74. )
  75. case .status:
  76. return Alert(
  77. title: Text("Status"),
  78. message: Text(statusMessage ?? ""),
  79. dismissButton: .default(Text("OK"))
  80. )
  81. case .none:
  82. return Alert(title: Text("Unknown Alert"))
  83. }
  84. }
  85. }
  86. }
  87. private func sendBolus() {
  88. isLoading = true
  89. bolusFieldIsFocused = false
  90. pushNotificationManager.sendBolusPushNotification(commandType: "bolus", bolusAmount: bolusAmount) { success, errorMessage in
  91. DispatchQueue.main.async {
  92. isLoading = false
  93. if success {
  94. statusMessage = "Bolus command sent successfully."
  95. } else {
  96. statusMessage = errorMessage ?? "Failed to send bolus command."
  97. }
  98. alertType = .status
  99. showAlert = true
  100. }
  101. }
  102. }
  103. private func authenticateUser(completion: @escaping (Bool) -> Void) {
  104. let context = LAContext()
  105. var error: NSError?
  106. let reason = "Confirm your identity to send bolus."
  107. if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
  108. context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, _ in
  109. DispatchQueue.main.async {
  110. completion(success)
  111. }
  112. }
  113. } else if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
  114. context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, _ in
  115. DispatchQueue.main.async {
  116. completion(success)
  117. }
  118. }
  119. } else {
  120. DispatchQueue.main.async {
  121. completion(false)
  122. }
  123. }
  124. }
  125. }