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

@@ -22,8 +22,7 @@ class AccountIndicator extends StatelessWidget {
),
),
const Spacer(),
if (trailing != null)
trailing!,
if (trailing != null) trailing!,
],
),
);

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

View 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);
}
}