|
|
@@ -23,6 +23,11 @@ struct LoopAPNSBolusView: View {
|
|
|
private let otpPeriod: TimeInterval = 30
|
|
|
private var otpTimer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
|
|
|
|
|
+ // Computed property to check if TOTP should be blocked
|
|
|
+ private var isTOTPBlocked: Bool {
|
|
|
+ TOTPService.shared.isTOTPBlocked(qrCodeURL: Storage.shared.loopAPNSQrCodeURL.value)
|
|
|
+ }
|
|
|
+
|
|
|
enum AlertType {
|
|
|
case success
|
|
|
case error
|
|
|
@@ -115,9 +120,29 @@ struct LoopAPNSBolusView: View {
|
|
|
Text("Send Insulin")
|
|
|
}
|
|
|
}
|
|
|
- .disabled(insulinAmount.doubleValue(for: .internationalUnit()) <= 0 || isLoading)
|
|
|
+ .disabled(insulinAmount.doubleValue(for: .internationalUnit()) <= 0 || isLoading || isTOTPBlocked)
|
|
|
.frame(maxWidth: .infinity)
|
|
|
}
|
|
|
+
|
|
|
+ // TOTP Blocking Warning Section
|
|
|
+ if isTOTPBlocked {
|
|
|
+ Section {
|
|
|
+ VStack(alignment: .leading, spacing: 8) {
|
|
|
+ HStack {
|
|
|
+ Image(systemName: "exclamationmark.triangle.fill")
|
|
|
+ .foregroundColor(.orange)
|
|
|
+ Text("TOTP Code Already Used")
|
|
|
+ .font(.headline)
|
|
|
+ .foregroundColor(.orange)
|
|
|
+ }
|
|
|
+ Text("This TOTP code has already been used for a command. Please wait for the next code to be generated before sending another command.")
|
|
|
+ .font(.caption)
|
|
|
+ .foregroundColor(.secondary)
|
|
|
+ .multilineTextAlignment(.leading)
|
|
|
+ }
|
|
|
+ .padding(.vertical, 4)
|
|
|
+ }
|
|
|
+ }
|
|
|
Section(header: Text("Security")) {
|
|
|
VStack(alignment: .leading) {
|
|
|
Text("Current OTP Code")
|
|
|
@@ -158,10 +183,30 @@ struct LoopAPNSBolusView: View {
|
|
|
loadRecommendedBolus()
|
|
|
// Reset timer state so it shows '-' until first tick
|
|
|
otpTimeRemaining = nil
|
|
|
+ // Don't reset TOTP usage flag here - let the timer handle it
|
|
|
+
|
|
|
+ // Validate TOTP state when view appears
|
|
|
+ _ = isTOTPBlocked
|
|
|
}
|
|
|
.onReceive(otpTimer) { _ in
|
|
|
let now = Date().timeIntervalSince1970
|
|
|
- otpTimeRemaining = Int(otpPeriod - (now.truncatingRemainder(dividingBy: otpPeriod)))
|
|
|
+ let newOtpTimeRemaining = Int(otpPeriod - (now.truncatingRemainder(dividingBy: otpPeriod)))
|
|
|
+
|
|
|
+ // Check if we've moved to a new TOTP period (when time remaining increases)
|
|
|
+ if let currentOtpTimeRemaining = otpTimeRemaining,
|
|
|
+ newOtpTimeRemaining > currentOtpTimeRemaining
|
|
|
+ {
|
|
|
+ // New TOTP code generated, reset the usage flag
|
|
|
+ TOTPService.shared.resetTOTPUsage()
|
|
|
+ }
|
|
|
+
|
|
|
+ // Also check if we're at the very beginning of a new period (when time remaining is close to 30)
|
|
|
+ if newOtpTimeRemaining >= 29 {
|
|
|
+ // We're at the start of a new TOTP period, reset the usage flag
|
|
|
+ TOTPService.shared.resetTOTPUsage()
|
|
|
+ }
|
|
|
+
|
|
|
+ otpTimeRemaining = newOtpTimeRemaining
|
|
|
|
|
|
// Check if recommended bolus calculation is older than 5 minutes (but less than 12 minutes)
|
|
|
if let lastLoopTime = lastLoopTime {
|
|
|
@@ -332,6 +377,8 @@ struct LoopAPNSBolusView: View {
|
|
|
DispatchQueue.main.async {
|
|
|
isLoading = false
|
|
|
if success {
|
|
|
+ // Mark TOTP code as used
|
|
|
+ TOTPService.shared.markTOTPAsUsed(qrCodeURL: Storage.shared.loopAPNSQrCodeURL.value)
|
|
|
alertMessage = "Insulin sent successfully!"
|
|
|
alertType = .success
|
|
|
LogManager.shared.log(
|