Navigation now flows entirely through go_router

This commit is contained in:
Arseni
2025-12-08 17:40:25 +03:00
parent f478219990
commit 64ad8c8b38
21 changed files with 705 additions and 550 deletions

View File

@@ -1,6 +1,8 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:collection/collection.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pweb/widgets/sidebar/destinations.dart'; import 'package:pweb/widgets/sidebar/destinations.dart';
@@ -17,6 +19,9 @@ class PayoutRoutes {
static const editWallet = 'payout-edit-wallet'; static const editWallet = 'payout-edit-wallet';
static const walletTopUp = 'payout-wallet-top-up'; static const walletTopUp = 'payout-wallet-top-up';
static const paymentTypeQuery = 'paymentType';
static const returnToQuery = 'returnTo';
static const dashboardPath = '/dashboard'; static const dashboardPath = '/dashboard';
static const recipientsPath = '/dashboard/recipients'; static const recipientsPath = '/dashboard/recipients';
static const addRecipientPath = '/dashboard/recipients/add'; static const addRecipientPath = '/dashboard/recipients/add';
@@ -103,10 +108,70 @@ class PayoutRoutes {
return null; return null;
} }
} }
static Map<String, String> buildQueryParameters({
PaymentType? paymentType,
PayoutDestination? returnTo,
}) {
final params = <String, String>{
if (paymentType != null) paymentTypeQuery: paymentType.name,
if (returnTo != null) returnToQuery: nameFor(returnTo),
};
return params;
}
static PaymentType? paymentTypeFromState(GoRouterState state) =>
paymentTypeFromRaw(state.uri.queryParameters[paymentTypeQuery]);
static PaymentType? paymentTypeFromRaw(String? raw) => raw == null
? null
: PaymentType.values.firstWhereOrNull((type) => type.name == raw);
static PayoutDestination fallbackFromState(
GoRouterState state, {
PayoutDestination defaultDestination = PayoutDestination.dashboard,
}) {
final raw = state.uri.queryParameters[returnToQuery];
return destinationFor(raw) ?? defaultDestination;
}
} }
extension PayoutNavigation on BuildContext { extension PayoutNavigation on BuildContext {
void goToPayout(PayoutDestination destination) => goNamed(PayoutRoutes.nameFor(destination)); void goToPayout(PayoutDestination destination) => goNamed(PayoutRoutes.nameFor(destination));
void pushToPayout(PayoutDestination destination) => pushNamed(PayoutRoutes.nameFor(destination)); void pushToPayout(PayoutDestination destination) => pushNamed(PayoutRoutes.nameFor(destination));
}
void goToPayment({
PaymentType? paymentType,
PayoutDestination? returnTo,
}) =>
goNamed(
PayoutRoutes.payment,
queryParameters: PayoutRoutes.buildQueryParameters(
paymentType: paymentType,
returnTo: returnTo,
),
);
void pushToPayment({
PaymentType? paymentType,
PayoutDestination? returnTo,
}) =>
pushNamed(
PayoutRoutes.payment,
queryParameters: PayoutRoutes.buildQueryParameters(
paymentType: paymentType,
returnTo: returnTo,
),
);
void pushToWalletTopUp({PayoutDestination? returnTo}) => pushNamed(
PayoutRoutes.walletTopUp,
queryParameters: PayoutRoutes.buildQueryParameters(returnTo: returnTo),
);
void pushToEditWallet({PayoutDestination? returnTo}) => pushNamed(
PayoutRoutes.editWallet,
queryParameters: PayoutRoutes.buildQueryParameters(returnTo: returnTo),
);
}

View File

