From bf39b1d4016bdd22843a9996afb6e716e793bd8c Mon Sep 17 00:00:00 2001 From: Arseni Date: Fri, 5 Dec 2025 20:29:43 +0300 Subject: [PATCH] Top Up Balance logic and Added fixes for routing --- .../pweb/lib/app/router/payout_routes.dart | 112 ++++++++++++ .../pweb/lib/app/router/payout_shell.dart | 160 ++++++++++++++++++ frontend/pweb/lib/app/router/router.dart | 72 ++++---- frontend/pweb/lib/data/mappers/wallet_ui.dart | 4 + frontend/pweb/lib/l10n/en.arb | 11 ++ frontend/pweb/lib/l10n/ru.arb | 11 ++ frontend/pweb/lib/models/wallet.dart | 18 +- .../dashboard/buttons/balance/balance.dart | 6 +- .../pages/dashboard/buttons/balance/card.dart | 6 +- .../dashboard/buttons/balance/carousel.dart | 14 +- .../pweb/lib/pages/dashboard/dashboard.dart | 7 +- .../pweb/lib/pages/payment_methods/page.dart | 4 +- .../widgets/payment_page_body.dart | 2 +- .../payout_page/wallet/edit/buttons/send.dart | 2 +- .../wallet/edit/buttons/top_up.dart | 15 +- .../pages/wallet_top_up/address_block.dart | 96 +++++++++++ .../pweb/lib/pages/wallet_top_up/content.dart | 76 +++++++++ .../pweb/lib/pages/wallet_top_up/details.dart | 60 +++++++ .../pweb/lib/pages/wallet_top_up/header.dart | 47 +++++ .../lib/pages/wallet_top_up/info_chip.dart | 48 ++++++ .../pweb/lib/pages/wallet_top_up/meta.dart | 37 ++++ .../pweb/lib/pages/wallet_top_up/page.dart | 44 +++++ .../pweb/lib/providers/page_selector.dart | 123 ++++++++++++-- frontend/pweb/lib/services/wallets.dart | 22 --- .../lib/widgets/sidebar/destinations.dart | 5 +- frontend/pweb/lib/widgets/sidebar/page.dart | 142 +++++++--------- frontend/pweb/pubspec.yaml | 3 +- 27 files changed, 972 insertions(+), 175 deletions(-) create mode 100644 frontend/pweb/lib/app/router/payout_routes.dart create mode 100644 frontend/pweb/lib/app/router/payout_shell.dart create mode 100644 frontend/pweb/lib/pages/wallet_top_up/address_block.dart create mode 100644 frontend/pweb/lib/pages/wallet_top_up/content.dart create mode 100644 frontend/pweb/lib/pages/wallet_top_up/details.dart create mode 100644 frontend/pweb/lib/pages/wallet_top_up/header.dart create mode 100644 frontend/pweb/lib/pages/wallet_top_up/info_chip.dart create mode 100644 frontend/pweb/lib/pages/wallet_top_up/meta.dart create mode 100644 frontend/pweb/lib/pages/wallet_top_up/page.dart diff --git a/frontend/pweb/lib/app/router/payout_routes.dart b/frontend/pweb/lib/app/router/payout_routes.dart new file mode 100644 index 0000000..43690b1 --- /dev/null +++ b/frontend/pweb/lib/app/router/payout_routes.dart @@ -0,0 +1,112 @@ +import 'package:flutter/widgets.dart'; + +import 'package:go_router/go_router.dart'; + +import 'package:pweb/widgets/sidebar/destinations.dart'; + + +class PayoutRoutes { + static const dashboard = 'dashboard'; + static const sendPayout = payment; + static const recipients = 'payout-recipients'; + static const addRecipient = 'payout-add-recipient'; + static const payment = 'payout-payment'; + static const settings = 'payout-settings'; + static const reports = 'payout-reports'; + static const methods = 'payout-methods'; + static const editWallet = 'payout-edit-wallet'; + static const walletTopUp = 'payout-wallet-top-up'; + + static const dashboardPath = '/dashboard'; + static const recipientsPath = '/dashboard/recipients'; + static const addRecipientPath = '/dashboard/recipients/add'; + static const paymentPath = '/dashboard/payment'; + static const settingsPath = '/dashboard/settings'; + static const reportsPath = '/dashboard/reports'; + static const methodsPath = '/dashboard/methods'; + static const editWalletPath = '/dashboard/methods/edit'; + static const walletTopUpPath = '/dashboard/wallet/top-up'; + + static String nameFor(PayoutDestination destination) { + switch (destination) { + case PayoutDestination.dashboard: + return dashboard; + case PayoutDestination.sendPayout: + return payment; + case PayoutDestination.recipients: + return recipients; + case PayoutDestination.addrecipient: + return addRecipient; + case PayoutDestination.payment: + return payment; + case PayoutDestination.settings: + return settings; + case PayoutDestination.reports: + return reports; + case PayoutDestination.methods: + return methods; + case PayoutDestination.editwallet: + return editWallet; + case PayoutDestination.walletTopUp: + return walletTopUp; + } + } + + static String pathFor(PayoutDestination destination) { + switch (destination) { + case PayoutDestination.dashboard: + return dashboardPath; + case PayoutDestination.sendPayout: + return paymentPath; + case PayoutDestination.recipients: + return recipientsPath; + case PayoutDestination.addrecipient: + return addRecipientPath; + case PayoutDestination.payment: + return paymentPath; + case PayoutDestination.settings: + return settingsPath; + case PayoutDestination.reports: + return reportsPath; + case PayoutDestination.methods: + return methodsPath; + case PayoutDestination.editwallet: + return editWalletPath; + case PayoutDestination.walletTopUp: + return walletTopUpPath; + } + } + + static PayoutDestination? destinationFor(String? routeName) { + switch (routeName) { + case dashboard: + return PayoutDestination.dashboard; + case sendPayout: + return PayoutDestination.payment; + case recipients: + return PayoutDestination.recipients; + case addRecipient: + return PayoutDestination.addrecipient; + case payment: + return PayoutDestination.payment; + case settings: + return PayoutDestination.settings; + case reports: + return PayoutDestination.reports; + case methods: + return PayoutDestination.methods; + case editWallet: + return PayoutDestination.editwallet; + case walletTopUp: + return PayoutDestination.walletTopUp; + default: + return null; + } + } +} + +extension PayoutNavigation on BuildContext { + void goToPayout(PayoutDestination destination) => goNamed(PayoutRoutes.nameFor(destination)); + + void pushToPayout(PayoutDestination destination) => pushNamed(PayoutRoutes.nameFor(destination)); +} diff --git a/frontend/pweb/lib/app/router/payout_shell.dart b/frontend/pweb/lib/app/router/payout_shell.dart new file mode 100644 index 0000000..96e3f39 --- /dev/null +++ b/frontend/pweb/lib/app/router/payout_shell.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; + +import 'package:go_router/go_router.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pshared/provider/recipient/provider.dart'; + +import 'package:pweb/app/router/pages.dart'; +import 'package:pweb/app/router/payout_routes.dart'; +import 'package:pweb/pages/address_book/form/page.dart'; +import 'package:pweb/pages/address_book/page/page.dart'; +import 'package:pweb/pages/dashboard/dashboard.dart'; +import 'package:pweb/pages/payment_methods/page.dart'; +import 'package:pweb/pages/payout_page/page.dart'; +import 'package:pweb/pages/payout_page/wallet/edit/page.dart'; +import 'package:pweb/pages/report/page.dart'; +import 'package:pweb/pages/settings/profile/page.dart'; +import 'package:pweb/pages/wallet_top_up/page.dart'; +import 'package:pweb/providers/page_selector.dart'; +import 'package:pweb/widgets/error/snackbar.dart'; +import 'package:pweb/widgets/sidebar/destinations.dart'; +import 'package:pweb/widgets/sidebar/page.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +RouteBase payoutShellRoute() => ShellRoute( + builder: (context, state, child) => PageSelector( + child: child, + routerState: state, + ), + routes: [ + GoRoute( + name: PayoutRoutes.dashboard, + path: routerPage(Pages.dashboard), + pageBuilder: (context, _) => NoTransitionPage( + child: DashboardPage( + onRecipientSelected: (recipient) => context + .read() + .selectRecipient(context, recipient), + onGoToPaymentWithoutRecipient: (type) => context + .read() + .startPaymentWithoutRecipient(context, type), + onTopUp: (wallet) => context + .read() + .openWalletTopUp(context, wallet), + ), + ), + ), + GoRoute( + name: PayoutRoutes.recipients, + path: PayoutRoutes.recipientsPath, + pageBuilder: (context, _) { + final loc = AppLocalizations.of(context)!; + return NoTransitionPage( + child: RecipientAddressBookPage( + onRecipientSelected: (recipient) => context + .read() + .selectRecipient(context, recipient, fromList: true), + onAddRecipient: () => context + .read() + .goToAddRecipient(context), + onEditRecipient: (recipient) => context + .read() + .editRecipient(context, recipient, fromList: true), + onDeleteRecipient: (recipient) => executeActionWithNotification( + context: context, + action: () async => + context.read().delete(recipient.id), + successMessage: loc.recipientDeletedSuccessfully, + errorMessage: loc.errorDeleteRecipient, + ), + ), + ); + }, + ), + GoRoute( + name: PayoutRoutes.addRecipient, + path: PayoutRoutes.addRecipientPath, + pageBuilder: (context, _) { + final selector = context.read(); + final recipient = selector.recipientProvider.currentObject; + return NoTransitionPage( + child: AdressBookRecipientForm( + recipient: recipient, + onSaved: (_) => selector.selectPage( + context, + PayoutDestination.recipients, + ), + ), + ); + }, + ), + GoRoute( + name: PayoutRoutes.payment, + path: PayoutRoutes.paymentPath, + pageBuilder: (context, _) => NoTransitionPage( + child: PaymentPage( + onBack: (_) => context + .read() + .goBackFromPayment(context), + ), + ), + ), + GoRoute( + name: PayoutRoutes.settings, + path: PayoutRoutes.settingsPath, + pageBuilder: (_, __) => const NoTransitionPage( + child: ProfileSettingsPage(), + ), + ), + GoRoute( + name: PayoutRoutes.reports, + path: PayoutRoutes.reportsPath, + pageBuilder: (_, __) => const NoTransitionPage( + child: OperationHistoryPage(), + ), + ), + GoRoute( + name: PayoutRoutes.methods, + path: PayoutRoutes.methodsPath, + pageBuilder: (context, _) => NoTransitionPage( + child: PaymentConfigPage( + onWalletTap: (wallet) => context + .read() + .selectWallet(context, wallet), + ), + ), + ), + GoRoute( + name: PayoutRoutes.editWallet, + path: PayoutRoutes.editWalletPath, + pageBuilder: (context, _) { + final provider = context.read(); + final wallet = provider.walletsProvider.selectedWallet; + final loc = AppLocalizations.of(context)!; + + return NoTransitionPage( + child: wallet != null + ? WalletEditPage( + onBack: () => provider.goBackFromWalletEdit(context), + ) + : Center(child: Text(loc.noWalletSelected)), + ); + }, + ), + GoRoute( + name: PayoutRoutes.walletTopUp, + path: PayoutRoutes.walletTopUpPath, + pageBuilder: (context, _) => NoTransitionPage( + child: WalletTopUpPage( + onBack: () => context + .read() + .goBackFromWalletTopUp(context), + ), + ), + ), + ], +); diff --git a/frontend/pweb/lib/app/router/router.dart b/frontend/pweb/lib/app/router/router.dart index 1b2fdce..c707d67 100644 --- a/frontend/pweb/lib/app/router/router.dart +++ b/frontend/pweb/lib/app/router/router.dart @@ -1,13 +1,14 @@ import 'package:go_router/go_router.dart'; -import 'package:pweb/app/router/pages.dart'; import 'package:pweb/app/router/page_params.dart'; +import 'package:pweb/app/router/pages.dart'; +import 'package:pweb/app/router/payout_shell.dart'; +import 'package:pweb/app/router/payout_routes.dart'; import 'package:pweb/pages/2fa/page.dart'; +import 'package:pweb/pages/errors/not_found.dart'; +import 'package:pweb/pages/login/page.dart'; import 'package:pweb/pages/signup/page.dart'; import 'package:pweb/pages/verification/page.dart'; -import 'package:pweb/widgets/sidebar/page.dart'; -import 'package:pweb/pages/login/page.dart'; -import 'package:pweb/pages/errors/not_found.dart'; GoRouter createRouter() => GoRouter( @@ -16,40 +17,33 @@ GoRouter createRouter() => GoRouter( GoRoute( name: Pages.root.name, path: routerPage(Pages.root), - builder: (_, _) => const LoginPage(), - routes: [ - GoRoute( - name: Pages.login.name, - path: routerPage(Pages.login), - builder: (_, _) => const LoginPage(), - ), - GoRoute( - name: Pages.dashboard.name, - path: routerPage(Pages.dashboard), - builder: (_, _) => const PageSelector(), - ), - GoRoute( - name: Pages.sfactor.name, - path: routerPage(Pages.sfactor), - builder: (context, _) => TwoFactorCodePage( - onVerificationSuccess: () { - // trigger organization load - context.goNamed(Pages.dashboard.name); - }, - ), - ), - GoRoute( - name: Pages.signup.name, - path: routerPage(Pages.signup), - builder: (_, _) => const SignUpPage(), - ), - GoRoute( - name: Pages.verify.name, - path: '${routerPage(Pages.verify)}${routerAddParam(PageParams.token)}', - builder: (_, state) => AccountVerificationPage(token: state.pathParameters[PageParams.token.name]!), - ), - ], + builder: (_, __) => const LoginPage(), ), + GoRoute( + name: Pages.login.name, + path: routerPage(Pages.login), + builder: (_, __) => const LoginPage(), + ), + GoRoute( + name: Pages.sfactor.name, + path: routerPage(Pages.sfactor), + builder: (context, _) => TwoFactorCodePage( + onVerificationSuccess: () => context.goNamed(PayoutRoutes.dashboard), + ), + ), + GoRoute( + name: Pages.signup.name, + path: routerPage(Pages.signup), + builder: (_, __) => const SignUpPage(), + ), + GoRoute( + name: Pages.verify.name, + path: '${routerPage(Pages.verify)}${routerAddParam(PageParams.token)}', + builder: (_, state) => AccountVerificationPage( + token: state.pathParameters[PageParams.token.name]!, + ), + ), + payoutShellRoute(), ], - errorBuilder: (_, _) => const NotFoundPage(), -); + errorBuilder: (_, __) => const NotFoundPage(), +); \ No newline at end of file diff --git a/frontend/pweb/lib/data/mappers/wallet_ui.dart b/frontend/pweb/lib/data/mappers/wallet_ui.dart index 0e3d962..396ee31 100644 --- a/frontend/pweb/lib/data/mappers/wallet_ui.dart +++ b/frontend/pweb/lib/data/mappers/wallet_ui.dart @@ -21,6 +21,10 @@ extension WalletUiMapper on domain.WalletModel { currency: currency, isHidden: true, calculatedAt: balance?.calculatedAt ?? DateTime.now(), + depositAddress: depositAddress, + network: asset.chain, + tokenSymbol: asset.tokenSymbol, + contractAddress: asset.contractAddress, ); } } diff --git a/frontend/pweb/lib/l10n/en.arb b/frontend/pweb/lib/l10n/en.arb index df0f5dd..2ec2221 100644 --- a/frontend/pweb/lib/l10n/en.arb +++ b/frontend/pweb/lib/l10n/en.arb @@ -469,6 +469,17 @@ "walletNameUpdateFailed": "Failed to update wallet name", "walletNameSaved": "Wallet name saved", "topUpBalance": "Top Up Balance", + "walletTopUpTitle": "Add funds to wallet", + "walletTopUpDetailsTitle": "Funding details", + "walletTopUpDescription": "Send funds to this address to increase your wallet balance.", + "walletTopUpAssetLabel": "Asset", + "walletTopUpNetworkLabel": "Network", + "walletTopUpAddressLabel": "Deposit address", + "walletTopUpQrLabel": "QR code for deposit", + "walletTopUpHint": "Only send funds on the specified network. Deposits may take a few minutes to confirm.", + "walletTopUpUnavailable": "Top-up details are unavailable for this wallet yet.", + "copyAddress": "Copy address", + "addressCopied": "Address copied", "addFunctionality": "Add functionality", "walletHistoryEmpty": "No history yet", "colType": "Type", diff --git a/frontend/pweb/lib/l10n/ru.arb b/frontend/pweb/lib/l10n/ru.arb index 0bd3b9f..d79cca5 100644 --- a/frontend/pweb/lib/l10n/ru.arb +++ b/frontend/pweb/lib/l10n/ru.arb @@ -470,6 +470,17 @@ "walletNameUpdateFailed": "Не удалось обновить название кошелька", "walletNameSaved": "Название кошелька сохранено", "topUpBalance": "Пополнить баланс", + "walletTopUpTitle": "Пополнение кошелька", + "walletTopUpDetailsTitle": "Данные для пополнения", + "walletTopUpDescription": "Отправьте средства на этот адрес, чтобы пополнить баланс кошелька.", + "walletTopUpAssetLabel": "Актив", + "walletTopUpNetworkLabel": "Сеть", + "walletTopUpAddressLabel": "Адрес для пополнения", + "walletTopUpQrLabel": "QR-код для пополнения", + "walletTopUpHint": "Отправляйте средства только в указанной сети. Подтверждение может занять несколько минут.", + "walletTopUpUnavailable": "Данные для пополнения пока недоступны для этого кошелька.", + "copyAddress": "Скопировать адрес", + "addressCopied": "Адрес скопирован", "addFunctionality": "Добавить функциональность", "walletHistoryEmpty": "История пуста", "colType": "Тип", diff --git a/frontend/pweb/lib/models/wallet.dart b/frontend/pweb/lib/models/wallet.dart index 1d663a7..c902ac3 100644 --- a/frontend/pweb/lib/models/wallet.dart +++ b/frontend/pweb/lib/models/wallet.dart @@ -9,6 +9,10 @@ class Wallet { final Currency currency; final bool isHidden; final DateTime calculatedAt; + final String? depositAddress; + final String? network; + final String? tokenSymbol; + final String? contractAddress; Wallet({ required this.id, @@ -18,6 +22,10 @@ class Wallet { required this.currency, required this.calculatedAt, this.isHidden = true, + this.depositAddress, + this.network, + this.tokenSymbol, + this.contractAddress, }); Wallet copyWith({ @@ -27,6 +35,10 @@ class Wallet { Currency? currency, String? walletUserID, bool? isHidden, + String? depositAddress, + String? network, + String? tokenSymbol, + String? contractAddress, }) => Wallet( id: id ?? this.id, name: name ?? this.name, @@ -35,5 +47,9 @@ class Wallet { walletUserID: walletUserID ?? this.walletUserID, isHidden: isHidden ?? this.isHidden, calculatedAt: calculatedAt, + depositAddress: depositAddress ?? this.depositAddress, + network: network ?? this.network, + tokenSymbol: tokenSymbol ?? this.tokenSymbol, + contractAddress: contractAddress ?? this.contractAddress, ); -} \ No newline at end of file +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/balance.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/balance.dart index 0b8d0e0..5afc3bb 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/balance.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/balance.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:pweb/models/wallet.dart'; import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart'; import 'package:pweb/providers/wallets.dart'; @@ -9,7 +10,9 @@ import 'package:pweb/generated/i18n/app_localizations.dart'; class BalanceWidget extends StatelessWidget { - const BalanceWidget({super.key}); + final ValueChanged onTopUp; + + const BalanceWidget({super.key, required this.onTopUp}); @override Widget build(BuildContext context) { @@ -30,6 +33,7 @@ class BalanceWidget extends StatelessWidget { WalletCarousel( wallets: wallets, onWalletChanged: walletsProvider.selectWallet, + onTopUp: onTopUp, ); } } diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/card.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/card.dart index 3586bd8..70bf526 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/card.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/card.dart @@ -12,10 +12,12 @@ import 'package:pweb/providers/wallets.dart'; class WalletCard extends StatelessWidget { final Wallet wallet; + final VoidCallback onTopUp; const WalletCard({ super.key, required this.wallet, + required this.onTopUp, }); @override @@ -43,7 +45,7 @@ class WalletCard extends StatelessWidget { ), BalanceAddFunds( onTopUp: () { - // TODO: Implement top-up functionality + onTopUp(); }, ), ], @@ -51,4 +53,4 @@ class WalletCard extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart index 426cd7b..1d98204 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart @@ -12,11 +12,13 @@ import 'package:pweb/providers/carousel.dart'; class WalletCarousel extends StatefulWidget { final List wallets; final ValueChanged onWalletChanged; + final ValueChanged onTopUp; const WalletCarousel({ super.key, required this.wallets, required this.onWalletChanged, + required this.onTopUp, }); @override @@ -33,6 +35,11 @@ class _WalletCarouselState extends State { _pageController = PageController( viewportFraction: WalletCardConfig.viewportFraction, ); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.wallets.isNotEmpty) { + widget.onWalletChanged(widget.wallets[_currentPage]); + } + }); } @override @@ -83,7 +90,10 @@ class _WalletCarouselState extends State { itemBuilder: (context, index) { return Padding( padding: WalletCardConfig.cardPadding, - child: WalletCard(wallet: widget.wallets[index]), + child: WalletCard( + wallet: widget.wallets[index], + onTopUp: () => widget.onTopUp(widget.wallets[index]), + ), ); }, ), @@ -110,4 +120,4 @@ class _WalletCarouselState extends State { ], ); } -} \ No newline at end of file +} diff --git a/frontend/pweb/lib/pages/dashboard/dashboard.dart b/frontend/pweb/lib/pages/dashboard/dashboard.dart index 6ec2d48..20e827f 100644 --- a/frontend/pweb/lib/pages/dashboard/dashboard.dart +++ b/frontend/pweb/lib/pages/dashboard/dashboard.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/recipient/recipient.dart'; +import 'package:pweb/models/wallet.dart'; import 'package:pweb/pages/dashboard/buttons/balance/balance.dart'; import 'package:pweb/pages/dashboard/buttons/buttons.dart'; import 'package:pweb/pages/dashboard/payouts/multiple/title.dart'; @@ -22,11 +23,13 @@ class AppSpacing { class DashboardPage extends StatefulWidget { final ValueChanged onRecipientSelected; final void Function(PaymentType type) onGoToPaymentWithoutRecipient; + final ValueChanged onTopUp; const DashboardPage({ super.key, required this.onRecipientSelected, required this.onGoToPaymentWithoutRecipient, + required this.onTopUp, }); @override @@ -75,7 +78,9 @@ class _DashboardPageState extends State { ], ), const SizedBox(height: AppSpacing.medium), - BalanceWidget(), + BalanceWidget( + onTopUp: widget.onTopUp, + ), const SizedBox(height: AppSpacing.small), if (_showContainerMultiple) TitleMultiplePayout(), const SizedBox(height: AppSpacing.medium), diff --git a/frontend/pweb/lib/pages/payment_methods/page.dart b/frontend/pweb/lib/pages/payment_methods/page.dart index fe907a8..ad22b9c 100644 --- a/frontend/pweb/lib/pages/payment_methods/page.dart +++ b/frontend/pweb/lib/pages/payment_methods/page.dart @@ -62,7 +62,7 @@ class _PaymentPageState extends State { final recipientProvider = context.read(); recipientProvider.setCurrentObject(recipient.id); - pageSelector.selectRecipient(recipient); + pageSelector.selectRecipient(context, recipient); _flowProvider.reset(pageSelector); _clearSearchField(); } @@ -72,7 +72,7 @@ class _PaymentPageState extends State { final recipientProvider = context.read(); recipientProvider.setCurrentObject(null); - pageSelector.selectRecipient(null); + pageSelector.selectRecipient(context, null); _flowProvider.reset(pageSelector); _clearSearchField(); } diff --git a/frontend/pweb/lib/pages/payment_methods/widgets/payment_page_body.dart b/frontend/pweb/lib/pages/payment_methods/widgets/payment_page_body.dart index e33dcfe..a49cc49 100644 --- a/frontend/pweb/lib/pages/payment_methods/widgets/payment_page_body.dart +++ b/frontend/pweb/lib/pages/payment_methods/widgets/payment_page_body.dart @@ -141,7 +141,7 @@ class PaymentBackButton extends StatelessWidget { if (onBack != null) { onBack!(pageSelector.selectedRecipient); } else { - pageSelector.goBackFromPayment(); + pageSelector.goBackFromPayment(context); } }, ), diff --git a/frontend/pweb/lib/pages/payout_page/wallet/edit/buttons/send.dart b/frontend/pweb/lib/pages/payout_page/wallet/edit/buttons/send.dart index 4232719..a3c48e3 100644 --- a/frontend/pweb/lib/pages/payout_page/wallet/edit/buttons/send.dart +++ b/frontend/pweb/lib/pages/payout_page/wallet/edit/buttons/send.dart @@ -25,7 +25,7 @@ class SendPayoutButton extends StatelessWidget { final wallet = walletsProvider.selectedWallet; if (wallet != null) { - pageSelectorProvider.startPaymentFromWallet(wallet); + pageSelectorProvider.startPaymentFromWallet(context, wallet); } }, child: Text(loc.payoutNavSendPayout), diff --git a/frontend/pweb/lib/pages/payout_page/wallet/edit/buttons/top_up.dart b/frontend/pweb/lib/pages/payout_page/wallet/edit/buttons/top_up.dart index 7e9b69c..ace5cdd 100644 --- a/frontend/pweb/lib/pages/payout_page/wallet/edit/buttons/top_up.dart +++ b/frontend/pweb/lib/pages/payout_page/wallet/edit/buttons/top_up.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'package:pweb/providers/page_selector.dart'; +import 'package:pweb/providers/wallets.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; @@ -15,9 +19,14 @@ class TopUpButton extends StatelessWidget{ elevation: 0, ), onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(loc.addFunctionality)), - ); + final wallet = context.read().selectedWallet; + if (wallet == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(loc.noWalletSelected)), + ); + return; + } + context.read().openWalletTopUp(context, wallet); }, child: Text(loc.topUpBalance), ); diff --git a/frontend/pweb/lib/pages/wallet_top_up/address_block.dart b/frontend/pweb/lib/pages/wallet_top_up/address_block.dart new file mode 100644 index 0000000..bfba856 --- /dev/null +++ b/frontend/pweb/lib/pages/wallet_top_up/address_block.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:qr_flutter/qr_flutter.dart'; + +import 'package:pweb/utils/dimensions.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class WalletTopUpAddressBlock extends StatelessWidget { + final String address; + final AppDimensions dimensions; + + const WalletTopUpAddressBlock({ + super.key, + required this.address, + required this.dimensions, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final loc = AppLocalizations.of(context)!; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + loc.walletTopUpAddressLabel, + style: theme.textTheme.titleSmall, + ), + TextButton.icon( + icon: const Icon(Icons.copy, size: 16), + label: Text(loc.copyAddress), + onPressed: () { + Clipboard.setData(ClipboardData(text: address)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(loc.addressCopied)), + ); + }, + ), + ], + ), + const SizedBox(height: 6), + Container( + width: double.infinity, + padding: EdgeInsets.all(dimensions.paddingMedium), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall), + border: Border.all(color: theme.colorScheme.outlineVariant), + color: theme.colorScheme.surfaceVariant.withOpacity(0.4), + ), + child: SelectableText( + address, + style: theme.textTheme.bodyLarge?.copyWith( + fontFeatures: const [FontFeature.tabularFigures()], + ), + ), + ), + SizedBox(height: dimensions.paddingLarge), + Text( + loc.walletTopUpQrLabel, + style: theme.textTheme.titleSmall, + ), + const SizedBox(height: 8), + Container( + padding: EdgeInsets.all(dimensions.paddingMedium), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall), + border: Border.all(color: theme.colorScheme.outlineVariant), + color: theme.colorScheme.surfaceVariant.withOpacity(0.4), + ), + child: Center( + child: QrImageView( + data: address, + backgroundColor: theme.colorScheme.onSecondary, + eyeStyle: QrEyeStyle( + eyeShape: QrEyeShape.square, + color: theme.colorScheme.onSurface, + ), + dataModuleStyle: QrDataModuleStyle( + dataModuleShape: QrDataModuleShape.square, + color: theme.colorScheme.onSurface, + ), + size: 220, + ), + ), + ), + ], + ); + } +} diff --git a/frontend/pweb/lib/pages/wallet_top_up/content.dart b/frontend/pweb/lib/pages/wallet_top_up/content.dart new file mode 100644 index 0000000..7f66779 --- /dev/null +++ b/frontend/pweb/lib/pages/wallet_top_up/content.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/models/wallet.dart'; +import 'package:pweb/pages/wallet_top_up/details.dart'; +import 'package:pweb/pages/wallet_top_up/header.dart'; +import 'package:pweb/pages/wallet_top_up/meta.dart'; +import 'package:pweb/utils/currency.dart'; +import 'package:pweb/utils/dimensions.dart'; + + +class WalletTopUpContent extends StatelessWidget { + final Wallet wallet; + final VoidCallback onBack; + + const WalletTopUpContent({ + super.key, + required this.wallet, + required this.onBack, + }); + + @override + Widget build(BuildContext context) { + final dimensions = AppDimensions(); + final theme = Theme.of(context); + + final address = _resolveAddress(wallet); + final network = wallet.network?.trim(); + final assetLabel = wallet.tokenSymbol ?? currencyCodeToSymbol(wallet.currency); + + return Align( + alignment: Alignment.topCenter, + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 960), + child: Padding( + padding: EdgeInsets.symmetric(vertical: dimensions.paddingLarge), + child: Material( + elevation: dimensions.elevationSmall, + color: theme.colorScheme.onSecondary, + borderRadius: BorderRadius.circular(dimensions.borderRadiusMedium), + child: Padding( + padding: EdgeInsets.all(dimensions.paddingXLarge), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + WalletTopUpHeader( + onBack: onBack, + walletName: wallet.name, + ), + SizedBox(height: dimensions.paddingLarge), + WalletTopUpMeta( + assetLabel: assetLabel, + network: network, + walletId: wallet.walletUserID, + ), + SizedBox(height: dimensions.paddingXLarge), + WalletTopUpDetails( + address: address, + dimensions: dimensions, + ), + ], + ), + ), + ), + ), + ), + ), + ); + } + + String? _resolveAddress(Wallet wallet) { + final candidate = wallet.depositAddress?.trim(); + if (candidate == null || candidate.isEmpty) return null; + return candidate; + } +} diff --git a/frontend/pweb/lib/pages/wallet_top_up/details.dart b/frontend/pweb/lib/pages/wallet_top_up/details.dart new file mode 100644 index 0000000..2417704 --- /dev/null +++ b/frontend/pweb/lib/pages/wallet_top_up/details.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/pages/wallet_top_up/address_block.dart'; +import 'package:pweb/utils/dimensions.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class WalletTopUpDetails extends StatelessWidget { + final String? address; + final AppDimensions dimensions; + + const WalletTopUpDetails({ + super.key, + required this.address, + required this.dimensions, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final loc = AppLocalizations.of(context)!; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + loc.walletTopUpDetailsTitle, + style: theme.textTheme.titleMedium, + ), + const SizedBox(height: 6), + Text( + loc.walletTopUpDescription, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + SizedBox(height: dimensions.paddingLarge), + if (address == null || address!.isEmpty) + Text( + loc.walletTopUpUnavailable, + style: theme.textTheme.bodyMedium, + ) + else ...[ + WalletTopUpAddressBlock( + address: address!, + dimensions: dimensions, + ), + SizedBox(height: dimensions.paddingLarge), + Text( + loc.walletTopUpHint, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + ], + ); + } +} diff --git a/frontend/pweb/lib/pages/wallet_top_up/header.dart b/frontend/pweb/lib/pages/wallet_top_up/header.dart new file mode 100644 index 0000000..fa5a23e --- /dev/null +++ b/frontend/pweb/lib/pages/wallet_top_up/header.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class WalletTopUpHeader extends StatelessWidget { + final VoidCallback onBack; + final String walletName; + + const WalletTopUpHeader({ + super.key, + required this.onBack, + required this.walletName, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final loc = AppLocalizations.of(context)!; + + return Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: onBack, + ), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + loc.walletTopUpTitle, + style: theme.textTheme.titleLarge, + ), + const SizedBox(height: 4), + Text( + walletName, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ], + ); + } +} diff --git a/frontend/pweb/lib/pages/wallet_top_up/info_chip.dart b/frontend/pweb/lib/pages/wallet_top_up/info_chip.dart new file mode 100644 index 0000000..fcb2ffc --- /dev/null +++ b/frontend/pweb/lib/pages/wallet_top_up/info_chip.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/utils/dimensions.dart'; + + +class WalletTopUpInfoChip extends StatelessWidget { + final String label; + final String value; + + const WalletTopUpInfoChip({ + super.key, + required this.label, + required this.value, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final dimensions = AppDimensions(); + + return Container( + padding: EdgeInsets.all(dimensions.paddingMedium), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall), + border: Border.all(color: theme.colorScheme.outlineVariant), + color: theme.colorScheme.surfaceVariant.withOpacity(0.4), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: theme.textTheme.labelMedium?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 4), + Text( + value, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } +} diff --git a/frontend/pweb/lib/pages/wallet_top_up/meta.dart b/frontend/pweb/lib/pages/wallet_top_up/meta.dart new file mode 100644 index 0000000..b9703d3 --- /dev/null +++ b/frontend/pweb/lib/pages/wallet_top_up/meta.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/pages/wallet_top_up/info_chip.dart'; +import 'package:pweb/utils/dimensions.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class WalletTopUpMeta extends StatelessWidget { + final String assetLabel; + final String walletId; + final String? network; + + const WalletTopUpMeta({ + super.key, + required this.assetLabel, + required this.walletId, + this.network, + }); + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context)!; + final dimensions = AppDimensions(); + + return Wrap( + spacing: dimensions.paddingLarge, + runSpacing: dimensions.paddingLarge, + children: [ + WalletTopUpInfoChip(label: loc.walletTopUpAssetLabel, value: assetLabel), + if (network != null && network!.isNotEmpty) + WalletTopUpInfoChip(label: loc.walletTopUpNetworkLabel, value: network!), + WalletTopUpInfoChip(label: loc.walletId, value: walletId), + ], + ); + } +} diff --git a/frontend/pweb/lib/pages/wallet_top_up/page.dart b/frontend/pweb/lib/pages/wallet_top_up/page.dart new file mode 100644 index 0000000..6cabada --- /dev/null +++ b/frontend/pweb/lib/pages/wallet_top_up/page.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pweb/pages/wallet_top_up/content.dart'; +import 'package:pweb/providers/wallets.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class WalletTopUpPage extends StatelessWidget { + final VoidCallback onBack; + + const WalletTopUpPage({super.key, required this.onBack}); + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context)!; + + return Consumer( + builder: (context, provider, child) { + if (provider.isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + if (provider.error != null) { + return Center( + child: Text(loc.notificationError(provider.error.toString())), + ); + } + + final wallet = provider.selectedWallet; + if (wallet == null) { + return Center(child: Text(loc.noWalletSelected)); + } + + return WalletTopUpContent( + wallet: wallet, + onBack: onBack, + ); + }, + ); + } +} diff --git a/frontend/pweb/lib/providers/page_selector.dart b/frontend/pweb/lib/providers/page_selector.dart index 20216f5..0a22ce8 100644 --- a/frontend/pweb/lib/providers/page_selector.dart +++ b/frontend/pweb/lib/providers/page_selector.dart @@ -12,6 +12,7 @@ import 'package:pshared/provider/recipient/provider.dart'; import 'package:pweb/models/wallet.dart'; import 'package:pweb/providers/wallets.dart'; //import 'package:pweb/services/amplitude.dart'; +import 'package:pweb/app/router/payout_routes.dart'; import 'package:pweb/widgets/sidebar/destinations.dart'; @@ -41,44 +42,93 @@ class PageSelectorProvider extends ChangeNotifier { methodsProvider = methodsProv; } - void selectPage(PayoutDestination dest) { - _selected = dest; + void syncDestination(PayoutDestination destination) { + if (_selected == destination) return; + _selected = destination; notifyListeners(); } - void selectRecipient(Recipient? recipient, {bool fromList = false}) { + void selectPage( + BuildContext context, + PayoutDestination dest, { + bool replace = true, + }) { + _selected = dest; + notifyListeners(); + _navigateTo(context, dest, replace: replace); + } + + void selectRecipient( + BuildContext context, + Recipient? recipient, { + bool fromList = false, + }) { + final previousDestination = _selected; recipientProvider.setCurrentObject(recipient?.id); _cameFromRecipientList = fromList; _setPreviousDestination(); _selected = PayoutDestination.payment; notifyListeners(); + if (previousDestination != PayoutDestination.payment) { + _navigateTo(context, PayoutDestination.payment, replace: false); + } } - void editRecipient(Recipient? recipient, {bool fromList = false}) { + void editRecipient( + BuildContext context, + Recipient? recipient, { + bool fromList = false, + }) { + final previousDestination = _selected; recipientProvider.setCurrentObject(recipient?.id); _cameFromRecipientList = fromList; _selected = PayoutDestination.addrecipient; notifyListeners(); + if (previousDestination != PayoutDestination.addrecipient) { + _navigateTo(context, PayoutDestination.addrecipient, replace: false); + } } - void goToAddRecipient() { + void goToAddRecipient(BuildContext context) { // AmplitudeService.recipientAddStarted(); + final previousDestination = _selected; recipientProvider.setCurrentObject(null); _selected = PayoutDestination.addrecipient; _cameFromRecipientList = false; notifyListeners(); + if (previousDestination != PayoutDestination.addrecipient) { + _navigateTo(context, PayoutDestination.addrecipient, replace: false); + } } - void startPaymentWithoutRecipient(PaymentType type) { + void startPaymentWithoutRecipient( + BuildContext context, + PaymentType type, + ) { + final previousDestination = _selected; recipientProvider.setCurrentObject(null); _type = type; _cameFromRecipientList = false; _setPreviousDestination(); _selected = PayoutDestination.payment; notifyListeners(); + if (previousDestination != PayoutDestination.payment) { + _navigateTo(context, PayoutDestination.payment, replace: false); + } } - void goBackFromPayment() { + void goBackFromPayment(BuildContext context) { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } else { + _navigateTo( + context, + _previousDestination ?? + (_cameFromRecipientList + ? PayoutDestination.recipients + : PayoutDestination.dashboard), + ); + } _selected = _previousDestination ?? (_cameFromRecipientList ? PayoutDestination.recipients @@ -89,22 +139,55 @@ class PageSelectorProvider extends ChangeNotifier { notifyListeners(); } - void goBackFromWalletEdit() { - selectPage(PayoutDestination.methods); + void goBackFromWalletEdit(BuildContext context) { + selectPage(context, PayoutDestination.methods); } - void selectWallet(Wallet wallet) { + void selectWallet(BuildContext context, Wallet wallet) { + final previousDestination = _selected; walletsProvider.selectWallet(wallet); _selected = PayoutDestination.editwallet; notifyListeners(); + if (previousDestination != PayoutDestination.editwallet) { + _navigateTo(context, PayoutDestination.editwallet, replace: false); + } } - void startPaymentFromWallet(Wallet wallet) { + void startPaymentFromWallet(BuildContext context, Wallet wallet) { + final previousDestination = _selected; _type = PaymentType.wallet; _cameFromRecipientList = false; _setPreviousDestination(); _selected = PayoutDestination.payment; notifyListeners(); + if (previousDestination != PayoutDestination.payment) { + _navigateTo(context, PayoutDestination.payment, replace: false); + } + } + + void openWalletTopUp(BuildContext context, Wallet wallet) { + final previousDestination = _selected; + _setPreviousDestination(); + walletsProvider.selectWallet(wallet); + _selected = PayoutDestination.walletTopUp; + notifyListeners(); + if (previousDestination != PayoutDestination.walletTopUp) { + _navigateTo(context, PayoutDestination.walletTopUp, replace: false); + } + } + + void goBackFromWalletTopUp(BuildContext context) { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } else { + _navigateTo( + context, + _previousDestination ?? PayoutDestination.dashboard, + ); + } + _selected = _previousDestination ?? PayoutDestination.dashboard; + _previousDestination = null; + notifyListeners(); } PaymentMethod? getPaymentMethodForWallet(Wallet wallet) { @@ -113,8 +196,7 @@ class PageSelectorProvider extends ChangeNotifier { } return methodsProvider.methods.firstWhereOrNull( - (method) => method.type == PaymentType.wallet && - (method.description?.contains(wallet.walletUserID) ?? false), + (method) => method.type == PaymentType.wallet && (method.description?.contains(wallet.walletUserID) ?? false), ); } @@ -159,11 +241,24 @@ class PageSelectorProvider extends ChangeNotifier { } void _setPreviousDestination() { - if (_selected != PayoutDestination.payment) { + if (_selected != PayoutDestination.payment && + _selected != PayoutDestination.walletTopUp) { _previousDestination = _selected; } } + void _navigateTo( + BuildContext context, + PayoutDestination destination, { + bool replace = true, + }) { + if (replace) { + context.goToPayout(destination); + } else { + context.pushToPayout(destination); + } + } + Recipient? get selectedRecipient => recipientProvider.currentObject; Wallet? get selectedWallet => walletsProvider.selectedWallet; } diff --git a/frontend/pweb/lib/services/wallets.dart b/frontend/pweb/lib/services/wallets.dart index 3dee265..c14c87c 100644 --- a/frontend/pweb/lib/services/wallets.dart +++ b/frontend/pweb/lib/services/wallets.dart @@ -1,6 +1,5 @@ import 'package:pshared/service/wallet.dart' as shared_wallet_service; -import 'package:pweb/models/currency.dart'; import 'package:pweb/models/wallet.dart'; import 'package:pweb/data/mappers/wallet_ui.dart'; @@ -10,27 +9,6 @@ abstract class WalletsService { Future getBalance(String organizationRef, String walletRef); } -class MockWalletsService implements WalletsService { - final List _wallets = [ - Wallet(id: '1124', walletUserID: 'WA-12345667', name: 'Main Wallet', balance: 10000000.0, currency: Currency.rub, calculatedAt: DateTime.now()), - Wallet(id: '2124', walletUserID: 'WA-76654321', name: 'Savings', balance: 2500.5, currency: Currency.usd, calculatedAt: DateTime.now()), - ]; - - @override - Future> getWallets(String _) async { - return _wallets; - } - - @override - Future getBalance(String _, String walletRef) async { - final wallet = _wallets.firstWhere( - (w) => w.id == walletRef, - orElse: () => throw Exception('Wallet not found'), - ); - return wallet.balance; - } -} - class ApiWalletsService implements WalletsService { @override Future> getWallets(String organizationRef) async { diff --git a/frontend/pweb/lib/widgets/sidebar/destinations.dart b/frontend/pweb/lib/widgets/sidebar/destinations.dart index 899e805..c651cd7 100644 --- a/frontend/pweb/lib/widgets/sidebar/destinations.dart +++ b/frontend/pweb/lib/widgets/sidebar/destinations.dart @@ -12,7 +12,8 @@ enum PayoutDestination { methods(Icons.credit_card, 'methods'), payment(Icons.payment, 'payout'), addrecipient(Icons.app_registration, 'add recipient'), - editwallet(Icons.wallet, 'edit wallet'); + editwallet(Icons.wallet, 'edit wallet'), + walletTopUp(Icons.qr_code_2_outlined, 'wallet top up'); const PayoutDestination(this.icon, this.labelKey); @@ -41,6 +42,8 @@ enum PayoutDestination { return loc.addRecipient; case PayoutDestination.editwallet: return loc.editWallet; + case PayoutDestination.walletTopUp: + return loc.walletTopUpTitle; } } } diff --git a/frontend/pweb/lib/widgets/sidebar/page.dart b/frontend/pweb/lib/widgets/sidebar/page.dart index e6a76cf..736e0a1 100644 --- a/frontend/pweb/lib/widgets/sidebar/page.dart +++ b/frontend/pweb/lib/widgets/sidebar/page.dart @@ -2,31 +2,29 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:go_router/go_router.dart'; + import 'package:pshared/models/resources.dart'; import 'package:pshared/provider/permissions.dart'; -import 'package:pshared/provider/recipient/provider.dart'; -import 'package:pweb/pages/address_book/form/page.dart'; -import 'package:pweb/pages/address_book/page/page.dart'; import 'package:pweb/pages/loader.dart'; -import 'package:pweb/pages/payment_methods/page.dart'; -import 'package:pweb/pages/payout_page/page.dart'; -import 'package:pweb/pages/payout_page/wallet/edit/page.dart'; -import 'package:pweb/pages/report/page.dart'; -import 'package:pweb/pages/settings/profile/page.dart'; -import 'package:pweb/pages/dashboard/dashboard.dart'; import 'package:pweb/providers/page_selector.dart'; import 'package:pweb/utils/logout.dart'; import 'package:pweb/widgets/appbar/app_bar.dart'; -import 'package:pweb/widgets/error/snackbar.dart'; import 'package:pweb/widgets/sidebar/destinations.dart'; import 'package:pweb/widgets/sidebar/sidebar.dart'; - -import 'package:pweb/generated/i18n/app_localizations.dart'; +import 'package:pweb/app/router/payout_routes.dart'; class PageSelector extends StatelessWidget { - const PageSelector({super.key}); + final Widget child; + final GoRouterState routerState; + + const PageSelector({ + super.key, + required this.child, + required this.routerState, + }); @override Widget build(BuildContext context) => PageViewLoader( @@ -36,88 +34,29 @@ class PageSelector extends StatelessWidget { final provider = context.watch(); - final loc = AppLocalizations.of(context)!; - final bool restrictedAccess = !permissions.canRead(ResourceType.chainWallets); final allowedDestinations = restrictedAccess ? { PayoutDestination.settings, PayoutDestination.methods, PayoutDestination.editwallet, + PayoutDestination.walletTopUp, } : PayoutDestination.values.toSet(); - final selected = allowedDestinations.contains(provider.selected) - ? provider.selected + final routeDestination = _destinationFromState(routerState) ?? provider.selected; + final selected = allowedDestinations.contains(routeDestination) + ? routeDestination : (restrictedAccess ? PayoutDestination.settings : PayoutDestination.dashboard); - if (selected != provider.selected) { - WidgetsBinding.instance.addPostFrameCallback((_) => provider.selectPage(selected)); + if (selected != routeDestination) { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.goToPayout(selected); + }); } - Widget content; - switch (selected) { - case PayoutDestination.dashboard: - content = DashboardPage( - onRecipientSelected: (recipient) => provider.selectRecipient(recipient), - onGoToPaymentWithoutRecipient: provider.startPaymentWithoutRecipient, - ); - break; - - case PayoutDestination.recipients: - content = RecipientAddressBookPage( - onRecipientSelected: (recipient) => - provider.selectRecipient(recipient, fromList: true), - onAddRecipient: provider.goToAddRecipient, - onEditRecipient: provider.editRecipient, - onDeleteRecipient: (recipient) => executeActionWithNotification( - context: context, - action: () async => context.read().delete(recipient.id), - successMessage: loc.recipientDeletedSuccessfully, - errorMessage: loc.errorDeleteRecipient, - ), - ); - break; - - case PayoutDestination.addrecipient: - final recipient = provider.recipientProvider.currentObject; - content = AdressBookRecipientForm( - recipient: recipient, - onSaved: (_) => provider.selectPage(PayoutDestination.recipients), - ); - break; - - case PayoutDestination.payment: - content = PaymentPage( - onBack: (_) => provider.goBackFromPayment(), - ); - break; - - case PayoutDestination.settings: - content = ProfileSettingsPage(); - break; - - case PayoutDestination.reports: - content = OperationHistoryPage(); - break; - - case PayoutDestination.methods: - content = PaymentConfigPage( - onWalletTap: provider.selectWallet, - ); - break; - - case PayoutDestination.editwallet: - final wallet = provider.walletsProvider.selectedWallet; - content = wallet != null - ? WalletEditPage( - onBack: provider.goBackFromWalletEdit, - ) - : Center(child: Text(loc.noWalletSelected)); - break; - - default: - content = Text(selected.name); + if (provider.selected != selected) { + provider.syncDestination(selected); } return Scaffold( @@ -134,14 +73,49 @@ class PageSelector extends StatelessWidget { children: [ PayoutSidebar( selected: selected, - onSelected: provider.selectPage, + onSelected: context.goToPayout, onLogout: () => logoutUtil(context), ), - Expanded(child: content), + Expanded(child: child), ], ), ), ); }, )); + + PayoutDestination? _destinationFromState(GoRouterState state) { + final byName = PayoutRoutes.destinationFor(state.name); + if (byName != null) return byName; + + final location = state.matchedLocation; + if (location.startsWith(PayoutRoutes.editWalletPath)) { + return PayoutDestination.editwallet; + } + if (location.startsWith(PayoutRoutes.walletTopUpPath)) { + return PayoutDestination.walletTopUp; + } + if (location.startsWith(PayoutRoutes.methodsPath)) { + return PayoutDestination.methods; + } + if (location.startsWith(PayoutRoutes.paymentPath)) { + return PayoutDestination.payment; + } + if (location.startsWith(PayoutRoutes.addRecipientPath)) { + return PayoutDestination.addrecipient; + } + if (location.startsWith(PayoutRoutes.recipientsPath)) { + return PayoutDestination.recipients; + } + if (location.startsWith(PayoutRoutes.settingsPath)) { + return PayoutDestination.settings; + } + if (location.startsWith(PayoutRoutes.reportsPath)) { + return PayoutDestination.reports; + } + if (location.startsWith(PayoutRoutes.dashboardPath)) { + return PayoutDestination.dashboard; + } + return null; + } } diff --git a/frontend/pweb/pubspec.yaml b/frontend/pweb/pubspec.yaml index fd9e4e1..aeb7e35 100644 --- a/frontend/pweb/pubspec.yaml +++ b/frontend/pweb/pubspec.yaml @@ -67,6 +67,7 @@ dependencies: syncfusion_flutter_charts: ^31.2.10 flutter_multi_formatter: ^2.13.7 dotted_border: ^3.1.0 + qr_flutter: ^4.1.0 @@ -96,7 +97,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - resources/logo.png + - resources/icon.png - resources/logo.si # An image asset can refer to one or more resolution-specific "variants", see