186 lines
5.7 KiB
Dart
186 lines
5.7 KiB
Dart
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';
|
|
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,
|
|
this.walletsController,
|
|
this.sourceController,
|
|
this.isBusy = false,
|
|
this.onChanged,
|
|
}) : assert(
|
|
(walletsController != null) != (sourceController != null),
|
|
'Provide either walletsController or sourceController',
|
|
);
|
|
|
|
final WalletsController? walletsController;
|
|
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,
|
|
};
|
|
|
|
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!),
|
|
onChanged: (value) {
|
|
if (value.type != PaymentSourceType.wallet) return;
|
|
wallets.selectWalletByRef(value.ref);
|
|
final selected = wallets.selectedWallet;
|
|
if (selected != null) {
|
|
onChanged?.call(selected);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
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(
|
|
'${wallet.name} - ${_walletBalance(wallet)}',
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
);
|
|
}),
|
|
...ledgerAccounts.map((ledger) {
|
|
return DropdownMenuItem<_SourceOptionKey>(
|
|
value: _ledgerKey(ledger.ledgerAccountRef),
|
|
child: Text(
|
|
'${ledger.name} - ${_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 _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;
|
|
}
|
|
}
|