Files
sendico/frontend/pweb/lib/widgets/payment/source_wallet_selector.dart
2026-03-04 17:43:18 +03:00

173 lines
5.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/models/ledger/account.dart';
import 'package:pshared/models/payment/source_type.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/utils/currency.dart';
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,
required this.sourceController,
this.isBusy = false,
this.onChanged,
});
final PaymentSourceController sourceController;
final bool isBusy;
final ValueChanged<Wallet>? onChanged;
@override
Widget build(BuildContext context) {
final source = sourceController;
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);
}
},
);
}
Widget _buildSourceSelector({
required BuildContext context,
required List<Wallet> wallets,
required List<LedgerAccount> ledgerAccounts,
required _SourceOptionKey? selectedValue,
required ValueChanged<_SourceOptionKey> onChanged,
}) {
final theme = Theme.of(context);
final l10n = AppLocalizations.of(context)!;
if (wallets.isEmpty && ledgerAccounts.isEmpty) {
return Text(l10n.noWalletsAvailable, style: theme.textTheme.bodySmall);
}
final items = <DropdownMenuItem<_SourceOptionKey>>[
...wallets.map((wallet) {
return DropdownMenuItem<_SourceOptionKey>(
value: _walletKey(wallet.id),
child: Text(
'${_walletDisplayName(wallet, l10n)} - ${_walletBalance(wallet)}',
overflow: TextOverflow.ellipsis,
),
);
}),
...ledgerAccounts.map((ledger) {
return DropdownMenuItem<_SourceOptionKey>(
value: _ledgerKey(ledger.ledgerAccountRef),
child: Text(
'${_ledgerDisplayName(ledger, l10n)} - ${_ledgerBalance(ledger)}',
overflow: TextOverflow.ellipsis,
),
);
}),
];
final knownValues = items
.map((item) => item.value)
.whereType<_SourceOptionKey>()
.toSet();
final effectiveValue = knownValues.contains(selectedValue)
? selectedValue
: null;
return DropdownButtonFormField<_SourceOptionKey>(
initialValue: effectiveValue,
isExpanded: true,
decoration: InputDecoration(
labelText: l10n.whereGetMoney,
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
),
items: items,
onChanged: isBusy
? null
: (value) {
if (value == null) return;
onChanged(value);
},
);
}
_SourceOptionKey _walletKey(String walletRef) =>
(type: PaymentSourceType.wallet, ref: walletRef);
_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)}';
}
String _ledgerBalance(LedgerAccount account) {
final money = account.balance?.balance;
final rawAmount = money?.amount.trim();
final amount = parseMoneyAmount(rawAmount, fallback: double.nan);
final amountText = amount.isNaN
? (rawAmount == null || rawAmount.isEmpty ? '--' : rawAmount)
: amountToString(amount);
final currencyCode = (money?.currency ?? account.currency)
.trim()
.toUpperCase();
final symbol = currencySymbolFromCode(currencyCode);
if (symbol != null && symbol.trim().isNotEmpty) {
return '$symbol $amountText';
}
if (currencyCode.isNotEmpty) {
return '$amountText $currencyCode';
}
return amountText;
}
}