import 'dart:async'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:pshared/models/auth/pending_login.dart'; import 'package:pshared/provider/account.dart'; import 'package:pshared/service/verification.dart'; class TwoFactorProvider extends ChangeNotifier { static final _logger = Logger('provider.two_factor'); late AccountProvider _accountProvider; TwoFactorProvider(); bool _isSubmitting = false; bool _isResending = false; bool _hasError = false; bool _verificationSuccess = false; String? _errorMessage; String? _currentPendingToken; Timer? _cooldownTimer; int _cooldownRemainingSeconds = 0; bool get isSubmitting => _isSubmitting; bool get isResending => _isResending; bool get hasError => _hasError; bool get verificationSuccess => _verificationSuccess; String? get errorMessage => _errorMessage; int get cooldownRemainingSeconds => _cooldownRemainingSeconds; bool get isCooldownActive => _cooldownRemainingSeconds > 0; PendingLogin? get pendingLogin => _accountProvider.pendingLogin; void update(AccountProvider accountProvider) { _accountProvider = accountProvider; final pending = accountProvider.pendingLogin; final token = pending?.pendingToken.token; if (token != _currentPendingToken || accountProvider.account == null) { _resetState(); _currentPendingToken = token; } _syncCooldown(pending); } Future submitCode(String code) async { _isSubmitting = true; _hasError = false; _errorMessage = null; _verificationSuccess = false; notifyListeners(); try { final pending = _accountProvider.pendingLogin; if (pending == null) { throw Exception('No pending login available'); } final account = await VerificationService.confirmLoginCode( pending: pending, code: code, ); _accountProvider.completePendingLogin(account); _verificationSuccess = true; _currentPendingToken = null; } catch (e) { _hasError = true; _errorMessage = e.toString(); _logger.warning('Failed to verify code: ${e.toString()}', e); } finally { _isSubmitting = false; notifyListeners(); } } Future resendCode() async { final pending = _accountProvider.pendingLogin; if (pending == null) { _logger.warning('No pending login to resend code for'); return; } if (_isResending || isCooldownActive) return; _isResending = true; _hasError = false; _errorMessage = null; notifyListeners(); try { final confirmation = await VerificationService.resendLoginCode(pending); _accountProvider.updatePendingLogin(confirmation); _startCooldown(confirmation.cooldownSeconds); } catch (e) { _logger.warning('Failed to resend login code', e); _hasError = true; _errorMessage = e.toString(); } finally { _isResending = false; notifyListeners(); } } void reset() { _resetState(); _currentPendingToken = null; } void _resetState() { _isSubmitting = false; _isResending = false; _hasError = false; _errorMessage = null; _verificationSuccess = false; _stopCooldown(); notifyListeners(); } void _syncCooldown(PendingLogin? pending) { if (pending == null) { _stopCooldown(notify: _cooldownRemainingSeconds != 0); return; } final remaining = pending.cooldownRemainingSeconds; if (remaining <= 0) { _stopCooldown(notify: _cooldownRemainingSeconds != 0); return; } if (_cooldownRemainingSeconds != remaining) { _startCooldown(remaining); } } void _startCooldown(int seconds) { _cooldownTimer?.cancel(); _cooldownRemainingSeconds = seconds; if (_cooldownRemainingSeconds <= 0) { _cooldownTimer = null; notifyListeners(); return; } _cooldownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_cooldownRemainingSeconds <= 1) { _cooldownRemainingSeconds = 0; _cooldownTimer?.cancel(); _cooldownTimer = null; notifyListeners(); return; } _cooldownRemainingSeconds -= 1; notifyListeners(); }); notifyListeners(); } void _stopCooldown({bool notify = false}) { _cooldownTimer?.cancel(); _cooldownTimer = null; final hadCooldown = _cooldownRemainingSeconds != 0; _cooldownRemainingSeconds = 0; if (notify && hadCooldown) { notifyListeners(); } } @override void dispose() { _stopCooldown(); super.dispose(); } }