Skip to content

Commit 3337b6a

Browse files
feat: Show income in balance overview (#711)
Co-authored-by: Tom Bursch <tombursch@gmail.com>
1 parent 6ed688f commit 3337b6a

File tree

5 files changed

+137
-34
lines changed

5 files changed

+137
-34
lines changed

kitchenowl/lib/cubits/expense_overview_cubit.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,17 @@ class ExpenseOverviewLoaded extends ExpenseOverviewState {
185185
return monthOverview[i]?.getExpenseTotalForPeriod() ?? 0;
186186
}
187187

188+
double getIncomeTotalForMonth(int i) {
189+
return monthOverview[i]?.getIncomeTotalForPeriod() ?? 0;
190+
}
191+
192+
double getTransactionAmountPercentage(double amount) {
193+
final total = amount > 0
194+
? getExpenseTotalForMonth(selectedMonthIndex)
195+
: getIncomeTotalForMonth(selectedMonthIndex);
196+
return total != 0 ? amount / total : -1;
197+
}
198+
188199
double getAverageForLastMonths(int n) =>
189200
monthOverview.values
190201
.skip(1)

kitchenowl/lib/l10n/app_el.arb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
}
123123
}
124124
},
125+
"@expenseOverviewExpenses": {},
125126
"@expensePaidBy": {},
126127
"@expensePaidFor": {},
127128
"@expenseReceivedBy": {},
@@ -542,6 +543,7 @@
542543
"expenseFactor": "Παράγοντας",
543544
"expenseOverviewComparedToPreviousMonth": "Συγκριτικά με προηγούμενους μήνες",
544545
"expenseOverviewTotalTitle": "Συνολικά έξοδα για τον μήνα {month}:",
546+
"expenseOverviewExpenses": "Έξοδα",
545547
"expensePaidBy": "Πληρώθηκε από",
546548
"expensePaidFor": "Πληρώθηκε εκ μέρους του",
547549
"expenseReceivedBy": "Λήφθηκε από",

kitchenowl/lib/l10n/app_en.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
}
125125
}
126126
},
127+
"@expenseOverviewExpenses": {},
127128
"@expensePaidBy": {},
128129
"@expensePaidFor": {},
129130
"@expenseReceivedBy": {},
@@ -543,7 +544,8 @@
543544
"expenseEmpty": "No expenses, start by creating one!",
544545
"expenseFactor": "Factor",
545546
"expenseOverviewComparedToPreviousMonth": "Compared to previous months",
546-
"expenseOverviewTotalTitle": "Total expenses for {month}:",
547+
"expenseOverviewTotalTitle": "Total for {month}:",
548+
"expenseOverviewExpenses": "Expenses",
547549
"expensePaidBy": "Paid by",
548550
"expensePaidFor": "Paid for",
549551
"expenseReceivedBy": "Received by",

kitchenowl/lib/models/expense_overview.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ class ExpenseOverview extends Model {
3434
}
3535

3636
double getExpenseTotalForPeriod() {
37-
return byCategory.isEmpty
38-
? 0
39-
: byCategory.values.reduce((v, e) => e > 0 ? v + e : v);
37+
return byCategory.values.where((v) => v > 0).fold(0.0, (sum, e) => sum + e);
38+
}
39+
40+
double getIncomeTotalForPeriod() {
41+
return byCategory.values.where((v) => v < 0).fold(0.0, (sum, e) => sum + e);
4042
}
4143

4244
@override

kitchenowl/lib/pages/expense_overview_page.dart

