mirror of
https://github.com/koloideal/Casha.git
synced 2026-06-10 10:25:28 +03:00
update
This commit is contained in:
@@ -117,9 +117,23 @@ final timeFilterProvider = StateProvider<TimeFilter>(
|
||||
(ref) => TimeFilter.lastMonth,
|
||||
);
|
||||
|
||||
final totalBalanceProvider = Provider<double>((ref) {
|
||||
// Base filtered transactions by active account
|
||||
final accountFilteredTransactionsProvider = Provider<List<Transaction>>((ref) {
|
||||
final txsAsync = ref.watch(transactionsProvider);
|
||||
final txs = txsAsync.valueOrNull ?? [];
|
||||
final activeAccount = ref.watch(activeAccountProvider);
|
||||
|
||||
// If activeAccount is null (Total Balance page), return all transactions
|
||||
if (activeAccount == null) {
|
||||
return txs;
|
||||
}
|
||||
|
||||
// Filter by account ID
|
||||
return txs.where((t) => t.accountId == activeAccount.id).toList();
|
||||
});
|
||||
|
||||
final totalBalanceProvider = Provider<double>((ref) {
|
||||
final txs = ref.watch(accountFilteredTransactionsProvider);
|
||||
final exchangeService = ref.watch(exchangeRateServiceProvider);
|
||||
final targetCurrency = ref.watch(currencyProvider).code;
|
||||
|
||||
@@ -134,8 +148,7 @@ final totalBalanceProvider = Provider<double>((ref) {
|
||||
});
|
||||
|
||||
final totalIncomeProvider = Provider<double>((ref) {
|
||||
final txsAsync = ref.watch(transactionsProvider);
|
||||
final txs = txsAsync.valueOrNull ?? [];
|
||||
final txs = ref.watch(accountFilteredTransactionsProvider);
|
||||
final filtered = txs.where((t) => t.type == TransactionType.income);
|
||||
final exchangeService = ref.watch(exchangeRateServiceProvider);
|
||||
final targetCurrency = ref.watch(currencyProvider).code;
|
||||
@@ -147,8 +160,7 @@ final totalIncomeProvider = Provider<double>((ref) {
|
||||
});
|
||||
|
||||
final totalExpenseProvider = Provider<double>((ref) {
|
||||
final txsAsync = ref.watch(transactionsProvider);
|
||||
final txs = txsAsync.valueOrNull ?? [];
|
||||
final txs = ref.watch(accountFilteredTransactionsProvider);
|
||||
final filtered = txs.where((t) => t.type == TransactionType.expense);
|
||||
final exchangeService = ref.watch(exchangeRateServiceProvider);
|
||||
final targetCurrency = ref.watch(currencyProvider).code;
|
||||
@@ -161,8 +173,7 @@ final totalExpenseProvider = Provider<double>((ref) {
|
||||
|
||||
final currentMonthExpenseProvider = Provider<double>((ref) {
|
||||
final now = DateTime.now();
|
||||
final txsAsync = ref.watch(transactionsProvider);
|
||||
final txs = txsAsync.valueOrNull ?? [];
|
||||
final txs = ref.watch(accountFilteredTransactionsProvider);
|
||||
final filtered = txs.where(
|
||||
(t) =>
|
||||
t.type == TransactionType.expense &&
|
||||
@@ -179,8 +190,7 @@ final currentMonthExpenseProvider = Provider<double>((ref) {
|
||||
});
|
||||
|
||||
final filteredTransactionsProvider = Provider<List<Transaction>>((ref) {
|
||||
final txsAsync = ref.watch(transactionsProvider);
|
||||
final txs = txsAsync.valueOrNull ?? [];
|
||||
final txs = ref.watch(accountFilteredTransactionsProvider);
|
||||
final query = ref.watch(searchQueryProvider).toLowerCase();
|
||||
final typeFilter = ref.watch(transactionFilterProvider);
|
||||
final timeFilter = ref.watch(timeFilterProvider);
|
||||
@@ -236,6 +246,25 @@ final accountsProvider = StreamProvider<List<Account>>((ref) async* {
|
||||
// Ephemeral UI state — active carousel index, starts at 0, not persisted
|
||||
final activeAccountIndexProvider = StateProvider<int>((ref) => 0);
|
||||
|
||||
// Returns the currently active Account based on carousel index
|
||||
final activeAccountProvider = Provider<Account?>((ref) {
|
||||
final index = ref.watch(activeAccountIndexProvider);
|
||||
final accountsAsync = ref.watch(accountsProvider);
|
||||
|
||||
if (index == 0) return null; // 0 means "Total Balance"
|
||||
|
||||
return accountsAsync.when(
|
||||
data: (accounts) {
|
||||
if (index > 0 && index <= accounts.length) {
|
||||
return accounts[index - 1];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
loading: () => null,
|
||||
error: (_, __) => null,
|
||||
);
|
||||
});
|
||||
|
||||
class CardColors {
|
||||
final Color primary;
|
||||
final Color secondary;
|
||||
|
||||
@@ -34,6 +34,7 @@ class BalanceCard extends ConsumerStatefulWidget {
|
||||
final Color? previewPrimary;
|
||||
final Color? previewSecondary;
|
||||
final GradientType? previewGradientType;
|
||||
final String? accountName;
|
||||
|
||||
const BalanceCard({
|
||||
super.key,
|
||||
@@ -43,6 +44,7 @@ class BalanceCard extends ConsumerStatefulWidget {
|
||||
this.previewPrimary,
|
||||
this.previewSecondary,
|
||||
this.previewGradientType,
|
||||
this.accountName,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -181,6 +183,25 @@ class BalanceCardState extends ConsumerState<BalanceCard>
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Stack(
|
||||
children: [
|
||||
if (widget.accountName != null)
|
||||
Positioned(
|
||||
top: 20,
|
||||
left: 24,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.accountName!,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.7),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
@@ -196,15 +217,17 @@ class BalanceCardState extends ConsumerState<BalanceCard>
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
s.totalBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
letterSpacing: 1.5,
|
||||
color: Colors.white.withOpacity(0.6),
|
||||
if (widget.accountName == null)
|
||||
Text(
|
||||
s.totalBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
letterSpacing: 1.5,
|
||||
color: Colors.white.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
if (widget.accountName == null)
|
||||
const SizedBox(height: 6),
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.center,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/services/card_color_service.dart';
|
||||
import '../../../core/services/haptic_service.dart';
|
||||
import '../../../shared/models/transaction.dart';
|
||||
import '../../settings/provider.dart';
|
||||
import '../provider.dart';
|
||||
import 'balance_card.dart';
|
||||
@@ -34,7 +36,8 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pageController = PageController(viewportFraction: 1.0);
|
||||
// 0.92 позволяет видеть края предыдущей/следующей карточки
|
||||
_pageController = PageController(viewportFraction: 0.92);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -50,63 +53,59 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
|
||||
return accountsAsync.when(
|
||||
data: (accounts) {
|
||||
// Debug logging
|
||||
debugPrint('BalanceCardCarousel: accounts.length = ${accounts.length}');
|
||||
if (accounts.isNotEmpty) {
|
||||
debugPrint('BalanceCardCarousel: first account = ${accounts[0].name}');
|
||||
}
|
||||
|
||||
// Page 0: Total balance
|
||||
// Pages 1..N: Account cards
|
||||
// Page N+1: AddAccountCard (if < 5 accounts)
|
||||
final totalPages = 1 + accounts.length + (accounts.length < 5 ? 1 : 0);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 220,
|
||||
child: PageView.builder(
|
||||
clipBehavior: Clip.none,
|
||||
controller: _pageController,
|
||||
itemCount: totalPages,
|
||||
onPageChanged: (index) {
|
||||
ref.read(activeAccountIndexProvider.notifier).state = index;
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
Widget pageContent;
|
||||
|
||||
if (index == 0) {
|
||||
// Page 0: Total balance card
|
||||
pageContent = BalanceCard(
|
||||
balance: widget.balance,
|
||||
currencyInfo: widget.currencyInfo,
|
||||
onLongPress: widget.onLongPress,
|
||||
previewPrimary: widget.previewPrimary,
|
||||
previewSecondary: widget.previewSecondary,
|
||||
previewGradientType: widget.previewGradientType,
|
||||
height: 230,
|
||||
// OverflowBox позволяет PageView игнорировать паддинги родителя (DashboardScreen)
|
||||
// и растянуться на всю ширину экрана
|
||||
child: OverflowBox(
|
||||
maxWidth: MediaQuery.of(context).size.width,
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
clipBehavior: Clip.none, // Не обрезает карточку при 3D-наклоне
|
||||
itemCount: totalPages,
|
||||
onPageChanged: (index) {
|
||||
ref.read(activeAccountIndexProvider.notifier).state = index;
|
||||
if (ref.read(hapticEnabledProvider)) {
|
||||
HapticService.light();
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
Widget cardWidget;
|
||||
|
||||
if (index == 0) {
|
||||
cardWidget = BalanceCard(
|
||||
balance: widget.balance,
|
||||
currencyInfo: widget.currencyInfo,
|
||||
onLongPress: widget.onLongPress,
|
||||
previewPrimary: widget.previewPrimary,
|
||||
previewSecondary: widget.previewSecondary,
|
||||
previewGradientType: widget.previewGradientType,
|
||||
);
|
||||
} else if (index <= accounts.length) {
|
||||
final account = accounts[index - 1];
|
||||
cardWidget = BalanceCard(
|
||||
balance: widget.balance, // TODO: Calculate per-account balance
|
||||
currencyInfo: widget.currencyInfo,
|
||||
onLongPress: null,
|
||||
accountName: account.name,
|
||||
);
|
||||
} else {
|
||||
cardWidget = AddAccountCard(
|
||||
onTap: () {},
|
||||
);
|
||||
}
|
||||
|
||||
// Отступ между карточками во время свайпа
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: cardWidget,
|
||||
);
|
||||
} else if (index <= accounts.length) {
|
||||
// Pages 1..N: Account cards
|
||||
final account = accounts[index - 1];
|
||||
debugPrint('BalanceCardCarousel: building account card at index $index for ${account.name}');
|
||||
pageContent = _AccountBalanceCard(
|
||||
account: account,
|
||||
balance: widget.balance, // TODO: Calculate per-account balance
|
||||
currencyInfo: widget.currencyInfo,
|
||||
);
|
||||
} else {
|
||||
// Page N+1: AddAccountCard
|
||||
pageContent = AddAccountCard(
|
||||
onTap: () {},
|
||||
);
|
||||
}
|
||||
|
||||
// Add horizontal padding to create gap between cards
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: pageContent,
|
||||
);
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -122,27 +121,21 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error: (error, stack) {
|
||||
debugPrint('BalanceCardCarousel error: $error');
|
||||
debugPrint('Stack: $stack');
|
||||
// Show total balance card on error
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 220,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: BalanceCard(
|
||||
balance: widget.balance,
|
||||
currencyInfo: widget.currencyInfo,
|
||||
onLongPress: widget.onLongPress,
|
||||
previewPrimary: widget.previewPrimary,
|
||||
previewSecondary: widget.previewSecondary,
|
||||
previewGradientType: widget.previewGradientType,
|
||||
),
|
||||
child: BalanceCard(
|
||||
balance: widget.balance,
|
||||
currencyInfo: widget.currencyInfo,
|
||||
onLongPress: widget.onLongPress,
|
||||
previewPrimary: widget.previewPrimary,
|
||||
previewSecondary: widget.previewSecondary,
|
||||
previewGradientType: widget.previewGradientType,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_DotIndicators(
|
||||
const _DotIndicators(
|
||||
count: 1,
|
||||
activeIndex: 0,
|
||||
),
|
||||
@@ -153,48 +146,6 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountBalanceCard extends ConsumerWidget {
|
||||
final dynamic account;
|
||||
final double balance;
|
||||
final CurrencyInfo currencyInfo;
|
||||
|
||||
const _AccountBalanceCard({
|
||||
required this.account,
|
||||
required this.balance,
|
||||
required this.currencyInfo,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
BalanceCard(
|
||||
balance: balance,
|
||||
currencyInfo: currencyInfo,
|
||||
onLongPress: null, // No long press for account cards
|
||||
previewPrimary: null,
|
||||
previewSecondary: null,
|
||||
previewGradientType: null,
|
||||
),
|
||||
Positioned(
|
||||
top: 16,
|
||||
left: 24,
|
||||
child: Text(
|
||||
account.name,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
letterSpacing: 1.5,
|
||||
color: Colors.white.withOpacity(0.6),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddAccountCard extends StatelessWidget {
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@@ -306,4 +257,4 @@ class _DotIndicators extends StatelessWidget {
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user