This commit is contained in:
2026-03-27 12:16:37 +03:00
parent b7047c0ec7
commit 123c7d0eb4
9 changed files with 991 additions and 259 deletions
+14 -7
View File
@@ -114,7 +114,7 @@ class TransactionsNotifier
final searchQueryProvider = StateProvider<String>((ref) => '');
enum TransactionFilter { all, income, expense }
enum TransactionFilter { all, income, expense, transfer }
enum TimeFilter { allTime, lastMonth }
@@ -146,7 +146,7 @@ final globalTotalBalanceProvider = Provider<double>((ref) {
final exchangeService = ref.watch(exchangeRateServiceProvider);
final targetCurrency = ref.watch(currencyProvider).code;
return txs.fold(0.0, (sum, t) {
return txs.where((t) => t.category != 'Transfer').fold(0.0, (sum, t) {
final converted = exchangeService.convert(
t.amount,
t.currencyCode,
@@ -173,7 +173,7 @@ final totalBalanceProvider = Provider<double>((ref) {
final exchangeService = ref.watch(exchangeRateServiceProvider);
return txs.fold(0.0, (sum, t) {
return txs.where((t) => t.category != 'Transfer').fold(0.0, (sum, t) {
final converted = exchangeService.convert(
t.amount,
t.currencyCode,
@@ -186,7 +186,9 @@ final totalBalanceProvider = Provider<double>((ref) {
final totalIncomeProvider = Provider<double>((ref) {
// Watch the filtered transactions directly
final txs = ref.watch(accountFilteredTransactionsProvider);
final filtered = txs.where((t) => t.type == TransactionType.income);
final filtered = txs.where(
(t) => t.type == TransactionType.income && t.category != 'Transfer',
);
// Watch the dependencies that change on swipe!
final index = ref.watch(activeAccountIndexProvider);
@@ -212,7 +214,9 @@ final totalIncomeProvider = Provider<double>((ref) {
final totalExpenseProvider = Provider<double>((ref) {
final txs = ref.watch(accountFilteredTransactionsProvider);
final filtered = txs.where((t) => t.type == TransactionType.expense);
final filtered = txs.where(
(t) => t.type == TransactionType.expense && t.category != 'Transfer',
);
final index = ref.watch(activeAccountIndexProvider);
final accountsAsync = ref.watch(accountsProvider);
@@ -291,6 +295,8 @@ final filteredTransactionsProvider = Provider<List<Transaction>>((ref) {
filtered = filtered
.where((t) => t.type == TransactionType.expense)
.toList();
} else if (typeFilter == TransactionFilter.transfer) {
filtered = filtered.where((t) => t.category == 'Transfer').toList();
}
if (query.isNotEmpty) {
@@ -402,8 +408,9 @@ class CardColorsNotifier extends StateNotifier<CardColors> {
Future<void> _load() async {
final currentGeneration = ++_loadGeneration;
final (c1, c2, lightG, darkG) =
await CardColorService.load(accountId: accountId);
final (c1, c2, lightG, darkG) = await CardColorService.load(
accountId: accountId,
);
if (currentGeneration != _loadGeneration) return; // stale
state = CardColors(c1, c2, lightG, darkG);
}
@@ -15,49 +15,66 @@ class FilterChips extends ConsumerWidget {
final timeFilter = ref.watch(timeFilterProvider);
final isDark = Theme.of(context).brightness == Brightness.dark;
return Row(
children: [
_FilterChip(
label: strings.filterAllTime,
isSelected: timeFilter == TimeFilter.allTime,
onTap: () => ref.read(timeFilterProvider.notifier).state = TimeFilter.allTime,
),
const SizedBox(width: 6),
_FilterChip(
label: strings.filterMonth,
isSelected: timeFilter == TimeFilter.lastMonth,
onTap: () => ref.read(timeFilterProvider.notifier).state = TimeFilter.lastMonth,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Container(
width: 1,
height: 20,
color: Theme.of(context).colorScheme.onSurface.withOpacity(
isDark ? 0.15 : 0.2,
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
clipBehavior: Clip.none,
child: Row(
children: [
_FilterChip(
label: strings.filterAllTime,
isSelected: timeFilter == TimeFilter.allTime,
onTap: () => ref.read(timeFilterProvider.notifier).state =
TimeFilter.allTime,
),
const SizedBox(width: 6),
_FilterChip(
label: strings.filterMonth,
isSelected: timeFilter == TimeFilter.lastMonth,
onTap: () => ref.read(timeFilterProvider.notifier).state =
TimeFilter.lastMonth,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Container(
width: 1,
height: 20,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(isDark ? 0.15 : 0.2),
),
),
),
_FilterChip(
label: strings.filterAll,
isSelected: typeFilter == TransactionFilter.all,
onTap: () => ref.read(transactionFilterProvider.notifier).state = TransactionFilter.all,
),
const SizedBox(width: 6),
_FilterChip(
label: strings.filterIncome,
isSelected: typeFilter == TransactionFilter.income,
color: AppColors.income,
onTap: () => ref.read(transactionFilterProvider.notifier).state = TransactionFilter.income,
),
const SizedBox(width: 6),
_FilterChip(
label: strings.filterExpense,
isSelected: typeFilter == TransactionFilter.expense,
color: AppColors.expense,
onTap: () => ref.read(transactionFilterProvider.notifier).state = TransactionFilter.expense,
),
],
_FilterChip(
label: strings.filterAll,
isSelected: typeFilter == TransactionFilter.all,
onTap: () => ref.read(transactionFilterProvider.notifier).state =
TransactionFilter.all,
),
const SizedBox(width: 6),
_FilterChip(
label: strings.filterIncome,
isSelected: typeFilter == TransactionFilter.income,
color: AppColors.income,
onTap: () => ref.read(transactionFilterProvider.notifier).state =
TransactionFilter.income,
),
const SizedBox(width: 6),
_FilterChip(
label: strings.filterExpense,
isSelected: typeFilter == TransactionFilter.expense,
color: AppColors.expense,
onTap: () => ref.read(transactionFilterProvider.notifier).state =
TransactionFilter.expense,
),
const SizedBox(width: 6),
_FilterChip(
label: strings.filterTransfer,
isSelected: typeFilter == TransactionFilter.transfer,
color: Colors.blueAccent,
onTap: () => ref.read(transactionFilterProvider.notifier).state =
TransactionFilter.transfer,
),
],
),
);
}
}
@@ -96,8 +113,8 @@ class _FilterChip extends StatelessWidget {
border: isSelected
? Border.all(color: chipColor, width: 1.5)
: isDark
? null
: Border.all(color: const Color(0xFFDDDDEE), width: 1),
? null
: Border.all(color: const Color(0xFFDDDDEE), width: 1),
),
child: Text(
label,
@@ -29,19 +29,23 @@ class TransactionTile extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final s = ref.watch(stringsProvider);
final fmt = ref.watch(amountFormatProvider);
final isTransfer = transaction.category == 'Transfer';
final isIncome = transaction.type == TransactionType.income;
final color = isIncome ? AppColors.income : AppColors.expense;
final catColor =
AppCategories.colors[transaction.category] ?? AppColors.accent;
final catIcon =
AppCategories.icons[transaction.category] ?? Icons.category_rounded;
final color = isTransfer
? const Color(0xFF7C6DED)
: (isIncome ? AppColors.income : AppColors.expense);
final catColor = isTransfer
? const Color(0xFF7C6DED)
: (AppCategories.colors[transaction.category] ?? AppColors.accent);
final catIcon = isTransfer
? Icons.swap_horiz_rounded
: (AppCategories.icons[transaction.category] ?? Icons.category_rounded);
// Check if we're on Total Balance page
final activeAccount = ref.watch(activeAccountProvider);
final displayCurrency = activeAccount?.currency ??
ref.watch(currencyProvider).code;
final showConverted =
transaction.currencyCode != displayCurrency;
final displayCurrency =
activeAccount?.currency ?? ref.watch(currencyProvider).code;
final showConverted = transaction.currencyCode != displayCurrency;
final exchangeService = ref.watch(exchangeRateServiceProvider);
final convertedAmount = showConverted
? exchangeService.convert(
@@ -50,8 +54,7 @@ class TransactionTile extends ConsumerWidget {
displayCurrency,
)
: 0.0;
final displaySymbol =
currencyMap[displayCurrency]?.symbol ?? '';
final displaySymbol = currencyMap[displayCurrency]?.symbol ?? '';
// Look up the account name by matching transaction.accountId
final accounts = ref.watch(accountsProvider).valueOrNull ?? [];
@@ -93,7 +96,9 @@ class TransactionTile extends ConsumerWidget {
children: [
Flexible(
child: Text(
s.categoryLabel(transaction.category),
isTransfer
? 'Transfer'
: s.categoryLabel(transaction.category),
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w600,
@@ -143,12 +148,12 @@ class TransactionTile extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
isIncome ? '+ ' : '- ',
isTransfer ? '' : (isIncome ? '+ ' : '- '),
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
color: color,
fontWeight: FontWeight.w700,
),
),
BynSign(fontSize: 14, color: color),
const SizedBox(width: 2),
@@ -156,14 +161,20 @@ class TransactionTile extends ConsumerWidget {
formatAmount('', transaction.amount, fmt),
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
color: color,
fontWeight: FontWeight.w700,
),
),
],
)
: Text(
'${isIncome ? '+ ' : '- '}${formatAmount(transaction.currency, transaction.amount, fmt)}',
isTransfer
? formatAmount(
transaction.currency,
transaction.amount,
fmt,
)
: '${isIncome ? '+ ' : '- '}${formatAmount(transaction.currency, transaction.amount, fmt)}',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w700,
@@ -184,10 +195,7 @@ class TransactionTile extends ConsumerWidget {
height: 1.3,
),
),
BynSign(
fontSize: 11,
color: color.withOpacity(0.5),
),
BynSign(fontSize: 11, color: color.withOpacity(0.5)),
const SizedBox(width: 2),
Text(
formatAmount('', convertedAmount, fmt),