Add budgets

This commit is contained in:
2025-05-04 19:26:11 +02:00
parent 5cc0bba09a
commit eced6e6a6d
23 changed files with 3211 additions and 1507 deletions

View File

@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/screen.dart';
import 'package:okane/ui/pages/account/account.dart';
import 'package:okane/ui/pages/budgets/budget_details.dart';
import 'package:okane/ui/pages/budgets/budgets.dart';
import 'package:okane/ui/pages/template_list.dart';
import 'package:okane/ui/pages/transaction_details.dart';
import 'package:okane/ui/pages/transaction_list.dart';
@@ -66,8 +68,8 @@ final _pages = <OkanePageItem>[
OkanePage.budgets,
Icons.pie_chart,
"Budgets",
Placeholder(),
null,
BudgetListPage(),
(_) => BudgetDetailsPage(),
),
];
@@ -105,6 +107,29 @@ class OkaneNavigationBar extends StatelessWidget {
}
}
class OkaneNavigationDrawer extends StatelessWidget {
const OkaneNavigationDrawer({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) => Drawer(
child: ListView(
children: _pages.map((p) => ListTile(
title: Text(p.label),
leading: Icon(p.icon),
selected: p.page == state.activePage,
onTap: () {
context.read<CoreCubit>().setPage(p.page);
Navigator.of(context).pop();
},
)).toList(),
),
),
);
}
}
class OkaneNavigationLayout extends StatelessWidget {
const OkaneNavigationLayout({super.key});
@@ -119,7 +144,27 @@ class OkaneNavigationLayout extends StatelessWidget {
_pages
.map(
(p) => switch (screenSize) {
ScreenSize.small => p.child,
ScreenSize.small => Column(
children: [
SizedBox(
height: 50,
child: Padding(
padding: EdgeInsets.all(8),
child: Row(
children: [
IconButton(
icon: Icon(Icons.menu),
onPressed: () {
Scaffold.of(context).openDrawer();
},
),
],
),
),
),
Expanded(child: p.child),
],
),
ScreenSize.normal =>
p.details != null
? Row(

View File

@@ -111,20 +111,6 @@ class AccountListPageState extends State<AccountListPage> {
child: UpcomingTransactionsCard(),
),
/*
BlocBuilder<CoreCubit, CoreState>(
builder:
(context, state) => Row(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.all(16),
child: AccountBalanceGraphCard(),
),
),
],
),
),*/
Row(
children: [
Padding(padding: EdgeInsets.all(16), child: BreakdownCard()),

View File

@@ -28,15 +28,17 @@ class TotalBalanceCard extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Total balance"),
Text(
"Total balance",
style: Theme.of(context).textTheme.titleLarge,
),
FutureBuilder(
future: _getTotalBalance(state.accounts),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text("...");
}
return Text(formatCurrency(snapshot.data!));
return Text(
snapshot.hasData ? formatCurrency(snapshot.data!) : "...",
style: Theme.of(context).textTheme.bodyLarge,
);
},
),
],

View File

@@ -1,6 +1,4 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/database/collections/recurrent.dart';
@@ -29,7 +27,12 @@ class UpcomingTransactionsCard extends StatelessWidget {
: upcomingRaw.sublist(0, min(upcomingRaw.length, 3));
final transactions =
upcoming.isEmpty
? [Text("No upcoming transactions")]
? [
Text(
"No upcoming transactions",
style: Theme.of(context).textTheme.bodyLarge,
),
]
: upcoming
.map(
(t) => ListTile(
@@ -59,7 +62,14 @@ class UpcomingTransactionsCard extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: <Widget>[Text("Upcoming Transactions")] + transactions,
children:
<Widget>[
Text(
"Upcoming Transactions",
style: Theme.of(context).textTheme.titleLarge,
),
] +
transactions,
),
),
);

View File

@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/budget.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
class AddBudgetPopup extends StatefulWidget {
final VoidCallback onDone;
const AddBudgetPopup({super.key, required this.onDone});
@override
AddBudgetState createState() => AddBudgetState();
}
class AddBudgetState extends State<AddBudgetPopup> {
final _budgetNameEditController = TextEditingController();
final _budgetIncomeEditController = TextEditingController();
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
decoration: InputDecoration(
hintText: "Budget name",
),
controller: _budgetNameEditController,
),
TextField(
decoration: InputDecoration(
hintText: "Income",
),
controller: _budgetIncomeEditController,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
OutlinedButton(
onPressed: () async {
if (_budgetNameEditController.text.isEmpty || _budgetIncomeEditController.text.isEmpty) {
return;
}
final bloc = GetIt.I.get<CoreCubit>();
final budget = Budget()
..name = _budgetNameEditController.text
..period = BudgetPeriod.month
..income = double.parse(_budgetIncomeEditController.text)
..account.value = bloc.activeAccount!;
await upsertBudget(budget);
widget.onDone();
},
child: Text("Add"),
),
],
),
],
);
}
}

