Skip to content

Commit

Permalink
feat: add DapState (#276)
Browse files Browse the repository at this point in the history
  • Loading branch information
ethan-tbd authored Aug 27, 2024
1 parent acca51c commit a026294
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 59 deletions.
56 changes: 56 additions & 0 deletions lib/features/dap/dap_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'package:dap/dap.dart';
import 'package:didpay/features/payment/payment_method.dart';

class DapState {
final MoneyAddress? selectedAddress;
final List<MoneyAddress>? moneyAddresses;

DapState({
this.selectedAddress,
this.moneyAddresses,
});

static const Map<String, String> protocolToKindMap = {
'addr': 'BTC_ONCHAIN_PAYOUT',
'lnaddr': 'BTC_LN_PAYOUT',
'sol': 'USDC_ONCHAIN',
};

String? get protocol => selectedAddress?.css.split(':').firstOrNull;

String? get paymentAddress => selectedAddress?.css.split(':').lastOrNull;

List<String>? get currencies =>
moneyAddresses?.map((address) => address.currency).toList();

MoneyAddress? getSelectedMoneyAddress(String? paymentCurrency) =>
moneyAddresses?.firstWhere(
(address) => address.currency.toUpperCase() == paymentCurrency,
);

List<PaymentMethod>? filterPaymentMethods(
List<PaymentMethod>? paymentMethods,
) {
final protocolKinds = moneyAddresses
?.map(
(address) =>
protocolToKindMap[address.css.split(':').firstOrNull ?? ''],
)
.toSet();

return paymentMethods
?.where(
(method) => protocolKinds?.contains(method.kind) ?? false,
)
.toList();
}

DapState copyWith({
MoneyAddress? selectedAddress,
List<MoneyAddress>? moneyAddresses,
}) =>
DapState(
selectedAddress: selectedAddress ?? this.selectedAddress,
moneyAddresses: moneyAddresses ?? this.moneyAddresses,
);
}
51 changes: 30 additions & 21 deletions lib/features/payment/payment_amount_page.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:didpay/features/dap/dap_state.dart';
import 'package:didpay/features/payin/payin.dart';
import 'package:didpay/features/payment/payment_amount_state.dart';
import 'package:didpay/features/payment/payment_details_page.dart';
Expand All @@ -24,9 +25,11 @@ import 'package:tbdex/tbdex.dart';

class PaymentAmountPage extends HookConsumerWidget {
final PaymentState paymentState;
final DapState? dapState;

const PaymentAmountPage({
required this.paymentState,
this.dapState,
super.key,
});

Expand All @@ -43,7 +46,7 @@ class PaymentAmountPage extends HookConsumerWidget {
() {
Future.delayed(
Duration.zero,
() => _getOfferings(context, ref, offerings),
() => _getOfferings(context, ref, dapState?.currencies, offerings),
);
return null;
},
Expand Down Expand Up @@ -72,6 +75,7 @@ class PaymentAmountPage extends HookConsumerWidget {
onRetry: () => _getOfferings(
context,
ref,
dapState?.currencies,
offerings,
),
),
Expand Down Expand Up @@ -134,33 +138,38 @@ class PaymentAmountPage extends HookConsumerWidget {
: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
final paymentCurrency = paymentState.transactionType ==
TransactionType.deposit
? state.value?.payinCurrency
: state.value?.payoutCurrency;

final paymentMethods = paymentState.transactionType ==
TransactionType.deposit
? state.value?.selectedOffering?.data.payin.methods
.map(PaymentMethod.fromPayinMethod)
.toList()
: state.value?.selectedOffering?.data.payout.methods
.map(PaymentMethod.fromPayoutMethod)
.toList();

final paymentDetailsState =
(paymentState.paymentDetailsState ??
PaymentDetailsState())
.copyWith(
paymentCurrency: paymentState.transactionType ==
TransactionType.deposit
? state.value?.payinCurrency
: state.value?.payoutCurrency,
paymentMethods: paymentState.transactionType ==
TransactionType.deposit
? state
.value?.selectedOffering?.data.payin.methods
.map(PaymentMethod.fromPayinMethod)
.toList()
: state
.value?.selectedOffering?.data.payout.methods
.map(PaymentMethod.fromPayoutMethod)
.toList(),
paymentCurrency: paymentCurrency,
paymentMethods:
dapState?.filterPaymentMethods(paymentMethods) ??
paymentMethods,
);

return PaymentDetailsPage(
paymentState: paymentState.copyWith(
paymentAmountState: state.value,
paymentDetailsState: paymentDetailsState.copyWith(
paymentMethods:
paymentDetailsState.filterDapProtocol(),
),
paymentDetailsState: paymentDetailsState,
),
dapState: dapState?.copyWith(
selectedAddress: dapState
?.getSelectedMoneyAddress(paymentCurrency),
),
);
},
Expand All @@ -174,14 +183,14 @@ class PaymentAmountPage extends HookConsumerWidget {
Future<void> _getOfferings(
BuildContext context,
WidgetRef ref,
List<String>? payoutCurrencies,
ValueNotifier<AsyncValue<Map<Pfi, List<Offering>>>> state,
) async {
state.value = const AsyncLoading();
try {
final offerings = await ref.read(tbdexServiceProvider).getOfferings(
ref.read(pfisProvider),
payinCurrency: paymentState.filterPayinCurrency,
payoutCurrency: paymentState.filterPayoutCurrency,
payoutCurrencies: payoutCurrencies,
);

if (context.mounted) {
Expand Down
6 changes: 5 additions & 1 deletion lib/features/payment/payment_details_page.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
import 'package:didpay/features/dap/dap_state.dart';
import 'package:didpay/features/did/did_provider.dart';
import 'package:didpay/features/kcc/kcc_consent_page.dart';
import 'package:didpay/features/payment/payment_details_state.dart';
Expand Down Expand Up @@ -27,9 +28,11 @@ import 'package:tbdex/tbdex.dart';

class PaymentDetailsPage extends HookConsumerWidget {
final PaymentState paymentState;
final DapState? dapState;

const PaymentDetailsPage({
required this.paymentState,
this.dapState,
super.key,
});

Expand Down Expand Up @@ -104,7 +107,7 @@ class PaymentDetailsPage extends HookConsumerWidget {
Header(
title:
paymentState.transactionType == TransactionType.send
? state.value.moneyAddresses != null
? dapState?.moneyAddresses != null
? Loc.of(context).checkTheirPaymentDetails
: Loc.of(context).enterTheirPaymentDetails
: Loc.of(context).enterYourPaymentDetails,
Expand Down Expand Up @@ -144,6 +147,7 @@ class PaymentDetailsPage extends HookConsumerWidget {
Expanded(
child: JsonSchemaForm(
state: state.value,
dapState: dapState,
onSubmit: (formData) async {
state.value = state.value.copyWith(formData: formData);

Expand Down
16 changes: 0 additions & 16 deletions lib/features/payment/payment_details_state.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:dap/dap.dart';
import 'package:didpay/features/payment/payment_method.dart';

class PaymentDetailsState {
Expand All @@ -8,7 +7,6 @@ class PaymentDetailsState {
final String? selectedPaymentType;
final PaymentMethod? selectedPaymentMethod;
final List<PaymentMethod>? paymentMethods;
final List<MoneyAddress>? moneyAddresses;
final List<String>? credentialsJwt;
final Map<String, String>? formData;

Expand All @@ -19,7 +17,6 @@ class PaymentDetailsState {
this.selectedPaymentType,
this.selectedPaymentMethod,
this.paymentMethods,
this.moneyAddresses,
this.credentialsJwt,
this.formData,
});
Expand All @@ -37,25 +34,13 @@ class PaymentDetailsState {
)
.toList();

List<PaymentMethod>? filterDapProtocol() => paymentMethods
?.where(
(method) => method.kind.contains(
PaymentMethod.protocolPaymentMap[
moneyAddresses?.firstOrNull?.css.split(':').firstOrNull ??
''] ??
'',
),
)
.toList();

PaymentDetailsState copyWith({
String? paymentCurrency,
String? paymentName,
String? exchangeId,
String? selectedPaymentType,
PaymentMethod? selectedPaymentMethod,
List<PaymentMethod>? paymentMethods,
List<MoneyAddress>? moneyAddresses,
List<String>? credentialsJwt,
Map<String, String>? formData,
}) {
Expand All @@ -67,7 +52,6 @@ class PaymentDetailsState {
selectedPaymentMethod:
selectedPaymentMethod ?? this.selectedPaymentMethod,
paymentMethods: paymentMethods ?? this.paymentMethods,
moneyAddresses: moneyAddresses ?? this.moneyAddresses,
credentialsJwt: credentialsJwt ?? this.credentialsJwt,
formData: formData ?? this.formData,
);
Expand Down
5 changes: 0 additions & 5 deletions lib/features/payment/payment_method.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,4 @@ class PaymentMethod {
fee: fee ?? this.fee,
);
}

static const Map<String, String> protocolPaymentMap = {
'addr': 'BTC_ONCHAIN_PAYOUT',
'lnaddr': 'BTC_LN_PAYOUT',
};
}
4 changes: 1 addition & 3 deletions lib/features/payment/payment_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@ class PaymentState {
switch (transactionType) {
case TransactionType.deposit:
return 'USDC';
// TODO(ethan-tbd): use dap currencies here when tbdex-dart supports list of currencies
case TransactionType.send:
return paymentDetailsState?.paymentCurrency ??
paymentDetailsState?.moneyAddresses?.firstOrNull?.currency
.toUpperCase();
case TransactionType.withdraw:
return null;
}
Expand Down
5 changes: 4 additions & 1 deletion lib/features/send/send_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:dap/dap.dart';
import 'package:didpay/features/dap/dap_form.dart';
import 'package:didpay/features/dap/dap_state.dart';
import 'package:didpay/features/feature_flags/feature_flag.dart';
import 'package:didpay/features/feature_flags/feature_flags_notifier.dart';
import 'package:didpay/features/payment/payment_amount_page.dart';
Expand Down Expand Up @@ -58,9 +59,11 @@ class SendPage extends HookConsumerWidget {
transactionType: TransactionType.send,
paymentDetailsState: PaymentDetailsState(
paymentName: recipientDap.dap,
moneyAddresses: moneyAddresses,
),
),
dapState: DapState(
moneyAddresses: moneyAddresses,
),
),
fullscreenDialog: true,
),
Expand Down
30 changes: 22 additions & 8 deletions lib/features/tbdex/tbdex_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,36 @@ final tbdexServiceProvider = Provider((_) => TbdexService());
class TbdexService {
Future<Map<Pfi, List<Offering>>> getOfferings(
List<Pfi> pfis, {
String? payinCurrency,
String? payoutCurrency,
List<String>? payinCurrencies,
List<String>? payoutCurrencies,
}) async {
final offeringsMap = <Pfi, List<Offering>>{};

for (final pfi in pfis) {
try {
final offerings = await TbdexHttpClient.listOfferings(
pfi.did,
filter: GetOfferingsFilter(
payinCurrency: payinCurrency,
payoutCurrency: payoutCurrency,
),
// TODO(ethan-tbd): update tbdex-dart to support list of payin and payout currencies
// filter: GetOfferingsFilter(
// payinCurrency: payinCurrency,
// payoutCurrency: payoutCurrency,
// ),
);

offeringsMap[pfi] = offerings;
// TODO(ethan-tbd): remove when tbdex-dart supports filtering by payin and payout currencies
final filteredOfferings = (payoutCurrencies == null)
? offerings
: offerings
.where(
(offering) => payoutCurrencies.contains(
offering.data.payout.currencyCode.toLowerCase(),
),
)
.toList();

if (filteredOfferings.isNotEmpty) {
offeringsMap[pfi] = filteredOfferings;
}
} on Exception catch (e) {
if (e is ValidationError) continue;
rethrow;
Expand All @@ -39,7 +53,7 @@ class TbdexService {
);
}

// temporarily filter out stored balance payin offerings
// TODO(ethan-tbd): remove later, temporarily filter out stored balance payin offerings
final filteredOfferingsMap = offeringsMap.map(
(key, value) => MapEntry(
key,
Expand Down
11 changes: 9 additions & 2 deletions lib/shared/json_schema_form.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:convert';

import 'package:didpay/features/dap/dap_state.dart';
import 'package:didpay/features/payment/payment_details_state.dart';
import 'package:didpay/shared/next_button.dart';
import 'package:didpay/shared/theme/grid.dart';
Expand All @@ -11,12 +12,14 @@ import 'package:mask_text_input_formatter/mask_text_input_formatter.dart';
class JsonSchemaForm extends HookWidget {
final PaymentDetailsState state;
final Future<void> Function(Map<String, String>) onSubmit;
final DapState? dapState;

final _formKey = GlobalKey<FormState>();

JsonSchemaForm({
required this.state,
required this.onSubmit,
this.dapState,
super.key,
});

Expand Down Expand Up @@ -58,9 +61,13 @@ class JsonSchemaForm extends HookWidget {
final pattern = valueMap['pattern'] as String?;

final formatter = TextInputUtil.getMaskFormatter(pattern);
final initialText = state.formData?[key] ??
state.moneyAddresses?.firstOrNull?.css.split(':').lastOrNull ??

final dapPrefillText = (key == 'chain'
? dapState?.protocol
: dapState?.paymentAddress) ??
'';

final initialText = state.formData?[key] ?? dapPrefillText;
final controller = TextEditingController(text: initialText);

final focusNode = FocusNode();
Expand Down
2 changes: 1 addition & 1 deletion test/features/home/home_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void main() async {
when(
() => mockTbdexService.getOfferings(
pfis,
payinCurrency: 'USDC',
payinCurrencies: ['USDC'],
),
).thenAnswer((_) async => offerings);

Expand Down
Loading

0 comments on commit a026294

Please sign in to comment.