@@ -4,10 +4,13 @@ import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/recipient/provider.dart'; import 'package:pshared/provider/recipient/provider.dart';
import 'package:pweb/app/router/pages.dart'; import 'package:pweb/app/router/pages.dart';
import 'package:pweb/app/router/payout_routes.dart'; import 'package:pweb/app/router/payout_routes.dart';
import 'package:pweb/models/wallet.dart';
import 'package:pweb/pages/address_book/form/page.dart'; import 'package:pweb/pages/address_book/form/page.dart';
import 'package:pweb/pages/address_book/page/page.dart'; import 'package:pweb/pages/address_book/page/page.dart';
import 'package:pweb/pages/dashboard/dashboard.dart'; import 'package:pweb/pages/dashboard/dashboard.dart';
@@ -17,7 +20,7 @@ import 'package:pweb/pages/payout_page/wallet/edit/page.dart';
import 'package:pweb/pages/report/page.dart'; import 'package:pweb/pages/report/page.dart';
import 'package:pweb/pages/settings/profile/page.dart'; import 'package:pweb/pages/settings/profile/page.dart';
import 'package:pweb/pages/wallet_top_up/page.dart'; import 'package:pweb/pages/wallet_top_up/page.dart';
import 'package:pweb/providers/page_selector.dart'; import 'package:pweb/providers/wallets.dart';
import 'package:pweb/widgets/error/snackbar.dart'; import 'package:pweb/widgets/error/snackbar.dart';
import 'package:pweb/widgets/sidebar/destinations.dart'; import 'package:pweb/widgets/sidebar/destinations.dart';
import 'package:pweb/widgets/sidebar/page.dart'; import 'package:pweb/widgets/sidebar/page.dart';
@@ -36,15 +39,22 @@ RouteBase payoutShellRoute() => ShellRoute(
path: routerPage(Pages.dashboard), path: routerPage(Pages.dashboard),
pageBuilder: (context, _) => NoTransitionPage( pageBuilder: (context, _) => NoTransitionPage(
child: DashboardPage( child: DashboardPage(
onRecipientSelected: (recipient) => context onRecipientSelected: (recipient) => _startPayment(
.read<PageSelectorProvider>() context,
.selectRecipient(context, recipient), recipient: recipient,
onGoToPaymentWithoutRecipient: (type) => context returnTo: PayoutDestination.dashboard,
.read<PageSelectorProvider>() ),
.startPaymentWithoutRecipient(context, type), onGoToPaymentWithoutRecipient: (type) => _startPayment(
onTopUp: (wallet) => context context,
.read<PageSelectorProvider>() recipient: null,
.openWalletTopUp(context, wallet), paymentType: type,
returnTo: PayoutDestination.dashboard,
),
onTopUp: (wallet) => _openWalletTopUp(
context,
wallet,
returnTo: PayoutDestination.dashboard,
),
), ),
), ),
), ),
@@ -55,15 +65,16 @@ RouteBase payoutShellRoute() => ShellRoute(
final loc = AppLocalizations.of(context)!; final loc = AppLocalizations.of(context)!;
return NoTransitionPage( return NoTransitionPage(
child: RecipientAddressBookPage( child: RecipientAddressBookPage(
onRecipientSelected: (recipient) => context onRecipientSelected: (recipient) => _startPayment(
.read<PageSelectorProvider>() context,
.selectRecipient(context, recipient, fromList: true), recipient: recipient,
onAddRecipient: () => context returnTo: PayoutDestination.recipients,
.read<PageSelectorProvider>() ),
.goToAddRecipient(context), onAddRecipient: () => _openAddRecipient(context),
onEditRecipient: (recipient) => context onEditRecipient: (recipient) => _openAddRecipient(
.read<PageSelectorProvider>() context,
.editRecipient(context, recipient, fromList: true), recipient: recipient,
),
onDeleteRecipient: (recipient) => executeActionWithNotification( onDeleteRecipient: (recipient) => executeActionWithNotification(
context: context, context: context,
action: () async => action: () async =>
@@ -79,15 +90,11 @@ RouteBase payoutShellRoute() => ShellRoute(
name: PayoutRoutes.addRecipient, name: PayoutRoutes.addRecipient,
path: PayoutRoutes.addRecipientPath, path: PayoutRoutes.addRecipientPath,
pageBuilder: (context, _) { pageBuilder: (context, _) {
final selector = context.read<PageSelectorProvider>(); final recipient = context.read<RecipientsProvider>().currentObject;
final recipient = selector.recipientProvider.currentObject;
return NoTransitionPage( return NoTransitionPage(
child: AdressBookRecipientForm( child: AdressBookRecipientForm(
recipient: recipient, recipient: recipient,
onSaved: (_) => selector.selectPage( onSaved: (_) => context.goToPayout(PayoutDestination.recipients),
context,
PayoutDestination.recipients,
),
), ),
); );
}, },
@@ -95,13 +102,20 @@ RouteBase payoutShellRoute() => ShellRoute(
GoRoute( GoRoute(
name: PayoutRoutes.payment, name: PayoutRoutes.payment,
path: PayoutRoutes.paymentPath, path: PayoutRoutes.paymentPath,
pageBuilder: (context, _) => NoTransitionPage( pageBuilder: (context, state) {
child: PaymentPage( final fallbackDestination = PayoutRoutes.fallbackFromState(
onBack: (_) => context state,
.read<PageSelectorProvider>() defaultDestination: PayoutDestination.dashboard,
.goBackFromPayment(context), );
),
), return NoTransitionPage(
child: PaymentPage(
onBack: (_) => _popOrGo(context, fallbackDestination),
initialPaymentType: PayoutRoutes.paymentTypeFromState(state),
fallbackDestination: fallbackDestination,
),
);
},
), ),
GoRoute( GoRoute(
name: PayoutRoutes.settings, name: PayoutRoutes.settings,
@@ -122,24 +136,30 @@ RouteBase payoutShellRoute() => ShellRoute(
path: PayoutRoutes.methodsPath, path: PayoutRoutes.methodsPath,
pageBuilder: (context, _) => NoTransitionPage( pageBuilder: (context, _) => NoTransitionPage(
child: PaymentConfigPage( child: PaymentConfigPage(
onWalletTap: (wallet) => context onWalletTap: (wallet) => _openWalletEdit(
.read<PageSelectorProvider>() context,
.selectWallet(context, wallet), wallet,
returnTo: PayoutDestination.methods,
),
), ),
), ),
), ),
GoRoute( GoRoute(
name: PayoutRoutes.editWallet, name: PayoutRoutes.editWallet,
path: PayoutRoutes.editWalletPath, path: PayoutRoutes.editWalletPath,
pageBuilder: (context, _) { pageBuilder: (context, state) {
final provider = context.read<PageSelectorProvider>(); final walletsProvider = context.read<WalletsProvider>();
final wallet = provider.walletsProvider.selectedWallet; final wallet = walletsProvider.selectedWallet;
final loc = AppLocalizations.of(context)!; final loc = AppLocalizations.of(context)!;
final fallbackDestination = PayoutRoutes.fallbackFromState(
state,
defaultDestination: PayoutDestination.methods,
);
return NoTransitionPage( return NoTransitionPage(
child: wallet != null child: wallet != null
? WalletEditPage( ? WalletEditPage(
onBack: () => provider.goBackFromWalletEdit(context), onBack: () => _popOrGo(context, fallbackDestination),
) )
: Center(child: Text(loc.noWalletSelected)), : Center(child: Text(loc.noWalletSelected)),
); );
@@ -148,13 +168,65 @@ RouteBase payoutShellRoute() => ShellRoute(
GoRoute( GoRoute(
name: PayoutRoutes.walletTopUp, name: PayoutRoutes.walletTopUp,
path: PayoutRoutes.walletTopUpPath, path: PayoutRoutes.walletTopUpPath,
pageBuilder: (context, _) => NoTransitionPage( pageBuilder: (context, state) {
child: WalletTopUpPage( final fallbackDestination = PayoutRoutes.fallbackFromState(
onBack: () => context state,
.read<PageSelectorProvider>() defaultDestination: PayoutDestination.dashboard,
.goBackFromWalletTopUp(context), );
),
), return NoTransitionPage(
child: WalletTopUpPage(
onBack: () => _popOrGo(context, fallbackDestination),
),
);
},
), ),
], ],
); );
void _startPayment(
BuildContext context, {
Recipient? recipient,
PaymentType? paymentType,
required PayoutDestination returnTo,
}) {
context.read<RecipientsProvider>().setCurrentObject(recipient?.id);
context.pushToPayment(
paymentType: paymentType,
returnTo: returnTo,
);
}
void _openAddRecipient(
BuildContext context, {
Recipient? recipient,
}) {
context.read<RecipientsProvider>().setCurrentObject(recipient?.id);
context.pushNamed(PayoutRoutes.addRecipient);
}
void _openWalletEdit(
BuildContext context,
Wallet wallet, {
required PayoutDestination returnTo,
}) {
context.read<WalletsProvider>().selectWallet(wallet);
context.pushToEditWallet(returnTo: returnTo);
}
void _openWalletTopUp(
BuildContext context,
Wallet wallet, {
required PayoutDestination returnTo,
}) {
context.read<WalletsProvider>().selectWallet(wallet);
context.pushToWalletTopUp(returnTo: returnTo);
}
void _popOrGo(BuildContext context, PayoutDestination destination) {
if (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
} else {
context.goToPayout(destination);
}
}

View File

@@ -20,7 +20,6 @@ import 'package:pweb/app/timeago.dart';
import 'package:pweb/providers/carousel.dart'; import 'package:pweb/providers/carousel.dart';
import 'package:pweb/providers/mock_payment.dart'; import 'package:pweb/providers/mock_payment.dart';
import 'package:pweb/providers/operatioins.dart'; import 'package:pweb/providers/operatioins.dart';
import 'package:pweb/providers/page_selector.dart';
import 'package:pweb/providers/two_factor.dart'; import 'package:pweb/providers/two_factor.dart';
import 'package:pweb/providers/upload_history.dart'; import 'package:pweb/providers/upload_history.dart';
import 'package:pweb/providers/wallets.dart'; import 'package:pweb/providers/wallets.dart';
@@ -94,12 +93,6 @@ void main() async {
create: (_) => MockPaymentProvider(), create: (_) => MockPaymentProvider(),
), ),
ChangeNotifierProxyProvider3<RecipientsProvider, WalletsProvider, PaymentMethodsProvider, PageSelectorProvider>(
create: (context) => PageSelectorProvider(),
update: (context, recipientProv, walletsProv, methodsProv, previous) =>
previous ?? PageSelectorProvider()..update(recipientProv, walletsProv, methodsProv),
),
ChangeNotifierProvider( ChangeNotifierProvider(
create: (_) => OperationProvider(OperationService())..loadOperations(), create: (_) => OperationProvider(OperationService())..loadOperations(),
), ),

View File

@@ -1,25 +0,0 @@
import 'package:flutter/material.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
Future<bool> showDeleteConfirmationDialog(BuildContext context) async {
final l10n = AppLocalizations.of(context)!;
return await showDialog<bool>(
context: context,
builder: (_) => AlertDialog(
title: Text(l10n.delete),
content: Text(l10n.deletePaymentConfirmation),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(l10n.cancel),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(l10n.delete),
),
],
),
) ?? false;
}

View File

@@ -1,19 +1,34 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/methods/type.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/recipient/pmethods.dart';
import 'package:pshared/provider/recipient/provider.dart'; import 'package:pshared/provider/recipient/provider.dart';
import 'package:pweb/providers/payment_flow_provider.dart'; import 'package:pweb/models/wallet.dart';
import 'package:pweb/pages/payment_methods/widgets/payment_page_body.dart'; import 'package:pweb/providers/payment_flow.dart';
import 'package:pweb/providers/page_selector.dart'; import 'package:pweb/pages/payment_methods/payment_page/body.dart';
import 'package:pweb/providers/wallets.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
class PaymentPage extends StatefulWidget { class PaymentPage extends StatefulWidget {
final ValueChanged<Recipient?>? onBack; final ValueChanged<Recipient?>? onBack;
final PaymentType? initialPaymentType;
final PayoutDestination fallbackDestination;
const PaymentPage({super.key, this.onBack}); const PaymentPage({
super.key,
this.onBack,
this.initialPaymentType,
this.fallbackDestination = PayoutDestination.dashboard,
});
@override @override
State<PaymentPage> createState() => _PaymentPageState(); State<PaymentPage> createState() => _PaymentPageState();
@@ -29,9 +44,8 @@ class _PaymentPageState extends State<PaymentPage> {
super.initState(); super.initState();
_searchController = TextEditingController(); _searchController = TextEditingController();
_searchFocusNode = FocusNode(); _searchFocusNode = FocusNode();
final pageSelector = context.read<PageSelectorProvider>();
_flowProvider = PaymentFlowProvider( _flowProvider = PaymentFlowProvider(
initialType: pageSelector.getDefaultPaymentType(), initialType: widget.initialPaymentType ?? PaymentType.bankAccount,
); );
WidgetsBinding.instance.addPostFrameCallback((_) => _initializePaymentPage()); WidgetsBinding.instance.addPostFrameCallback((_) => _initializePaymentPage());
@@ -46,11 +60,15 @@ class _PaymentPageState extends State<PaymentPage> {
} }
void _initializePaymentPage() { void _initializePaymentPage() {
final pageSelector = context.read<PageSelectorProvider>(); final methodsProvider = context.read<PaymentMethodsProvider>();
_handleWalletAutoSelection(methodsProvider);
pageSelector.handleWalletAutoSelection(); final recipient = context.read<RecipientsProvider>().currentObject;
_syncFlowProvider(
_flowProvider.syncWithSelector(pageSelector); recipient: recipient,
methodsProvider: methodsProvider,
preferredType: widget.initialPaymentType,
);
} }
void _handleSearchChanged(String query) { void _handleSearchChanged(String query) {
@@ -58,22 +76,28 @@ class _PaymentPageState extends State<PaymentPage> {
} }
void _handleRecipientSelected(Recipient recipient) { void _handleRecipientSelected(Recipient recipient) {
final pageSelector = context.read<PageSelectorProvider>();
final recipientProvider = context.read<RecipientsProvider>(); final recipientProvider = context.read<RecipientsProvider>();
final methodsProvider = context.read<PaymentMethodsProvider>();
recipientProvider.setCurrentObject(recipient.id); recipientProvider.setCurrentObject(recipient.id);
pageSelector.selectRecipient(context, recipient); _flowProvider.reset(
_flowProvider.reset(pageSelector); recipient: recipient,
availableTypes: _availablePaymentTypes(recipient, methodsProvider),
preferredType: widget.initialPaymentType,
);
_clearSearchField(); _clearSearchField();
} }
void _handleRecipientCleared() { void _handleRecipientCleared() {
final pageSelector = context.read<PageSelectorProvider>();
final recipientProvider = context.read<RecipientsProvider>(); final recipientProvider = context.read<RecipientsProvider>();
final methodsProvider = context.read<PaymentMethodsProvider>();
recipientProvider.setCurrentObject(null); recipientProvider.setCurrentObject(null);
pageSelector.selectRecipient(context, null); _flowProvider.reset(
_flowProvider.reset(pageSelector); recipient: null,
availableTypes: _availablePaymentTypes(null, methodsProvider),
preferredType: widget.initialPaymentType,
);
_clearSearchField(); _clearSearchField();
} }
@@ -90,13 +114,26 @@ class _PaymentPageState extends State<PaymentPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final pageSelector = context.watch<PageSelectorProvider>(); final methodsProvider = context.watch<PaymentMethodsProvider>();
_flowProvider.syncWithSelector(pageSelector); final recipientProvider = context.watch<RecipientsProvider>();
final recipient = recipientProvider.currentObject;
final availableTypes = _availablePaymentTypes(recipient, methodsProvider);
_syncFlowProvider(
recipient: recipient,
methodsProvider: methodsProvider,
preferredType: recipient != null ? widget.initialPaymentType : null,
);
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: _flowProvider, value: _flowProvider,
child: PaymentPageBody( child: PaymentPageBody(
onBack: widget.onBack, onBack: widget.onBack,
fallbackDestination: widget.fallbackDestination,
recipient: recipient,
recipientProvider: recipientProvider,
methodsProvider: methodsProvider,
availablePaymentTypes: availableTypes,
searchController: _searchController, searchController: _searchController,
searchFocusNode: _searchFocusNode, searchFocusNode: _searchFocusNode,
onSearchChanged: _handleSearchChanged, onSearchChanged: _handleSearchChanged,
@@ -106,4 +143,56 @@ class _PaymentPageState extends State<PaymentPage> {
), ),
); );
} }
}
void _handleWalletAutoSelection(PaymentMethodsProvider methodsProvider) {
final wallet = context.read<WalletsProvider>().selectedWallet;
if (wallet == null) return;
final matchingMethod = _getPaymentMethodForWallet(wallet, methodsProvider);
if (matchingMethod != null) {
methodsProvider.setCurrentObject(matchingMethod.id);
}
}
void _syncFlowProvider({
required Recipient? recipient,
required PaymentMethodsProvider methodsProvider,
PaymentType? preferredType,
}) {
_flowProvider.sync(
recipient: recipient,
availableTypes: _availablePaymentTypes(recipient, methodsProvider),
preferredType: preferredType,
);
}
MethodMap _availablePaymentTypes(
Recipient? recipient,
PaymentMethodsProvider methodsProvider,
) {
if (recipient == null || !methodsProvider.isReady) return {};
final methodsForRecipient = methodsProvider.methods.where(
(method) => !method.isArchived && method.recipientRef == recipient.id,
);
return {
for (final method in methodsForRecipient) method.type: method.data,
};
}
PaymentMethod? _getPaymentMethodForWallet(
Wallet wallet,
PaymentMethodsProvider methodsProvider,
) {
if (methodsProvider.methods.isEmpty) {
return null;
}
return methodsProvider.methods.firstWhereOrNull(
(method) =>
method.type == PaymentType.wallet &&
(method.description?.contains(wallet.walletUserID) ?? false),
);
}
}

View File

@@ -1,33 +0,0 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/type.dart';
class PaymentDetailsSection extends StatelessWidget {
final bool isFormVisible;
final VoidCallback? onToggle;
final PaymentType selectedType;
final Object? data;
final bool isEditable;
const PaymentDetailsSection({
super.key,
required this.isFormVisible,
this.onToggle,
required this.selectedType,
required this.data,
required this.isEditable,
});
@override
Widget build(BuildContext context) {
return PaymentDetailsSection(
isFormVisible: isFormVisible,
onToggle: onToggle,
selectedType: selectedType,
data: data,
isEditable: isEditable,
);
}
}

View File

@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pweb/app/router/payout_routes.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
class PaymentBackButton extends StatelessWidget {
final ValueChanged<Recipient?>? onBack;
final Recipient? recipient;
final PayoutDestination fallbackDestination;
const PaymentBackButton({
super.key,
required this.onBack,
required this.recipient,
required this.fallbackDestination,
});
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
if (onBack != null) {
onBack!(recipient);
} else {
if (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
} else {
context.goToPayout(fallbackDestination);
}
}
},
),
);
}
}

View File

@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/recipient/pmethods.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
import 'package:pweb/pages/payment_methods/widgets/state_view.dart';
import 'package:pweb/pages/payment_methods/payment_page/page.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentPageBody extends StatelessWidget {
final ValueChanged<Recipient?>? onBack;
final Recipient? recipient;
final RecipientsProvider recipientProvider;
final PaymentMethodsProvider methodsProvider;
final MethodMap availablePaymentTypes;
final PayoutDestination fallbackDestination;
final TextEditingController searchController;
final FocusNode searchFocusNode;
final ValueChanged<String> onSearchChanged;
final ValueChanged<Recipient> onRecipientSelected;
final VoidCallback onRecipientCleared;
final VoidCallback onSend;
const PaymentPageBody({
super.key,
required this.onBack,
required this.recipient,
required this.recipientProvider,
required this.methodsProvider,
required this.availablePaymentTypes,
required this.fallbackDestination,
required this.searchController,
required this.searchFocusNode,
required this.onSearchChanged,
required this.onRecipientSelected,
required this.onRecipientCleared,
required this.onSend,
});
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
if (methodsProvider.isLoading) {
return const PaymentMethodsLoadingView();
}
if (methodsProvider.error != null) {
return PaymentMethodsErrorView(
message: loc.notificationError(methodsProvider.error ?? loc.noErrorInformation),
);
}
return PaymentPageContent(
onBack: onBack,
recipient: recipient,
recipientProvider: recipientProvider,
methodsProvider: methodsProvider,
availablePaymentTypes: availablePaymentTypes,
fallbackDestination: fallbackDestination,
searchController: searchController,
searchFocusNode: searchFocusNode,
onSearchChanged: onSearchChanged,
onRecipientSelected: onRecipientSelected,
onRecipientCleared: onRecipientCleared,
onSend: onSend,
);
}
}

