Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cancel flow for fetching quotes #208

Merged
merged 24 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/features/account/account_balance_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ class AccountBalanceCard extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final pfis = ref.watch(pfisProvider);
final accountBalance = ref.watch(accountBalanceProvider(pfis));
final accountBalance = ref.watch(accountBalanceProvider);

final accountTotal = accountBalance.asData?.value?.total ?? '';
final accountCurrency = accountBalance.asData?.value?.currencyCode ?? '';

AccountBalanceNotifier getAccountBalanceNotifier() =>
ref.read(accountBalanceProvider(pfis).notifier);
ref.read(accountBalanceProvider.notifier);

useEffect(
() {
Future.microtask(
() async => getAccountBalanceNotifier().startPolling(),
() async => getAccountBalanceNotifier().startPolling(pfis),
);
return getAccountBalanceNotifier().stopPolling;
},
Expand Down
27 changes: 12 additions & 15 deletions lib/features/account/account_balance_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,23 @@ import 'package:didpay/features/pfis/pfi.dart';
import 'package:didpay/features/tbdex/tbdex_service.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

final accountBalanceProvider = AsyncNotifierProvider.family
.autoDispose<AccountBalanceNotifier, AccountBalance?, List<Pfi>>(
final accountBalanceProvider =
AsyncNotifierProvider.autoDispose<AccountBalanceNotifier, AccountBalance?>(
AccountBalanceNotifier.new,
);

class AccountBalanceNotifier
extends AutoDisposeFamilyAsyncNotifier<AccountBalance?, List<Pfi>> {
late List<Pfi>? _pfis;
class AccountBalanceNotifier extends AutoDisposeAsyncNotifier<AccountBalance?> {
Timer? _timer;

@override
FutureOr<AccountBalance?> build(List<Pfi> arg) async {
_pfis = arg;
return fetchAccountBalance();
}
FutureOr<AccountBalance?> build() async => null;

Future<AccountBalance?> fetchAccountBalance() async {
if (_pfis == null || _pfis!.isEmpty) return null;
Future<AccountBalance?> fetchAccountBalance(List<Pfi>? pfis) async {
if (pfis == null || pfis.isEmpty) return null;

try {
final tbdexService = ref.read(tbdexServiceProvider);
final accountBalance = await tbdexService.getAccountBalance(_pfis!);
final accountBalance = await tbdexService.getAccountBalance(pfis);

state = AsyncValue.data(accountBalance);
return accountBalance;
Expand All @@ -36,11 +31,13 @@ class AccountBalanceNotifier
return null;
}

void startPolling() {
void startPolling(List<Pfi> pfis) {
_timer?.cancel();
fetchAccountBalance(pfis);

_timer = Timer.periodic(
const Duration(seconds: 30),
(timer) => fetchAccountBalance(),
const Duration(minutes: 3),
(timer) => fetchAccountBalance(pfis),
);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/features/feature_flags/lucid/lucid_offerings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import 'package:didpay/features/tbdex/tbdex_service.dart';
import 'package:didpay/features/transaction/transaction.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/error_message.dart';
import 'package:didpay/shared/loading_message.dart';
import 'package:didpay/shared/header.dart';
import 'package:didpay/shared/loading_message.dart';
import 'package:didpay/shared/next_button.dart';
import 'package:didpay/shared/theme/grid.dart';
import 'package:flutter/material.dart';
Expand Down
24 changes: 13 additions & 11 deletions lib/features/payment/payment_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import 'package:didpay/features/transaction/transaction.dart';
import 'package:didpay/features/vcs/vcs_notifier.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/error_message.dart';
import 'package:didpay/shared/loading_message.dart';
import 'package:didpay/shared/header.dart';
import 'package:didpay/shared/json_schema_form.dart';
import 'package:didpay/shared/loading_message.dart';
import 'package:didpay/shared/modal/modal_flow.dart';
import 'package:didpay/shared/theme/grid.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -56,7 +56,7 @@ class PaymentDetailsPage extends HookConsumerWidget {
!shouldShowPaymentTypeSelector || selectedPaymentType.value != null;

return Scaffold(
appBar: AppBar(),
appBar: rfq.value?.isLoading ?? false ? null : AppBar(),
body: SafeArea(
child: rfq.value != null
? rfq.value!.when(
Expand Down Expand Up @@ -298,16 +298,18 @@ class PaymentDetailsPage extends HookConsumerWidget {
state.value = AsyncData(rfq);
await Navigator.of(context)
.push(
MaterialPageRoute(
builder: (context) => PaymentReviewPage(
paymentState: paymentState.copyWith(
exchangeId: rfq.metadata.id,
claims: claims,
),
),
MaterialPageRoute(
builder: (context) => PaymentReviewPage(
paymentState: paymentState.copyWith(
exchangeId: rfq.metadata.id,
claims: claims,
),
)
.then((_) => state.value = null);
),
),
)
.then((_) {
if (context.mounted) state.value = null;
});
});
} on Exception catch (e) {
state.value = AsyncError(e, StackTrace.current);
Expand Down
169 changes: 112 additions & 57 deletions lib/features/payment/payment_review_page.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:decimal/decimal.dart';
import 'package:didpay/features/app/app.dart';
import 'package:didpay/features/did/did_provider.dart';
import 'package:didpay/features/payment/payment_fee_details.dart';
import 'package:didpay/features/payment/payment_state.dart';
import 'package:didpay/features/tbdex/tbdex_quote_notifier.dart';
import 'package:didpay/features/tbdex/tbdex_service.dart';
import 'package:didpay/features/transaction/transaction.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/confirmation_message.dart';
import 'package:didpay/shared/error_message.dart';
import 'package:didpay/shared/loading_message.dart';
import 'package:didpay/shared/currency_formatter.dart';
import 'package:didpay/shared/error_message.dart';
import 'package:didpay/shared/exit_dialog.dart';
import 'package:didpay/shared/header.dart';
import 'package:didpay/shared/loading_message.dart';
import 'package:didpay/shared/next_button.dart';
import 'package:didpay/shared/theme/grid.dart';
import 'package:flutter/material.dart';
Expand All @@ -25,70 +28,79 @@ class PaymentReviewPage extends HookConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final quote = useState<AsyncValue<Quote>>(const AsyncLoading());
final quote = useState<AsyncValue<Quote?>>(ref.watch(quoteProvider));

final order = useState<AsyncValue<Order>?>(null);

TbdexQuoteNotifier getQuoteNotifier() => ref.read(quoteProvider.notifier);

useEffect(
() {
Future.microtask(() async => _pollForQuote(ref, quote));
return null;
Future.microtask(
() async => _pollForQuote(ref, getQuoteNotifier(), quote),
);
return getQuoteNotifier().stopPolling;
},
[],
);

return Scaffold(
appBar: AppBar(),
appBar: _buildAppBar(context, ref, quote.value, getQuoteNotifier()),
body: SafeArea(
child: order.value == null
? quote.value.when(
data: (q) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Header(
title: Loc.of(context).reviewYourPayment,
subtitle: Loc.of(context).makeSureInfoIsCorrect,
),
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: Grid.side),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: Grid.sm),
_buildAmounts(context, q.data),
_buildFeeDetails(context, q.data),
_buildPaymentDetails(context),
],
data: (q) => q == null
? Container()
: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Header(
title: Loc.of(context).reviewYourPayment,
subtitle: Loc.of(context).makeSureInfoIsCorrect,
),
),
),
),
NextButton(
onPressed: () => _submitOrder(
context,
ref,
paymentState,
order,
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: Grid.side,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: Grid.sm),
_buildAmounts(context, q.data),
_buildFeeDetails(context, q.data),
_buildPaymentDetails(context),
],
),
),
),
),
NextButton(
onPressed: () => _submitOrder(
context,
ref,
paymentState,
order,
),
title:
'${Loc.of(context).pay} ${PaymentFeeDetails.calculateTotalAmount(q.data)} ${q.data.payin.currencyCode}',
),
],
),
title:
'${Loc.of(context).pay} ${PaymentFeeDetails.calculateTotalAmount(q.data)} ${q.data.payin.currencyCode}',
),
],
),
loading: () => LoadingMessage(
message: Loc.of(context).gettingYourQuote,
),
error: (error, _) => ErrorMessage(
message: error.toString(),
onRetry: () => _pollForQuote(ref, quote),
onRetry: () => _pollForQuote(ref, getQuoteNotifier(), quote),
),
)
: order.value!.when(
data: (_) => ConfirmationMessage(
message: Loc.of(context).orderConfirmed),
message: Loc.of(context).orderConfirmed,
),
loading: () => LoadingMessage(
message: Loc.of(context).confirmingYourOrder,
),
Expand All @@ -106,6 +118,48 @@ class PaymentReviewPage extends HookConsumerWidget {
);
}

