focus-flow/apps/focus_flow_flutter/lib/widgets/top_bar.dart

255 lines
7.7 KiB
Dart

/// Renders the Top Bar widget for the FocusFlow Flutter UI.
library;
import 'package:flutter/material.dart';
import '../theme/focus_flow_tokens.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, super.key});
/// Date label shown beside the date navigation controls.
final String dateLabel;
@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,
onPressed: () {},
),
_IconButton(
icon: Icons.chevron_left,
metrics: metrics,
onPressed: () {},
),
SizedBox(
width: metrics.dateWidth,
height: metrics.controlHeight,
child: Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
dateLabel,
style: TextStyle(
color: FocusFlowTokens.textPrimary,
fontSize: metrics.dateSize,
),
),
),
),
),
_IconButton(
icon: Icons.chevron_right,
metrics: metrics,
onPressed: () {},
),
const Spacer(),
_ModeToggle(metrics: metrics),
SizedBox(width: metrics.settingsGap),
_SettingsButton(metrics: metrics),
],
),
);
},
);
}
}
class _TopBarMetrics {
const _TopBarMetrics(this.scale);
static const referenceHeight = 48.0;
static const _referenceWidth = 900.0;
static const _minimumScale = 0.44;
factory _TopBarMetrics.fromWidth(double width) {
final safeWidth = width.isFinite ? width : _referenceWidth;
final scale = (safeWidth / _referenceWidth).clamp(_minimumScale, 1.0);
return _TopBarMetrics(scale.toDouble());
}
final double scale;
double get titleWidth => 112 * scale;
double get titleSize => FocusFlowTokens.pageTitleSize * scale;
double get titleGap => 30 * scale;
double get controlHeight => referenceHeight * scale;
double get iconButtonSize => referenceHeight * scale;
double get iconSize => 24 * scale;
double get dateWidth => 144 * scale;
double get dateSize => 17 * scale;
double get modeWidth => 220 * scale;
double get modeHeight => referenceHeight * scale;
double get modeLabelSize => 17 * scale;
double get settingsGap => 28 * scale;
double get settingsWidth => 136 * scale;
double get settingsHeight => referenceHeight * scale;
double get settingsIconSize => 20 * scale;
double get settingsTextSize => 15 * scale;
double get settingsTextGap => 8 * scale;
double get borderRadius => FocusFlowTokens.smallButtonRadius * scale;
double get borderWidth => scale < 0.7 ? 0.8 : 1;
EdgeInsets get buttonPadding => EdgeInsets.symmetric(horizontal: 12 * scale);
}
class _SettingsButton extends StatelessWidget {
const _SettingsButton({required this.metrics});
final _TopBarMetrics metrics;
@override
Widget build(BuildContext context) {
return OutlinedButton(
onPressed: () {},
style: OutlinedButton.styleFrom(
foregroundColor: FocusFlowTokens.textPrimary,
side: BorderSide(
color: FocusFlowTokens.subtleBorder,
width: metrics.borderWidth,
),
fixedSize: Size(metrics.settingsWidth, metrics.settingsHeight),
minimumSize: Size.zero,
padding: metrics.buttonPadding,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(metrics.borderRadius),
),
),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.wb_sunny_outlined, size: metrics.settingsIconSize),
SizedBox(width: metrics.settingsTextGap),
Text(
'Settings',
style: TextStyle(
fontSize: metrics.settingsTextSize,
fontWeight: FontWeight.w600,
),
),
],
),
),
);
}
}
class _IconButton extends StatelessWidget {
const _IconButton({
required this.icon,
required this.metrics,
required this.onPressed,
});
final IconData icon;
final _TopBarMetrics metrics;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: onPressed,
constraints: BoxConstraints.tightFor(
width: metrics.iconButtonSize,
height: metrics.iconButtonSize,
),
color: FocusFlowTokens.textPrimary,
iconSize: metrics.iconSize,
padding: EdgeInsets.zero,
icon: Icon(icon),
visualDensity: VisualDensity.compact,
);
}
}
class _ModeToggle extends StatelessWidget {
const _ModeToggle({required this.metrics});
final _TopBarMetrics metrics;
@override
Widget build(BuildContext context) {
return Container(
height: metrics.modeHeight,
width: metrics.modeWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(metrics.borderRadius),
border: Border.all(
color: FocusFlowTokens.subtleBorder,
width: metrics.borderWidth,
),
),
child: Row(
children: [
Expanded(
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: FocusFlowTokens.glassPanelStrong,
borderRadius: BorderRadius.circular(metrics.borderRadius),
border: Border.all(
color: FocusFlowTokens.navBorder,
width: metrics.borderWidth,
),
),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'Compact',
style: TextStyle(fontSize: metrics.modeLabelSize),
),
),
),
),
Expanded(
child: TextButton(
onPressed: () {},
style: TextButton.styleFrom(
minimumSize: Size.zero,
padding: EdgeInsets.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'Normal',
style: TextStyle(
color: FocusFlowTokens.textPrimary,
fontSize: metrics.modeLabelSize,
),
),
),
),
),
],
),
);
}
}