BolusView.swift 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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 alertMessage: String? = nil
  19. @State private var isLoading = false
  20. @State private var statusMessage: String? = nil
  21. enum AlertType {
  22. case confirmBolus
  23. case status
  24. case validation
  25. }
  26. var body: some View {
  27. NavigationView {
  28. VStack {
  29. Form {
  30. Section {
  31. HKQuantityInputView(
  32. label: "Bolus Amount",
  33. quantity: $bolusAmount,
  34. unit: .internationalUnit(),
  35. maxLength: 4,
  36. minValue: HKQuantity(unit: .internationalUnit(), doubleValue: 0.05),
  37. maxValue: maxBolus.value,
  38. isFocused: $bolusFieldIsFocused,
  39. onValidationError: { message in
  40. handleValidationError(message)
  41. }
  42. )
  43. }
  44. LoadingButtonView(
  45. buttonText: "Send Bolus",
  46. progressText: "Sending Bolus...",
  47. isLoading: isLoading,
  48. action: {
  49. bolusFieldIsFocused = false
  50. DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
  51. if bolusAmount.doubleValue(for: HKUnit.internationalUnit()) > 0.0 {
  52. alertType = .confirmBolus
  53. showAlert = true
  54. }
  55. }
  56. },
  57. isDisabled: isLoading
  58. )
  59. }
  60. .navigationTitle("Bolus")
  61. .navigationBarTitleDisplayMode(.inline)
  62. }
  63. .alert(isPresented: $showAlert) {
  64. switch alertType {
  65. case .confirmBolus:
  66. return Alert(
  67. title: Text("Confirm Bolus"),
  68. message: Text("Are you sure you want to send \(bolusAmount.doubleValue(for: HKUnit.internationalUnit()), specifier: "%.2f") U?"),
  69. primaryButton: .default(Text("Confirm"), action: {
  70. authenticateUser { success in
  71. if success {
  72. sendBolus()
  73. }
  74. }
  75. }),
  76. secondaryButton: .cancel()
  77. )
  78. case .status:
  79. return Alert(
  80. title: Text("Status"),
  81. message: Text(statusMessage ?? ""),
  82. dismissButton: .default(Text("OK"))
  83. )
  84. case .validation:
  85. return Alert(
  86. title: Text("Validation Error"),
  87. message: Text(alertMessage ?? "Invalid input."),
  88. dismissButton: .default(Text("OK"))
  89. )
  90. case .none:
  91. return Alert(title: Text("Unknown Alert"))
  92. }
  93. }
  94. }
  95. }
  96. private func sendBolus() {
  97. isLoading = true
  98. pushNotificationManager.sendBolusPushNotification(commandType: "bolus", bolusAmount: bolusAmount) { success, errorMessage in
  99. DispatchQueue.main.async {
  100. isLoading = false
  101. if success {
  102. statusMessage = "Bolus command sent successfully."
  103. bolusAmount = HKQuantity(unit: .internationalUnit(), doubleValue: 0.0)
  104. } else {
  105. statusMessage = errorMessage ?? "Failed to send bolus command."
  106. }
  107. alertType = .status
  108. showAlert = true
  109. }
  110. }
  111. }
  112. private func authenticateUser(completion: @escaping (Bool) -> Void) {
  113. let context = LAContext()
  114. var error: NSError?
  115. let reason = "Confirm your identity to send bolus."
  116. if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
  117. context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, _ in
  118. DispatchQueue.main.async {
  119. completion(success)
  120. }
  121. }
  122. } else if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
  123. context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, _ in
  124. DispatchQueue.main.async {
  125. completion(success)
  126. }
  127. }
  128. } else {
  129. DispatchQueue.main.async {
  130. completion(false)
  131. }
  132. }
  133. }
  134. private func handleValidationError(_ message: String) {
  135. alertMessage = message
  136. alertType = .validation
  137. showAlert = true
  138. }
  139. }