AppBar _buildAppBar(
BuildContext context,
WidgetRef ref,
AsyncValue<Quote?> quote,
TbdexQuoteNotifier quoteNotifier,
) =>
AppBar(
leading: quote.isLoading
? IconButton(
onPressed: () async => showDialog(
context: context,
builder: (dialogContext) => ExitDialog(
title: Loc.of(context)
.stoptxnType(paymentState.transactionType.name),
description: Loc.of(context).ifYouExitNow,
onExit: () async {
quoteNotifier.stopPolling();
await ref
.read(tbdexServiceProvider)
.submitClose(
ref.read(didProvider),
paymentState.selectedPfi,
paymentState.exchangeId,
)
.then(
(_) =>
Navigator.of(dialogContext).pushAndRemoveUntil(
MaterialPageRoute(
builder: (dialogContext) => const App(),
),
(route) => false,
),
);
},
onStay: () async => Navigator.pop(dialogContext),
),
),
icon: const Icon(Icons.close),
)
: null,
);

Widget _buildAmounts(BuildContext context, QuoteData quote) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expand Down Expand Up @@ -210,21 +264,22 @@ class PaymentReviewPage extends HookConsumerWidget {

Future<void> _pollForQuote(
WidgetRef ref,
ValueNotifier<AsyncValue<Quote>> state,
TbdexQuoteNotifier quoteNotifier,
ValueNotifier<AsyncValue<Quote?>> state,
) async {
if (paymentState.exchangeId != null && paymentState.selectedPfi != null) {
try {
await ref
.read(tbdexServiceProvider)
.pollForQuote(
ref.read(didProvider),
paymentState.selectedPfi!,
paymentState.exchangeId!,
)
.then((quote) => state.value = AsyncData(quote));
} on Exception catch (e) {
state.value = AsyncError(e, StackTrace.current);
}
state.value = const AsyncLoading();

try {
await quoteNotifier
.startPolling(paymentState.selectedPfi, paymentState.exchangeId)
?.then((quote) {
if (quote != null) {
state.value = AsyncData(quote);
quoteNotifier.stopPolling();
}
});
} on Exception catch (e) {
state.value = AsyncError(e, StackTrace.current);
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/features/pfis/pfis_add_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import 'package:didpay/features/pfis/pfis_notifier.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/confirmation_message.dart';
import 'package:didpay/shared/error_message.dart';
import 'package:didpay/shared/loading_message.dart';
import 'package:didpay/shared/header.dart';
import 'package:didpay/shared/loading_message.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
Expand Down
4 changes: 2 additions & 2 deletions lib/features/send/send_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import 'package:didpay/features/did/did_form.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/confirmation_message.dart';
import 'package:didpay/shared/error_message.dart';
import 'package:didpay/shared/loading_message.dart';
import 'package:didpay/shared/header.dart';
import 'package:didpay/shared/loading_message.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
Expand All @@ -23,7 +23,7 @@ class SendDetailsPage extends HookConsumerWidget {
child: send.value != null
? send.value!.when(
data: (_) => ConfirmationMessage(
message: Loc.of(context).yourPaymentWasSent),
message: Loc.of(context).yourPaymentWasSent,),
loading: () =>
LoadingMessage(message: Loc.of(context).sendingPayment),
error: (error, _) => ErrorMessage(
Expand Down
Loading
Loading