View File

@@ -2,10 +2,12 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/pmethods.dart';
import 'package:pshared/provider/recipient/provider.dart'; import 'package:pshared/provider/recipient/provider.dart';
import 'package:pweb/pages/payment_methods/back_button.dart';
import 'package:pweb/pages/payment_methods/header.dart'; import 'package:pweb/pages/payment_methods/header.dart';
import 'package:pweb/pages/payment_methods/method_selector.dart'; import 'package:pweb/pages/payment_methods/method_selector.dart';
import 'package:pweb/pages/payment_methods/send_button.dart'; import 'package:pweb/pages/payment_methods/send_button.dart';
@@ -13,15 +15,20 @@ import 'package:pweb/pages/dashboard/payouts/payment_form.dart';
import 'package:pweb/pages/payment_methods/widgets/payment_info_section.dart'; import 'package:pweb/pages/payment_methods/widgets/payment_info_section.dart';
import 'package:pweb/pages/payment_methods/widgets/recipient_section.dart'; import 'package:pweb/pages/payment_methods/widgets/recipient_section.dart';
import 'package:pweb/pages/payment_methods/widgets/section_title.dart'; import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
import 'package:pweb/providers/page_selector.dart'; import 'package:pweb/providers/payment_flow.dart';
import 'package:pweb/providers/payment_flow_provider.dart';
import 'package:pweb/utils/dimensions.dart'; import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentPageBody extends StatelessWidget { class PaymentPageContent extends StatelessWidget {
final ValueChanged<Recipient?>? onBack; final ValueChanged<Recipient?>? onBack;
final Recipient? recipient;
final RecipientsProvider recipientProvider;
final PaymentMethodsProvider methodsProvider;
final MethodMap availablePaymentTypes;
final PayoutDestination fallbackDestination;
final TextEditingController searchController; final TextEditingController searchController;
final FocusNode searchFocusNode; final FocusNode searchFocusNode;
final ValueChanged<String> onSearchChanged; final ValueChanged<String> onSearchChanged;
@@ -29,9 +36,14 @@ class PaymentPageBody extends StatelessWidget {
final VoidCallback onRecipientCleared; final VoidCallback onRecipientCleared;
final VoidCallback onSend; final VoidCallback onSend;
const PaymentPageBody({ const PaymentPageContent({
super.key, super.key,
required this.onBack, required this.onBack,
required this.recipient,
required this.recipientProvider,
required this.methodsProvider,
required this.availablePaymentTypes,
required this.fallbackDestination,
required this.searchController, required this.searchController,
required this.searchFocusNode, required this.searchFocusNode,
required this.onSearchChanged, required this.onSearchChanged,
@@ -43,21 +55,9 @@ class PaymentPageBody extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final dimensions = AppDimensions(); final dimensions = AppDimensions();
final pageSelector = context.watch<PageSelectorProvider>();
final methodsProvider = context.watch<PaymentMethodsProvider>();
final recipientProvider = context.watch<RecipientsProvider>();
final flowProvider = context.watch<PaymentFlowProvider>(); final flowProvider = context.watch<PaymentFlowProvider>();
final recipient = pageSelector.selectedRecipient;
final loc = AppLocalizations.of(context)!; final loc = AppLocalizations.of(context)!;
if (methodsProvider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (methodsProvider.error != null) {
return Center(child: Text(loc.notificationError(methodsProvider.error ?? loc.noErrorInformation)));
}
return Align( return Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: ConstrainedBox( child: ConstrainedBox(
@@ -73,18 +73,20 @@ class PaymentPageBody extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
PaymentBackButton(onBack: onBack, pageSelector: pageSelector), PaymentBackButton(
onBack: onBack,
recipient: recipient,
fallbackDestination: fallbackDestination,
),
SizedBox(height: dimensions.paddingSmall), SizedBox(height: dimensions.paddingSmall),
PaymentHeader(), PaymentHeader(),
SizedBox(height: dimensions.paddingXXLarge), SizedBox(height: dimensions.paddingXXLarge),
SectionTitle(loc.sourceOfFunds), SectionTitle(loc.sourceOfFunds),
SizedBox(height: dimensions.paddingSmall), SizedBox(height: dimensions.paddingSmall),
PaymentMethodSelector( PaymentMethodSelector(
onMethodChanged: (m) => methodsProvider.setCurrentObject(m.id), onMethodChanged: (m) => methodsProvider.setCurrentObject(m.id),
), ),
SizedBox(height: dimensions.paddingXLarge), SizedBox(height: dimensions.paddingXLarge),
RecipientSection( RecipientSection(
recipient: recipient, recipient: recipient,
dimensions: dimensions, dimensions: dimensions,
@@ -95,19 +97,15 @@ class PaymentPageBody extends StatelessWidget {
onRecipientSelected: onRecipientSelected, onRecipientSelected: onRecipientSelected,
onRecipientCleared: onRecipientCleared, onRecipientCleared: onRecipientCleared,
), ),
SizedBox(height: dimensions.paddingXLarge), SizedBox(height: dimensions.paddingXLarge),
PaymentInfoSection( PaymentInfoSection(
dimensions: dimensions, dimensions: dimensions,
pageSelector: pageSelector,
flowProvider: flowProvider, flowProvider: flowProvider,
recipient: recipient, recipient: recipient,
availableTypes: availablePaymentTypes,
), ),
SizedBox(height: dimensions.paddingLarge), SizedBox(height: dimensions.paddingLarge),
const PaymentFormWidget(), const PaymentFormWidget(),
SizedBox(height: dimensions.paddingXXXLarge), SizedBox(height: dimensions.paddingXXXLarge),
SendButton(onPressed: onSend), SendButton(onPressed: onSend),
SizedBox(height: dimensions.paddingLarge), SizedBox(height: dimensions.paddingLarge),
@@ -120,31 +118,3 @@ class PaymentPageBody extends StatelessWidget {
); );
} }
} }
class PaymentBackButton extends StatelessWidget {
final ValueChanged<Recipient?>? onBack;
final PageSelectorProvider pageSelector;
const PaymentBackButton({
super.key,
required this.onBack,
required this.pageSelector,
});
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
if (onBack != null) {
onBack!(pageSelector.selectedRecipient);
} else {
pageSelector.goBackFromPayment(context);
}
},
),
);
}
}

View File

@@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/recipient/pmethods.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pweb/pages/payment_methods/back_button.dart';
import 'package:pweb/pages/payment_methods/header.dart';
import 'package:pweb/pages/payment_methods/method_selector.dart';
import 'package:pweb/pages/payment_methods/send_button.dart';
import 'package:pweb/pages/dashboard/payouts/payment_form.dart';
import 'package:pweb/pages/payment_methods/widgets/payment_info_section.dart';
import 'package:pweb/pages/payment_methods/widgets/recipient_section.dart';
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
import 'package:pweb/providers/payment_flow.dart';
import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentPageContent extends StatelessWidget {
final ValueChanged<Recipient?>? onBack;
final Recipient? recipient;
final RecipientsProvider recipientProvider;
final PaymentMethodsProvider methodsProvider;
final MethodMap availablePaymentTypes;
final PayoutDestination fallbackDestination;
final TextEditingController searchController;
final FocusNode searchFocusNode;
final ValueChanged<String> onSearchChanged;
final ValueChanged<Recipient> onRecipientSelected;
final VoidCallback onRecipientCleared;
final VoidCallback onSend;
const PaymentPageContent({
super.key,
required this.onBack,
required this.recipient,
required this.recipientProvider,
required this.methodsProvider,
required this.availablePaymentTypes,
required this.fallbackDestination,
required this.searchController,
required this.searchFocusNode,
required this.onSearchChanged,
required this.onRecipientSelected,
required this.onRecipientCleared,
required this.onSend,
});
@override
Widget build(BuildContext context) {
final dimensions = AppDimensions();
final flowProvider = context.watch<PaymentFlowProvider>();
final loc = AppLocalizations.of(context)!;
return Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: dimensions.maxContentWidth),
child: Material(
elevation: dimensions.elevationSmall,
borderRadius: BorderRadius.circular(dimensions.borderRadiusMedium),
color: Theme.of(context).colorScheme.onSecondary,
child: Padding(
padding: EdgeInsets.all(dimensions.paddingLarge),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PaymentBackButton(
onBack: onBack,
recipient: recipient,
fallbackDestination: fallbackDestination,
),
SizedBox(height: dimensions.paddingSmall),
PaymentHeader(),
SizedBox(height: dimensions.paddingXXLarge),
SectionTitle(loc.sourceOfFunds),
SizedBox(height: dimensions.paddingSmall),
PaymentMethodSelector(
onMethodChanged: (m) => methodsProvider.setCurrentObject(m.id),
),
SizedBox(height: dimensions.paddingXLarge),
RecipientSection(
recipient: recipient,
dimensions: dimensions,
recipientProvider: recipientProvider,
searchController: searchController,
searchFocusNode: searchFocusNode,
onSearchChanged: onSearchChanged,
onRecipientSelected: onRecipientSelected,
onRecipientCleared: onRecipientCleared,
),
SizedBox(height: dimensions.paddingXLarge),
PaymentInfoSection(
dimensions: dimensions,
flowProvider: flowProvider,
recipient: recipient,
availableTypes: availablePaymentTypes,
),
SizedBox(height: dimensions.paddingLarge),
const PaymentFormWidget(),
SizedBox(height: dimensions.paddingXXXLarge),
SendButton(onPressed: onSend),
SizedBox(height: dimensions.paddingLarge),
],
),
),
),
),
),
);
}
}

