mirror of
https://github.com/koloideal/Casha.git
synced 2026-06-10 10:25:28 +03:00
stableee
This commit is contained in:
@@ -30,10 +30,15 @@ class AddTransactionState {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory AddTransactionState.fromTransaction(Transaction tx) {
|
factory AddTransactionState.fromTransaction(Transaction tx) {
|
||||||
|
// Override type to transfer when category is 'Transfer'
|
||||||
|
final resolvedType = (tx.category == 'Transfer')
|
||||||
|
? TransactionType.transfer
|
||||||
|
: tx.type;
|
||||||
|
|
||||||
return AddTransactionState(
|
return AddTransactionState(
|
||||||
amount: tx.amount,
|
amount: tx.amount,
|
||||||
category: tx.category,
|
category: tx.category,
|
||||||
type: tx.type,
|
type: resolvedType,
|
||||||
date: tx.date,
|
date: tx.date,
|
||||||
note: tx.note ?? '',
|
note: tx.note ?? '',
|
||||||
editingId: tx.id,
|
editingId: tx.id,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -75,6 +76,30 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
|||||||
if (widget.initial != null) {
|
if (widget.initial != null) {
|
||||||
_amountController.text = widget.initial!.amount.toString();
|
_amountController.text = widget.initial!.amount.toString();
|
||||||
_noteController.text = widget.initial!.note ?? '';
|
_noteController.text = widget.initial!.note ?? '';
|
||||||
|
|
||||||
|
// Pre-populate toAccountId when opening Transfer for edit
|
||||||
|
if (widget.initial!.category == 'Transfer') {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final allTxs = ref.read(transactionsProvider).valueOrNull ?? [];
|
||||||
|
final counterpart = allTxs.firstWhereOrNull(
|
||||||
|
(t) =>
|
||||||
|
t.category == 'Transfer' &&
|
||||||
|
t.type == TransactionType.income &&
|
||||||
|
t.amount == widget.initial!.amount &&
|
||||||
|
t.date.year == widget.initial!.date.year &&
|
||||||
|
t.date.month == widget.initial!.date.month &&
|
||||||
|
t.date.day == widget.initial!.date.day &&
|
||||||
|
t.date.hour == widget.initial!.date.hour &&
|
||||||
|
t.date.minute == widget.initial!.date.minute &&
|
||||||
|
t.note == widget.initial!.note,
|
||||||
|
);
|
||||||
|
if (counterpart != null) {
|
||||||
|
ref
|
||||||
|
.read(addTransactionProvider(widget.initial).notifier)
|
||||||
|
.setToAccountId(counterpart.accountId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final activeAccount = ref.read(activeAccountProvider);
|
final activeAccount = ref.read(activeAccountProvider);
|
||||||
@@ -207,6 +232,58 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
|||||||
? state.overrideCurrencyCode
|
? state.overrideCurrencyCode
|
||||||
: curr.code;
|
: curr.code;
|
||||||
|
|
||||||
|
if (state.isEditing) {
|
||||||
|
// Update both sides of the transfer pair
|
||||||
|
// Update the expense side (widget.initial = expense record)
|
||||||
|
final updatedExpense = Transaction(
|
||||||
|
id: widget.initial!.id,
|
||||||
|
amount: amount,
|
||||||
|
category: 'Transfer',
|
||||||
|
type: TransactionType.expense,
|
||||||
|
date: finalDateTime,
|
||||||
|
note: note,
|
||||||
|
currency: currency,
|
||||||
|
currencyCode: currencyCode,
|
||||||
|
accountId: widget.initial!.accountId, // locked, unchanged
|
||||||
|
);
|
||||||
|
await ref.read(transactionsProvider.notifier).update(updatedExpense);
|
||||||
|
|
||||||
|
// Find and update the income counterpart
|
||||||
|
final allTxs = ref.read(transactionsProvider).valueOrNull ?? [];
|
||||||
|
final counterpart = allTxs.firstWhereOrNull(
|
||||||
|
(t) =>
|
||||||
|
t.category == 'Transfer' &&
|
||||||
|
t.type == TransactionType.income &&
|
||||||
|
t.accountId == state.toAccountId &&
|
||||||
|
t.amount ==
|
||||||
|
widget.initial!.amount && // match by original amount
|
||||||
|
t.date.year == widget.initial!.date.year &&
|
||||||
|
t.date.month == widget.initial!.date.month &&
|
||||||
|
t.date.day == widget.initial!.date.day &&
|
||||||
|
t.date.hour == widget.initial!.date.hour &&
|
||||||
|
t.date.minute == widget.initial!.date.minute &&
|
||||||
|
t.note == widget.initial!.note,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (counterpart != null) {
|
||||||
|
final updatedIncome = Transaction(
|
||||||
|
id: counterpart.id,
|
||||||
|
amount: amount, // updated amount
|
||||||
|
category: 'Transfer',
|
||||||
|
type: TransactionType.income,
|
||||||
|
date: finalDateTime,
|
||||||
|
note: note,
|
||||||
|
currency: currency, // updated currency
|
||||||
|
currencyCode: currencyCode,
|
||||||
|
accountId: counterpart.accountId, // locked, unchanged
|
||||||
|
);
|
||||||
|
await ref.read(transactionsProvider.notifier).update(updatedIncome);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) context.pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final expense = Transaction(
|
final expense = Transaction(
|
||||||
id: _uuid.v4(),
|
id: _uuid.v4(),
|
||||||
amount: amount,
|
amount: amount,
|
||||||
@@ -378,8 +455,9 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
|||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
final isEditing = state.isEditing;
|
final isEditing = state.isEditing;
|
||||||
final activeAccount = ref.watch(activeAccountProvider);
|
final activeAccount = ref.watch(activeAccountProvider);
|
||||||
final isAccountLocked = activeAccount != null;
|
|
||||||
final isTransfer = state.type == TransactionType.transfer;
|
final isTransfer = state.type == TransactionType.transfer;
|
||||||
|
final isEditingTransfer = isEditing && isTransfer;
|
||||||
|
final isAccountLocked = activeAccount != null || isEditingTransfer;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@@ -112,6 +113,35 @@ class TransactionsNotifier
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a map: transactionId -> paired Transaction (its counterpart)
|
||||||
|
final transferPairsProvider = Provider<Map<String, Transaction>>((ref) {
|
||||||
|
final txs = ref.watch(transactionsProvider).valueOrNull ?? [];
|
||||||
|
final transfers = txs.where((t) => t.category == 'Transfer').toList();
|
||||||
|
final Map<String, Transaction> pairs = {};
|
||||||
|
|
||||||
|
for (final tx in transfers) {
|
||||||
|
if (pairs.containsKey(tx.id)) continue; // already paired
|
||||||
|
// Find counterpart: opposite type, same amount, same date (minute precision), same note
|
||||||
|
final counterpart = transfers.firstWhereOrNull(
|
||||||
|
(other) =>
|
||||||
|
other.id != tx.id &&
|
||||||
|
other.type != tx.type &&
|
||||||
|
other.amount == tx.amount &&
|
||||||
|
other.date.year == tx.date.year &&
|
||||||
|
other.date.month == tx.date.month &&
|
||||||
|
other.date.day == tx.date.day &&
|
||||||
|
other.date.hour == tx.date.hour &&
|
||||||
|
other.date.minute == tx.date.minute &&
|
||||||
|
other.note == tx.note,
|
||||||
|
);
|
||||||
|
if (counterpart != null) {
|
||||||
|
pairs[tx.id] = counterpart;
|
||||||
|
pairs[counterpart.id] = tx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pairs;
|
||||||
|
});
|
||||||
|
|
||||||
final searchQueryProvider = StateProvider<String>((ref) => '');
|
final searchQueryProvider = StateProvider<String>((ref) => '');
|
||||||
|
|
||||||
enum TransactionFilter { all, income, expense, transfer }
|
enum TransactionFilter { all, income, expense, transfer }
|
||||||
@@ -273,6 +303,8 @@ final filteredTransactionsProvider = Provider<List<Transaction>>((ref) {
|
|||||||
final query = ref.watch(searchQueryProvider).toLowerCase();
|
final query = ref.watch(searchQueryProvider).toLowerCase();
|
||||||
final typeFilter = ref.watch(transactionFilterProvider);
|
final typeFilter = ref.watch(transactionFilterProvider);
|
||||||
final timeFilter = ref.watch(timeFilterProvider);
|
final timeFilter = ref.watch(timeFilterProvider);
|
||||||
|
final activeAccount = ref.watch(activeAccountProvider);
|
||||||
|
final transferPairs = ref.watch(transferPairsProvider);
|
||||||
|
|
||||||
var filtered = txs;
|
var filtered = txs;
|
||||||
|
|
||||||
@@ -308,6 +340,20 @@ final filteredTransactionsProvider = Provider<List<Transaction>>((ref) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filtered.sort((a, b) => b.date.compareTo(a.date));
|
filtered.sort((a, b) => b.date.compareTo(a.date));
|
||||||
|
|
||||||
|
// Deduplicate transfers for Total Balance view
|
||||||
|
if (activeAccount == null) {
|
||||||
|
filtered = filtered.where((t) {
|
||||||
|
if (t.category != 'Transfer') return true;
|
||||||
|
// On Total Balance: show only expense side of complete pairs
|
||||||
|
// If income side has a known pair, hide it
|
||||||
|
if (t.type == TransactionType.income && transferPairs.containsKey(t.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
return filtered;
|
return filtered;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import '../../../core/constants.dart';
|
|||||||
import '../../../core/l10n/app_strings.dart';
|
import '../../../core/l10n/app_strings.dart';
|
||||||
import '../../../core/l10n/locale_provider.dart';
|
import '../../../core/l10n/locale_provider.dart';
|
||||||
import '../../../shared/models/transaction.dart';
|
import '../../../shared/models/transaction.dart';
|
||||||
|
import '../../../shared/models/account.dart';
|
||||||
import '../../../shared/providers/amount_format_provider.dart';
|
import '../../../shared/providers/amount_format_provider.dart';
|
||||||
import '../../../shared/utils/currency_utils.dart';
|
import '../../../shared/utils/currency_utils.dart';
|
||||||
import '../../../shared/widgets/byn_sign.dart';
|
import '../../../shared/widgets/byn_sign.dart';
|
||||||
@@ -68,6 +69,10 @@ class TransactionTile extends ConsumerWidget {
|
|||||||
accountLabel = '${accountLabel.substring(0, 10)}...';
|
accountLabel = '${accountLabel.substring(0, 10)}...';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transfer pairing logic
|
||||||
|
final pairs = ref.watch(transferPairsProvider);
|
||||||
|
final counterpart = pairs[transaction.id];
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => context.push('/add', extra: transaction),
|
onTap: () => context.push('/add', extra: transaction),
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -95,19 +100,29 @@ class TransactionTile extends ConsumerWidget {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: isTransfer
|
||||||
isTransfer
|
? _buildTransferLabel(
|
||||||
? 'Transfer'
|
context,
|
||||||
: s.categoryLabel(transaction.category),
|
ref,
|
||||||
|
counterpart,
|
||||||
|
accounts,
|
||||||
|
activeAccount,
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
s.categoryLabel(transaction.category),
|
||||||
style: Theme.of(context).textTheme.bodyMedium
|
style: Theme.of(context).textTheme.bodyMedium
|
||||||
?.copyWith(
|
?.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (activeAccount == null && accountLabel.isNotEmpty) ...[
|
if (!isTransfer &&
|
||||||
|
activeAccount == null &&
|
||||||
|
accountLabel.isNotEmpty) ...[
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
_AccountTag(label: accountLabel),
|
_AccountTag(label: accountLabel),
|
||||||
],
|
],
|
||||||
@@ -148,35 +163,60 @@ class TransactionTile extends ConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
isTransfer ? '' : (isIncome ? '+ ' : '- '),
|
_getAmountPrefix(
|
||||||
|
isTransfer,
|
||||||
|
isIncome,
|
||||||
|
activeAccount,
|
||||||
|
),
|
||||||
style: Theme.of(context).textTheme.bodyMedium
|
style: Theme.of(context).textTheme.bodyMedium
|
||||||
?.copyWith(
|
?.copyWith(
|
||||||
color: color,
|
color: _getAmountColor(
|
||||||
|
context,
|
||||||
|
isTransfer,
|
||||||
|
isIncome,
|
||||||
|
activeAccount,
|
||||||
|
color,
|
||||||
|
),
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BynSign(fontSize: 14, color: color),
|
BynSign(
|
||||||
|
fontSize: 14,
|
||||||
|
color: _getAmountColor(
|
||||||
|
context,
|
||||||
|
isTransfer,
|
||||||
|
isIncome,
|
||||||
|
activeAccount,
|
||||||
|
color,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 2),
|
const SizedBox(width: 2),
|
||||||
Text(
|
Text(
|
||||||
formatAmount('', transaction.amount, fmt),
|
formatAmount('', transaction.amount, fmt),
|
||||||
style: Theme.of(context).textTheme.bodyMedium
|
style: Theme.of(context).textTheme.bodyMedium
|
||||||
?.copyWith(
|
?.copyWith(
|
||||||
color: color,
|
color: _getAmountColor(
|
||||||
|
context,
|
||||||
|
isTransfer,
|
||||||
|
isIncome,
|
||||||
|
activeAccount,
|
||||||
|
color,
|
||||||
|
),
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
isTransfer
|
'${_getAmountPrefix(isTransfer, isIncome, activeAccount)}${formatAmount(transaction.currency, transaction.amount, fmt)}',
|
||||||
? formatAmount(
|
|
||||||
transaction.currency,
|
|
||||||
transaction.amount,
|
|
||||||
fmt,
|
|
||||||
)
|
|
||||||
: '${isIncome ? '+ ' : '- '}${formatAmount(transaction.currency, transaction.amount, fmt)}',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
color: color,
|
color: _getAmountColor(
|
||||||
|
context,
|
||||||
|
isTransfer,
|
||||||
|
isIncome,
|
||||||
|
activeAccount,
|
||||||
|
color,
|
||||||
|
),
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -226,6 +266,151 @@ class TransactionTile extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildTransferLabel(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
Transaction? counterpart,
|
||||||
|
List<Account> accounts,
|
||||||
|
Account? activeAccount,
|
||||||
|
) {
|
||||||
|
// Fallback if no counterpart
|
||||||
|
if (counterpart == null) {
|
||||||
|
return Text(
|
||||||
|
'Transfer',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final isExpense = transaction.type == TransactionType.expense;
|
||||||
|
final sourceAccountId = isExpense
|
||||||
|
? transaction.accountId
|
||||||
|
: counterpart.accountId;
|
||||||
|
final destAccountId = isExpense
|
||||||
|
? counterpart.accountId
|
||||||
|
: transaction.accountId;
|
||||||
|
|
||||||
|
final sourceAccountName = _accountName(accounts, sourceAccountId);
|
||||||
|
final destAccountName = _accountName(accounts, destAccountId);
|
||||||
|
final onSurface = Theme.of(context).colorScheme.onSurface;
|
||||||
|
|
||||||
|
// Total Balance view (activeAccount == null), showing expense side
|
||||||
|
if (activeAccount == null) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Transfer',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Flexible(
|
||||||
|
child: _TransferChip(
|
||||||
|
label: '$sourceAccountName → $destAccountName',
|
||||||
|
icon: Icons.swap_horiz_rounded,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account view
|
||||||
|
if (isExpense) {
|
||||||
|
// Expense side: Transfer to <destAccountName>
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Transfer',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'to',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: onSurface.withOpacity(0.45),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Flexible(child: _TransferChip(label: destAccountName, icon: null)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Income side: Transfer from <sourceAccountName>
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Transfer',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'from',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: onSurface.withOpacity(0.45),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Flexible(child: _TransferChip(label: sourceAccountName, icon: null)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _accountName(List<Account> accounts, int? id) {
|
||||||
|
if (id == null) return '?';
|
||||||
|
return accounts.firstWhereOrNull((a) => a.id == id)?.name ?? '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getAmountPrefix(
|
||||||
|
bool isTransfer,
|
||||||
|
bool isIncome,
|
||||||
|
Account? activeAccount,
|
||||||
|
) {
|
||||||
|
// Total Balance view with Transfer expense: no prefix
|
||||||
|
if (isTransfer && activeAccount == null && !isIncome) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
// All other cases: show + or −
|
||||||
|
return isIncome ? '+ ' : '\u2212 ';
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getAmountColor(
|
||||||
|
BuildContext context,
|
||||||
|
bool isTransfer,
|
||||||
|
bool isIncome,
|
||||||
|
Account? activeAccount,
|
||||||
|
Color defaultColor,
|
||||||
|
) {
|
||||||
|
// Total Balance view with Transfer expense: neutral color
|
||||||
|
if (isTransfer && activeAccount == null && !isIncome) {
|
||||||
|
return Theme.of(context).colorScheme.onSurface.withOpacity(0.8);
|
||||||
|
}
|
||||||
|
// Transfer in account view or Total Balance income: use income/expense colors
|
||||||
|
if (isTransfer) {
|
||||||
|
return isIncome ? AppColors.income : AppColors.expense;
|
||||||
|
}
|
||||||
|
// Non-transfer: use default color
|
||||||
|
return defaultColor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AccountTag extends StatelessWidget {
|
class _AccountTag extends StatelessWidget {
|
||||||
@@ -266,6 +451,48 @@ class _AccountTag extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TransferChip extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final IconData? icon;
|
||||||
|
const _TransferChip({required this.label, this.icon});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF7C6DED).withOpacity(0.12),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xFF7C6DED).withOpacity(0.35),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (icon != null) ...[
|
||||||
|
Icon(icon, size: 11, color: const Color(0xFF7C6DED)),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
],
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Color(0xFF7C6DED),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class EmptyState extends StatelessWidget {
|
class EmptyState extends StatelessWidget {
|
||||||
final AppStrings strings;
|
final AppStrings strings;
|
||||||
const EmptyState({super.key, required this.strings});
|
const EmptyState({super.key, required this.strings});
|
||||||
|
|||||||
Reference in New Issue
Block a user