View File

@@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/budget.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/add_expense_category.dart';
class AddBudgetItemPopup extends StatefulWidget {
final VoidCallback onDone;
final Budget budget;
const AddBudgetItemPopup({super.key, required this.onDone, required this.budget});
@override
AddBudgetItemState createState() => AddBudgetItemState();
}
class AddBudgetItemState extends State<AddBudgetItemPopup> {
final _budgetItemAmountEditController = TextEditingController();
ExpenseCategory? _expenseCategory;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Text("Expense category"),
OutlinedButton(
onPressed: () async {
final category = await showDialogOrModal(
context: context,
builder: (_) => AddExpenseCategory(),
);
if (category == null) {
return;
}
setState(() => _expenseCategory = category);
},
child: Text(_expenseCategory?.name ?? "None"),
),
],
),
TextField(
decoration: InputDecoration(
hintText: "Amount",
),
controller: _budgetItemAmountEditController,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
OutlinedButton(
onPressed: () async {
if (_budgetItemAmountEditController.text.isEmpty || _expenseCategory == null) {
return;
}
if (widget.budget.items.where((i) => i.expenseCategory.value!.name == _expenseCategory!.name).firstOrNull != null) {
return;
}
final item = BudgetItem()
..expenseCategory.value = _expenseCategory
..amount = double.parse(_budgetItemAmountEditController.text);
await upsertBudgetItem(item);
widget.budget.items.add(item);
await upsertBudget(widget.budget);
widget.onDone();
},
child: Text("Add"),
),
],
),
],
);
}
}

View File