Lines changed: 116 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:kitchenowl/models/expense.dart';
1010
import 'package:kitchenowl/models/household.dart';
1111
import 'package:kitchenowl/pages/expense_add_update_page.dart';
1212
import 'package:kitchenowl/pages/expense_month_list_page.dart';
13+
import 'package:kitchenowl/styles/colors.dart';
1314
import 'package:kitchenowl/widgets/chart_bar_member_distribution.dart';
1415
import 'package:kitchenowl/widgets/chart_bar_months.dart';
1516
import 'package:kitchenowl/widgets/chart_line_current_month.dart';
@@ -93,7 +94,9 @@ class _ExpenseOverviewPageState extends State<ExpenseOverviewPage> {
9394

9495
final totalForSelectedMonth =
9596
state.getTotalForMonth(state.selectedMonthIndex);
96-
final expenseTotalForSelectedMonth =
97+
final totalIncomeForSelectedMonth =
98+
state.getIncomeTotalForMonth(state.selectedMonthIndex);
99+
final totalExpensesForSelectedMonth =
97100
state.getExpenseTotalForMonth(state.selectedMonthIndex);
98101

99102
return CustomScrollView(
@@ -150,34 +153,117 @@ class _ExpenseOverviewPageState extends State<ExpenseOverviewPage> {
150153
),
151154
),
152155
const SizedBox(height: 32),
153-
Row(
154-
children: [
155-
Expanded(
156-
child: Text(
156+
ExpansionTile(
157+
tilePadding: EdgeInsets.zero,
158+
childrenPadding: EdgeInsets.zero,
159+
title: Card(
160+
elevation: 3,
161+
child: ListTile(
162+
title: Text(
157163
AppLocalizations.of(context)!
158164
.expenseOverviewTotalTitle(
159165
_monthOffsetToString(state.selectedMonthIndex),
160166
),
161167
style: Theme.of(context).textTheme.titleMedium,
162168
),
169+
trailing: Row(
170+
crossAxisAlignment: CrossAxisAlignment.center,
171+
mainAxisSize: MainAxisSize.min,
172+
children: [
173+
Icon(
174+
state.trendUp(totalForSelectedMonth,
175+
state.getAverageForLastMonths(6))
176+
? Icons.trending_up_rounded
177+
: Icons.trending_down_rounded,
178+
),
179+
SizedBox(width: 8),
180+
Text(
181+
NumberFormat.simpleCurrency()
182+
.format(totalForSelectedMonth.abs()),
183+
style: Theme.of(context)
184+
.textTheme
185+
.titleLarge
186+
?.copyWith(
187+
color: totalForSelectedMonth < 0
188+
? Theme.of(context)
189+
.colorScheme
190+
.error
191+
: AppColors.green,
192+
),
193+
),
194+
if (state
195+
.getAverageForLastMonths(6)
196+
.isFinite) ...[
197+
SizedBox(width: 8),
198+
Text(
199+
"⌀ ${NumberFormat.simpleCurrency().format(state.getAverageForLastMonths(6))}",
200+
style: Theme.of(context)
201+
.textTheme
202+
.bodyMedium
203+
?.copyWith(
204+
color: Theme.of(context)
205+
.colorScheme
206+
.onSurface
207+
.withOpacity(0.6),
208+
),
209+
),
210+
],
211+
],
212+
),
163213
),
164-
Icon(state.trendUp(totalForSelectedMonth,
165-
state.getAverageForLastMonths(6))
166-
? Icons.trending_up_rounded
167-
: Icons.trending_down_rounded),
168-
Text(
169-
" ${NumberFormat.simpleCurrency().format(
170-
totalForSelectedMonth,
171-
)}",
172-
style: Theme.of(context).textTheme.titleMedium,
173-
),
174-
if (state.getAverageForLastMonths(6).isFinite)
175-
Text(
176-
" ⌀ ${NumberFormat.simpleCurrency().format(
177-
state.getAverageForLastMonths(6),
178-
)}",
179-
style: Theme.of(context).textTheme.titleMedium,
214+
),
215+
children: [
216+
Card(
217+
child: Column(
218+
children: [
219+
ListTile(
220+
leading: Icon(Icons.add_circle_outline,
221+
color: AppColors.green),
222+
title: Text(
223+
AppLocalizations.of(context)!.income,
224+
style:
225+
Theme.of(context).textTheme.bodyMedium,
226+
),
227+
trailing: Text(
228+
NumberFormat.simpleCurrency().format(
229+
totalIncomeForSelectedMonth.abs()),
230+
style: Theme.of(context)
231+
.textTheme
232+
.bodyMedium
233+
?.copyWith(
234+
color: AppColors.green,
235+
fontWeight: FontWeight.w500,
236+
),
237+
),
238+
),
239+
Divider(thickness: 1),
240+
ListTile(
241+
leading: Icon(Icons.remove_circle_outline,
242+
color:
243+
Theme.of(context).colorScheme.error),
244+
title: Text(
245+
AppLocalizations.of(context)!
246+
.expenseOverviewExpenses,
247+
style:
248+
Theme.of(context).textTheme.bodyMedium,
249+
),
250+
trailing: Text(
251+
NumberFormat.simpleCurrency()
252+
.format(totalExpensesForSelectedMonth),
253+
style: Theme.of(context)
254+
.textTheme
255+
.bodyMedium
256+
?.copyWith(
257+
color: Theme.of(context)
258+
.colorScheme
259+
.error,
260+
fontWeight: FontWeight.w500,
261+
),
262+
),
263+
),
264+
],
180265
),
266+
),
181267
],
182268
),
183269
if (state.monthOverview[state.selectedMonthIndex] != null)
@@ -212,6 +298,8 @@ class _ExpenseOverviewPageState extends State<ExpenseOverviewPage> {
212298
.sorted((a, b) => b.value.compareTo(a.value))
213299
.elementAt(i);
214300
final amount = entry.value;
301+
final amountPercentage =
302+
state.getTransactionAmountPercentage(amount);
215303
final category = entry.key < 0
216304
? null
217305
: state.categories
@@ -220,10 +308,9 @@ class _ExpenseOverviewPageState extends State<ExpenseOverviewPage> {
220308
return Card(
221309
child: ListTile(
222310
leading: Padding(
223-
padding:
224-
(amount / expenseTotalForSelectedMonth >= 0)
225-
? EdgeInsets.zero
226-
: const EdgeInsets.all(4.0),
311+
padding: (amountPercentage > 0)
312+
? EdgeInsets.zero
313+
: const EdgeInsets.all(4.0),
227314
child: ExpenseCategoryIcon(
228315
name: category?.name ?? '🪙',
229316
color: category?.color,
@@ -234,11 +321,10 @@ class _ExpenseOverviewPageState extends State<ExpenseOverviewPage> {
234321
trailing: Text(
235322
NumberFormat.simpleCurrency().format(amount),
236323
),
237-
subtitle:
238-
(amount / expenseTotalForSelectedMonth >= 0)
239-
? Text(NumberFormat.percentPattern().format(
240-
amount / expenseTotalForSelectedMonth))
241-
: null,
324+
subtitle: (amountPercentage > 0)
325+
? Text(NumberFormat.percentPattern()
326+
.format(amountPercentage))
327+
: null,
242328
onTap: () => Navigator.of(context).push(
243329
MaterialPageRoute(
244330
builder: (context) => ExpenseMonthListPage(

0 commit comments

Comments
 (0)