bar tap correct

This commit is contained in:
davmar
2026-09-17 01:00:11 +02:00
parent b62164dde5
commit a23739d717
+192 -135
View File
@@ -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,
); );
} }
} }