@@ -0,0 +1,374 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/budget.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/pages/account/breakdown_card.dart';
import 'package:okane/ui/pages/budgets/add_budget_item.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
class BudgetDetailsPage extends StatelessWidget {
final bool isPage;
const BudgetDetailsPage({this.isPage = false, super.key});
static MaterialPageRoute<void> get mobileRoute =>
MaterialPageRoute(builder: (_) => BudgetDetailsPage(isPage: true));
void _addBudgetItem(BuildContext context, CoreState state) {
showDialogOrModal(
context: context,
builder:
(_) => AddBudgetItemPopup(
budget: state.activeBudget!,
onDone: () {
Navigator.of(context).pop();
},
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
if (isPage)
SizedBox(
height: 50,
child: Row(
children: [
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
),
BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
if (state.activeBudget == null) {
return Text("No budget selected");
}
if (state.activeBudget!.items.isEmpty) {
return Row(
children: [
Text("No budget items added"),
Padding(
padding: EdgeInsets.only(left: 16),
child: IconButton(
onPressed: () => _addBudgetItem(context, state),
icon: Icon(Icons.add),
),
),
],
);
}
final bloc = GetIt.I.get<CoreCubit>();
final today = DateTime.now();
return FutureBuilder(
future: getTransactionsInTimeframe(
bloc.activeAccount!,
today,
TransactionQueryDateOption.thisMonth,
),
builder: (context, snapshot) {
final daysLeft = switch (state.activeBudget!.period) {
BudgetPeriod.month =>
monthEnding(today).difference(today).inDays,
};
if (!snapshot.hasData) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Budget items",
style: Theme.of(context).textTheme.titleMedium,
),
ListView.builder(
shrinkWrap: true,
itemCount: state.activeBudget!.items.length,
itemBuilder: (context, index) {
final item = state.activeBudget!.items.elementAt(
index,
);
final amount = formatCurrency(item.amount);
return ListTile(
title: Text(
"${item.expenseCategory.value!.name} ($amount)",
),
subtitle: Text("..."),
);
},
),
],
);
}
final spending = <String, double>{};
for (final t in snapshot.data!) {
if (t.expenseCategory.value == null) {
continue;
}
spending.update(
t.expenseCategory.value!.name,
(value) => value + t.amount,
ifAbsent: () => t.amount,
);
}
final budgetTotal = state.activeBudget!.items
.map((i) => i.amount)
.reduce((acc, val) => acc + val);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 100,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
Padding(
padding: EdgeInsets.all(8),
child: SizedBox(
height: 100,
width: 150,
child: Card(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Days left",
textAlign: TextAlign.center,
style:
Theme.of(
context,
).textTheme.titleLarge,
),
Text(
daysLeft.toString(),
textAlign: TextAlign.center,
style:
Theme.of(
context,
).textTheme.bodyLarge,
),
],
),
),
),
),
Padding(
padding: EdgeInsets.all(8),
child: SizedBox(
height: 100,
width: 150,
child: Card(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Budget total",
textAlign: TextAlign.center,
style:
Theme.of(
context,
).textTheme.titleLarge,
),
Text(
formatCurrency(budgetTotal),
textAlign: TextAlign.center,
style:
Theme.of(
context,
).textTheme.bodyLarge,
),
],
),
),
),
),
],
),
),
Padding(
padding: EdgeInsets.all(8),
child: SizedBox(
child: Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
"Budget breakdown",
style:
Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.all(16),
child: SizedBox(
width: 150,
height: 150,
child: AspectRatio(
aspectRatio: 1,
child: PieChart(
PieChartData(
borderData: FlBorderData(
show: false,
),
sectionsSpace: 0,
centerSpaceRadius: 35,
sections:
state.activeBudget!.items
.map(
(
i,
) => PieChartSectionData(
value: i.amount.abs(),
title: formatCurrency(
i.amount.abs(),
),
titleStyle: TextStyle(
fontWeight:
FontWeight.bold,
),
radius: 40,
color: colorHash(
i
.expenseCategory
.value!
.name,
),
),
)
.toList(),
),
),
),
),
),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 8,
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children:
state.activeBudget!.items
.map(
(i) => LegendItem(
text:
i
.expenseCategory
.value!
.name,
color: colorHash(
i
.expenseCategory
.value!
.name,
),
),
)
.toList(),
),
),
],
),
],
),
),
),
),
Padding(
padding: EdgeInsets.all(8),
child: Row(
children: [
Text(
"Budget items",
style: Theme.of(context).textTheme.titleMedium,
),
Padding(
padding: EdgeInsets.only(left: 16),
child: IconButton(
icon: Icon(Icons.add),
onPressed: () => _addBudgetItem(context, state),
),
),
],
),
),
Padding(
padding: EdgeInsets.all(8),
child: ListView.builder(
shrinkWrap: true,
itemCount: state.activeBudget!.items.length,
itemBuilder: (context, index) {
final item = state.activeBudget!.items.elementAt(
index,
);
final amount = formatCurrency(item.amount);
final spent =
spending[item.expenseCategory.value!.name];
final left =
spent == null
? item.amount
: item.amount + spent;
final subtitleText =
left < 0
? "${formatCurrency(left)} over"
: "${formatCurrency(left)} left";
return ListTile(
title: Text(
"${item.expenseCategory.value!.name} ($amount)",
),
subtitle: Text(
subtitleText,
style: TextStyle(
color: left < 0 ? Colors.red : Colors.green,
),
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {},
),
);
},
),
),
],
);
},
);
},
),
],
),
);
}
}

View File

@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/screen.dart';
import 'package:okane/ui/pages/budgets/add_budget.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
class BudgetListPage extends StatelessWidget {
const BudgetListPage({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
if (state.budgets.isEmpty) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("No budgets"),
OutlinedButton(
onPressed: () {
showDialogOrModal(
context: context,
builder:
(_) => AddBudgetPopup(
onDone: () {
Navigator.of(context).pop();
},
),
);
},
child: Text("Add"),
),
],
);
}
return ListView.builder(
itemCount: state.budgets.length,
itemBuilder:
(context, index) => ListTile(
title: Text(state.budgets[index].name),
selected: state.budgets[index] == state.activeBudget,
trailing: IconButton(
icon: Icon(
Icons.delete,
color: Colors.redAccent,
),
onPressed: () {
// TODO
},
),
onTap: () {
GetIt.I.get<CoreCubit>().setActiveBudget(
state.budgets[index],
);
if (getScreenSize(context) == ScreenSize.small) {
Navigator.of(context).pushNamed("/budgets/details");
}
},
),
);
},
);
}
}

View File

