Make the pie chart widget reusable
This commit is contained in:
@@ -195,7 +195,8 @@ class OkaneNavigationLayout extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
|
||||
if (p.showAccountName && state.activeAccountIndex != null)
|
||||
if (p.showAccountName &&
|
||||
state.activeAccountIndex != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
@@ -218,7 +219,8 @@ class OkaneNavigationLayout extends StatelessWidget {
|
||||
),
|
||||
ScreenSize.normal => Column(
|
||||
children: [
|
||||
if (p.showAccountName && state.activeAccountIndex != null)
|
||||
if (p.showAccountName &&
|
||||
state.activeAccountIndex != null)
|
||||
AccountIndicator(
|
||||
accountName:
|
||||
state
|
||||
|
||||
@@ -9,6 +9,8 @@ import 'package:okane/ui/pages/account/total_balance_card.dart';
|
||||
import 'package:okane/ui/pages/account/upcoming_transactions_card.dart';
|
||||
import 'package:okane/ui/state/core.dart';
|
||||
import 'package:okane/ui/utils.dart';
|
||||
import 'package:okane/ui/widgets/piechart.dart';
|
||||
import 'package:okane/ui/widgets/piechart_card.dart';
|
||||
|
||||
class AccountListPage extends StatefulWidget {
|
||||
final bool isPage;
|
||||
@@ -102,18 +104,25 @@ class AccountListPageState extends State<AccountListPage> {
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
child: TotalBalanceCard(),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
child: UpcomingTransactionsCard(),
|
||||
),
|
||||
|
||||
Row(
|
||||
Wrap(
|
||||
children: [
|
||||
Padding(padding: EdgeInsets.all(16), child: BreakdownCard()),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
//child: BreakdownCard(),
|
||||
child: PieChartCard(
|
||||
titleText: "Spending Breakdown",
|
||||
fallbackText: "No spending available",
|
||||
items: [],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -8,6 +8,8 @@ import 'package:okane/database/collections/transaction.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/piechart.dart';
|
||||
import 'package:okane/ui/widgets/piechart_card.dart';
|
||||
|
||||
const CATEGORY_INCOME = "Income";
|
||||
const CATEGORY_OTHER = "Other";
|
||||
@@ -71,101 +73,126 @@ class BreakdownCard extends StatelessWidget {
|
||||
return (expenses: expenses, colors: colors, usable: usableMoney);
|
||||
}
|
||||
|
||||
Widget _buildCard(Widget child, String? subtitle) {
|
||||
return ResponsiveCard(
|
||||
titleText: "Expense Breakdown",
|
||||
subtitleText: subtitle,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCenterText(String text) {
|
||||
return _buildCard(Center(child: Text(text)), null);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bloc = GetIt.I.get<CoreCubit>();
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: BlocBuilder<CoreCubit, CoreState>(
|
||||
builder: (context, state) {
|
||||
if (bloc.activeAccount == null) {
|
||||
return Text("No active account");
|
||||
return BlocBuilder<CoreCubit, CoreState>(
|
||||
builder: (context, state) {
|
||||
if (bloc.activeAccount == null) {
|
||||
return _buildCenterText("No account active");
|
||||
}
|
||||
|
||||
return FutureBuilder(
|
||||
future: getLastTransactions(bloc.activeAccount!, DateTime.now(), 30),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return _buildCard(
|
||||
Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: SizedBox(
|
||||
width: 150 - 16 * 2,
|
||||
height: 150 - 16 * 2,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
return FutureBuilder(
|
||||
future: getLastTransactions(
|
||||
bloc.activeAccount!,
|
||||
DateTime.now(),
|
||||
30,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
final title = Padding(
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
child: Text("Expense Breakdown"),
|
||||
);
|
||||
if (!snapshot.hasData) {
|
||||
return Column(children: [title, CircularProgressIndicator()]);
|
||||
}
|
||||
|
||||
if (snapshot.data!.isEmpty) {
|
||||
return Column(children: [title, Text("No transactions")]);
|
||||
}
|
||||
|
||||
final data = _computeSections(snapshot.data!);
|
||||
final sectionData =
|
||||
data.expenses.entries
|
||||
.map(
|
||||
(entry) => PieChartSectionData(
|
||||
value: entry.value,
|
||||
title: formatCurrency(entry.value, precise: false),
|
||||
titleStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||
radius: 40,
|
||||
color: data.colors[entry.key]!,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
return Column(
|
||||
children: [
|
||||
title,
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 150,
|
||||
height: 150,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
borderData: FlBorderData(show: false),
|
||||
sectionsSpace: 0,
|
||||
centerSpaceRadius: 35,
|
||||
sections: sectionData,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:
|
||||
data.expenses.keys
|
||||
.map(
|
||||
(key) => LegendItem(
|
||||
text: key,
|
||||
color: data.colors[key]!,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 16),
|
||||
child: Text(
|
||||
"Available money: ${formatCurrency(data.usable)}",
|
||||
final data = _computeSections(snapshot.data!);
|
||||
final sectionData =
|
||||
data.expenses.entries
|
||||
.map(
|
||||
(entry) => PieChartSectionData(
|
||||
value: entry.value,
|
||||
title: formatCurrency(entry.value, precise: false),
|
||||
titleStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||
radius: 40,
|
||||
color: data.colors[entry.key]!,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
if (sectionData.isEmpty) {
|
||||
return _buildCenterText("No expenses available");
|
||||
}
|
||||
return OkanePieChart(
|
||||
items:
|
||||
data.expenses.entries
|
||||
.map(
|
||||
(e) => (
|
||||
title: e.key,
|
||||
value: e.value,
|
||||
color: colorHash(e.key),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
return ResponsiveCard(
|
||||
titleText: "Expense Breakdown",
|
||||
child: BlocBuilder<CoreCubit, CoreState>(
|
||||
builder: (context, state) {
|
||||
if (bloc.activeAccount == null) {
|
||||
return Text("No active account");
|
||||
}
|
||||
|
||||
return FutureBuilder(
|
||||
future: getLastTransactions(
|
||||
bloc.activeAccount!,
|
||||
DateTime.now(),
|
||||
30,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return CircularProgressIndicator();
|
||||
}
|
||||
|
||||
final data = _computeSections(snapshot.data!);
|
||||
final sectionData =
|
||||
data.expenses.entries
|
||||
.map(
|
||||
(entry) => PieChartSectionData(
|
||||
value: entry.value,
|
||||
title: formatCurrency(entry.value, precise: false),
|
||||
titleStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||
radius: 40,
|
||||
color: data.colors[entry.key]!,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
if (sectionData.isEmpty) {
|
||||
return Center(child: Text("No expenses"));
|
||||
}
|
||||
return OkanePieChart(
|
||||
items:
|
||||
data.expenses.entries
|
||||
.map(
|
||||
(e) => (
|
||||
title: e.key,
|
||||
value: e.value,
|
||||
color: colorHash(e.key),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class TotalBalanceCard extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"Total balance",
|
||||
"Total balance",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
FutureBuilder(
|
||||
|
||||
@@ -23,44 +23,45 @@ class AddBudgetState extends State<AddBudgetPopup> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: "Budget name",
|
||||
),
|
||||
decoration: InputDecoration(hintText: "Budget name"),
|
||||
controller: _budgetNameEditController,
|
||||
),
|
||||
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: "Income",
|
||||
),
|
||||
decoration: InputDecoration(hintText: "Income"),
|
||||
controller: _budgetIncomeEditController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false,
|
||||
decimal: true,
|
||||
),
|
||||
),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
if (_budgetNameEditController.text.isEmpty || _budgetIncomeEditController.text.isEmpty) {
|
||||
return;
|
||||
}
|
||||
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
|
||||
..includeOtherSpendings = false
|
||||
..income = double.parse(_budgetIncomeEditController.text)
|
||||
..account.value = bloc.activeAccount!;
|
||||
await upsertBudget(budget);
|
||||
widget.onDone();
|
||||
},
|
||||
child: Text("Add"),
|
||||
final bloc = GetIt.I.get<CoreCubit>();
|
||||
final budget =
|
||||
Budget()
|
||||
..name = _budgetNameEditController.text
|
||||
..period = BudgetPeriod.month
|
||||
..includeOtherSpendings = false
|
||||
..income = double.parse(_budgetIncomeEditController.text)
|
||||
..account.value = bloc.activeAccount!;
|
||||
await upsertBudget(budget);
|
||||
widget.onDone();
|
||||
},
|
||||
child: Text("Add"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ class AddBudgetItemPopup extends StatefulWidget {
|
||||
final VoidCallback onDone;
|
||||
final Budget budget;
|
||||
|
||||
const AddBudgetItemPopup({super.key, required this.onDone, required this.budget});
|
||||
const AddBudgetItemPopup({
|
||||
super.key,
|
||||
required this.onDone,
|
||||
required this.budget,
|
||||
});
|
||||
|
||||
@override
|
||||
AddBudgetItemState createState() => AddBudgetItemState();
|
||||
@@ -31,55 +35,67 @@ class AddBudgetItemState extends State<AddBudgetItemPopup> {
|
||||
Text("Expense category"),
|
||||
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
final category = await showDialogOrModal(
|
||||
context: context,
|
||||
builder: (_) => AddExpenseCategory(),
|
||||
);
|
||||
if (category == null) {
|
||||
return;
|
||||
}
|
||||
onPressed: () async {
|
||||
final category = await showDialogOrModal(
|
||||
context: context,
|
||||
builder: (_) => AddExpenseCategory(),
|
||||
);
|
||||
if (category == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _expenseCategory = category);
|
||||
},
|
||||
child: Text(_expenseCategory?.name ?? "None"),
|
||||
setState(() => _expenseCategory = category);
|
||||
},
|
||||
child: Text(_expenseCategory?.name ?? "None"),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: "Amount",
|
||||
),
|
||||
decoration: InputDecoration(hintText: "Amount"),
|
||||
controller: _budgetItemAmountEditController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
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;
|
||||
}
|
||||
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"),
|
||||
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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ 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';
|
||||
import 'package:okane/ui/widgets/piechart.dart';
|
||||
import 'package:okane/ui/widgets/piechart_card.dart';
|
||||
|
||||
class BudgetDetailsPage extends StatelessWidget {
|
||||
final bool isPage;
|
||||
@@ -256,211 +258,48 @@ class BudgetDetailsPage extends StatelessWidget {
|
||||
),
|
||||
|
||||
Wrap(
|
||||
children: [
|
||||
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,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: PieChartCard(
|
||||
fallbackText: "",
|
||||
valueConverter: formatCurrency,
|
||||
items:
|
||||
state.activeBudget!.items
|
||||
.map(
|
||||
(i) => (
|
||||
title: i.expenseCategory.value!.name,
|
||||
value: i.amount,
|
||||
color: colorHash(
|
||||
i.expenseCategory.value!.name,
|
||||
),
|
||||
),
|
||||
),
|
||||
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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
titleText: "Budget breakdown",
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: SizedBox(
|
||||
child: Card(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
"Spending breakdown",
|
||||
style:
|
||||
Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge,
|
||||
textAlign: TextAlign.center,
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: PieChartCard(
|
||||
fallbackText: "No spending available",
|
||||
valueConverter: formatCurrency,
|
||||
items:
|
||||
spending.entries
|
||||
.map(
|
||||
(e) => (
|
||||
title: e.key,
|
||||
value: e.value.abs(),
|
||||
color: colorHash(e.key),
|
||||
),
|
||||
),
|
||||
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:
|
||||
spending.entries
|
||||
.map(
|
||||
(
|
||||
e,
|
||||
) => PieChartSectionData(
|
||||
value:
|
||||
e.value
|
||||
.abs(),
|
||||
title:
|
||||
formatCurrency(
|
||||
e.value
|
||||
.abs(),
|
||||
),
|
||||
titleStyle: TextStyle(
|
||||
fontWeight:
|
||||
FontWeight
|
||||
.bold,
|
||||
),
|
||||
radius: 40,
|
||||
color:
|
||||
colorHash(
|
||||
e.key,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children:
|
||||
spending.keys
|
||||
.map(
|
||||
(k) => LegendItem(
|
||||
text: k,
|
||||
color: colorHash(k),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
titleText: "Spending Breakdown",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
|
||||
@@ -19,9 +19,7 @@ class BudgetListPage extends StatelessWidget {
|
||||
if (state.budgets.isEmpty) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text("No budgets"),
|
||||
],
|
||||
children: [Text("No budgets")],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,18 +72,18 @@ class BudgetListPage extends StatelessWidget {
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
child: FloatingActionButton(
|
||||
child: Icon(Icons.add),
|
||||
onPressed: () {
|
||||
showDialogOrModal(
|
||||
context: context,
|
||||
builder:
|
||||
(_) => AddBudgetPopup(
|
||||
onDone: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Icon(Icons.add),
|
||||
onPressed: () {
|
||||
showDialogOrModal(
|
||||
context: context,
|
||||
builder:
|
||||
(_) => AddBudgetPopup(
|
||||
onDone: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -6,45 +6,47 @@ class EditBudgetPopup extends StatefulWidget {
|
||||
final Budget budget;
|
||||
|
||||
final VoidCallback onDone;
|
||||
|
||||
const EditBudgetPopup({required this.budget, required this.onDone, super.key});
|
||||
|
||||
|
||||
const EditBudgetPopup({
|
||||
required this.budget,
|
||||
required this.onDone,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
EditBudgetState createState() => EditBudgetState();
|
||||
}
|
||||
|
||||
class EditBudgetState extends State<EditBudgetPopup> {
|
||||
final _budgetNameEditController = TextEditingController();
|
||||
|
||||
|
||||
late bool _includeOtherSpendings;
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
|
||||
_budgetNameEditController.text = widget.budget.name;
|
||||
_includeOtherSpendings = widget.budget.includeOtherSpendings;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: "Name",
|
||||
),
|
||||
decoration: InputDecoration(hintText: "Name"),
|
||||
controller: _budgetNameEditController,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text("Include other spendings"),
|
||||
Switch(
|
||||
value: _includeOtherSpendings,
|
||||
onChanged: (value) {
|
||||
setState(() => _includeOtherSpendings = value);
|
||||
},
|
||||
value: _includeOtherSpendings,
|
||||
onChanged: (value) {
|
||||
setState(() => _includeOtherSpendings = value);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -52,26 +54,28 @@ class EditBudgetState extends State<EditBudgetPopup> {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
if (_budgetNameEditController.text.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (_budgetNameEditController.text == widget.budget.name && _includeOtherSpendings == widget.budget.includeOtherSpendings) {
|
||||
widget.onDone();
|
||||
return;
|
||||
}
|
||||
|
||||
widget.budget
|
||||
..name = _budgetNameEditController.text
|
||||
..includeOtherSpendings = _includeOtherSpendings;
|
||||
await upsertBudget(widget.budget);
|
||||
onPressed: () async {
|
||||
if (_budgetNameEditController.text.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (_budgetNameEditController.text == widget.budget.name &&
|
||||
_includeOtherSpendings ==
|
||||
widget.budget.includeOtherSpendings) {
|
||||
widget.onDone();
|
||||
},
|
||||
child: Text("Save"),
|
||||
return;
|
||||
}
|
||||
|
||||
widget.budget
|
||||
..name = _budgetNameEditController.text
|
||||
..includeOtherSpendings = _includeOtherSpendings;
|
||||
await upsertBudget(widget.budget);
|
||||
widget.onDone();
|
||||
},
|
||||
child: Text("Save"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,9 +99,7 @@ class CoreCubit extends Cubit<CoreState> {
|
||||
emit(state.copyWith(expenseCategories: await getExpenseCategories()));
|
||||
});
|
||||
_budgetsStreamSubscription?.cancel();
|
||||
_budgetsStreamSubscription = watchBudgets(activeAccount!).listen((
|
||||
_,
|
||||
) async {
|
||||
_budgetsStreamSubscription = watchBudgets(activeAccount!).listen((_) async {
|
||||
emit(state.copyWith(budgets: await getBudgets(activeAccount!)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ 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 {
|
||||
@@ -41,18 +42,19 @@ 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,
|
||||
List<Budget> budgets,
|
||||
Budget? activeBudget});
|
||||
$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
|
||||
@@ -80,52 +82,66 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
|
||||
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>,
|
||||
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);
|
||||
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 +149,24 @@ 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,
|
||||
List<Budget> budgets,
|
||||
Budget? activeBudget});
|
||||
$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,8 +174,9 @@ 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
|
||||
@@ -174,77 +193,90 @@ class __$$CoreStateImplCopyWithImpl<$Res>
|
||||
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>,
|
||||
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?,
|
||||
));
|
||||
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 [],
|
||||
final List<Budget> budgets = const [],
|
||||
this.activeBudget = null})
|
||||
: _accounts = accounts,
|
||||
_recurringTransactions = recurringTransactions,
|
||||
_transactions = transactions,
|
||||
_transactionTemplates = transactionTemplates,
|
||||
_beneficiaries = beneficiaries,
|
||||
_expenseCategories = expenseCategories,
|
||||
_budgets = budgets;
|
||||
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()
|
||||
@@ -341,16 +373,26 @@ 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));
|
||||
@@ -358,18 +400,19 @@ class _$CoreStateImpl implements _CoreState {
|
||||
|
||||
@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),
|
||||
const DeepCollectionEquality().hash(_budgets),
|
||||
activeBudget);
|
||||
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
|
||||
@@ -379,18 +422,19 @@ 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,
|
||||
final List<Budget> budgets,
|
||||
final Budget? activeBudget}) = _$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;
|
||||
|
||||
@@ -17,7 +17,7 @@ Future<T?> showDialogOrModal<T>({
|
||||
builder:
|
||||
(context) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 32 + MediaQuery.of(context).viewInsets.bottom,
|
||||
bottom: 32 + MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
child: builder(context),
|
||||
),
|
||||
@@ -74,12 +74,5 @@ String formatCurrency(double amount, {bool precise = true}) {
|
||||
}
|
||||
|
||||
DateTime monthEnding(DateTime now) {
|
||||
return DateTime(
|
||||
now.year,
|
||||
now.month,
|
||||
32,
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
);
|
||||
}
|
||||
return DateTime(now.year, now.month, 32, 23, 59, 59);
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ class AccountIndicator extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
if (trailing != null)
|
||||
trailing!,
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
84
lib/ui/widgets/piechart.dart
Normal file
84
lib/ui/widgets/piechart.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:okane/ui/pages/account/breakdown_card.dart';
|
||||
|
||||
typedef OkanePieChartSection = ({String title, double value, Color color});
|
||||
|
||||
typedef OkanePieChartValueConverter = String Function(double);
|
||||
|
||||
String numToString(double input) {
|
||||
return input.toString();
|
||||
}
|
||||
|
||||
class OkanePieChart extends StatelessWidget {
|
||||
// Width of the pie chart
|
||||
final double width;
|
||||
|
||||
// Height of the pie chart
|
||||
final double height;
|
||||
|
||||
final List<OkanePieChartSection> items;
|
||||
|
||||
final OkanePieChartValueConverter valueConverter;
|
||||
|
||||
const OkanePieChart({
|
||||
required this.items,
|
||||
this.valueConverter = numToString,
|
||||
this.width = 150,
|
||||
this.height = 150,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
borderData: FlBorderData(show: false),
|
||||
sectionsSpace: 5,
|
||||
centerSpaceRadius: 35,
|
||||
sections:
|
||||
items
|
||||
.map(
|
||||
(i) => PieChartSectionData(
|
||||
value: i.value,
|
||||
title: valueConverter(i.value),
|
||||
titleStyle: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
radius: 40,
|
||||
color: i.color,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:
|
||||
items
|
||||
.map((i) => LegendItem(text: i.title, color: i.color))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
82
lib/ui/widgets/piechart_card.dart
Normal file
82
lib/ui/widgets/piechart_card.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:okane/screen.dart';
|
||||
import 'package:okane/ui/widgets/piechart.dart';
|
||||
|
||||
class ResponsiveCard extends StatelessWidget {
|
||||
final String titleText;
|
||||
final String? subtitleText;
|
||||
|
||||
final Widget child;
|
||||
|
||||
const ResponsiveCard({
|
||||
super.key,
|
||||
required this.titleText,
|
||||
required this.child,
|
||||
this.subtitleText,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenSize = getScreenSize(context);
|
||||
final card = Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8, left: 8, right: 8),
|
||||
child: Text(
|
||||
titleText,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge!.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
|
||||
child,
|
||||
|
||||
if (subtitleText != null) Text(subtitleText!),
|
||||
],
|
||||
),
|
||||
);
|
||||
return switch (screenSize) {
|
||||
ScreenSize.small => Row(children: [Expanded(child: card)]),
|
||||
ScreenSize.normal => Container(
|
||||
constraints: BoxConstraints(maxWidth: ScreenSize.normal.size),
|
||||
child: card,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class PieChartCard extends StatelessWidget {
|
||||
final String titleText;
|
||||
|
||||
// Text to display when items is empty.
|
||||
final String fallbackText;
|
||||
|
||||
final OkanePieChartValueConverter valueConverter;
|
||||
|
||||
final List<OkanePieChartSection> items;
|
||||
|
||||
const PieChartCard({
|
||||
super.key,
|
||||
this.valueConverter = numToString,
|
||||
required this.items,
|
||||
required this.fallbackText,
|
||||
required this.titleText,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child =
|
||||
items.isEmpty
|
||||
? SizedBox(
|
||||
width: 150,
|
||||
height: 150,
|
||||
child: Center(child: Text(fallbackText)),
|
||||
)
|
||||
: OkanePieChart(valueConverter: valueConverter, items: items);
|
||||
|
||||
return ResponsiveCard(titleText: titleText, child: child);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user