import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:sxchnge_analytics/sxchnge_analytics.dart'; import '../models/wallet.dart'; import '../services/contracts.dart'; class WalletProvider extends ChangeNotifier { WalletProvider(this.service, this.analytics); final WalletService service; final AnalyticsService analytics; WalletSnapshot? data; bool loading = false, failed = false, profileLoading = false, profileFailed = false, saving = false, saveFailed = false; String? preferredLocale; bool _disposed = false; int _revision = 0; Timer? _poll; final _settlements = {}; CurrencyAccount? account(String? id) { for (final a in data?.accounts ?? []) { if (a.id == id) return a; } return null; } WalletOperation? operation(String id) { for (final o in data?.operations ?? []) { if (o.id == id) return o; } return null; } bool get hasPending => data?.operations.any((o) => o.status == OperationStatus.pending) ?? false; Future refresh() async { final rev = ++_revision; loading = true; failed = false; notifyListeners(); try { final result = await service.fetch(); if (_disposed || rev != _revision) return; data = result; for (final o in result.operations) { if (o.status == OperationStatus.pending) continue; final pair = _settlements.remove(o.id); if (pair != null) { pair.$1.finish( pair.$2, simulated: service.isSimulated, failure: o.status == OperationStatus.failed ? JourneyFailure.declined : null, terminal: true, ); pair.$1.dispose(); } } } catch (_) { if (_disposed || rev != _revision) return; failed = true; } if (_disposed || rev != _revision) return; loading = false; _poll?.cancel(); if (hasPending) { _poll = Timer(const Duration(seconds: 3), () { if (!_disposed) refresh(); }); } notifyListeners(); } void track( WalletOperation operation, JourneyRecorder recorder, JourneyKind kind, ) { final step = recorder.begin( kind, JourneyStep.settlement, simulated: service.isSimulated, ); _settlements[operation.id] = (recorder, step); final snapshot = data; if (snapshot != null) { data = WalletSnapshot( accounts: snapshot.accounts, operations: [ operation, ...snapshot.operations.where((o) => o.id != operation.id), ], ); } unawaited(refresh()); } Future loadProfile() async { if (profileLoading) return; profileLoading = true; profileFailed = false; notifyListeners(); try { final result = await service.profile(); if (_disposed) return; preferredLocale = result.locale; } catch (_) { if (_disposed) return; profileFailed = true; } profileLoading = false; notifyListeners(); } Future saveLocale(String locale) async { if (saving) return false; saving = true; saveFailed = false; notifyListeners(); try { final result = await service.saveLocale(locale); if (_disposed) return false; preferredLocale = result.locale; } catch (_) { if (_disposed) return false; saveFailed = true; } saving = false; notifyListeners(); return !saveFailed; } @override void dispose() { _disposed = true; _poll?.cancel(); for (final p in _settlements.values) { p.$1.dispose(abandonActive: false); } _settlements.clear(); super.dispose(); } }