ledger top up functionality and few small fixes for project architechture and design

This commit is contained in:
Arseni
2026-03-05 21:49:23 +03:00
parent 39c04beb21
commit 97b16542c2
41 changed files with 764 additions and 455 deletions

View File

@@ -5,19 +5,21 @@ import 'package:pshared/models/recipient/recipient.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
import 'package:pweb/controllers/payments/page_ui.dart';
import 'package:pweb/pages/payout_page/send/page_handlers.dart';
import 'package:pweb/utils/payment/page_handlers.dart';
import 'package:pweb/pages/payout_page/send/page_view.dart';
class PaymentPage extends StatefulWidget {
final ValueChanged<Recipient?>? onBack;
final PaymentType? initialPaymentType;
final String? initialDestinationLedgerAccountRef;
final PayoutDestination fallbackDestination;
const PaymentPage({
super.key,
this.onBack,
this.initialPaymentType,
this.initialDestinationLedgerAccountRef,
this.fallbackDestination = PayoutDestination.dashboard,
});
@@ -34,7 +36,11 @@ class _PaymentPageState extends State<PaymentPage> {
_uiController = PaymentPageUiController();
WidgetsBinding.instance.addPostFrameCallback(
(_) => initializePaymentPage(context, widget.initialPaymentType),
(_) => initializePaymentPage(
context,
widget.initialPaymentType,
destinationLedgerAccountRef: widget.initialDestinationLedgerAccountRef,
),
);
}

View File

@@ -1,98 +0,0 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/payment/flow.dart';
import 'package:pshared/provider/payment/provider.dart';
import 'package:pshared/provider/payment/quotation/quotation.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pweb/app/router/payout_routes.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
import 'package:pweb/widgets/dialogs/payment_status_dialog.dart';
import 'package:pweb/controllers/payments/page.dart';
import 'package:pweb/controllers/payments/page_ui.dart';
import 'package:pweb/controllers/payouts/payout_verification.dart';
import 'package:pweb/utils/payment/payout_verification_flow.dart';
void initializePaymentPage(
BuildContext context,
PaymentType? initialPaymentType,
) {
final flowProvider = context.read<PaymentFlowProvider>();
flowProvider.setPreferredType(initialPaymentType);
}
void handleSearchChanged(PaymentPageUiController uiController, String query) {
uiController.setQuery(query);
}
void handleRecipientSelected(
BuildContext context,
PaymentPageUiController uiController,
Recipient recipient,
) {
final recipientProvider = context.read<RecipientsProvider>();
recipientProvider.setCurrentObject(recipient.id);
uiController.clearSearch();
}
void handleRecipientCleared(
BuildContext context,
PaymentPageUiController uiController,
) {
final recipientProvider = context.read<RecipientsProvider>();
recipientProvider.setCurrentObject(null);
uiController.clearSearch();
}
Future<void> handleSendPayment({
required State state,
required PayoutDestination fallbackDestination,
}) async {
final context = state.context;
final paymentProvider = context.read<PaymentProvider>();
final quotationProvider = context.read<QuotationProvider>();
final controller = context.read<PaymentPageController>();
final verificationController = context.read<PayoutVerificationController>();
if (paymentProvider.isLoading) return;
final verificationContextKey =
quotationProvider.quotation?.quoteRef ??
quotationProvider.quotation?.idempotencyKey;
final verified = await runPayoutVerification(
context: context,
controller: verificationController,
contextKey: verificationContextKey,
);
if (!verified || !state.mounted) return;
final isSuccess = await controller.sendPayment();
if (!state.mounted) return;
await showPaymentStatusDialog(context, isSuccess: isSuccess);
if (!state.mounted) return;
if (isSuccess) {
controller.handleSuccess();
context.goToPayout(fallbackDestination);
}
}
void handleAddRecipient(BuildContext context) {
final recipients = context.read<RecipientsProvider>();
recipients.setCurrentObject(null);
context.pushToPayout(PayoutDestination.addrecipient);
}
void handleAddPaymentMethod(BuildContext context) {
final recipients = context.read<RecipientsProvider>();
final recipient = recipients.currentObject;
if (recipient == null) return;
recipients.setCurrentObject(recipient.id);
context.pushNamed(PayoutRoutes.editRecipient);
}

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/payment/flow.dart';
import 'package:pshared/provider/payment/quotation/quotation.dart';
import 'package:pshared/provider/recipient/pmethods.dart';
import 'package:pshared/provider/recipient/provider.dart';
@@ -14,6 +15,7 @@ import 'package:pweb/controllers/payments/page_ui.dart';
import 'package:pweb/controllers/payouts/payout_verification.dart';
import 'package:pweb/models/state/control_state.dart';
class PaymentPageView extends StatelessWidget {
final PaymentPageUiController uiController;
final ValueChanged<Recipient?>? onBack;
@@ -47,6 +49,7 @@ class PaymentPageView extends StatelessWidget {
final uiController = context.watch<PaymentPageUiController>();
final methodsProvider = context.watch<PaymentMethodsProvider>();
final recipientProvider = context.watch<RecipientsProvider>();
final flowProvider = context.watch<PaymentFlowProvider>();
final quotationProvider = context.watch<QuotationProvider>();
final verificationController = context
.watch<PayoutVerificationController>();
@@ -58,10 +61,12 @@ class PaymentPageView extends StatelessWidget {
recipients: recipientProvider.recipients,
query: uiController.query,
);
final hasDestinationSelection =
flowProvider.selectedPaymentData != null;
final sendState =
verificationController.isCooldownActiveFor(verificationContextKey)
? ControlState.disabled
: (recipient == null
: (!hasDestinationSelection
? ControlState.disabled
: ControlState.enabled);

View File

@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/recipient/payment_method_draft.dart';
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel.dart';
import 'package:pweb/pages/payout_page/send/widgets/payment_info/header.dart';
import 'package:pweb/models/state/control_state.dart';
import 'package:pweb/models/state/visibility.dart';
import 'package:pweb/utils/dimensions.dart';
class PaymentInfoManualDetailsSection extends StatelessWidget {
final AppDimensions dimensions;
final String title;
final VisibilityState titleVisibility;
final PaymentMethodData data;
const PaymentInfoManualDetailsSection({
super.key,
required this.dimensions,
required this.title,
required this.titleVisibility,
required this.data,
});
@override
Widget build(BuildContext context) {
final entry = RecipientMethodDraft(type: data.type, data: data);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PaymentInfoHeader(
dimensions: dimensions,
title: title,
visibility: titleVisibility,
),
PaymentMethodPanel(
selectedType: data.type,
selectedIndex: 0,
entries: [entry],
onRemove: (_) {},
onChanged: (_, ignored) {},
editState: ControlState.disabled,
deleteVisibility: VisibilityState.hidden,
),
],
);
}
}

View File

@@ -6,6 +6,7 @@ import 'package:pshared/provider/payment/flow.dart';
import 'package:pweb/pages/payout_page/send/widgets/payment_info/methods_section.dart';
import 'package:pweb/pages/payout_page/send/widgets/payment_info/methods_state.dart';
import 'package:pweb/pages/payout_page/send/widgets/payment_info/manual_details.dart';
import 'package:pweb/pages/payout_page/send/widgets/payment_info/no_methods.dart';
import 'package:pweb/pages/payout_page/send/widgets/payment_info/no_recipient.dart';
import 'package:pweb/models/state/visibility.dart';
@@ -35,8 +36,9 @@ class PaymentInfoSection extends StatelessWidget {
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
final flowProvider = context.watch<PaymentFlowProvider>();
final manualData = flowProvider.manualPaymentData;
if (!flowProvider.hasRecipient) {
if (!flowProvider.hasRecipient && manualData == null) {
return PaymentInfoNoRecipientSection(
dimensions: dimensions,
title: loc.paymentInfo,
@@ -44,6 +46,15 @@ class PaymentInfoSection extends StatelessWidget {
);
}
if (!flowProvider.hasRecipient && manualData != null) {
return PaymentInfoManualDetailsSection(
dimensions: dimensions,
title: loc.paymentInfo,
titleVisibility: titleVisibility,
data: manualData,
);
}
final methods = flowProvider.methodsForRecipient;
final types = visiblePaymentTypes;

View File

@@ -81,7 +81,7 @@ class RecipientSection extends StatelessWidget {
ShortListAddressBookPayout(
recipients: recipientProvider.recipients,
onSelected: onRecipientSelected,
trailing: AddRecipientTile(
leading: AddRecipientTile(
label: loc.addRecipient,
onTap: onAddRecipient,
),

View File

@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/payment/flow.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pweb/pages/payout_page/send/widgets/payment_info/section.dart';
@@ -46,24 +49,30 @@ class PaymentRecipientDetailsCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final flowProvider = context.watch<PaymentFlowProvider>();
final isRecipientSelectionLocked =
!flowProvider.hasRecipient && flowProvider.manualPaymentData != null;
return PaymentSectionCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RecipientSection(
recipient: recipient,
dimensions: dimensions,
recipientProvider: recipientProvider,
searchQuery: searchQuery,
filteredRecipients: filteredRecipients,
searchController: searchController,
searchFocusNode: searchFocusNode,
onSearchChanged: onSearchChanged,
onRecipientSelected: onRecipientSelected,
onRecipientCleared: onRecipientCleared,
onAddRecipient: onAddRecipient,
),
SizedBox(height: dimensions.paddingMedium),
if (!isRecipientSelectionLocked) ...[
RecipientSection(
recipient: recipient,
dimensions: dimensions,
recipientProvider: recipientProvider,
searchQuery: searchQuery,
filteredRecipients: filteredRecipients,
searchController: searchController,
searchFocusNode: searchFocusNode,
onSearchChanged: onSearchChanged,
onRecipientSelected: onRecipientSelected,
onRecipientCleared: onRecipientCleared,
onAddRecipient: onAddRecipient,
),
SizedBox(height: dimensions.paddingMedium),
],
PaymentInfoSection(
dimensions: dimensions,
titleVisibility: VisibilityState.hidden,

View File

@@ -1,9 +1,13 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/models/payment/source_type.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pweb/app/router/payout_routes.dart';
@@ -17,11 +21,35 @@ class TopUpButton extends StatelessWidget {
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
final source = context.watch<PaymentSourceController>();
final canTopUp = source.selectedType == PaymentSourceType.wallet;
final selectedType = source.selectedType;
final selectedLedger = source.selectedLedgerAccount;
final canTopUp =
selectedType == PaymentSourceType.wallet ||
(selectedType == PaymentSourceType.ledger && selectedLedger != null);
return ElevatedButton(
style: ElevatedButton.styleFrom(shadowColor: null, elevation: 0),
onPressed: canTopUp ? () => context.pushToWalletTopUp() : null,
onPressed: !canTopUp
? null
: () {
if (selectedType == PaymentSourceType.wallet) {
context.pushToWalletTopUp();
return;
}
if (selectedType == PaymentSourceType.ledger &&
selectedLedger != null) {
context.read<RecipientsProvider>().setCurrentObject(null);
context.pushNamed(
PayoutRoutes.payment,
queryParameters: PayoutRoutes.buildQueryParameters(
paymentType: PaymentType.ledger,
destinationLedgerAccountRef:
selectedLedger.ledgerAccountRef,
),
);
}
},
child: Text(loc.topUpBalance),
);
}