This commit is contained in:
Arseni
2026-03-04 17:43:18 +03:00
parent 80b25a8608
commit aff804ec58
46 changed files with 1090 additions and 345 deletions

View File

@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:pweb/models/payment/source_funds.dart';
import 'package:pweb/pages/payout_page/send/widgets/section/card.dart';
import 'package:pweb/pages/payout_page/send/widgets/section/title.dart';
class SourceOfFundsPanel extends StatelessWidget {
const SourceOfFundsPanel({
super.key,
required this.title,
required this.sourceSelector,
this.visibleStates = const <SourceOfFundsVisibleState>{},
this.stateWidgets = const <SourceOfFundsVisibleState, Widget>{},
this.selectorSpacing = 8,
this.sectionSpacing = 12,
this.padding,
});
final String title;
final Widget sourceSelector;
final Set<SourceOfFundsVisibleState> visibleStates;
final Map<SourceOfFundsVisibleState, Widget> stateWidgets;
final double selectorSpacing;
final double sectionSpacing;
final EdgeInsetsGeometry? padding;
@override
Widget build(BuildContext context) {
final headerAction = _stateWidget(SourceOfFundsVisibleState.headerAction);
final headerActions = headerAction == null
? const <Widget>[]
: <Widget>[headerAction];
final bodySections = _buildBodySections();
return PaymentSectionCard(
padding: padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: SectionTitle(title)),
...headerActions,
],
),
SizedBox(height: selectorSpacing),
sourceSelector,
if (bodySections.isNotEmpty) ...[
SizedBox(height: sectionSpacing),
const Divider(height: 1),
SizedBox(height: sectionSpacing),
...bodySections,
],
],
),
);
}
List<Widget> _buildBodySections() {
const orderedStates = <SourceOfFundsVisibleState>[
SourceOfFundsVisibleState.summary,
SourceOfFundsVisibleState.quoteStatus,
SourceOfFundsVisibleState.sendAction,
];
final sections = <Widget>[];
for (final state in orderedStates) {
final section = _stateWidget(state);
if (section == null) continue;
if (sections.isNotEmpty) {
sections.add(SizedBox(height: sectionSpacing));
}
sections.add(section);
}
return sections;
}
Widget? _stateWidget(SourceOfFundsVisibleState state) {
if (!visibleStates.contains(state)) return null;
return stateWidgets[state];
}
}

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:pshared/controllers/balance_mask/wallets.dart';
import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/models/ledger/account.dart';
import 'package:pshared/models/payment/source_type.dart';
@@ -10,78 +9,52 @@ import 'package:pshared/utils/money.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
typedef _SourceOptionKey = ({PaymentSourceType type, String ref});
class SourceWalletSelector extends StatelessWidget {
const SourceWalletSelector({
super.key,
this.walletsController,
this.sourceController,
required this.sourceController,
this.isBusy = false,
this.onChanged,
}) : assert(
(walletsController != null) != (sourceController != null),
'Provide either walletsController or sourceController',
);
});
final WalletsController? walletsController;
final PaymentSourceController? sourceController;
final PaymentSourceController sourceController;
final bool isBusy;
final ValueChanged<Wallet>? onChanged;
@override
Widget build(BuildContext context) {
final source = sourceController;
if (source != null) {
final selectedWallet = source.selectedWallet;
final selectedLedger = source.selectedLedgerAccount;
final selectedValue = switch (source.selectedType) {
PaymentSourceType.wallet =>
selectedWallet == null ? null : _walletKey(selectedWallet.id),
PaymentSourceType.ledger =>
selectedLedger == null
? null
: _ledgerKey(selectedLedger.ledgerAccountRef),
null => null,
};
final selectedWallet = source.selectedWallet;
final selectedLedger = source.selectedLedgerAccount;
final selectedValue = switch (source.selectedType) {
PaymentSourceType.wallet =>
selectedWallet == null ? null : _walletKey(selectedWallet.id),
PaymentSourceType.ledger =>
selectedLedger == null
? null
: _ledgerKey(selectedLedger.ledgerAccountRef),
null => null,
};
return _buildSourceSelector(
context: context,
wallets: source.wallets,
ledgerAccounts: source.ledgerAccounts,
selectedValue: selectedValue,
onChanged: (value) {
if (value.type == PaymentSourceType.wallet) {
source.selectWalletByRef(value.ref);
final selected = source.selectedWallet;
if (selected != null) {
onChanged?.call(selected);
}
return;
}
if (value.type == PaymentSourceType.ledger) {
source.selectLedgerByRef(value.ref);
}
},
);
}
final wallets = walletsController!;
return _buildSourceSelector(
context: context,
wallets: wallets.wallets,
ledgerAccounts: const <LedgerAccount>[],
selectedValue: wallets.selectedWalletRef == null
? null
: _walletKey(wallets.selectedWalletRef!),
wallets: source.wallets,
ledgerAccounts: source.ledgerAccounts,
selectedValue: selectedValue,
onChanged: (value) {
if (value.type != PaymentSourceType.wallet) return;
wallets.selectWalletByRef(value.ref);
final selected = wallets.selectedWallet;
if (selected != null) {
onChanged?.call(selected);
if (value.type == PaymentSourceType.wallet) {
source.selectWalletByRef(value.ref);
final selected = source.selectedWallet;
if (selected != null) {
onChanged?.call(selected);
}
return;
}
if (value.type == PaymentSourceType.ledger) {
source.selectLedgerByRef(value.ref);
}
},
);
@@ -106,7 +79,7 @@ class SourceWalletSelector extends StatelessWidget {
return DropdownMenuItem<_SourceOptionKey>(
value: _walletKey(wallet.id),
child: Text(
'${wallet.name} - ${_walletBalance(wallet)}',
'${_walletDisplayName(wallet, l10n)} - ${_walletBalance(wallet)}',
overflow: TextOverflow.ellipsis,
),
);
@@ -115,7 +88,7 @@ class SourceWalletSelector extends StatelessWidget {
return DropdownMenuItem<_SourceOptionKey>(
value: _ledgerKey(ledger.ledgerAccountRef),
child: Text(
'${ledger.name} - ${_ledgerBalance(ledger)}',
'${_ledgerDisplayName(ledger, l10n)} - ${_ledgerBalance(ledger)}',
overflow: TextOverflow.ellipsis,
),
);
@@ -157,6 +130,20 @@ class SourceWalletSelector extends StatelessWidget {
_SourceOptionKey _ledgerKey(String ledgerAccountRef) =>
(type: PaymentSourceType.ledger, ref: ledgerAccountRef);
String _walletDisplayName(Wallet wallet, AppLocalizations l10n) {
final name = wallet.name.trim();
if (name.isNotEmpty) return name;
return l10n.paymentTypeWallet;
}
String _ledgerDisplayName(LedgerAccount ledger, AppLocalizations l10n) {
final name = ledger.name.trim();
if (name.isNotEmpty) return name;
return l10n.paymentTypeLedger;
}
String _walletBalance(Wallet wallet) {
final symbol = currencyCodeToSymbol(wallet.currency);
return '$symbol ${amountToString(wallet.balance)}';