import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../main.dart' show db; import '../database/daos/month_dao.dart'; import '../database/daos/settings_dao.dart'; import '../database/database.dart'; import '../services/month_assembler.dart'; import '../services/month_cloner.dart'; import '../theme.dart'; import '../widgets/top_bar.dart'; import '../widgets/core_spend/ledger_table.dart'; import '../widgets/bills_panel.dart'; import '../widgets/luxury_card.dart'; import '../widgets/misc_spend_card.dart'; import '../widgets/settings_modal.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); @override State createState() => _DashboardScreenState(); } class _DashboardScreenState extends State { late MonthDao _monthDao; late SettingsDao _settingsDao; int? _currentMonthId; bool _loading = true; @override void initState() { super.initState(); _monthDao = MonthDao(db); _settingsDao = SettingsDao(db); _initCurrentMonth(); } static const _prefKey = 'selectedMonthId'; Future _initCurrentMonth() async { final prefs = await SharedPreferences.getInstance(); final savedId = prefs.getInt(_prefKey); // Try to restore saved month, fall back to current month if (savedId != null) { try { await _monthDao.loadMonthData(savedId); setState(() { _currentMonthId = savedId; _loading = false; }); return; } catch (_) { // Saved month no longer exists, fall through } } final month = await _monthDao.getOrCreateCurrentMonth(); setState(() { _currentMonthId = month.id; _loading = false; }); prefs.setInt(_prefKey, month.id); } Future _switchMonth(int monthId) async { setState(() => _currentMonthId = monthId); final prefs = await SharedPreferences.getInstance(); prefs.setInt(_prefKey, monthId); } Future _deleteMonth(int monthId) async { final allMonths = await _monthDao.watchAllMonths().first; final idx = allMonths.indexWhere((m) => m.id == monthId); final prevId = idx > 0 ? allMonths[idx - 1].id : (allMonths.length > 1 ? allMonths.first.id : null); await _monthDao.deleteMonth(monthId); if (prevId != null && prevId != monthId) { setState(() => _currentMonthId = prevId); } else { await _initCurrentMonth(); } } Future _cloneToNextMonth() async { if (_currentMonthId == null) return; final data = await _monthDao.loadMonthData(_currentMonthId!); final nextMonth = data.plan.month == 12 ? 1 : data.plan.month + 1; final nextYear = data.plan.month == 12 ? data.plan.year + 1 : data.plan.year; final existing = await _monthDao.getMonth(nextYear, nextMonth); if (existing != null) { setState(() => _currentMonthId = existing.id); return; } final newId = await cloneMonth( db: db, sourceMonthPlanId: _currentMonthId!, targetYear: nextYear, targetMonth: nextMonth, ); setState(() => _currentMonthId = newId); final prefs = await SharedPreferences.getInstance(); prefs.setInt(_prefKey, newId); } @override Widget build(BuildContext context) { if (_loading || _currentMonthId == null) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } return Scaffold( body: StreamBuilder>( stream: _monthDao.watchAllMonths(), builder: (context, allMonthsSnap) { final allMonths = allMonthsSnap.data ?? []; // Stream per-month payment rules return StreamBuilder>( stream: _settingsDao.watchPaymentRules(_currentMonthId!), builder: (context, rulesSnap) { final monthRules = rulesSnap.data ?? []; return FutureBuilder( future: _monthDao.loadMonthData(_currentMonthId!), builder: (context, monthSnap) { if (!monthSnap.hasData) { return const Center(child: CircularProgressIndicator()); } final monthData = monthSnap.data!; final computed = assembleMonth(monthData, rules: monthRules); final miscTotalCents = monthData.miscItems .where((m) => m.isEnabled) .fold(0, (sum, m) => sum + m.amountCents); final netBalanceCents = computed.remainingCents - monthData.plan.eatingOutBudgetCents - miscTotalCents; return Column( children: [ TopBar( remainingCents: netBalanceCents, bufferCents: monthData.plan.bufferAmountCents, eatingOutBudgetCents: monthData.plan.eatingOutBudgetCents, currentPlan: monthData.plan, allMonths: allMonths, bills: monthData.bills, monthDao: _monthDao, onMonthSelected: _switchMonth, onCloneMonth: _cloneToNextMonth, onDeleteMonth: _deleteMonth, onSettingsTap: () => showSettingsModal( context: context, monthData: monthData, onDataChanged: () => setState(() {}), ), onDataChanged: () => setState(() {}), ), Expanded( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 6, child: Padding( padding: const EdgeInsets.all(cardGap), child: Align( alignment: Alignment.topLeft, child: Container( decoration: BoxDecoration( color: bgCard, border: Border.all(color: borderColor), borderRadius: BorderRadius.circular(6), ), clipBehavior: Clip.antiAlias, child: LedgerTable( computed: computed, monthDao: _monthDao, onDataChanged: () => setState(() {}), ), ), ), ), ), Expanded( flex: 4, child: SingleChildScrollView( padding: const EdgeInsets.only(top: cardGap, right: cardGap, bottom: cardGap), child: Column( children: [ BillsPanel( bills: monthData.bills, monthDao: _monthDao, monthPlanId: _currentMonthId!, ruleResult: computed.ruleResult, onDataChanged: () => setState(() {}), ), const SizedBox(height: cardGap), LuxuryCard( remainingCents: computed.remainingCents, eatingOutBudgetCents: monthData.plan.eatingOutBudgetCents, miscTotalCents: monthData.miscItems .where((m) => m.isEnabled) .fold(0, (sum, m) => sum + m.amountCents), monthDao: _monthDao, monthPlanId: _currentMonthId!, onDataChanged: () => setState(() {}), ), const SizedBox(height: cardGap), MiscSpendCard( items: monthData.miscItems, monthDao: _monthDao, monthPlanId: _currentMonthId!, onDataChanged: () => setState(() {}), ), ], ), ), ), ], ), ), ], ); }, ); }, ); }, ), ); } }