View File

@@ -6,8 +6,7 @@ import 'package:pshared/models/recipient/recipient.dart';
import 'package:pweb/pages/payment_methods/form.dart'; import 'package:pweb/pages/payment_methods/form.dart';
import 'package:pweb/pages/payment_methods/widgets/section_title.dart'; import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
import 'package:pweb/providers/page_selector.dart'; import 'package:pweb/providers/payment_flow.dart';
import 'package:pweb/providers/payment_flow_provider.dart';
import 'package:pweb/utils/dimensions.dart'; import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/utils/payment/selector_type.dart'; import 'package:pweb/utils/payment/selector_type.dart';
@@ -16,14 +15,14 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentInfoSection extends StatelessWidget { class PaymentInfoSection extends StatelessWidget {
final AppDimensions dimensions; final AppDimensions dimensions;
final PageSelectorProvider pageSelector; final MethodMap availableTypes;
final PaymentFlowProvider flowProvider; final PaymentFlowProvider flowProvider;
final Recipient? recipient; final Recipient? recipient;
const PaymentInfoSection({ const PaymentInfoSection({
super.key, super.key,
required this.dimensions, required this.dimensions,
required this.pageSelector, required this.availableTypes,
required this.flowProvider, required this.flowProvider,
required this.recipient, required this.recipient,
}); });
@@ -32,11 +31,11 @@ class PaymentInfoSection extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!; final loc = AppLocalizations.of(context)!;
final hasRecipient = recipient != null; final hasRecipient = recipient != null;
final MethodMap availableTypes = hasRecipient final MethodMap resolvedAvailableTypes = hasRecipient
? pageSelector.getAvailablePaymentTypes() ? availableTypes
: {for (final type in PaymentType.values) type: null}; : {for (final type in PaymentType.values) type: null};
if (hasRecipient && availableTypes.isEmpty) { if (hasRecipient && resolvedAvailableTypes.isEmpty) {
return Text(loc.recipientNoPaymentDetails); return Text(loc.recipientNoPaymentDetails);
} }
@@ -48,7 +47,7 @@ class PaymentInfoSection extends StatelessWidget {
SectionTitle(loc.paymentInfo), SectionTitle(loc.paymentInfo),
SizedBox(height: dimensions.paddingSmall), SizedBox(height: dimensions.paddingSmall),
PaymentTypeSelector( PaymentTypeSelector(
availableTypes: availableTypes, availableTypes: resolvedAvailableTypes,
selectedType: selectedType, selectedType: selectedType,
onSelected: (type) => flowProvider.selectType( onSelected: (type) => flowProvider.selectType(
type, type,
@@ -63,7 +62,7 @@ class PaymentInfoSection extends StatelessWidget {
flowProvider.setManualPaymentData(data); flowProvider.setManualPaymentData(data);
} }
}, },
initialData: hasRecipient ? availableTypes[selectedType] : flowProvider.manualPaymentData, initialData: hasRecipient ? resolvedAvailableTypes[selectedType] : flowProvider.manualPaymentData,
isEditable: !hasRecipient, isEditable: !hasRecipient,
), ),
], ],

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class PaymentMethodsLoadingView extends StatelessWidget {
const PaymentMethodsLoadingView({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: CircularProgressIndicator());
}
}
class PaymentMethodsErrorView extends StatelessWidget {
final String message;
const PaymentMethodsErrorView({super.key, required this.message});
@override
Widget build(BuildContext context) {
return Center(child: Text(message));
}
}