@@ -47,72 +47,89 @@ class TransactionDetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final widget = BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
if (state.activeTransaction == null) {
return Text("No transaction selected");
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: [
Row(
return Scaffold(
body: Column(
children: [
if (isPage)
SizedBox(
height: 50,
child: Row(
children: [
StreamBuilder(
stream: watchBeneficiaryObject(
state.activeTransaction!.beneficiary.value!.id,
),
builder: (context, snapshot) {
final obj =
snapshot.data ??
state.activeTransaction!.beneficiary.value!;
return ImageWrapper(
title: obj.name,
path: obj.imagePath,
onTap: () => _updateBeneficiaryIcon(obj),
);
},
),
Padding(
padding: EdgeInsets.only(left: 8),
child: Text(
state.activeTransaction!.beneficiary.value!.name,
),
),
Spacer(),
IconButton(
icon: Icon(Icons.edit),
icon: Icon(Icons.arrow_back),
onPressed: () {
// TODO: Implement
Navigator.of(context).pop();
},
),
],
),
Wrap(
spacing: 8,
children:
state.activeTransaction!.tags
.map((tag) => Chip(label: Text(tag)))
.toList(),
),
Row(
children: [
state.activeTransaction!.amount > 0
? Icon(Icons.add)
: Icon(Icons.remove),
Text(formatCurrency(state.activeTransaction!.amount)),
],
),
],
),
);
},
);
),
Expanded(
child: BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
if (state.activeTransaction == null) {
return Text("No transaction selected");
}
if (isPage) {
return Scaffold(body: widget);
}
return widget;
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: [
Row(
children: [
StreamBuilder(
stream: watchBeneficiaryObject(
state.activeTransaction!.beneficiary.value!.id,
),
builder: (context, snapshot) {
final obj =
snapshot.data ??
state.activeTransaction!.beneficiary.value!;
return ImageWrapper(
title: obj.name,
path: obj.imagePath,
onTap: () => _updateBeneficiaryIcon(obj),
);
},
),
Padding(
padding: EdgeInsets.only(left: 8),
child: Text(
state.activeTransaction!.beneficiary.value!.name,
),
),
Spacer(),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
// TODO: Implement
},
),
],
),
Wrap(
spacing: 8,
children:
state.activeTransaction!.tags
.map((tag) => Chip(label: Text(tag)))
.toList(),
),
Row(
children: [
state.activeTransaction!.amount > 0
? Icon(Icons.add)
: Icon(Icons.remove),
Text(formatCurrency(state.activeTransaction!.amount)),
],
),
],
),
);
},
),
),
],
),
);
}
}

View File

@@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:grouped_list/grouped_list.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/database.dart';
import 'package:okane/screen.dart';
import 'package:okane/ui/pages/account/balance_graph_card.dart';
import 'package:okane/ui/state/core.dart';

View File

@@ -4,6 +4,7 @@ import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/budget.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/collections/recurrent.dart';
import 'package:okane/database/collections/template.dart';
@@ -25,6 +26,8 @@ abstract class CoreState with _$CoreState {
@Default([]) List<TransactionTemplate> transactionTemplates,
@Default([]) List<Beneficiary> beneficiaries,
@Default([]) List<ExpenseCategory> expenseCategories,
@Default([]) List<Budget> budgets,
@Default(null) Budget? activeBudget,
}) = _CoreState;
}
@@ -37,6 +40,7 @@ class CoreCubit extends Cubit<CoreState> {
StreamSubscription<void>? _transactionsStreamSubscription;
StreamSubscription<void>? _beneficiariesStreamSubscription;
StreamSubscription<void>? _expenseCategoryStreamSubscription;
StreamSubscription<void>? _budgetsStreamSubscription;
void setupAccountStream() {
_accountsStreamSubscription?.cancel();
@@ -94,6 +98,12 @@ class CoreCubit extends Cubit<CoreState> {
) async {
emit(state.copyWith(expenseCategories: await getExpenseCategories()));
});
_budgetsStreamSubscription?.cancel();
_budgetsStreamSubscription = watchBudgets(activeAccount!).listen((
_,
) async {
emit(state.copyWith(budgets: await getBudgets(activeAccount!)));
});
}
Future<void> init() async {
@@ -108,6 +118,7 @@ class CoreCubit extends Cubit<CoreState> {
transactionTemplates: await getTransactionTemplates(account),
recurringTransactions: await getRecurringTransactions(account),
expenseCategories: await getExpenseCategories(),
budgets: await getBudgets(account),
),
);
@@ -132,6 +143,9 @@ class CoreCubit extends Cubit<CoreState> {
beneficiaries: await getBeneficiaries(),
transactionTemplates: await getTransactionTemplates(account),
recurringTransactions: await getRecurringTransactions(account),
budgets: await getBudgets(account),
activeBudget: null,
activeTransaction: null,
),
);
setupStreams(account);
@@ -145,6 +159,10 @@ class CoreCubit extends Cubit<CoreState> {
emit(state.copyWith(accounts: accounts));
}
void setActiveBudget(Budget? budget) {
emit(state.copyWith(activeBudget: budget));
}
Account? get activeAccount =>
state.activeAccountIndex == null
? null

