ynab_lite/lib/providers/db_provider.dart
2026-07-11 18:10:09 +02:00

108 lines
4.5 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:drift/drift.dart';
import '../data/db.dart';
export '../data/db.dart' show Account, CategoryGroup, Category, Transaction, Budget;
// Singleton DB provider
final dbProvider = Provider<AppDatabase>((ref) {
final db = AppDatabase();
ref.onDispose(() => db.close());
db.seedIfEmpty();
return db;
});
// ── Accounts ────────────────────────────────────────────────────────────
final accountsProvider = StreamProvider<List<Account>>((ref) {
final db = ref.watch(dbProvider);
return db.select(db.accounts).watch();
});
final openAccountsProvider = Provider<List<Account>>((ref) {
final accounts = ref.watch(accountsProvider).valueOrNull ?? [];
return accounts.where((a) => !a.isClosed).toList();
});
final totalBalanceProvider = Provider<double>((ref) {
final accounts = ref.watch(openAccountsProvider);
return accounts.fold(0.0, (sum, a) => sum + a.balance);
});
// ── Category Groups ─────────────────────────────────────────────────────
final categoryGroupsProvider = StreamProvider<List<CategoryGroup>>((ref) {
final db = ref.watch(dbProvider);
return (db.select(db.categoryGroups)
..orderBy([(g) => OrderingTerm(expression: g.sortOrder)]))
.watch();
});
// ── Categories ──────────────────────────────────────────────────────────
final categoriesProvider = StreamProvider<List<Category>>((ref) {
final db = ref.watch(dbProvider);
return (db.select(db.categories)
..orderBy([(c) => OrderingTerm(expression: c.sortOrder)]))
.watch();
});
final categoriesByGroupProvider = Provider<Map<int, List<Category>>>((ref) {
final cats = ref.watch(categoriesProvider).valueOrNull ?? [];
final map = <int, List<Category>>{};
for (final c in cats) {
map.putIfAbsent(c.groupId, () => []).add(c);
}
return map;
});
// ── Transactions ────────────────────────────────────────────────────────
final allTransactionsProvider = StreamProvider<List<Transaction>>((ref) {
final db = ref.watch(dbProvider);
return (db.select(db.transactions)
..orderBy([(t) => OrderingTerm(expression: t.date, mode: OrderingMode.desc)]))
.watch();
});
final recentTransactionsProvider = Provider<List<Transaction>>((ref) {
final all = ref.watch(allTransactionsProvider).valueOrNull ?? [];
return all.take(10).toList();
});
final monthlyTransactionsProvider = Provider.family<List<Transaction>, String>((ref, month) {
final all = ref.watch(allTransactionsProvider).valueOrNull ?? [];
return all.where((t) {
final ym = '${t.date.year}-${t.date.month.toString().padLeft(2, '0')}';
return ym == month;
}).toList();
});
// ── Budget ──────────────────────────────────────────────────────────────
final budgetsProvider = StreamProvider<List<Budget>>((ref) {
final db = ref.watch(dbProvider);
return db.select(db.budgets).watch();
});
final currentMonthProvider = Provider<String>((ref) {
final now = DateTime.now();
return '${now.year}-${now.month.toString().padLeft(2, '0')}';
});
final monthlyBudgetProvider = Provider.family<double?, int>((ref, categoryId) {
final month = ref.watch(currentMonthProvider);
final budgets = ref.watch(budgetsProvider).valueOrNull ?? [];
final match = budgets.where(
(b) => b.categoryId == categoryId && b.month == month,
);
return match.isNotEmpty ? match.first.budgeted : null;
});
final categorySpentProvider = Provider.family<double, int>((ref, categoryId) {
final month = ref.watch(currentMonthProvider);
final txs = ref.watch(monthlyTransactionsProvider(month));
final filtered = txs.where((t) => t.categoryId == categoryId);
return filtered.fold(0.0, (sum, t) => sum + t.amount);
});
final categoryRemainingProvider = Provider.family<double, int>((ref, categoryId) {
final budgeted = ref.watch(monthlyBudgetProvider(categoryId)) ?? 0.0;
final spent = ref.watch(categorySpentProvider(categoryId));
return budgeted + spent; // spent is negative, so budgeted + spent = remaining
});