BolusView.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. // LoopFollow
  2. // BolusView.swift
  3. // Created by Jonas Björkert on 2024-09-17.
  4. import HealthKit
  5. import LocalAuthentication
  6. import SwiftUI
  7. struct BolusView: View {
  8. @Environment(\.presentationMode) private var presentationMode
  9. @State private var bolusAmount = HKQuantity(unit: .internationalUnit(), doubleValue: 0.0)
  10. private let pushNotificationManager = PushNotificationManager()
  11. @ObservedObject private var maxBolus = Storage.shared.maxBolus
  12. @FocusState private var bolusFieldIsFocused: Bool
  13. @State private var showAlert = false
  14. @State private var alertType: AlertType? = nil
  15. @State private var alertMessage: String? = nil
  16. @State private var isLoading = false
  17. @State private var statusMessage: String? = nil
  18. enum AlertType {
  19. case confirmBolus
  20. case statusSuccess
  21. case statusFailure
  22. case validation
  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. onValidationError: { message in
  38. handleValidationError(message)
  39. }
  40. )
  41. }
  42. LoadingButtonView(
  43. buttonText: "Send Bolus",
  44. progressText: "Sending Bolus...",
  45. isLoading: isLoading,
  46. action: {
  47. bolusFieldIsFocused = false
  48. DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
  49. if bolusAmount.doubleValue(for: HKUnit.internationalUnit()) > 0.0 {
  50. alertType = .confirmBolus
  51. showAlert = true
  52. }
  53. }
  54. },
  55. isDisabled: isLoading
  56. )
  57. }
  58. .navigationTitle("Bolus")
  59. .navigationBarTitleDisplayMode(.inline)
  60. }
  61. .alert(isPresented: $showAlert) {
  62. switch alertType {
  63. case .confirmBolus:
  64. return Alert(
  65. title: Text("Confirm Bolus"),
  66. message: Text("Are you sure you want to send \(bolusAmount.doubleValue(for: HKUnit.internationalUnit()), specifier: "%.2f") U?"),
  67. primaryButton: .default(Text("Confirm"), action: {
  68. authenticateUser { success in
  69. if success {
  70. sendBolus()
  71. }
  72. }
  73. }),
  74. secondaryButton: .cancel()
  75. )
  76. case .statusSuccess:
  77. return Alert(
  78. title: Text("Status"),
  79. message: Text(statusMessage ?? ""),
  80. dismissButton: .default(Text("OK"), action: {
  81. presentationMode.wrappedValue.dismiss()
  82. })
  83. )
  84. case .statusFailure:
  85. return Alert(
  86. title: Text("Status"),
  87. message: Text(statusMessage ?? ""),
  88. dismissButton: .default(Text("OK"))
  89. )
  90. case .validation:
  91. return Alert(
  92. title: Text("Validation Error"),
  93. message: Text(alertMessage ?? "Invalid input."),
  94. dismissButton: .default(Text("OK"))
  95. )
  96. case .none:
  97. return Alert(title: Text("Unknown Alert"))
  98. }
  99. }
  100. }
  101. }
  102. private func sendBolus() {
  103. isLoading = true
  104. pushNotificationManager.sendBolusPushNotification(bolusAmount: bolusAmount) { success, errorMessage in
  105. DispatchQueue.main.async {
  106. isLoading = false
  107. if success {
  108. statusMessage = "Bolus command sent successfully."
  109. LogManager.shared.log(category: .apns, message: "sendBolusPushNotification succeeded - Bolus: \(bolusAmount.doubleValue(for: .internationalUnit())) U")
  110. bolusAmount = HKQuantity(unit: .internationalUnit(), doubleValue: 0.0)
  111. alertType = .statusSuccess
  112. } else {
  113. statusMessage = errorMessage ?? "Failed to send bolus command."
  114. LogManager.shared.log(category: .apns, message: "sendBolusPushNotification failed with error: \(errorMessage ?? "unknown error")")
  115. alertType = .statusFailure
  116. }
  117. showAlert = true
  118. }
  119. }
  120. }
  121. private func authenticateUser(completion: @escaping (Bool) -> Void) {
  122. let context = LAContext()
  123. var error: NSError?
  124. let reason = "Confirm your identity to send bolus."
  125. if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
  126. context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, _ in
  127. DispatchQueue.main.async {
  128. completion(success)
  129. }
  130. }
  131. } else if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
  132. context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, _ in
  133. DispatchQueue.main.async {
  134. completion(success)
  135. }
  136. }
  137. } else {
  138. DispatchQueue.main.async {
  139. completion(false)
  140. }
  141. }
  142. }
  143. private func handleValidationError(_ message: String) {
  144. alertMessage = message
  145. alertType = .validation
  146. showAlert = true
  147. }
  148. }