focus-flow/apps/focus_flow_flutter/lib/widgets/top_bar.dart
pyr0ball 6f0da3c8aa feat(ui): wire Settings dialog to persisted OwnerSettings
Adds a getOwnerSettings read query to scheduler_core (previously only
an internal helper existed) and an updateOwnerSettings command on the
Flutter command controller, then builds a shared Settings dialog
surfacing timezone, day window, compact mode, and backlog staleness
thresholds. Both existing no-op entry points (sidebar nav item, top
bar button) now open the same dialog and persist edits through the
existing OwnerSettings/BacklogStalenessSettings backend.

Per the design spec (circuitforge-plans/focus-flow/superpowers/specs/
2026-07-07-settings-screen-design.md), the compact-mode toggle
persists but is disclosed in-UI as not yet affecting Today rendering,
since that wiring is separate scope (#11).

Closes: eva/focus-flow#16
2026-07-07 14:38:16 -07:00

144 lines
5 KiB
Dart

// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only
/// Renders the Top Bar widget for the FocusFlow Flutter UI.
library;
import 'package:flutter/material.dart';
import '../theme/focus_flow_tokens.dart';
part 'top_bar/top_bar_icon_button.dart';
part 'top_bar/top_bar_metrics.dart';
part 'top_bar/top_bar_mode_toggle.dart';
part 'top_bar/top_bar_settings_button.dart';
/// Header bar for the compact Today view.
class TopBar extends StatelessWidget {
/// Creates a top bar with a display-ready [dateLabel].
const TopBar({
required this.dateLabel,
this.onPreviousDate,
this.onNextDate,
this.onDatePressed,
this.onDateLabelPressed,
this.onOpenSettings,
super.key,
});
/// Date label shown beside the date navigation controls.
final String dateLabel;
/// Callback invoked when the previous-day control is activated.
final VoidCallback? onPreviousDate;
/// Callback invoked when the next-day control is activated.
final VoidCallback? onNextDate;
/// Callback invoked when the choose-date control is activated.
final VoidCallback? onDatePressed;
/// Callback invoked when the displayed date label is activated.
final VoidCallback? onDateLabelPressed;
/// Callback invoked when the Settings button is activated.
final VoidCallback? onOpenSettings;
/// Builds the widget subtree for this component.
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final metrics = _TopBarMetrics.fromWidth(constraints.maxWidth);
return SizedBox(
height: _TopBarMetrics.referenceHeight,
child: Row(
children: [
SizedBox(
width: metrics.titleWidth,
height: _TopBarMetrics.referenceHeight,
child: FittedBox(
alignment: Alignment.centerLeft,
fit: BoxFit.scaleDown,
child: Text(
'Today',
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
fontSize: metrics.titleSize,
),
),
),
),
SizedBox(width: metrics.titleGap),
_IconButton(
icon: Icons.calendar_today,
metrics: metrics,
tooltip: 'Choose date',
onPressed: onDatePressed,
),
_IconButton(
icon: Icons.chevron_left,
metrics: metrics,
tooltip: 'Previous day',
onPressed: onPreviousDate,
),
SizedBox(
width: metrics.dateWidth,
height: metrics.controlHeight,
child: Tooltip(
message: 'Scroll to current time',
child: Semantics(
button: true,
label: 'Scroll to current time, $dateLabel',
child: TextButton(
key: const ValueKey('top-bar-date-button'),
onPressed: onDateLabelPressed,
style: TextButton.styleFrom(
foregroundColor: FocusFlowTokens.textPrimary,
padding: EdgeInsets.symmetric(
horizontal: 6 * metrics.scale,
),
minimumSize: Size(
metrics.dateWidth,
metrics.controlHeight,
),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
metrics.borderRadius,
),
),
),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
dateLabel,
maxLines: 1,
overflow: TextOverflow.visible,
style: TextStyle(
color: FocusFlowTokens.textPrimary,
fontSize: metrics.dateSize,
),
),
),
),
),
),
),
_IconButton(
icon: Icons.chevron_right,
metrics: metrics,
tooltip: 'Next day',
onPressed: onNextDate,
),
const Spacer(),
_ModeToggle(metrics: metrics),
SizedBox(width: metrics.settingsGap),
_SettingsButton(metrics: metrics, onPressed: onOpenSettings),
],
),
);
},
);
}
}