View File

@@ -2,8 +2,11 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pweb/providers/page_selector.dart'; import 'package:pshared/models/payment/type.dart';
import 'package:pweb/app/router/payout_routes.dart';
import 'package:pweb/providers/wallets.dart'; import 'package:pweb/providers/wallets.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
@@ -20,12 +23,14 @@ class SendPayoutButton extends StatelessWidget {
elevation: 0, elevation: 0,
), ),
onPressed: () { onPressed: () {
final pageSelectorProvider = context.read<PageSelectorProvider>();
final walletsProvider = context.read<WalletsProvider>(); final walletsProvider = context.read<WalletsProvider>();
final wallet = walletsProvider.selectedWallet; final wallet = walletsProvider.selectedWallet;
if (wallet != null) { if (wallet != null) {
pageSelectorProvider.startPaymentFromWallet(context, wallet); context.pushToPayment(
paymentType: PaymentType.wallet,
returnTo: PayoutDestination.editwallet,
);
} }
}, },
child: Text(loc.payoutNavSendPayout), child: Text(loc.payoutNavSendPayout),

View File

@@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pweb/providers/page_selector.dart'; import 'package:pweb/app/router/payout_routes.dart';
import 'package:pweb/providers/wallets.dart'; import 'package:pweb/providers/wallets.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
@@ -26,7 +27,7 @@ class TopUpButton extends StatelessWidget{
); );
return; return;
} }
context.read<PageSelectorProvider>().openWalletTopUp(context, wallet); context.pushToWalletTopUp(returnTo: PayoutDestination.editwallet);
}, },
child: Text(loc.topUpBalance), child: Text(loc.topUpBalance),
); );