View File

@@ -12,8 +12,7 @@ part of 'core.dart';
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$CoreState {
@@ -29,6 +28,8 @@ mixin _$CoreState {
List<Beneficiary> get beneficiaries => throw _privateConstructorUsedError;
List<ExpenseCategory> get expenseCategories =>
throw _privateConstructorUsedError;
List<Budget> get budgets => throw _privateConstructorUsedError;
Budget? get activeBudget => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$CoreStateCopyWith<CoreState> get copyWith =>
@@ -40,17 +41,18 @@ abstract class $CoreStateCopyWith<$Res> {
factory $CoreStateCopyWith(CoreState value, $Res Function(CoreState) then) =
_$CoreStateCopyWithImpl<$Res, CoreState>;
@useResult
$Res call({
OkanePage activePage,
int? activeAccountIndex,
Transaction? activeTransaction,
List<Account> accounts,
List<RecurringTransaction> recurringTransactions,
List<Transaction> transactions,
List<TransactionTemplate> transactionTemplates,
List<Beneficiary> beneficiaries,
List<ExpenseCategory> expenseCategories,
});
$Res call(
{OkanePage activePage,
int? activeAccountIndex,
Transaction? activeTransaction,
List<Account> accounts,
List<RecurringTransaction> recurringTransactions,
List<Transaction> transactions,
List<TransactionTemplate> transactionTemplates,
List<Beneficiary> beneficiaries,
List<ExpenseCategory> expenseCategories,
List<Budget> budgets,
Budget? activeBudget});
}
/// @nodoc
@@ -75,57 +77,55 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
Object? transactionTemplates = null,
Object? beneficiaries = null,
Object? expenseCategories = null,
Object? budgets = null,
Object? activeBudget = freezed,
}) {
return _then(
_value.copyWith(
activePage:
null == activePage
? _value.activePage
: activePage // ignore: cast_nullable_to_non_nullable
as OkanePage,
activeAccountIndex:
freezed == activeAccountIndex
? _value.activeAccountIndex
: activeAccountIndex // ignore: cast_nullable_to_non_nullable
as int?,
activeTransaction:
freezed == activeTransaction
? _value.activeTransaction
: activeTransaction // ignore: cast_nullable_to_non_nullable
as Transaction?,
accounts:
null == accounts
? _value.accounts
: accounts // ignore: cast_nullable_to_non_nullable
as List<Account>,
recurringTransactions:
null == recurringTransactions
? _value.recurringTransactions
: recurringTransactions // ignore: cast_nullable_to_non_nullable
as List<RecurringTransaction>,
transactions:
null == transactions
? _value.transactions
: transactions // ignore: cast_nullable_to_non_nullable
as List<Transaction>,
transactionTemplates:
null == transactionTemplates
? _value.transactionTemplates
: transactionTemplates // ignore: cast_nullable_to_non_nullable
as List<TransactionTemplate>,
beneficiaries:
null == beneficiaries
? _value.beneficiaries
: beneficiaries // ignore: cast_nullable_to_non_nullable
as List<Beneficiary>,
expenseCategories:
null == expenseCategories
? _value.expenseCategories
: expenseCategories // ignore: cast_nullable_to_non_nullable
as List<ExpenseCategory>,
)
as $Val,
);
return _then(_value.copyWith(
activePage: null == activePage
? _value.activePage
: activePage // ignore: cast_nullable_to_non_nullable
as OkanePage,
activeAccountIndex: freezed == activeAccountIndex
? _value.activeAccountIndex
: activeAccountIndex // ignore: cast_nullable_to_non_nullable
as int?,
activeTransaction: freezed == activeTransaction
? _value.activeTransaction
: activeTransaction // ignore: cast_nullable_to_non_nullable
as Transaction?,
accounts: null == accounts
? _value.accounts
: accounts // ignore: cast_nullable_to_non_nullable
as List<Account>,
recurringTransactions: null == recurringTransactions
? _value.recurringTransactions
: recurringTransactions // ignore: cast_nullable_to_non_nullable
as List<RecurringTransaction>,
transactions: null == transactions
? _value.transactions
: transactions // ignore: cast_nullable_to_non_nullable
as List<Transaction>,
transactionTemplates: null == transactionTemplates
? _value.transactionTemplates
: transactionTemplates // ignore: cast_nullable_to_non_nullable
as List<TransactionTemplate>,
beneficiaries: null == beneficiaries
? _value.beneficiaries
: beneficiaries // ignore: cast_nullable_to_non_nullable
as List<Beneficiary>,
expenseCategories: null == expenseCategories
? _value.expenseCategories
: expenseCategories // ignore: cast_nullable_to_non_nullable
as List<ExpenseCategory>,
budgets: null == budgets
? _value.budgets
: budgets // ignore: cast_nullable_to_non_nullable
as List<Budget>,
activeBudget: freezed == activeBudget
? _value.activeBudget
: activeBudget // ignore: cast_nullable_to_non_nullable
as Budget?,
) as $Val);
}
}
@@ -133,22 +133,22 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
abstract class _$$CoreStateImplCopyWith<$Res>
implements $CoreStateCopyWith<$Res> {
factory _$$CoreStateImplCopyWith(
_$CoreStateImpl value,
$Res Function(_$CoreStateImpl) then,
) = __$$CoreStateImplCopyWithImpl<$Res>;
_$CoreStateImpl value, $Res Function(_$CoreStateImpl) then) =
__$$CoreStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
OkanePage activePage,
int? activeAccountIndex,
Transaction? activeTransaction,
List<Account> accounts,
List<RecurringTransaction> recurringTransactions,
List<Transaction> transactions,
List<TransactionTemplate> transactionTemplates,
List<Beneficiary> beneficiaries,
List<ExpenseCategory> expenseCategories,
});
$Res call(
{OkanePage activePage,
int? activeAccountIndex,
Transaction? activeTransaction,
List<Account> accounts,
List<RecurringTransaction> recurringTransactions,
List<Transaction> transactions,
List<TransactionTemplate> transactionTemplates,
List<Beneficiary> beneficiaries,
List<ExpenseCategory> expenseCategories,
List<Budget> budgets,
Budget? activeBudget});
}
/// @nodoc
@@ -156,9 +156,8 @@ class __$$CoreStateImplCopyWithImpl<$Res>
extends _$CoreStateCopyWithImpl<$Res, _$CoreStateImpl>
implements _$$CoreStateImplCopyWith<$Res> {
__$$CoreStateImplCopyWithImpl(
_$CoreStateImpl _value,
$Res Function(_$CoreStateImpl) _then,
) : super(_value, _then);
_$CoreStateImpl _value, $Res Function(_$CoreStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
@@ -172,78 +171,80 @@ class __$$CoreStateImplCopyWithImpl<$Res>
Object? transactionTemplates = null,
Object? beneficiaries = null,
Object? expenseCategories = null,
Object? budgets = null,
Object? activeBudget = freezed,
}) {
return _then(
_$CoreStateImpl(
activePage:
null == activePage
? _value.activePage
: activePage // ignore: cast_nullable_to_non_nullable
as OkanePage,
activeAccountIndex:
freezed == activeAccountIndex
? _value.activeAccountIndex
: activeAccountIndex // ignore: cast_nullable_to_non_nullable
as int?,
activeTransaction:
freezed == activeTransaction
? _value.activeTransaction
: activeTransaction // ignore: cast_nullable_to_non_nullable
as Transaction?,
accounts:
null == accounts
? _value._accounts
: accounts // ignore: cast_nullable_to_non_nullable
as List<Account>,
recurringTransactions:
null == recurringTransactions
? _value._recurringTransactions
: recurringTransactions // ignore: cast_nullable_to_non_nullable
as List<RecurringTransaction>,
transactions:
null == transactions
? _value._transactions
: transactions // ignore: cast_nullable_to_non_nullable
as List<Transaction>,
transactionTemplates:
null == transactionTemplates
? _value._transactionTemplates
: transactionTemplates // ignore: cast_nullable_to_non_nullable
as List<TransactionTemplate>,
beneficiaries:
null == beneficiaries
? _value._beneficiaries
: beneficiaries // ignore: cast_nullable_to_non_nullable
as List<Beneficiary>,
expenseCategories:
null == expenseCategories
? _value._expenseCategories
: expenseCategories // ignore: cast_nullable_to_non_nullable
as List<ExpenseCategory>,
),
);
return _then(_$CoreStateImpl(
activePage: null == activePage
? _value.activePage
: activePage // ignore: cast_nullable_to_non_nullable
as OkanePage,
activeAccountIndex: freezed == activeAccountIndex
? _value.activeAccountIndex
: activeAccountIndex // ignore: cast_nullable_to_non_nullable
as int?,
activeTransaction: freezed == activeTransaction
? _value.activeTransaction
: activeTransaction // ignore: cast_nullable_to_non_nullable
as Transaction?,
accounts: null == accounts
? _value._accounts
: accounts // ignore: cast_nullable_to_non_nullable
as List<Account>,
recurringTransactions: null == recurringTransactions
? _value._recurringTransactions
: recurringTransactions // ignore: cast_nullable_to_non_nullable
as List<RecurringTransaction>,
transactions: null == transactions
? _value._transactions
: transactions // ignore: cast_nullable_to_non_nullable
as List<Transaction>,
transactionTemplates: null == transactionTemplates
? _value._transactionTemplates
: transactionTemplates // ignore: cast_nullable_to_non_nullable
as List<TransactionTemplate>,
beneficiaries: null == beneficiaries
? _value._beneficiaries
: beneficiaries // ignore: cast_nullable_to_non_nullable
as List<Beneficiary>,
expenseCategories: null == expenseCategories
? _value._expenseCategories
: expenseCategories // ignore: cast_nullable_to_non_nullable
as List<ExpenseCategory>,
budgets: null == budgets
? _value._budgets
: budgets // ignore: cast_nullable_to_non_nullable
as List<Budget>,
activeBudget: freezed == activeBudget
? _value.activeBudget
: activeBudget // ignore: cast_nullable_to_non_nullable
as Budget?,
));
}
}
/// @nodoc
class _$CoreStateImpl implements _CoreState {
const _$CoreStateImpl({
this.activePage = OkanePage.accounts,
this.activeAccountIndex,
this.activeTransaction = null,
final List<Account> accounts = const [],
final List<RecurringTransaction> recurringTransactions = const [],
final List<Transaction> transactions = const [],
final List<TransactionTemplate> transactionTemplates = const [],
final List<Beneficiary> beneficiaries = const [],
final List<ExpenseCategory> expenseCategories = const [],
}) : _accounts = accounts,
_recurringTransactions = recurringTransactions,
_transactions = transactions,
_transactionTemplates = transactionTemplates,
_beneficiaries = beneficiaries,
_expenseCategories = expenseCategories;
const _$CoreStateImpl(
{this.activePage = OkanePage.accounts,
this.activeAccountIndex,
this.activeTransaction = null,
final List<Account> accounts = const [],
final List<RecurringTransaction> recurringTransactions = const [],
final List<Transaction> transactions = const [],
final List<TransactionTemplate> transactionTemplates = const [],
final List<Beneficiary> beneficiaries = const [],
final List<ExpenseCategory> expenseCategories = const [],
final List<Budget> budgets = const [],
this.activeBudget = null})
: _accounts = accounts,
_recurringTransactions = recurringTransactions,
_transactions = transactions,
_transactionTemplates = transactionTemplates,
_beneficiaries = beneficiaries,
_expenseCategories = expenseCategories,
_budgets = budgets;
@override
@JsonKey()
@@ -310,9 +311,22 @@ class _$CoreStateImpl implements _CoreState {
return EqualUnmodifiableListView(_expenseCategories);
}
final List<Budget> _budgets;
@override
@JsonKey()
List<Budget> get budgets {
if (_budgets is EqualUnmodifiableListView) return _budgets;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_budgets);
}
@override
@JsonKey()
final Budget? activeBudget;
@override
String toString() {
return 'CoreState(activePage: $activePage, activeAccountIndex: $activeAccountIndex, activeTransaction: $activeTransaction, accounts: $accounts, recurringTransactions: $recurringTransactions, transactions: $transactions, transactionTemplates: $transactionTemplates, beneficiaries: $beneficiaries, expenseCategories: $expenseCategories)';
return 'CoreState(activePage: $activePage, activeAccountIndex: $activeAccountIndex, activeTransaction: $activeTransaction, accounts: $accounts, recurringTransactions: $recurringTransactions, transactions: $transactions, transactionTemplates: $transactionTemplates, beneficiaries: $beneficiaries, expenseCategories: $expenseCategories, budgets: $budgets, activeBudget: $activeBudget)';
}
@override
@@ -327,41 +341,35 @@ class _$CoreStateImpl implements _CoreState {
(identical(other.activeTransaction, activeTransaction) ||
other.activeTransaction == activeTransaction) &&
const DeepCollectionEquality().equals(other._accounts, _accounts) &&
const DeepCollectionEquality().equals(
other._recurringTransactions,
_recurringTransactions,
) &&
const DeepCollectionEquality().equals(
other._transactions,
_transactions,
) &&
const DeepCollectionEquality().equals(
other._transactionTemplates,
_transactionTemplates,
) &&
const DeepCollectionEquality().equals(
other._beneficiaries,
_beneficiaries,
) &&
const DeepCollectionEquality().equals(
other._expenseCategories,
_expenseCategories,
));
const DeepCollectionEquality()
.equals(other._recurringTransactions, _recurringTransactions) &&
const DeepCollectionEquality()
.equals(other._transactions, _transactions) &&
const DeepCollectionEquality()
.equals(other._transactionTemplates, _transactionTemplates) &&
const DeepCollectionEquality()
.equals(other._beneficiaries, _beneficiaries) &&
const DeepCollectionEquality()
.equals(other._expenseCategories, _expenseCategories) &&
const DeepCollectionEquality().equals(other._budgets, _budgets) &&
(identical(other.activeBudget, activeBudget) ||
other.activeBudget == activeBudget));
}
@override
int get hashCode => Object.hash(
runtimeType,
activePage,
activeAccountIndex,
activeTransaction,
const DeepCollectionEquality().hash(_accounts),
const DeepCollectionEquality().hash(_recurringTransactions),
const DeepCollectionEquality().hash(_transactions),
const DeepCollectionEquality().hash(_transactionTemplates),
const DeepCollectionEquality().hash(_beneficiaries),
const DeepCollectionEquality().hash(_expenseCategories),
);
runtimeType,
activePage,
activeAccountIndex,
activeTransaction,
const DeepCollectionEquality().hash(_accounts),
const DeepCollectionEquality().hash(_recurringTransactions),
const DeepCollectionEquality().hash(_transactions),
const DeepCollectionEquality().hash(_transactionTemplates),
const DeepCollectionEquality().hash(_beneficiaries),
const DeepCollectionEquality().hash(_expenseCategories),
const DeepCollectionEquality().hash(_budgets),
activeBudget);
@JsonKey(ignore: true)
@override
@@ -371,17 +379,18 @@ class _$CoreStateImpl implements _CoreState {
}
abstract class _CoreState implements CoreState {
const factory _CoreState({
final OkanePage activePage,
final int? activeAccountIndex,
final Transaction? activeTransaction,
final List<Account> accounts,
final List<RecurringTransaction> recurringTransactions,
final List<Transaction> transactions,
final List<TransactionTemplate> transactionTemplates,
final List<Beneficiary> beneficiaries,
final List<ExpenseCategory> expenseCategories,
}) = _$CoreStateImpl;
const factory _CoreState(
{final OkanePage activePage,
final int? activeAccountIndex,
final Transaction? activeTransaction,
final List<Account> accounts,
final List<RecurringTransaction> recurringTransactions,
final List<Transaction> transactions,
final List<TransactionTemplate> transactionTemplates,
final List<Beneficiary> beneficiaries,
final List<ExpenseCategory> expenseCategories,
final List<Budget> budgets,
final Budget? activeBudget}) = _$CoreStateImpl;
@override
OkanePage get activePage;
@@ -402,6 +411,10 @@ abstract class _CoreState implements CoreState {
@override
List<ExpenseCategory> get expenseCategories;
@override
List<Budget> get budgets;
@override
Budget? get activeBudget;
@override
@JsonKey(ignore: true)
_$$CoreStateImplCopyWith<_$CoreStateImpl> get copyWith =>
throw _privateConstructorUsedError;

View File

@@ -69,3 +69,14 @@ String formatCurrency(double amount, {bool precise = true}) {
}
return "${amount.toStringAsFixed(2)}";
}
DateTime monthEnding(DateTime now) {
return DateTime(
now.year,
now.month,
32,
23,
59,
59,
);
}