diff --git a/packages/espressocash_app/lib/features/payment_request/screens/payment_request_screen.dart b/packages/espressocash_app/lib/features/payment_request/screens/payment_request_screen.dart index d6a5b6a7db..e7592e547a 100644 --- a/packages/espressocash_app/lib/features/payment_request/screens/payment_request_screen.dart +++ b/packages/espressocash_app/lib/features/payment_request/screens/payment_request_screen.dart @@ -1,9 +1,12 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import '../../../di.dart'; import '../../../ui/loader.dart'; import '../data/repository.dart'; import '../models/payment_request.dart'; +import '../services/payment_request_service.dart'; import '../widgets/request_success.dart'; import '../widgets/share_request.dart'; @@ -33,6 +36,21 @@ class _PaymentRequestScreenState extends State { void initState() { super.initState(); _stream = sl().watchById(widget.id); + + _watcher(); + } + + Future _watcher() async { + final request = await _stream.first; + + sl().initWatcher(request); + } + + @override + void dispose() { + sl().disposeWatcher(); + + super.dispose(); } @override diff --git a/packages/espressocash_app/lib/features/payment_request/services/payment_request_service.dart b/packages/espressocash_app/lib/features/payment_request/services/payment_request_service.dart index cd4966563c..0e42073c48 100644 --- a/packages/espressocash_app/lib/features/payment_request/services/payment_request_service.dart +++ b/packages/espressocash_app/lib/features/payment_request/services/payment_request_service.dart @@ -5,6 +5,7 @@ import 'package:dfunc/dfunc.dart'; import 'package:espressocash_api/espressocash_api.dart'; import 'package:flutter/foundation.dart'; +import 'package:get_it/get_it.dart'; import 'package:injectable/injectable.dart'; import 'package:rxdart/rxdart.dart'; import 'package:solana/solana.dart'; @@ -20,7 +21,7 @@ import '../data/repository.dart'; import '../models/payment_request.dart'; @Singleton(scope: authScope) -class PaymentRequestService { +class PaymentRequestService implements Disposable { PaymentRequestService( this._repository, this._solanaClient, @@ -38,6 +39,8 @@ class PaymentRequestService { final Map> _subscriptions = {}; final Map _currentBackoffs = {}; + StreamSubscription? _watcher; + @PostConstruct(preResolve: true) Future init() async { final pendingPayments = await _repository.getAllPending(); @@ -48,14 +51,32 @@ class PaymentRequestService { } void _subscribe(PaymentRequest request) { - _waitForTx(request); + if (!request.state.isInitial) return; + + _subscriptions[request.id]?.cancel(); + _subscriptions[request.id] = _createSubscription(request); } - void _waitForTx(PaymentRequest request) { + void initWatcher(PaymentRequest request) { if (!request.state.isInitial) return; + _watcher?.cancel(); + _watcher = _createSubscription(request, interval: _focusedInterval); + } + + void disposeWatcher() { + _watcher?.cancel(); + } + + StreamSubscription _createSubscription( + PaymentRequest request, { + Duration interval = _backgroundInterval, + }) { final reference = request.payRequest.reference?.firstOrNull; - if (reference == null) return; + + if (reference == null) { + return const Stream.empty().listen(null); + } Stream solanaPayTransaction() => _solanaClient .findSolanaPayTransaction( @@ -65,10 +86,9 @@ class PaymentRequestService { .asStream() .whereType(); - _subscriptions[request.id] = - Stream.periodic(const Duration(seconds: 30)) - .flatMap((a) => solanaPayTransaction()) - .mergeWith([solanaPayTransaction()]).listen( + return Stream.periodic(interval) + .flatMap((a) => solanaPayTransaction()) + .mergeWith([solanaPayTransaction()]).listen( (id) { _verifyTx(id, request); }, @@ -80,8 +100,8 @@ class PaymentRequestService { _currentBackoffs[request.id] = _maxBackoff; } await Future.delayed(_currentBackoffs[request.id]!); - // ignore: avoid-recursive-calls, called in async callback - _waitForTx(request); + + _subscribe(request); }, ); } @@ -113,6 +133,7 @@ class PaymentRequestService { _refreshBalance(); await _subscriptions[request.id]?.cancel(); + await _watcher?.cancel(); } on Exception { _currentBackoffs[request.id] = (_currentBackoffs[request.id] ?? _minBackoff) * _backoffStep; @@ -169,9 +190,21 @@ class PaymentRequestService { return paymentRequest; } + Future cancel(String id) async { + await _repository.delete(id); + + await _subscriptions[id]?.cancel(); + } + Future unshortenLink(String shortLink) => _ecClient .unshortenLink(UnshortenLinkRequestDto(shortLink: shortLink)) .then((e) => Uri.parse(e.fullLink)); + + @override + Future onDispose() async { + await _watcher?.cancel(); + await Future.wait(_subscriptions.values.map((it) => it.cancel())); + } } Future _randomPublicKey([dynamic _]) async { @@ -184,6 +217,9 @@ const _backoffStep = 2; const _minBackoff = Duration(seconds: 2); const _maxBackoff = Duration(minutes: 1); +const _backgroundInterval = Duration(seconds: 30); +const _focusedInterval = Duration(seconds: 1); + extension on PaymentRequestState { bool get isInitial => this == PaymentRequestState.initial; } diff --git a/packages/espressocash_app/lib/features/payment_request/widgets/share_request.dart b/packages/espressocash_app/lib/features/payment_request/widgets/share_request.dart index 22ab4452df..298f64109f 100644 --- a/packages/espressocash_app/lib/features/payment_request/widgets/share_request.dart +++ b/packages/espressocash_app/lib/features/payment_request/widgets/share_request.dart @@ -12,8 +12,8 @@ import '../../../ui/text_button.dart'; import '../../../ui/theme.dart'; import '../../conversion_rates/widgets/extensions.dart'; import '../../tokens/token_list.dart'; -import '../data/repository.dart'; import '../models/payment_request.dart'; +import '../services/payment_request_service.dart'; class ShareRequestPayment extends StatelessWidget { const ShareRequestPayment({ @@ -94,7 +94,7 @@ class ShareRequestPayment extends StatelessWidget { message: context .l10n.paymentRequest_lblCancelConfirmationSubtitle, onConfirm: () { - sl().delete(request.id); + sl().cancel(request.id); Navigator.of(context).pop(); }, ),