mirror of
https://github.com/koloideal/Casha.git
synced 2026-08-08 09:41:13 +03:00
237 lines
8.2 KiB
Dart
237 lines
8.2 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../shared/models/account.dart';
|
|
import '../../shared/models/transaction.dart';
|
|
import '../dashboard/provider.dart';
|
|
import '../settings/provider.dart';
|
|
|
|
enum StatsTimeFilter { allTime, month }
|
|
|
|
final statsTimeFilterProvider =
|
|
NotifierProvider<_StatsTimeFilterNotifier, StatsTimeFilter>(
|
|
_StatsTimeFilterNotifier.new,
|
|
);
|
|
|
|
class _StatsTimeFilterNotifier extends Notifier<StatsTimeFilter> {
|
|
@override
|
|
StatsTimeFilter build() => StatsTimeFilter.month;
|
|
|
|
void set(StatsTimeFilter v) => state = v;
|
|
}
|
|
|
|
class StatsSummary {
|
|
final double income;
|
|
final double expense;
|
|
final double balance;
|
|
final int transactionCount;
|
|
final double averageIncome;
|
|
final double averageExpense;
|
|
|
|
const StatsSummary({
|
|
required this.income,
|
|
required this.expense,
|
|
required this.balance,
|
|
required this.transactionCount,
|
|
required this.averageIncome,
|
|
required this.averageExpense,
|
|
});
|
|
}
|
|
|
|
String _resolveTargetCurrency(
|
|
int activeIndex,
|
|
List<Account> accounts,
|
|
String globalCurrency,
|
|
) {
|
|
if (activeIndex > 0 && activeIndex <= accounts.length) {
|
|
return accounts[activeIndex - 1].currency;
|
|
}
|
|
return globalCurrency;
|
|
}
|
|
|
|
List<Transaction> _filterScopedTransactions(
|
|
List<Transaction> txs,
|
|
StatsTimeFilter timeFilter,
|
|
) {
|
|
var filtered = txs.where((t) => t.category != 'Transfer');
|
|
if (timeFilter == StatsTimeFilter.month) {
|
|
final now = DateTime.now();
|
|
filtered = filtered.where(
|
|
(t) => t.date.year == now.year && t.date.month == now.month,
|
|
);
|
|
}
|
|
return filtered.toList();
|
|
}
|
|
|
|
final statsCurrencyProvider = Provider<CurrencyInfo>((ref) {
|
|
final index = ref.watch(activeAccountIndexProvider);
|
|
final accountsAsync = ref.watch(accountsProvider);
|
|
final globalCurrency = ref.watch(currencyProvider).code;
|
|
final accounts = accountsAsync.value ?? [];
|
|
final code = _resolveTargetCurrency(index, accounts, globalCurrency);
|
|
return CurrencyInfo(currencyMap[code]?.symbol ?? '\$', code);
|
|
});
|
|
|
|
final statsScopedTransactionsProvider = Provider<List<Transaction>>((ref) {
|
|
final txs = ref.watch(accountFilteredTransactionsProvider);
|
|
final timeFilter = ref.watch(statsTimeFilterProvider);
|
|
return _filterScopedTransactions(txs, timeFilter);
|
|
});
|
|
|
|
final statsIncomeTotalProvider = Provider<double>((ref) {
|
|
final exchange = ref.watch(exchangeRateServiceProvider);
|
|
final index = ref.watch(activeAccountIndexProvider);
|
|
final accountsAsync = ref.watch(accountsProvider);
|
|
final globalCurrency = ref.watch(currencyProvider).code;
|
|
final accounts = accountsAsync.value ?? [];
|
|
final target = _resolveTargetCurrency(index, accounts, globalCurrency);
|
|
return ref
|
|
.watch(statsScopedTransactionsProvider)
|
|
.where((t) => t.type == TransactionType.income)
|
|
.fold(0.0, (sum, t) => sum + exchange.convert(t.amount, t.currencyCode, target));
|
|
});
|
|
|
|
final statsExpenseTotalProvider = Provider<double>((ref) {
|
|
final exchange = ref.watch(exchangeRateServiceProvider);
|
|
final index = ref.watch(activeAccountIndexProvider);
|
|
final accountsAsync = ref.watch(accountsProvider);
|
|
final globalCurrency = ref.watch(currencyProvider).code;
|
|
final accounts = accountsAsync.value ?? [];
|
|
final target = _resolveTargetCurrency(index, accounts, globalCurrency);
|
|
return ref
|
|
.watch(statsScopedTransactionsProvider)
|
|
.where((t) => t.type == TransactionType.expense)
|
|
.fold(0.0, (sum, t) => sum + exchange.convert(t.amount, t.currencyCode, target));
|
|
});
|
|
|
|
final statsSummaryProvider = Provider<StatsSummary>((ref) {
|
|
final exchange = ref.watch(exchangeRateServiceProvider);
|
|
final index = ref.watch(activeAccountIndexProvider);
|
|
final accountsAsync = ref.watch(accountsProvider);
|
|
final globalCurrency = ref.watch(currencyProvider).code;
|
|
final accounts = accountsAsync.value ?? [];
|
|
final target = _resolveTargetCurrency(index, accounts, globalCurrency);
|
|
final transactions = ref.watch(statsScopedTransactionsProvider);
|
|
|
|
var income = 0.0;
|
|
var expense = 0.0;
|
|
var incomeCount = 0;
|
|
var expenseCount = 0;
|
|
|
|
for (final transaction in transactions) {
|
|
final amount = exchange.convert(transaction.amount, transaction.currencyCode, target);
|
|
if (transaction.type == TransactionType.income) {
|
|
income += amount;
|
|
incomeCount++;
|
|
}
|
|
if (transaction.type == TransactionType.expense) {
|
|
expense += amount;
|
|
expenseCount++;
|
|
}
|
|
}
|
|
|
|
return StatsSummary(
|
|
income: income,
|
|
expense: expense,
|
|
balance: income - expense,
|
|
transactionCount: transactions.length,
|
|
averageIncome: incomeCount == 0 ? 0 : income / incomeCount,
|
|
averageExpense: expenseCount == 0 ? 0 : expense / expenseCount,
|
|
);
|
|
});
|
|
|
|
final categoryExpenseProvider = Provider<Map<String, double>>((ref) {
|
|
final exchange = ref.watch(exchangeRateServiceProvider);
|
|
final index = ref.watch(activeAccountIndexProvider);
|
|
final accountsAsync = ref.watch(accountsProvider);
|
|
final globalCurrency = ref.watch(currencyProvider).code;
|
|
final accounts = accountsAsync.value ?? [];
|
|
final target = _resolveTargetCurrency(index, accounts, globalCurrency);
|
|
final map = <String, double>{};
|
|
for (final t in ref.watch(statsScopedTransactionsProvider)) {
|
|
if (t.type != TransactionType.expense) continue;
|
|
map[t.category] = (map[t.category] ?? 0) + exchange.convert(t.amount, t.currencyCode, target);
|
|
}
|
|
return map;
|
|
});
|
|
|
|
final categoryIncomeProvider = Provider<Map<String, double>>((ref) {
|
|
final exchange = ref.watch(exchangeRateServiceProvider);
|
|
final index = ref.watch(activeAccountIndexProvider);
|
|
final accountsAsync = ref.watch(accountsProvider);
|
|
final globalCurrency = ref.watch(currencyProvider).code;
|
|
final accounts = accountsAsync.value ?? [];
|
|
final target = _resolveTargetCurrency(index, accounts, globalCurrency);
|
|
final map = <String, double>{};
|
|
for (final t in ref.watch(statsScopedTransactionsProvider)) {
|
|
if (t.type != TransactionType.income) continue;
|
|
map[t.category] = (map[t.category] ?? 0) + exchange.convert(t.amount, t.currencyCode, target);
|
|
}
|
|
return map;
|
|
});
|
|
|
|
final monthlyBreakdownProvider = Provider<List<MonthlyData>>((ref) {
|
|
final txs = ref.watch(accountFilteredTransactionsProvider);
|
|
final exchange = ref.watch(exchangeRateServiceProvider);
|
|
final index = ref.watch(activeAccountIndexProvider);
|
|
final accountsAsync = ref.watch(accountsProvider);
|
|
final globalCurrency = ref.watch(currencyProvider).code;
|
|
final accounts = accountsAsync.value ?? [];
|
|
final target = _resolveTargetCurrency(index, accounts, globalCurrency);
|
|
final now = DateTime.now();
|
|
final months = <MonthlyData>[];
|
|
|
|
for (var i = 5; i >= 0; i--) {
|
|
final month = DateTime(now.year, now.month - i, 1);
|
|
final total = txs
|
|
.where(
|
|
(t) =>
|
|
t.type == TransactionType.expense &&
|
|
t.category != 'Transfer' &&
|
|
t.date.year == month.year &&
|
|
t.date.month == month.month,
|
|
)
|
|
.fold(0.0, (sum, t) {
|
|
return sum + exchange.convert(t.amount, t.currencyCode, target);
|
|
});
|
|
months.add(MonthlyData(month: month, amount: total));
|
|
}
|
|
|
|
return months;
|
|
});
|
|
|
|
final monthlyIncomeBreakdownProvider = Provider<List<MonthlyData>>((ref) {
|
|
final txs = ref.watch(accountFilteredTransactionsProvider);
|
|
final exchange = ref.watch(exchangeRateServiceProvider);
|
|
final index = ref.watch(activeAccountIndexProvider);
|
|
final accountsAsync = ref.watch(accountsProvider);
|
|
final globalCurrency = ref.watch(currencyProvider).code;
|
|
final accounts = accountsAsync.value ?? [];
|
|
final target = _resolveTargetCurrency(index, accounts, globalCurrency);
|
|
final now = DateTime.now();
|
|
final months = <MonthlyData>[];
|
|
|
|
for (var i = 5; i >= 0; i--) {
|
|
final month = DateTime(now.year, now.month - i, 1);
|
|
final total = txs
|
|
.where(
|
|
(t) =>
|
|
t.type == TransactionType.income &&
|
|
t.category != 'Transfer' &&
|
|
t.date.year == month.year &&
|
|
t.date.month == month.month,
|
|
)
|
|
.fold(0.0, (sum, t) {
|
|
return sum + exchange.convert(t.amount, t.currencyCode, target);
|
|
});
|
|
months.add(MonthlyData(month: month, amount: total));
|
|
}
|
|
|
|
return months;
|
|
});
|
|
|
|
class MonthlyData {
|
|
final DateTime month;
|
|
final double amount;
|
|
|
|
MonthlyData({required this.month, required this.amount});
|
|
}
|