Skip to content

Commit

Permalink
feat: poll for quote (#142)
Browse files Browse the repository at this point in the history
  • Loading branch information
ethan-tbd authored May 3, 2024
1 parent 180801f commit 39ed788
Show file tree
Hide file tree
Showing 16 changed files with 390 additions and 232 deletions.
9 changes: 2 additions & 7 deletions lib/features/payin/deposit_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'package:didpay/config/config.dart';
import 'package:didpay/features/account/account_providers.dart';
import 'package:didpay/features/home/transaction.dart';
import 'package:didpay/features/payin/payin.dart';
import 'package:didpay/features/payin/payin_details_page.dart';
Expand All @@ -24,11 +22,8 @@ class DepositPage extends HookConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final country = ref.read(countryProvider);
final pfi = Config.getPfi(country);

// TODO(ethan-tbd): filter offerings with STORED_BALANCE as payout, https://github.com/TBD54566975/didpay/issues/132
final offerings = ref.watch(offeringsProvider(pfi?.didUri ?? ''));
final offerings = ref.watch(offeringsProvider);

final payinAmount = useState('0');
final payoutAmount = useState<double>(0);
Expand Down Expand Up @@ -137,7 +132,7 @@ class DepositPage extends HookConsumerWidget {
builder: (context) => PayinDetailsPage(
rfqState: rfqState.copyWith(
payinAmount: payinAmount,
offeringId: selectedOffering?.metadata.id ?? '',
offering: selectedOffering,
payinMethod: selectedOffering?.data.payin.methods.firstOrNull,
payoutMethod: selectedOffering?.data.payout.methods.firstOrNull,
),
Expand Down
62 changes: 7 additions & 55 deletions lib/features/payin/payin_details_page.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import 'package:collection/collection.dart';
import 'package:didpay/features/home/transaction.dart';
import 'package:didpay/features/payin/search_payin_methods_page.dart';
import 'package:didpay/features/payment/payment_details.dart';
import 'package:didpay/features/payment/payment_state.dart';
import 'package:didpay/features/payment/review_payment_page.dart';
import 'package:didpay/features/payment/search_payment_types_page.dart';
import 'package:didpay/features/tbdex/rfq_state.dart';
import 'package:didpay/features/tbdex/tbdex_providers.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/json_schema_form.dart';
import 'package:didpay/shared/theme/grid.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
Expand Down Expand Up @@ -81,7 +79,12 @@ class PayinDetailsPage extends HookConsumerWidget {
selectedPayinMethod,
filteredPayinMethods,
),
_buildForm(context, ref, selectedPayinMethod.value),
PaymentDetails.buildForm(
context,
ref,
rfqState.copyWith(payinMethod: selectedPayinMethod.value),
paymentState,
),
],
),
),
Expand Down Expand Up @@ -203,55 +206,4 @@ class PayinDetailsPage extends HookConsumerWidget {
],
);
}

Widget _buildForm(
BuildContext context,
WidgetRef ref,
PayinMethod? selectedPayinMethod,
) =>
selectedPayinMethod == null
? _buildDisabledButton(context)
: Expanded(
child: JsonSchemaForm(
schema: selectedPayinMethod.requiredPaymentDetails?.toJson(),
onSubmit: (formData) {
// TODO(ethan-tbd): wait for quote to come back before navigating, https://github.com/TBD54566975/didpay/issues/131
ref.read(
rfqProvider(
rfqState.copyWith(payinMethod: selectedPayinMethod),
),
);

Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ReviewPaymentPage(
rfqState: rfqState,
paymentState: paymentState.copyWith(
serviceFee: selectedPayinMethod.fee,
paymentName: selectedPayinMethod.name ??
selectedPayinMethod.kind,
formData: formData,
),
),
),
);
},
),
);

