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'; import 'package:pweb/models/flow_status.dart'; class TwoFactorProvider extends ChangeNotifier { static final _logger = Logger('provider.two_factor'); late AccountProvider _accountProvider; TwoFactorProvider(); FlowStatus _status = FlowStatus.idle; String? _errorMessage; String? _currentPendingToken; Timer? _cooldownTimer; int _cooldownRemainingSeconds = 0; FlowStatus get status => _status; bool get isSubmitting => _status == FlowStatus.submitting; bool get isResending => _status == FlowStatus.resending; bool get hasError => _status == FlowStatus.error; bool get verificationSuccess => _status == FlowStatus.success; 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 { _errorMessage = null; _setStatus(FlowStatus.submitting); 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); _currentPendingToken = null; _setStatus(FlowStatus.success); } catch (e) { _errorMessage = e.toString(); _logger.warning('Failed to verify code: ${e.toString()}', e); _setStatus(FlowStatus.error); } } Future resendCode() async { final pending = _accountProvider.pendingLogin; if (pending == null) { _logger.warning('No pending login to resend code for'); return; } if (isResending || isCooldownActive) return; _errorMessage = null; _setStatus(FlowStatus.resending); try { final confirmation = await VerificationService.resendLoginCode(pending); _accountProvider.updatePendingLogin(confirmation); _startCooldown(confirmation.cooldownSeconds); _setStatus(FlowStatus.idle); } catch (e) { _logger.warning('Failed to resend login code', e); _errorMessage = e.toString(); _setStatus(FlowStatus.error); } } void reset() { _resetState(); _currentPendingToken = null; } void _resetState() { _status = FlowStatus.idle; _errorMessage = null; _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(); } } void _setStatus(FlowStatus status) { _status = status; notifyListeners(); } @override void dispose() { _stopCooldown(); super.dispose(); } }