import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:pshared/models/auto_refresh_mode.dart'; import 'package:pshared/models/payment/quote/status_type.dart'; import 'package:pshared/provider/payment/quotation/quotation.dart'; import 'package:pweb/providers/quotation/auto_refresh.dart'; class QuotationController extends ChangeNotifier { QuotationProvider? _quotation; AutoRefreshMode _autoRefreshMode = AutoRefreshMode.on; final QuotationAutoRefreshController _autoRefreshController = QuotationAutoRefreshController(); Timer? _ticker; void update(QuotationProvider quotation) { if (identical(_quotation, quotation)) return; _quotation?.removeListener(_handleQuotationChanged); _quotation = quotation; _quotation?.addListener(_handleQuotationChanged); _handleQuotationChanged(); } bool get isLoading => _quotation?.isLoading ?? false; Exception? get error => _quotation?.error; bool get canRefresh => _quotation?.canRefresh ?? false; bool get isReady => _quotation?.isReady ?? false; AutoRefreshMode get autoRefreshMode => _autoRefreshMode; DateTime? get quoteExpiresAt => _quotation?.quoteExpiresAt; Duration? get timeLeft { final expiresAt = quoteExpiresAt; if (expiresAt == null) return null; return expiresAt.difference(DateTime.now().toUtc()); } bool get isExpired { final remaining = timeLeft; if (remaining == null) return false; return remaining <= Duration.zero; } QuoteStatusType get quoteStatus { if (isLoading) return QuoteStatusType.loading; if (error != null) return QuoteStatusType.error; if (_quotation?.quotation == null) return QuoteStatusType.missing; if (isExpired) return QuoteStatusType.expired; return QuoteStatusType.active; } bool get hasLiveQuote => isReady && _quotation?.quotation != null && !isExpired; void setAutoRefreshMode(AutoRefreshMode mode) { if (_autoRefreshMode == mode) return; _autoRefreshMode = mode; _syncAutoRefresh(); notifyListeners(); } void refreshQuotation() { _quotation?.refreshQuotation(); } void _handleQuotationChanged() { _syncAutoRefresh(); _syncTicker(); notifyListeners(); } void _syncTicker() { final expiresAt = quoteExpiresAt; if (expiresAt == null) { _stopTicker(); return; } final remaining = expiresAt.difference(DateTime.now().toUtc()); if (remaining <= Duration.zero) { _stopTicker(); return; } _ticker ??= Timer.periodic(const Duration(seconds: 1), (_) { final expiresAt = quoteExpiresAt; if (expiresAt == null) { _stopTicker(); return; } final remaining = expiresAt.difference(DateTime.now().toUtc()); if (remaining <= Duration.zero) { _stopTicker(); } notifyListeners(); }); } void _stopTicker() { _ticker?.cancel(); _ticker = null; } void _syncAutoRefresh() { final quotation = _quotation; if (quotation == null) { _autoRefreshController.reset(); return; } final isAutoRefreshEnabled = _autoRefreshMode == AutoRefreshMode.on; _autoRefreshController.setEnabled(isAutoRefreshEnabled); final canAutoRefresh = isAutoRefreshEnabled && quotation.canRefresh; _autoRefreshController.sync( isLoading: quotation.isLoading, canRefresh: canAutoRefresh, expiresAt: quoteExpiresAt, onRefresh: _refreshQuotation, ); } Future _refreshQuotation() async { await _quotation?.refreshQuotation(); } @override void dispose() { _quotation?.removeListener(_handleQuotationChanged); _autoRefreshController.dispose(); _stopTicker(); super.dispose(); } }