Compare commits
1
Commits
b62164dde5
...
v2.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a23739d717 |
+192
-135
@@ -957,10 +957,12 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
|||||||
final resolvedLabelIds = labelId != null
|
final resolvedLabelIds = labelId != null
|
||||||
? {labelId}
|
? {labelId}
|
||||||
: (labelIds ??
|
: (labelIds ??
|
||||||
{
|
(_metric == OverviewMetricMode.labels
|
||||||
// Open with the same labels currently shown on the chart.
|
? {
|
||||||
for (final id in _includedLabelIds) id,
|
// Open with the same labels currently shown on the chart.
|
||||||
});
|
for (final id in _includedLabelIds) id,
|
||||||
|
}
|
||||||
|
: <String>{}));
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => TransactionsScreen(
|
builder: (_) => TransactionsScreen(
|
||||||
@@ -968,7 +970,8 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
|||||||
initialLabelIds: resolvedLabelIds,
|
initialLabelIds: resolvedLabelIds,
|
||||||
initialLabelMatch: LabelMatchMode.or,
|
initialLabelMatch: LabelMatchMode.or,
|
||||||
initialFlow: flow,
|
initialFlow: flow,
|
||||||
initialSearchInMonthOnly: !yearly && resolvedLabelIds.isNotEmpty,
|
// A specific bar/period should always scope to that month (or year).
|
||||||
|
initialSearchInMonthOnly: !yearly,
|
||||||
initialYear: yearly ? period.year : null,
|
initialYear: yearly ? period.year : null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1615,19 +1618,23 @@ class _ChartCard extends StatelessWidget {
|
|||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
shape: RoundedRectangleBorder(borderRadius: radius),
|
shape: RoundedRectangleBorder(borderRadius: radius),
|
||||||
// Whole card opens the chart filter; nested buttons/legend keep their own taps.
|
// Header/legend open the chart-wide filter; bars handle their own taps.
|
||||||
child: InkWell(
|
child: Padding(
|
||||||
onTap: onChartTap,
|
padding: EdgeInsets.fromLTRB(
|
||||||
borderRadius: radius,
|
compact ? 12 : 14,
|
||||||
child: Padding(
|
compact ? 10 : 12,
|
||||||
padding: EdgeInsets.fromLTRB(
|
compact ? 10 : 14,
|
||||||
compact ? 12 : 14,
|
compact ? 12 : 16,
|
||||||
compact ? 10 : 12,
|
),
|
||||||
compact ? 10 : 14,
|
child: Column(
|
||||||
compact ? 12 : 16,
|
children: [
|
||||||
),
|
Material(
|
||||||
child: Column(
|
color: Colors.transparent,
|
||||||
children: [
|
child: InkWell(
|
||||||
|
onTap: onChartTap,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
if (compact) ...[
|
if (compact) ...[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -1774,6 +1781,10 @@ class _ChartCard extends StatelessWidget {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: chartHeight,
|
height: chartHeight,
|
||||||
child: metric == OverviewMetricMode.flow
|
child: metric == OverviewMetricMode.flow
|
||||||
@@ -1800,7 +1811,6 @@ class _ChartCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1858,104 +1868,122 @@ class _FlowBars extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return BarChart(
|
||||||
behavior: HitTestBehavior.opaque,
|
BarChartData(
|
||||||
onTap: onChartTap,
|
maxY: safeMax,
|
||||||
child: IgnorePointer(
|
groupsSpace: period == OverviewPeriodMode.yearly ? 16 : 10,
|
||||||
// fl_chart otherwise owns the gesture arena and empty taps go nowhere.
|
barTouchData: BarTouchData(
|
||||||
// Tap opens this chart's filters; legend / history still open columns.
|
handleBuiltInTouches: false,
|
||||||
child: BarChart(
|
touchTooltipData: BarTouchTooltipData(
|
||||||
BarChartData(
|
getTooltipItem: (group, groupIndex, rod, rodIndex) => null,
|
||||||
maxY: safeMax,
|
|
||||||
groupsSpace: period == OverviewPeriodMode.yearly ? 16 : 10,
|
|
||||||
barTouchData: const BarTouchData(enabled: false),
|
|
||||||
titlesData: FlTitlesData(
|
|
||||||
topTitles:
|
|
||||||
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
|
||||||
rightTitles:
|
|
||||||
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
|
||||||
leftTitles: AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: true,
|
|
||||||
reservedSize: 42,
|
|
||||||
interval: safeMax / 3,
|
|
||||||
getTitlesWidget: (value, meta) {
|
|
||||||
if (value <= 0) {
|
|
||||||
return Text('0', style: theme.textTheme.labelSmall);
|
|
||||||
}
|
|
||||||
if ((value - safeMax).abs() < 0.01) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
return Text(
|
|
||||||
_compactMoney(value),
|
|
||||||
style: theme.textTheme.labelSmall,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottomTitles: AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: true,
|
|
||||||
reservedSize: 28,
|
|
||||||
getTitlesWidget: (value, meta) {
|
|
||||||
final index = value.toInt();
|
|
||||||
if (index < 0 || index >= months.length) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
if (period == OverviewPeriodMode.monthly &&
|
|
||||||
index % 2 == 1 &&
|
|
||||||
months.length > 8) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8),
|
|
||||||
child: Text(
|
|
||||||
_periodAxisLabel(months[index], period),
|
|
||||||
style: theme.textTheme.labelSmall,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
gridData: FlGridData(
|
|
||||||
show: true,
|
|
||||||
drawVerticalLine: false,
|
|
||||||
horizontalInterval: safeMax / 3,
|
|
||||||
getDrawingHorizontalLine: (value) => FlLine(
|
|
||||||
color: colors.outlineVariant.withValues(alpha: 0.35),
|
|
||||||
strokeWidth: 1,
|
|
||||||
dashArray: const [4, 4],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
borderData: FlBorderData(show: false),
|
|
||||||
barGroups: [
|
|
||||||
for (var i = 0; i < months.length; i++)
|
|
||||||
BarChartGroupData(
|
|
||||||
x: i,
|
|
||||||
barsSpace: 3,
|
|
||||||
barRods: [
|
|
||||||
BarChartRodData(
|
|
||||||
toY: monthly[_periodKey(months[i], period)]!.expense,
|
|
||||||
width: period == OverviewPeriodMode.yearly ? 14 : 9,
|
|
||||||
borderRadius:
|
|
||||||
const BorderRadius.vertical(top: Radius.circular(6)),
|
|
||||||
color: _kExpenseColor,
|
|
||||||
),
|
|
||||||
BarChartRodData(
|
|
||||||
toY: monthly[_periodKey(months[i], period)]!.income,
|
|
||||||
width: period == OverviewPeriodMode.yearly ? 14 : 9,
|
|
||||||
borderRadius:
|
|
||||||
const BorderRadius.vertical(top: Radius.circular(6)),
|
|
||||||
color: _kIncomeColor,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
duration: Duration.zero,
|
touchCallback: (event, response) {
|
||||||
|
if (event is! FlTapUpEvent) return;
|
||||||
|
final spot = response?.spot;
|
||||||
|
if (spot == null) {
|
||||||
|
onChartTap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final index = spot.touchedBarGroupIndex;
|
||||||
|
if (index < 0 || index >= months.length) {
|
||||||
|
onChartTap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final rod = spot.touchedRodDataIndex;
|
||||||
|
// Rod 0 = expense, rod 1 = income (see barGroups below).
|
||||||
|
final flow = rod == 0
|
||||||
|
? TxFlowFilter.expense
|
||||||
|
: rod == 1
|
||||||
|
? TxFlowFilter.income
|
||||||
|
: null;
|
||||||
|
onMonthTap(months[index], flow: flow);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
topTitles:
|
||||||
|
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||||
|
rightTitles:
|
||||||
|
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||||
|
leftTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 42,
|
||||||
|
interval: safeMax / 3,
|
||||||
|
getTitlesWidget: (value, meta) {
|
||||||
|
if (value <= 0) {
|
||||||
|
return Text('0', style: theme.textTheme.labelSmall);
|
||||||
|
}
|
||||||
|
if ((value - safeMax).abs() < 0.01) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return Text(
|
||||||
|
_compactMoney(value),
|
||||||
|
style: theme.textTheme.labelSmall,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 28,
|
||||||
|
getTitlesWidget: (value, meta) {
|
||||||
|
final index = value.toInt();
|
||||||
|
if (index < 0 || index >= months.length) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
if (period == OverviewPeriodMode.monthly &&
|
||||||
|
index % 2 == 1 &&
|
||||||
|
months.length > 8) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
child: Text(
|
||||||
|
_periodAxisLabel(months[index], period),
|
||||||
|
style: theme.textTheme.labelSmall,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
gridData: FlGridData(
|
||||||
|
show: true,
|
||||||
|
drawVerticalLine: false,
|
||||||
|
horizontalInterval: safeMax / 3,
|
||||||
|
getDrawingHorizontalLine: (value) => FlLine(
|
||||||
|
color: colors.outlineVariant.withValues(alpha: 0.35),
|
||||||
|
strokeWidth: 1,
|
||||||
|
dashArray: const [4, 4],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
borderData: FlBorderData(show: false),
|
||||||
|
barGroups: [
|
||||||
|
for (var i = 0; i < months.length; i++)
|
||||||
|
BarChartGroupData(
|
||||||
|
x: i,
|
||||||
|
barsSpace: 3,
|
||||||
|
barRods: [
|
||||||
|
BarChartRodData(
|
||||||
|
toY: monthly[_periodKey(months[i], period)]!.expense,
|
||||||
|
width: period == OverviewPeriodMode.yearly ? 14 : 9,
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius.vertical(top: Radius.circular(6)),
|
||||||
|
color: _kExpenseColor,
|
||||||
|
),
|
||||||
|
BarChartRodData(
|
||||||
|
toY: monthly[_periodKey(months[i], period)]!.income,
|
||||||
|
width: period == OverviewPeriodMode.yearly ? 14 : 9,
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius.vertical(top: Radius.circular(6)),
|
||||||
|
color: _kIncomeColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
duration: Duration.zero,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2165,16 +2193,47 @@ class _LabelMonthBars extends StatelessWidget {
|
|||||||
final axisInterval = ((safeMax - safeMin) / 3).abs();
|
final axisInterval = ((safeMax - safeMin) / 3).abs();
|
||||||
final interval = axisInterval <= 0 ? 1.0 : axisInterval;
|
final interval = axisInterval <= 0 ? 1.0 : axisInterval;
|
||||||
|
|
||||||
return GestureDetector(
|
return BarChart(
|
||||||
behavior: HitTestBehavior.opaque,
|
BarChartData(
|
||||||
onTap: onChartTap,
|
minY: safeMin,
|
||||||
child: IgnorePointer(
|
maxY: safeMax,
|
||||||
child: BarChart(
|
groupsSpace: period == OverviewPeriodMode.yearly ? 16 : 10,
|
||||||
BarChartData(
|
barTouchData: BarTouchData(
|
||||||
minY: safeMin,
|
handleBuiltInTouches: false,
|
||||||
maxY: safeMax,
|
touchTooltipData: BarTouchTooltipData(
|
||||||
groupsSpace: period == OverviewPeriodMode.yearly ? 16 : 10,
|
getTooltipItem: (group, groupIndex, rod, rodIndex) => null,
|
||||||
barTouchData: const BarTouchData(enabled: false),
|
),
|
||||||
|
touchCallback: (event, response) {
|
||||||
|
if (event is! FlTapUpEvent) return;
|
||||||
|
final spot = response?.spot;
|
||||||
|
if (spot == null) {
|
||||||
|
onChartTap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final index = spot.touchedBarGroupIndex;
|
||||||
|
if (index < 0 || index >= months.length) {
|
||||||
|
onChartTap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final month = months[index];
|
||||||
|
if (stacked) {
|
||||||
|
final stackIndex = spot.touchedStackItemIndex;
|
||||||
|
if (stackIndex >= 0 && stackIndex < topLabels.length) {
|
||||||
|
onMonthTap(month, labelId: topLabels[stackIndex].label.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// "Other" segment or whole column → that period with chart labels.
|
||||||
|
onMonthTap(month);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final rod = spot.touchedRodDataIndex;
|
||||||
|
if (rod >= 0 && rod < topLabels.length) {
|
||||||
|
onMonthTap(month, labelId: topLabels[rod].label.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onMonthTap(month);
|
||||||
|
},
|
||||||
|
),
|
||||||
titlesData: FlTitlesData(
|
titlesData: FlTitlesData(
|
||||||
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||||
rightTitles:
|
rightTitles:
|
||||||
@@ -2237,18 +2296,16 @@ class _LabelMonthBars extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
borderData: FlBorderData(show: false),
|
borderData: FlBorderData(show: false),
|
||||||
barGroups: [
|
barGroups: [
|
||||||
for (var i = 0; i < months.length; i++)
|
for (var i = 0; i < months.length; i++)
|
||||||
BarChartGroupData(
|
BarChartGroupData(
|
||||||
x: i,
|
x: i,
|
||||||
barsSpace: stacked ? 0 : 2,
|
barsSpace: stacked ? 0 : 2,
|
||||||
barRods: rodsFor(months[i]),
|
barRods: rodsFor(months[i]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
duration: Duration.zero,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
duration: Duration.zero,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user