Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfb551328b |
@@ -487,6 +487,16 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
: null),
|
||||
periodMode: favourite.period,
|
||||
),
|
||||
onChartTap: () => _openChartFilter(
|
||||
context,
|
||||
labelIds: {
|
||||
for (final id in favourite.labelIds) id,
|
||||
},
|
||||
flow: favourite.metric == OverviewMetricMode.labels
|
||||
? favourite.flow
|
||||
: null,
|
||||
periodMode: favourite.period,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -609,6 +619,10 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
flow: flow ??
|
||||
(_metric == OverviewMetricMode.labels ? _labelFlow : null),
|
||||
),
|
||||
onChartTap: () => _openChartFilter(
|
||||
context,
|
||||
flow: _metric == OverviewMetricMode.labels ? _labelFlow : null,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
if (_metric == OverviewMetricMode.flow) ...[
|
||||
@@ -784,6 +798,34 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Open Transactions with this chart's filters (no specific bar/column).
|
||||
void _openChartFilter(
|
||||
BuildContext context, {
|
||||
Set<String>? labelIds,
|
||||
TxFlowFilter? flow,
|
||||
OverviewPeriodMode? periodMode,
|
||||
}) {
|
||||
final mode = periodMode ?? _period;
|
||||
final yearly = mode == OverviewPeriodMode.yearly;
|
||||
final resolvedLabelIds = labelIds ??
|
||||
{
|
||||
for (final id in _includedLabelIds) id,
|
||||
};
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => TransactionsScreen(
|
||||
// Monthly charts show a year — open that whole year with the chart filter.
|
||||
initialMonth: yearly ? null : DateTime(_cursor.year, 12),
|
||||
initialYear: yearly ? null : _cursor.year,
|
||||
initialLabelIds: resolvedLabelIds,
|
||||
initialLabelMatch: LabelMatchMode.or,
|
||||
initialFlow: flow,
|
||||
initialSearchInMonthOnly: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MonthBucket {
|
||||
@@ -1183,6 +1225,7 @@ class _ChartCard extends StatelessWidget {
|
||||
required this.availableLabelCount,
|
||||
required this.onLabelTap,
|
||||
required this.onMonthTap,
|
||||
required this.onChartTap,
|
||||
this.onPeriodModeChanged,
|
||||
this.onEdit,
|
||||
});
|
||||
@@ -1211,6 +1254,7 @@ class _ChartCard extends StatelessWidget {
|
||||
String? labelId,
|
||||
TxFlowFilter? flow,
|
||||
}) onMonthTap;
|
||||
final VoidCallback onChartTap;
|
||||
final ValueChanged<OverviewPeriodMode>? onPeriodModeChanged;
|
||||
final VoidCallback? onEdit;
|
||||
|
||||
@@ -1551,6 +1595,8 @@ class _ChartCard extends StatelessWidget {
|
||||
],
|
||||
SizedBox(
|
||||
height: chartHeight,
|
||||
child: _TappableChartArea(
|
||||
onChartTap: onChartTap,
|
||||
child: metric == OverviewMetricMode.flow
|
||||
? _FlowBars(
|
||||
months: months,
|
||||
@@ -1558,6 +1604,7 @@ class _ChartCard extends StatelessWidget {
|
||||
currency: currency,
|
||||
period: period,
|
||||
onMonthTap: onMonthTap,
|
||||
onChartTap: onChartTap,
|
||||
)
|
||||
: _LabelMonthBars(
|
||||
months: months,
|
||||
@@ -1568,6 +1615,8 @@ class _ChartCard extends StatelessWidget {
|
||||
barStyle: labelBarStyle,
|
||||
invertChart: invertChart,
|
||||
onMonthTap: onMonthTap,
|
||||
onChartTap: onChartTap,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -1577,6 +1626,69 @@ class _ChartCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lets empty-chart taps open the chart filter without double-firing when a bar
|
||||
/// was just selected (fl_chart + parent [GestureDetector] both see the tap).
|
||||
class _TappableChartArea extends StatefulWidget {
|
||||
const _TappableChartArea({
|
||||
required this.onChartTap,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final VoidCallback onChartTap;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<_TappableChartArea> createState() => _TappableChartAreaState();
|
||||
}
|
||||
|
||||
class _TappableChartAreaState extends State<_TappableChartArea> {
|
||||
var _handling = false;
|
||||
|
||||
void _runExclusive(VoidCallback action) {
|
||||
if (_handling) return;
|
||||
_handling = true;
|
||||
action();
|
||||
Future<void>.delayed(const Duration(milliseconds: 400), () {
|
||||
if (mounted) _handling = false;
|
||||
});
|
||||
}
|
||||
|
||||
void openChartFilter() => _runExclusive(widget.onChartTap);
|
||||
|
||||
void openBar(VoidCallback action) => _runExclusive(action);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: openChartFilter,
|
||||
child: _ChartTapScope(
|
||||
openChartFilter: openChartFilter,
|
||||
openBar: openBar,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChartTapScope extends InheritedWidget {
|
||||
const _ChartTapScope({
|
||||
required this.openChartFilter,
|
||||
required this.openBar,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
final VoidCallback openChartFilter;
|
||||
final void Function(VoidCallback action) openBar;
|
||||
|
||||
static _ChartTapScope? maybeOf(BuildContext context) {
|
||||
return context.getInheritedWidgetOfExactType<_ChartTapScope>();
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(_ChartTapScope oldWidget) => false;
|
||||
}
|
||||
|
||||
class _FlowBars extends StatelessWidget {
|
||||
const _FlowBars({
|
||||
required this.months,
|
||||
@@ -1584,6 +1696,7 @@ class _FlowBars extends StatelessWidget {
|
||||
required this.currency,
|
||||
required this.period,
|
||||
required this.onMonthTap,
|
||||
required this.onChartTap,
|
||||
});
|
||||
|
||||
final List<DateTime> months;
|
||||
@@ -1595,6 +1708,7 @@ class _FlowBars extends StatelessWidget {
|
||||
String? labelId,
|
||||
TxFlowFilter? flow,
|
||||
}) onMonthTap;
|
||||
final VoidCallback onChartTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -1613,7 +1727,10 @@ class _FlowBars extends StatelessWidget {
|
||||
});
|
||||
|
||||
if (empty) {
|
||||
return Center(
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onChartTap,
|
||||
child: Center(
|
||||
child: Text(
|
||||
period == OverviewPeriodMode.yearly
|
||||
? 'No income or expenses in these years'
|
||||
@@ -1621,6 +1738,7 @@ class _FlowBars extends StatelessWidget {
|
||||
style: theme.textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1632,15 +1750,25 @@ class _FlowBars extends StatelessWidget {
|
||||
touchCallback: (event, response) {
|
||||
if (event is! FlTapUpEvent) return;
|
||||
final spot = response?.spot;
|
||||
if (spot == null) return;
|
||||
final index = spot.touchedBarGroupIndex;
|
||||
if (index < 0 || index >= months.length) return;
|
||||
final index = spot?.touchedBarGroupIndex;
|
||||
if (spot == null ||
|
||||
index == null ||
|
||||
index < 0 ||
|
||||
index >= months.length) {
|
||||
// Parent GestureDetector also handles empty-area taps.
|
||||
return;
|
||||
}
|
||||
final rodIndex = spot.touchedRodDataIndex;
|
||||
// Rods: 0 = expense, 1 = income.
|
||||
final flow = rodIndex == 1
|
||||
? TxFlowFilter.income
|
||||
: TxFlowFilter.expense;
|
||||
final scope = _ChartTapScope.maybeOf(context);
|
||||
if (scope != null) {
|
||||
scope.openBar(() => onMonthTap(months[index], flow: flow));
|
||||
} else {
|
||||
onMonthTap(months[index], flow: flow);
|
||||
}
|
||||
},
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||
@@ -1755,6 +1883,7 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
required this.barStyle,
|
||||
required this.invertChart,
|
||||
required this.onMonthTap,
|
||||
required this.onChartTap,
|
||||
});
|
||||
|
||||
static const _maxSeriesLabels = 6;
|
||||
@@ -1771,6 +1900,7 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
String? labelId,
|
||||
TxFlowFilter? flow,
|
||||
}) onMonthTap;
|
||||
final VoidCallback onChartTap;
|
||||
|
||||
double _plot(double value) => invertChart ? -value : value;
|
||||
|
||||
@@ -1779,12 +1909,16 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
final theme = Theme.of(context);
|
||||
final colors = theme.colorScheme;
|
||||
if (labelSpend.isEmpty) {
|
||||
return Center(
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onChartTap,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'No labeled activity in this period.\nTag transactions to see them here.',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1841,12 +1975,16 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
if (maxY == 0 && minY == 0) {
|
||||
return Center(
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onChartTap,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'No labeled activity in this period.\nTag transactions to see them here.',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
final span = (maxY - minY).abs();
|
||||
@@ -1949,9 +2087,14 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
touchCallback: (event, response) {
|
||||
if (event is! FlTapUpEvent) return;
|
||||
final spot = response?.spot;
|
||||
if (spot == null) return;
|
||||
final index = spot.touchedBarGroupIndex;
|
||||
if (index < 0 || index >= months.length) return;
|
||||
final index = spot?.touchedBarGroupIndex;
|
||||
if (spot == null ||
|
||||
index == null ||
|
||||
index < 0 ||
|
||||
index >= months.length) {
|
||||
// Parent GestureDetector also handles empty-area taps.
|
||||
return;
|
||||
}
|
||||
String? labelId;
|
||||
if (stacked) {
|
||||
final stackIdx = spot.touchedStackItemIndex;
|
||||
@@ -1965,7 +2108,14 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
}
|
||||
// Last rod may be "Other" — open month with chart labels only.
|
||||
}
|
||||
final scope = _ChartTapScope.maybeOf(context);
|
||||
if (scope != null) {
|
||||
scope.openBar(
|
||||
() => onMonthTap(months[index], labelId: labelId),
|
||||
);
|
||||
} else {
|
||||
onMonthTap(months[index], labelId: labelId);
|
||||
}
|
||||
},
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||
|
||||
Reference in New Issue
Block a user