View File

@@ -1,264 +0,0 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/methods/type.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/recipient/pmethods.dart';
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';
class PageSelectorProvider extends ChangeNotifier {
PayoutDestination _selected = PayoutDestination.dashboard;
PaymentType? _type;
bool _cameFromRecipientList = false;
PayoutDestination? _previousDestination;
late RecipientsProvider recipientProvider;
late WalletsProvider walletsProvider;
late PaymentMethodsProvider methodsProvider;
PayoutDestination get selected => _selected;
PaymentType? get type => _type;
bool get cameFromRecipientList => _cameFromRecipientList;
PageSelectorProvider();
void update(
RecipientsProvider recipientProv,
WalletsProvider walletsProv,
PaymentMethodsProvider methodsProv,
) {
recipientProvider = recipientProv;
walletsProvider = walletsProv;
methodsProvider = methodsProv;
}
void syncDestination(PayoutDestination destination) {
if (_selected == destination) return;
_selected = destination;
notifyListeners();
}
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(
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(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(
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(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
: PayoutDestination.dashboard);
_type = null;
_previousDestination = null;
_cameFromRecipientList = false;
notifyListeners();
}
void goBackFromWalletEdit(BuildContext context) {
selectPage(context, PayoutDestination.methods);
}
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(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) {
if (methodsProvider.methods.isEmpty) {
return null;
}
return methodsProvider.methods.firstWhereOrNull(
(method) => method.type == PaymentType.wallet && (method.description?.contains(wallet.walletUserID) ?? false),
);
}
MethodMap getAvailablePaymentTypes() {
final recipient = selectedRecipient;
if ((recipient == null) || !methodsProvider.isReady) return {};
final methodsForRecipient = methodsProvider.methods.where(
(method) => !method.isArchived && method.recipientRef == recipient.id,
);
return {
for (final method in methodsForRecipient) method.type: method.data,
};
}
PaymentType getDefaultPaymentType() {
final availableTypes = getAvailablePaymentTypes();
final currentType = _type ?? PaymentType.bankAccount;
if (availableTypes.containsKey(currentType)) {
return currentType;
}
if (availableTypes.isNotEmpty) {
return availableTypes.keys.first;
}
return PaymentType.bankAccount;
}
bool shouldShowPaymentForm() {
return selectedRecipient == null;
}
void handleWalletAutoSelection() {
if (selectedWallet != null) {
final wallet = selectedWallet!;
final matchingMethod = getPaymentMethodForWallet(wallet);
if (matchingMethod != null) {
methodsProvider.setCurrentObject(matchingMethod.id);
}
}
}
void _setPreviousDestination() {
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;
}

View File

@@ -0,0 +1,110 @@
import 'package:flutter/foundation.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart';
class PaymentFlowProvider extends ChangeNotifier {
PaymentType _selectedType;
PaymentMethodData? _manualPaymentData;
PaymentFlowProvider({
required PaymentType initialType,
}) : _selectedType = initialType;
PaymentType get selectedType => _selectedType;
PaymentMethodData? get manualPaymentData => _manualPaymentData;
void sync({
required Recipient? recipient,
required MethodMap availableTypes,
PaymentType? preferredType,
}) {
final resolvedType = _resolveSelectedType(
recipient: recipient,
availableTypes: availableTypes,
preferredType: preferredType,
);
var hasChanges = false;
if (resolvedType != _selectedType) {
_selectedType = resolvedType;
hasChanges = true;
}
if (recipient != null && _manualPaymentData != null) {
_manualPaymentData = null;
hasChanges = true;
}
if (hasChanges) notifyListeners();
}
void reset({
required Recipient? recipient,
required MethodMap availableTypes,
PaymentType? preferredType,
}) {
final resolvedType = _resolveSelectedType(
recipient: recipient,
availableTypes: availableTypes,
preferredType: preferredType,
);
var hasChanges = false;
if (resolvedType != _selectedType) {
_selectedType = resolvedType;
hasChanges = true;
}
if (_manualPaymentData != null) {
_manualPaymentData = null;
hasChanges = true;
}
if (hasChanges) notifyListeners();
}
void selectType(PaymentType type, {bool resetManualData = false}) {
if (_selectedType == type && (!resetManualData || _manualPaymentData == null)) {
return;
}
_selectedType = type;
if (resetManualData) {
_manualPaymentData = null;
}
notifyListeners();
}
void setManualPaymentData(PaymentMethodData? data) {
_manualPaymentData = data;
notifyListeners();
}
PaymentType _resolveSelectedType({
required Recipient? recipient,
required MethodMap availableTypes,
PaymentType? preferredType,
}) {
if (recipient == null) {
return preferredType ?? _selectedType;
}
if (availableTypes.isEmpty) {
return preferredType ?? PaymentType.bankAccount;
}
if (availableTypes.keys.contains(_selectedType)) {
return _selectedType;
}
if (preferredType != null && availableTypes.keys.contains(preferredType)) {
return preferredType;
}
return availableTypes.keys.first;
}
}

View File

@@ -1,79 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pweb/providers/page_selector.dart';
class PaymentFlowProvider extends ChangeNotifier {
PaymentType _selectedType;
PaymentMethodData? _manualPaymentData;
PaymentFlowProvider({
required PaymentType initialType,
}) : _selectedType = initialType;
PaymentType get selectedType => _selectedType;
PaymentMethodData? get manualPaymentData => _manualPaymentData;
void syncWithSelector(PageSelectorProvider selector) {
final recipient = selector.selectedRecipient;
final resolvedType = _resolveSelectedType(selector, recipient);
var hasChanges = false;
if (resolvedType != _selectedType) {
_selectedType = resolvedType;
hasChanges = true;
}
if (recipient != null && _manualPaymentData != null) {
_manualPaymentData = null;
hasChanges = true;
}
if (hasChanges) notifyListeners();
}
void reset(PageSelectorProvider selector) {
_selectedType = selector.getDefaultPaymentType();
_manualPaymentData = null;
notifyListeners();
}
void selectType(PaymentType type, {bool resetManualData = false}) {
if (_selectedType == type && (!resetManualData || _manualPaymentData == null)) {
return;
}
_selectedType = type;
if (resetManualData) {
_manualPaymentData = null;
}
notifyListeners();
}
void setManualPaymentData(PaymentMethodData? data) {
_manualPaymentData = data;
notifyListeners();
}
PaymentType _resolveSelectedType(
PageSelectorProvider selector,
Recipient? recipient,
) {
final available = selector.getAvailablePaymentTypes();
final current = _selectedType;
if (recipient == null) {
return current;
}
if (available.keys.contains(current)) {
return current;
}
return selector.getDefaultPaymentType();
}
}

View File

@@ -8,7 +8,6 @@ import 'package:pshared/models/resources.dart';
import 'package:pshared/provider/permissions.dart'; import 'package:pshared/provider/permissions.dart';
import 'package:pweb/pages/loader.dart'; import 'package:pweb/pages/loader.dart';
import 'package:pweb/providers/page_selector.dart';
import 'package:pweb/utils/logout.dart'; import 'package:pweb/utils/logout.dart';
import 'package:pweb/widgets/appbar/app_bar.dart'; import 'package:pweb/widgets/appbar/app_bar.dart';
import 'package:pweb/widgets/sidebar/destinations.dart'; import 'package:pweb/widgets/sidebar/destinations.dart';
@@ -32,9 +31,10 @@ class PageSelector extends StatelessWidget {
final permissions = context.read<PermissionsProvider>(); final permissions = context.read<PermissionsProvider>();
if (!permissions.isReady) return Center(child: CircularProgressIndicator()); if (!permissions.isReady) return Center(child: CircularProgressIndicator());
final provider = context.watch<PageSelectorProvider>();
final bool restrictedAccess = !permissions.canRead(ResourceType.chainWallets); final bool restrictedAccess = !permissions.canRead(ResourceType.chainWallets);
final fallbackDestination = restrictedAccess
? PayoutDestination.settings
: PayoutDestination.dashboard;
final allowedDestinations = restrictedAccess final allowedDestinations = restrictedAccess
? <PayoutDestination>{ ? <PayoutDestination>{
PayoutDestination.settings, PayoutDestination.settings,
@@ -44,10 +44,10 @@ class PageSelector extends StatelessWidget {
} }
: PayoutDestination.values.toSet(); : PayoutDestination.values.toSet();
final routeDestination = _destinationFromState(routerState) ?? provider.selected; final routeDestination = _destinationFromState(routerState);
final selected = allowedDestinations.contains(routeDestination) final selected = routeDestination != null && allowedDestinations.contains(routeDestination)
? routeDestination ? routeDestination
: (restrictedAccess ? PayoutDestination.settings : PayoutDestination.dashboard); : fallbackDestination;
if (selected != routeDestination) { if (selected != routeDestination) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -55,10 +55,6 @@ class PageSelector extends StatelessWidget {
}); });
} }
if (provider.selected != selected) {
provider.syncDestination(selected);
}
return Scaffold( return Scaffold(
appBar: PayoutAppBar( appBar: PayoutAppBar(
title: Text(selected.localizedLabel(context)), title: Text(selected.localizedLabel(context)),