This commit is contained in:
2026-03-25 01:01:40 +03:00
parent 782c9047f9
commit ceea63812b
2 changed files with 121 additions and 39 deletions
@@ -42,7 +42,11 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
void initState() { void initState() {
super.initState(); super.initState();
// 0.92 позволяет видеть края предыдущей/следующей карточки // 0.92 позволяет видеть края предыдущей/следующей карточки
_pageController = PageController(viewportFraction: 0.92); final savedIndex = ref.read(activeAccountIndexProvider);
_pageController = PageController(
viewportFraction: 0.92,
initialPage: savedIndex,
);
} }
@override @override
@@ -70,7 +74,8 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
maxWidth: MediaQuery.of(context).size.width, maxWidth: MediaQuery.of(context).size.width,
child: PageView.builder( child: PageView.builder(
controller: _pageController, controller: _pageController,
clipBehavior: Clip.none, // Не обрезает карточку при 3D-наклоне clipBehavior:
Clip.none, // Не обрезает карточку при 3D-наклоне
itemCount: totalPages, itemCount: totalPages,
onPageChanged: (index) { onPageChanged: (index) {
ref.read(activeAccountIndexProvider.notifier).state = index; ref.read(activeAccountIndexProvider.notifier).state = index;
@@ -92,28 +97,40 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
); );
} else if (index <= accounts.length) { } else if (index <= accounts.length) {
final account = accounts[index - 1]; final account = accounts[index - 1];
final accountColors = ref.watch(accountCardColorsProvider(account.id)); final accountColors = ref.watch(
accountCardColorsProvider(account.id),
);
// Calculate this specific account's balance // Calculate this specific account's balance
final txs = ref.watch(transactionsProvider).valueOrNull ?? []; final txs =
final accountTxs = txs.where((t) => t.accountId == account.id).toList(); ref.watch(transactionsProvider).valueOrNull ?? [];
final exchangeService = ref.watch(exchangeRateServiceProvider); final accountTxs = txs
.where((t) => t.accountId == account.id)
.toList();
final exchangeService = ref.watch(
exchangeRateServiceProvider,
);
final accountBalance = accountTxs.fold(0.0, (sum, t) { final accountBalance = accountTxs.fold(0.0, (sum, t) {
final converted = exchangeService.convert( final converted = exchangeService.convert(
t.amount, t.amount,
t.currencyCode, t.currencyCode,
account.currency, // target is the account's own currency account
.currency, // target is the account's own currency
); );
return t.type == TransactionType.income ? sum + converted : sum - converted; return t.type == TransactionType.income
? sum + converted
: sum - converted;
}); });
cardWidget = BalanceCard( cardWidget = BalanceCard(
balance: accountBalance, // Use the dynamically calculated balance! balance:
accountBalance, // Use the dynamically calculated balance!
currencyInfo: CurrencyInfo( currencyInfo: CurrencyInfo(
currencyMap[account.currency]?.symbol ?? '\$', currencyMap[account.currency]?.symbol ?? '\$',
account.currency, account.currency,
), ),
onLongPress: () => widget.onAccountLongPress?.call(account), onLongPress: () =>
widget.onAccountLongPress?.call(account),
accountName: account.name, accountName: account.name,
accountColors: accountColors, accountColors: accountColors,
); );
@@ -133,10 +150,7 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_DotIndicators( _DotIndicators(count: totalPages, activeIndex: activeIndex),
count: totalPages,
activeIndex: activeIndex,
),
], ],
); );
}, },
@@ -159,10 +173,7 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
const _DotIndicators( const _DotIndicators(count: 1, activeIndex: 0),
count: 1,
activeIndex: 0,
),
], ],
); );
}, },
@@ -180,7 +191,10 @@ class AddAccountCard extends StatelessWidget {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), // Reduced margins for larger size margin: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
), // Reduced margins for larger size
child: CustomPaint( child: CustomPaint(
painter: _DashedBorderPainter(), painter: _DashedBorderPainter(),
child: Container( child: Container(
@@ -196,14 +210,18 @@ class AddAccountCard extends StatelessWidget {
Icon( Icon(
Icons.add_rounded, Icons.add_rounded,
size: 36, // Slightly bigger icon size: 36, // Slightly bigger icon
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5), color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.5),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Add account', 'Add account',
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5), color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.5),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -240,10 +258,7 @@ class _DashedBorderPainter extends CustomPainter {
for (final metric in pathMetrics) { for (final metric in pathMetrics) {
double distance = 0; double distance = 0;
while (distance < metric.length) { while (distance < metric.length) {
final segment = metric.extractPath( final segment = metric.extractPath(distance, distance + dashWidth);
distance,
distance + dashWidth,
);
canvas.drawPath(segment, paint); canvas.drawPath(segment, paint);
distance += dashWidth + dashSpace; distance += dashWidth + dashSpace;
} }
@@ -258,10 +273,7 @@ class _DotIndicators extends StatelessWidget {
final int count; final int count;
final int activeIndex; final int activeIndex;
const _DotIndicators({ const _DotIndicators({required this.count, required this.activeIndex});
required this.count,
required this.activeIndex,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -284,4 +296,4 @@ class _DotIndicators extends StatelessWidget {
}), }),
); );
} }
} }
@@ -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';
@@ -8,6 +9,7 @@ import '../../../core/l10n/locale_provider.dart';
import '../../../shared/models/transaction.dart'; import '../../../shared/models/transaction.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 '../provider.dart';
class TransactionTile extends ConsumerWidget { class TransactionTile extends ConsumerWidget {
final Transaction transaction; final Transaction transaction;
@@ -32,6 +34,21 @@ class TransactionTile extends ConsumerWidget {
final catIcon = final catIcon =
AppCategories.icons[transaction.category] ?? Icons.category_rounded; AppCategories.icons[transaction.category] ?? Icons.category_rounded;
// Check if we're on Total Balance page
final activeAccount = ref.watch(activeAccountProvider);
// Look up the account name by matching transaction.accountId
final accounts = ref.watch(accountsProvider).valueOrNull ?? [];
final txAccount = accounts.firstWhereOrNull(
(a) => a.id == transaction.accountId,
);
// Build account label with 10-character limit
String accountLabel = txAccount?.name ?? '';
if (accountLabel.length > 10) {
accountLabel = '${accountLabel.substring(0, 10)}...';
}
return GestureDetector( return GestureDetector(
onTap: () => context.push('/add', extra: transaction), onTap: () => context.push('/add', extra: transaction),
child: Container( child: Container(
@@ -56,12 +73,24 @@ class TransactionTile extends ConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Row(
s.categoryLabel(transaction.category), children: [
style: Theme.of(context).textTheme.bodyMedium?.copyWith( Flexible(
fontWeight: FontWeight.w600, child: Text(
color: Theme.of(context).colorScheme.onSurface, s.categoryLabel(transaction.category),
), style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
overflow: TextOverflow.ellipsis,
),
),
if (activeAccount == null && accountLabel.isNotEmpty) ...[
const SizedBox(width: 6),
_AccountTag(label: accountLabel),
],
],
), ),
if (transaction.note != null && transaction.note!.isNotEmpty) if (transaction.note != null && transaction.note!.isNotEmpty)
Text( Text(
@@ -75,7 +104,10 @@ class TransactionTile extends ConsumerWidget {
) )
else else
Text( Text(
DateFormat('d MMM yyyy · HH:mm', s.dateLocale).format(transaction.date), DateFormat(
'd MMM yyyy · HH:mm',
s.dateLocale,
).format(transaction.date),
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of( color: Theme.of(
context, context,
@@ -99,6 +131,44 @@ class TransactionTile extends ConsumerWidget {
} }
} }
class _AccountTag extends StatelessWidget {
final String label;
const _AccountTag({required this.label});
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return IntrinsicWidth(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
decoration: BoxDecoration(
color: isDark
? Colors.white.withOpacity(0.08)
: const Color(0xFFF0EFFE),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: isDark
? Colors.white.withOpacity(0.12)
: const Color(0xFFD0CAFF),
width: 1,
),
),
child: Text(
label,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w500,
color: isDark
? Colors.white.withOpacity(0.55)
: const Color(0xFF7C6DED),
letterSpacing: 0.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});