This commit is contained in:
2026-03-22 01:35:21 +03:00
parent 606caab8d8
commit ca5bfa7258
3 changed files with 100 additions and 6 deletions
+2
View File
@@ -98,6 +98,8 @@ class AppStrings {
String get noCategoriesYet => _ru ? 'Нет категорий' : 'No categories yet'; String get noCategoriesYet => _ru ? 'Нет категорий' : 'No categories yet';
String get noExpenseData => _ru ? 'Нет данных о расходах' : 'No expense data'; String get noExpenseData => _ru ? 'Нет данных о расходах' : 'No expense data';
String get addExpensesToSeeBreakdown => _ru ? 'Добавьте расходы, чтобы увидеть разбивку' : 'Add some expenses to see the breakdown'; String get addExpensesToSeeBreakdown => _ru ? 'Добавьте расходы, чтобы увидеть разбивку' : 'Add some expenses to see the breakdown';
String get noIncomeData => _ru ? 'Нет данных о доходах' : 'No income data';
String get addIncomeToSeeBreakdown => _ru ? 'Добавьте доходы, чтобы увидеть разбивку' : 'Add some income to see the breakdown';
String get total => _ru ? 'Всего' : 'Total'; String get total => _ru ? 'Всего' : 'Total';
String get lastSixMonths => _ru ? 'Последние 6 месяцев' : 'Last 6 Months'; String get lastSixMonths => _ru ? 'Последние 6 месяцев' : 'Last 6 Months';
+11
View File
@@ -13,6 +13,17 @@ final categoryExpenseProvider = Provider<Map<String, double>>((ref) {
return map; return map;
}); });
final categoryIncomeProvider = Provider<Map<String, double>>((ref) {
final txs = ref.watch(transactionsProvider)
.where((t) => t.type == TransactionType.income);
final map = <String, double>{};
for (final t in txs) {
map[t.category] = (map[t.category] ?? 0) + t.amount;
}
return map;
});
final monthlyBreakdownProvider = Provider<List<MonthlyData>>((ref) { final monthlyBreakdownProvider = Provider<List<MonthlyData>>((ref) {
final txs = ref.watch(transactionsProvider) final txs = ref.watch(transactionsProvider)
.where((t) => t.type == TransactionType.expense); .where((t) => t.type == TransactionType.expense);
+87 -6
View File
@@ -21,11 +21,14 @@ class CategoriesScreen extends ConsumerStatefulWidget {
class _CategoriesScreenState extends ConsumerState<CategoriesScreen> { class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
int _touchedIndex = -1; int _touchedIndex = -1;
ChartType _chartType = ChartType.pie; ChartType _chartType = ChartType.pie;
bool _showIncome = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = ref.watch(stringsProvider); final s = ref.watch(stringsProvider);
final data = ref.watch(categoryExpenseProvider); final data = _showIncome
? ref.watch(categoryIncomeProvider)
: ref.watch(categoryExpenseProvider);
final monthlyData = ref.watch(monthlyBreakdownProvider); final monthlyData = ref.watch(monthlyBreakdownProvider);
final total = data.values.fold(0.0, (a, b) => a + b); final total = data.values.fold(0.0, (a, b) => a + b);
final currencyInfo = ref.watch(currencyProvider); final currencyInfo = ref.watch(currencyProvider);
@@ -57,8 +60,82 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => setState(() {
_showIncome = false;
_touchedIndex = -1;
}),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: !_showIncome
? AppColors.accent
: Colors.transparent,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: !_showIncome
? AppColors.accent
: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
width: 1.5,
),
),
child: Text(
s.expenses,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: !_showIncome
? Colors.white
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () => setState(() {
_showIncome = true;
_touchedIndex = -1;
}),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: _showIncome
? AppColors.accent
: Colors.transparent,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: _showIncome
? AppColors.accent
: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
width: 1.5,
),
),
child: Text(
s.income,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: _showIncome
? Colors.white
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
),
),
),
],
),
const SizedBox(height: 16),
if (data.isEmpty) if (data.isEmpty)
const Expanded(child: _EmptyState()) Expanded(child: _EmptyState(isIncome: _showIncome))
else else
Expanded( Expanded(
child: ListView( child: ListView(
@@ -94,6 +171,7 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
amount: amount, amount: amount,
total: total, total: total,
currency: currencyInfo.symbol, currency: currencyInfo.symbol,
isIncome: _showIncome,
), ),
); );
}), }),
@@ -390,12 +468,14 @@ class _CategoryRow extends ConsumerWidget {
final double amount; final double amount;
final double total; final double total;
final String currency; final String currency;
final bool isIncome;
const _CategoryRow({ const _CategoryRow({
required this.rank, required this.rank,
required this.category, required this.category,
required this.amount, required this.amount,
required this.total, required this.total,
required this.currency, required this.currency,
required this.isIncome,
}); });
@override @override
@@ -457,7 +537,7 @@ class _CategoryRow extends ConsumerWidget {
Text( Text(
formatAmount(currency, amount, fmt), formatAmount(currency, amount, fmt),
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: AppColors.expense, color: isIncome ? AppColors.income : AppColors.expense,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
@@ -488,7 +568,8 @@ class _CategoryRow extends ConsumerWidget {
} }
class _EmptyState extends ConsumerWidget { class _EmptyState extends ConsumerWidget {
const _EmptyState(); final bool isIncome;
const _EmptyState({required this.isIncome});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@@ -511,7 +592,7 @@ class _EmptyState extends ConsumerWidget {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
s.noExpenseData, isIncome ? s.noIncomeData : s.noExpenseData,
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -519,7 +600,7 @@ class _EmptyState extends ConsumerWidget {
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Text( Text(
s.addExpensesToSeeBreakdown, isIncome ? s.addIncomeToSeeBreakdown : s.addExpensesToSeeBreakdown,
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
), ),