Widget _buildDisabledButton(BuildContext context) => Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(child: Container()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.side),
child: FilledButton(
onPressed: null,
child: Text(Loc.of(context).next),
),
),
],
),
);
}
90 changes: 90 additions & 0 deletions lib/features/payment/payment_details.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import 'package:didpay/features/home/transaction.dart';
import 'package:didpay/features/payment/payment_state.dart';
import 'package:didpay/features/payment/review_payment_page.dart';
import 'package:didpay/features/tbdex/rfq_state.dart';
import 'package:didpay/features/tbdex/tbdex.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/json_schema_form.dart';
import 'package:didpay/shared/theme/grid.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:tbdex/tbdex.dart';

class PaymentDetails {
static Widget buildForm(
BuildContext context,
WidgetRef ref,
RfqState rfqState,
PaymentState paymentState,
) {
final paymentMethod =
paymentState.transactionType == TransactionType.deposit
? rfqState.payinMethod
: rfqState.payoutMethod;

final isDisabled = paymentMethod.isDisabled;
final schema = paymentMethod.schema;
final fee = paymentMethod.serviceFee;
final paymentName = paymentMethod.paymentName;

return isDisabled
? _buildDisabledButton(context)
: Expanded(
child: JsonSchemaForm(
schema: schema,
onSubmit: (formData) =>
// TODO(mistermoe): check requiredClaims and navigate to kcc flow if needed, https://github.com/TBD54566975/didpay/issues/122
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ReviewPaymentPage(
rfq: Tbdex.createRfq(ref, rfqState),
paymentState: paymentState.copyWith(
serviceFee: fee,
paymentName: paymentName,
formData: formData,
),
),
),
),
),
);
}

static Widget _buildDisabledButton(BuildContext context) => Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(child: Container()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.side),
child: FilledButton(
onPressed: null,
child: Text(Loc.of(context).next),
),
),
],
),
);
}

extension _PaymentMethodOperations on Object? {
bool get isDisabled => this == null;

String? get schema => this is PayinMethod
? (this as PayinMethod?)?.requiredPaymentDetails?.toJson()
: this is PayoutMethod
? (this as PayoutMethod?)?.requiredPaymentDetails?.toJson()
: null;

String? get serviceFee => this is PayinMethod
? (this as PayinMethod?)?.fee
: this is PayoutMethod
? (this as PayoutMethod?)?.fee
: null;

String? get paymentName => this is PayinMethod
? (this as PayinMethod?)?.name ?? (this as PayinMethod?)?.kind
: this is PayoutMethod
? (this as PayoutMethod?)?.name ?? (this as PayoutMethod?)?.kind
: null;
}
111 changes: 70 additions & 41 deletions lib/features/payment/review_payment_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,87 @@ import 'package:auto_size_text/auto_size_text.dart';
import 'package:didpay/features/home/transaction.dart';
import 'package:didpay/features/payment/payment_confirmation_page.dart';
import 'package:didpay/features/payment/payment_state.dart';
import 'package:didpay/features/tbdex/rfq_state.dart';
import 'package:didpay/features/tbdex/quote_notifier.dart';
import 'package:didpay/features/tbdex/tbdex_providers.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/fee_details.dart';
import 'package:didpay/shared/theme/grid.dart';
import 'package:didpay/shared/utils/currency_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:tbdex/tbdex.dart';

class ReviewPaymentPage extends HookWidget {
// TODO(ethan-tbd): replace with quote, https://github.com/TBD54566975/didpay/issues/131
final RfqState rfqState;
class ReviewPaymentPage extends HookConsumerWidget {
final Rfq rfq;
final PaymentState paymentState;

const ReviewPaymentPage({
required this.rfqState,
required this.rfq,
required this.paymentState,
super.key,
});

@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
QuoteAsyncNotifier getQuoteNotifier() => ref.read(quoteProvider.notifier);
final quoteResult = ref.watch(quoteProvider);

useEffect(
() {
Future.delayed(Duration.zero, () {
ref.read(rfqProvider(rfq));
getQuoteNotifier().startPolling(rfq.metadata.id);
});
return getQuoteNotifier().stopPolling;
},
[],
);

return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.side),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(context),
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
child: quoteResult.when(
data: (quote) => quote != null
? Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: Grid.sm),
_buildAmounts(context),
_buildFeeDetails(context),
_buildPaymentDetails(context),
_buildHeader(context),
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: Grid.sm),
_buildAmounts(context, quote.data),
_buildFeeDetails(context, quote.data),
_buildPaymentDetails(context),
],
),
),
),
_buildSubmitButton(context),
],
),
),
)
: _loading(),
loading: _loading,
error: (error, stackTrace) => Center(
child: Text(
'Failed to get quote: $error',
style: Theme.of(context).textTheme.displaySmall,
),
_buildSubmitButton(context),
],
),
),
),
),
);
}

