few fixes and made sure ledger widget displays the name of ledger wallet

This commit is contained in:
Arseni
2026-03-05 01:48:53 +03:00
parent c59538869b
commit 519a2b1304
18 changed files with 293 additions and 249 deletions

View File

@@ -1,172 +0,0 @@
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;
}
}

View File

@@ -0,0 +1,31 @@
import 'package:pshared/models/ledger/account.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
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;
}

View File

@@ -0,0 +1,19 @@
import 'package:pshared/models/ledger/account.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
String walletDisplayName(Wallet wallet, AppLocalizations l10n) {
return sourceDisplayName(name: wallet.name, fallback: l10n.paymentTypeWallet);
}
String ledgerDisplayName(LedgerAccount ledger, AppLocalizations l10n) {
return sourceDisplayName(name: ledger.name, fallback: l10n.paymentTypeLedger);
}
String sourceDisplayName({required String name, required String fallback}) {
final normalized = name.trim();
if (normalized.isNotEmpty) return normalized;
return fallback;
}

View File

@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/ledger/account.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pweb/widgets/payment/source_wallet_selector/balance_formatter.dart';
import 'package:pweb/widgets/payment/source_wallet_selector/display_name.dart';
import 'package:pweb/widgets/payment/source_wallet_selector/options.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
List<DropdownMenuItem<SourceOptionKey>> buildSourceSelectorItems({
required List<Wallet> wallets,
required List<LedgerAccount> ledgerAccounts,
required AppLocalizations l10n,
}) {
return <DropdownMenuItem<SourceOptionKey>>[
...wallets.map((wallet) {
return DropdownMenuItem<SourceOptionKey>(
value: walletOptionKey(wallet.id),
child: Text(
'${walletDisplayName(wallet, l10n)} - ${walletBalance(wallet)}',
overflow: TextOverflow.ellipsis,
),
);
}),
...ledgerAccounts.map((ledger) {
return DropdownMenuItem<SourceOptionKey>(
value: ledgerOptionKey(ledger.ledgerAccountRef),
child: Text(
'${ledgerDisplayName(ledger, l10n)} - ${ledgerBalance(ledger)}',
overflow: TextOverflow.ellipsis,
),
);
}),
];
}

View File

@@ -0,0 +1,25 @@
import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/models/payment/source_type.dart';
typedef SourceOptionKey = ({PaymentSourceType type, String ref});
SourceOptionKey walletOptionKey(String walletRef) =>
(type: PaymentSourceType.wallet, ref: walletRef);
SourceOptionKey ledgerOptionKey(String ledgerAccountRef) =>
(type: PaymentSourceType.ledger, ref: ledgerAccountRef);
SourceOptionKey? resolveSelectedSourceOption(PaymentSourceController source) {
final selectedWallet = source.selectedWallet;
final selectedLedger = source.selectedLedgerAccount;
return switch (source.selectedType) {
PaymentSourceType.wallet =>
selectedWallet == null ? null : walletOptionKey(selectedWallet.id),
PaymentSourceType.ledger =>
selectedLedger == null
? null
: ledgerOptionKey(selectedLedger.ledgerAccountRef),
null => null,
};
}

View File

@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/ledger/account.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pweb/widgets/payment/source_wallet_selector/dropdown_items.dart';
import 'package:pweb/widgets/payment/source_wallet_selector/options.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
Widget buildSourceSelectorField({
required BuildContext context,
required List<Wallet> wallets,
required List<LedgerAccount> ledgerAccounts,
required SourceOptionKey? selectedValue,
required ValueChanged<SourceOptionKey> onChanged,
required bool isBusy,
}) {
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 = buildSourceSelectorItems(
wallets: wallets,
ledgerAccounts: ledgerAccounts,
l10n: l10n,
);
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);
},
);
}

View File

@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/models/payment/source_type.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pweb/widgets/payment/source_wallet_selector/options.dart';
import 'package:pweb/widgets/payment/source_wallet_selector/selector_field.dart';
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;
return buildSourceSelectorField(
context: context,
wallets: source.wallets,
ledgerAccounts: source.ledgerAccounts,
selectedValue: resolveSelectedSourceOption(source),
onChanged: (value) => _onSourceChanged(source, value),
isBusy: isBusy,
);
}
void _onSourceChanged(PaymentSourceController source, SourceOptionKey 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);
}
}
}