From a23739d717394b51c839c3bbd782b4d3ccb664fc Mon Sep 17 00:00:00 2001 From: davmar Date: Thu, 17 Sep 2026 01:00:11 +0200 Subject: [PATCH] bar tap correct --- lib/screens/overview_screen.dart | 327 ++++++++++++++++++------------- 1 file changed, 192 insertions(+), 135 deletions(-) diff --git a/lib/screens/overview_screen.dart b/lib/screens/overview_screen.dart index 7918d64..290714e 100644 --- a/lib/screens/overview_screen.dart +++ b/lib/screens/overview_screen.dart @@ -957,10 +957,12 @@ class _OverviewScreenState extends State { final resolvedLabelIds = labelId != null ? {labelId} : (labelIds ?? - { - // Open with the same labels currently shown on the chart. - for (final id in _includedLabelIds) id, - }); + (_metric == OverviewMetricMode.labels + ? { + // Open with the same labels currently shown on the chart. + for (final id in _includedLabelIds) id, + } + : {})); Navigator.of(context).push( MaterialPageRoute( builder: (_) => TransactionsScreen( @@ -968,7 +970,8 @@ class _OverviewScreenState extends State { initialLabelIds: resolvedLabelIds, initialLabelMatch: LabelMatchMode.or, 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, ), ), @@ -1615,19 +1618,23 @@ class _ChartCard extends StatelessWidget { margin: EdgeInsets.zero, clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder(borderRadius: radius), - // Whole card opens the chart filter; nested buttons/legend keep their own taps. - child: InkWell( - onTap: onChartTap, - borderRadius: radius, - child: Padding( - padding: EdgeInsets.fromLTRB( - compact ? 12 : 14, - compact ? 10 : 12, - compact ? 10 : 14, - compact ? 12 : 16, - ), - child: Column( - children: [ + // Header/legend open the chart-wide filter; bars handle their own taps. + child: Padding( + padding: EdgeInsets.fromLTRB( + compact ? 12 : 14, + compact ? 10 : 12, + compact ? 10 : 14, + compact ? 12 : 16, + ), + child: Column( + children: [ + Material( + color: Colors.transparent, + child: InkWell( + onTap: onChartTap, + borderRadius: BorderRadius.circular(12), + child: Column( + children: [ if (compact) ...[ Row( children: [ @@ -1774,6 +1781,10 @@ class _ChartCard extends StatelessWidget { const SizedBox(height: 8), ], ], + ], + ), + ), + ), SizedBox( height: chartHeight, child: metric == OverviewMetricMode.flow @@ -1800,7 +1811,6 @@ class _ChartCard extends StatelessWidget { ], ), ), - ), ); } } @@ -1858,104 +1868,122 @@ class _FlowBars extends StatelessWidget { ); } - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: onChartTap, - child: IgnorePointer( - // fl_chart otherwise owns the gesture arena and empty taps go nowhere. - // Tap opens this chart's filters; legend / history still open columns. - child: BarChart( - BarChartData( - 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, - ), - ], - ), - ], + return BarChart( + BarChartData( + maxY: safeMax, + groupsSpace: period == OverviewPeriodMode.yearly ? 16 : 10, + barTouchData: BarTouchData( + handleBuiltInTouches: false, + touchTooltipData: BarTouchTooltipData( + getTooltipItem: (group, groupIndex, rod, rodIndex) => null, ), - 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 interval = axisInterval <= 0 ? 1.0 : axisInterval; - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: onChartTap, - child: IgnorePointer( - child: BarChart( - BarChartData( - minY: safeMin, - maxY: safeMax, - groupsSpace: period == OverviewPeriodMode.yearly ? 16 : 10, - barTouchData: const BarTouchData(enabled: false), + return BarChart( + BarChartData( + minY: safeMin, + maxY: safeMax, + groupsSpace: period == OverviewPeriodMode.yearly ? 16 : 10, + barTouchData: BarTouchData( + handleBuiltInTouches: false, + touchTooltipData: BarTouchTooltipData( + getTooltipItem: (group, groupIndex, rod, rodIndex) => null, + ), + 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( topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: @@ -2237,18 +2296,16 @@ class _LabelMonthBars extends StatelessWidget { ), ), borderData: FlBorderData(show: false), - barGroups: [ - for (var i = 0; i < months.length; i++) - BarChartGroupData( - x: i, - barsSpace: stacked ? 0 : 2, - barRods: rodsFor(months[i]), - ), - ], - ), - duration: Duration.zero, - ), + barGroups: [ + for (var i = 0; i < months.length; i++) + BarChartGroupData( + x: i, + barsSpace: stacked ? 0 : 2, + barRods: rodsFor(months[i]), + ), + ], ), + duration: Duration.zero, ); } }