Widget _loading() => const Center(child: CircularProgressIndicator());

Widget _buildHeader(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(vertical: Grid.xs),
child: Column(
Expand All @@ -79,7 +108,7 @@ class ReviewPaymentPage extends HookWidget {
),
);

Widget _buildAmounts(BuildContext context) => Column(
Widget _buildAmounts(BuildContext context, QuoteData quote) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
Expand All @@ -89,8 +118,8 @@ class ReviewPaymentPage extends HookWidget {
Flexible(
child: AutoSizeText(
CurrencyUtil.formatFromString(
rfqState.payinAmount ?? '0',
currency: paymentState.payinCurrency.toUpperCase(),
quote.payin.amount,
currency: quote.payin.currencyCode.toUpperCase(),
),
style: Theme.of(context).textTheme.headlineMedium,
maxLines: 1,
Expand All @@ -100,7 +129,7 @@ class ReviewPaymentPage extends HookWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.xxs),
child: Text(
paymentState.payinCurrency,
quote.payin.currencyCode,
style: Theme.of(context).textTheme.headlineSmall,
),
),
Expand All @@ -115,14 +144,14 @@ class ReviewPaymentPage extends HookWidget {
children: [
Flexible(
child: AutoSizeText(
paymentState.payoutAmount,
quote.payout.amount,
style: Theme.of(context).textTheme.headlineMedium,
maxLines: 1,
),
),
const SizedBox(width: Grid.xs),
Text(
paymentState.payoutCurrency,
quote.payout.currencyCode,
style: Theme.of(context).textTheme.headlineSmall,
),
],
Expand Down Expand Up @@ -160,24 +189,24 @@ class ReviewPaymentPage extends HookWidget {
return Text(label, style: style);
}

Widget _buildFeeDetails(BuildContext context) => Padding(
// TODO(ethan-tbd): clean up this widget, https://github.com/TBD54566975/didpay/issues/143
Widget _buildFeeDetails(BuildContext context, QuoteData quote) => Padding(
padding: const EdgeInsets.symmetric(vertical: Grid.lg),
child: FeeDetails(
payinCurrency: Loc.of(context).usd,
payoutCurrency: paymentState.payinCurrency != Loc.of(context).usd
? paymentState.payinCurrency
: paymentState.payoutCurrency,
payoutCurrency: quote.payin.currencyCode != Loc.of(context).usd
? quote.payin.currencyCode
: quote.payout.currencyCode,
exchangeRate: paymentState.exchangeRate,
serviceFee:
double.parse(paymentState.serviceFee ?? '0').toStringAsFixed(2),
total: paymentState.payinCurrency != Loc.of(context).usd
serviceFee: double.parse(quote.payout.fee ?? '0').toStringAsFixed(2),
total: paymentState.transactionType == TransactionType.deposit
? (double.parse(
(rfqState.payinAmount ?? '0').replaceAll(',', ''),
) +
double.parse(paymentState.serviceFee ?? '0'))
quote.payin.amount.replaceAll(',', ''),
) -
double.parse(quote.payin.fee ?? '0'))
.toStringAsFixed(2)
: (double.parse(paymentState.payoutAmount.replaceAll(',', '')) +
double.parse(paymentState.serviceFee ?? '0'))
: (double.parse(quote.payout.amount.replaceAll(',', '')) -
double.parse(quote.payout.fee ?? '0'))
.toStringAsFixed(2),
),
);
Expand Down
Loading

0 comments on commit 39ed788

Please sign in to comment.