feat(bar-app): inline spend chart bars/line toggle in the Spend header

Move the bars/line choice out of Settings and into the Spend section header's
blank space as a small inline toggle, so the user flips the chart style right
where the chart is. Persistence unchanged.
This commit is contained in:
Tam Nhu Tran
2026-06-09 17:33:40 -04:00
parent 0787c445ca
commit a700a92171
3 changed files with 32 additions and 12 deletions
@@ -22,6 +22,10 @@ struct BarAnalyticsView: View {
/// Controls whether the spend sparkline renders as bars or a line graph.
/// Passed in by BarMenuView so it reflects the user's persisted choice live.
var spendChartStyle: SpendChartStyle = .bars
/// Inline flip for the spend chart style, surfaced as a small toggle in the
/// Spend header (using the otherwise-blank space) so the user switches
/// bars/line in place rather than digging into Settings. nil for `.breakdown`.
var onToggleSpendStyle: (() -> Void)? = nil
private var lastActive: String? {
BarFormatting.lastActiveLabel(
@@ -71,7 +75,23 @@ struct BarAnalyticsView: View {
/// line, and a thin inline 30-day sparkline when there is real spend.
private var spendStrip: some View {
VStack(alignment: .leading, spacing: 5) {
SectionLabel("Spend")
HStack(spacing: 6) {
SectionLabel("Spend")
Spacer()
// Inline bars/line toggle in the header's blank space no Settings trip.
if let toggle = onToggleSpendStyle, analytics.hasRecentData, !sparklineIsEmpty {
Button(action: toggle) {
Image(
systemName: spendChartStyle == .bars
? "chart.line.uptrend.xyaxis" : "chart.bar.fill"
)
.font(.system(size: 10))
}
.buttonStyle(.borderless)
.foregroundStyle(.tertiary)
.help("Spend graph: switch to \(spendChartStyle == .bars ? "line" : "bars")")
}
}
if analytics.hasRecentData {
Text(spendCaption)
.font(.caption2)
@@ -50,12 +50,17 @@ struct BarMenuView: View {
accountsSection
// (3) SPEND demoted to a thin informational strip below the cockpit.
// spendChartStyle is threaded from the viewModel so a live change in
// Settings immediately updates the chart without a refresh.
// spendChartStyle is threaded from the viewModel and toggled inline
// from the Spend header, so a change updates the chart immediately.
if let analytics = viewModel.analytics {
Divider()
BarAnalyticsView(analytics: analytics, section: .spend,
spendChartStyle: viewModel.spendChartStyle)
BarAnalyticsView(
analytics: analytics, section: .spend,
spendChartStyle: viewModel.spendChartStyle,
onToggleSpendStyle: {
viewModel.spendChartStyle =
viewModel.spendChartStyle == .bars ? .line : .bars
})
}
// (4) POOL ACCOUNTS compact generic rows, subordinate.
@@ -62,13 +62,8 @@ struct BarPreferencesView: View {
Text("Dark").tag(BarAppearance.dark)
}
.pickerStyle(.segmented)
// Changing this live-updates the spend chart in the open dropdown because
// viewModel.spendChartStyle is @Published and SpendChartStyleStore persists it.
Picker("Spend graph", selection: $viewModel.spendChartStyle) {
Text("Bars").tag(SpendChartStyle.bars)
Text("Line").tag(SpendChartStyle.line)
}
.pickerStyle(.segmented)
// Spend graph bars/line is toggled inline in the dropdown's Spend header,
// not here kept out of Settings so the choice lives where the chart is.
}
}