Make the pie chart widget reusable

This commit is contained in:
2025-05-06 21:59:03 +02:00
parent d40d24f759
commit 63b5354b72
24 changed files with 2264 additions and 1924 deletions

View File

@@ -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: [],
),
),
],
),
],

View File

@@ -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(),
);
},
);
},
),
);
}

View File

@@ -29,7 +29,7 @@ class TotalBalanceCard extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Total balance",
"Total balance",
style: Theme.of(context).textTheme.titleLarge,
),
FutureBuilder(