import 'package:flutter/foundation.dart'; import 'package:please_pay_me/core/state/async_value.dart'; import 'package:please_pay_me/data/api/api_client.dart'; import 'package:please_pay_me/data/models/budget.dart'; import 'package:please_pay_me/data/repositories/repositories.dart'; /// Source of truth for budgets: the overview, the budget list and the expense /// form all read the selected envelope from here. class BudgetsController extends ChangeNotifier { BudgetsController({ required BudgetRepository budgets, required ExpenseRepository expenses, }) : _budgets = budgets, _expenses = expenses; final BudgetRepository _budgets; final ExpenseRepository _expenses; AsyncValue> _state = const AsyncValue.loading(); bool _mutating = false; AsyncValue> get state => _state; /// True while a write is in flight — used to disable buttons. bool get isMutating => _mutating; List get items => _state.valueOrNull ?? const []; /// Currently selected envelope; `null` when the user has no budgets at all. BudgetStatus? get selected { final all = items; if (all.isEmpty) return null; for (final status in all) { if (status.selected) return status; } return all.first; } bool get hasBudgets => items.isNotEmpty; Future load({bool silent = false}) async { if (!silent) { _state = const AsyncValue.loading(); notifyListeners(); } try { _state = AsyncValue.data(await _budgets.list()); } on ApiException catch (error) { _state = AsyncValue.error(error.message); } notifyListeners(); } Future select(int budgetId) { return _mutate(() => _budgets.select(budgetId)); } Future setActive(int budgetId, {required bool isActive}) { return _mutate(() => _budgets.setActive(budgetId, isActive: isActive)); } Future create({ required String name, required double totalAmount, required DateTime endDate, DateTime? startDate, }) { return _mutate( () => _budgets.create( name: name, totalAmount: totalAmount, endDate: endDate, startDate: startDate, ), ); } Future update({ required int budgetId, String? name, double? totalAmount, DateTime? endDate, DateTime? startDate, bool resetExpenses = false, }) { return _mutate( () => _budgets.update( budgetId: budgetId, name: name, totalAmount: totalAmount, endDate: endDate, startDate: startDate, resetExpenses: resetExpenses, ), ); } Future delete(int budgetId) => _mutate(() => _budgets.delete(budgetId)); Future addExpense({ required double amount, String? note, DateTime? spentAt, int? budgetId, }) { return _mutate( () => _expenses.create( amount: amount, note: note, spentAt: spentAt, budgetId: budgetId ?? selected?.budget.id, ), ); } Future undoLastExpense() { return _mutate(() => _expenses.undoLast(budgetId: selected?.budget.id)); } /// Runs a write, reloads the list and returns an error message or `null`. Future _mutate(Future Function() action) async { _mutating = true; notifyListeners(); try { await action(); await load(silent: true); return null; } on ApiException catch (error) { return error.message; } finally { _mutating = false; notifyListeners(); } } }