Files
valuecards/lib/widgets/practice_chrome.dart
T
2026-09-18 00:01:42 +02:00

169 lines
4.1 KiB
Dart

/// Shared layout chrome for Values Sort screens.
library;
import 'package:flutter/material.dart';
import '../utils/constants.dart';
import 'theme_toggle_button.dart';
/// Centers content and applies mobile margins from the design system.
class PracticeScaffold extends StatelessWidget {
const PracticeScaffold({
super.key,
required this.child,
this.header,
this.footer,
this.padding,
this.showThemeToggle = true,
});
final Widget child;
final Widget? header;
final Widget? footer;
final EdgeInsetsGeometry? padding;
final bool showThemeToggle;
@override
Widget build(BuildContext context) {
final pad = padding ??
const EdgeInsets.fromLTRB(20, 8, 20, 16);
return Scaffold(
body: SafeArea(
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: kMaxContentWidth),
child: Column(
children: [
if (showThemeToggle)
const Padding(
padding: EdgeInsets.fromLTRB(12, 4, 12, 0),
child: Align(
alignment: Alignment.centerRight,
child: ThemeToggleButton(),
),
),
if (header != null) header!,
Expanded(
child: Padding(
padding: pad,
child: child,
),
),
if (footer != null)
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 12),
child: footer!,
),
],
),
),
),
),
);
}
}
/// Compact brand row used on the welcome screen.
class BrandMark extends StatelessWidget {
const BrandMark({super.key});
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final labels = Theme.of(context).textTheme;
return Row(
children: [
ClipOval(
child: Image.asset(
'assets/images/emblem.png',
width: 32,
height: 32,
fit: BoxFit.cover,
errorBuilder: (_, _, _) => Container(
width: 32,
height: 32,
color: colors.primaryContainer,
child: Icon(Icons.spa, size: 18, color: colors.onPrimary),
),
),
),
const SizedBox(width: 8),
Text(
'VALUES SORT',
style: labels.labelSmall?.copyWith(
letterSpacing: 2.2,
color: colors.onSurfaceVariant,
),
),
],
);
}
}
/// Full-width primary pill CTA.
class PrimaryPillButton extends StatelessWidget {
const PrimaryPillButton({
super.key,
required this.label,
required this.onPressed,
this.icon,
});
final String label;
final VoidCallback? onPressed;
final IconData? icon;
@override
Widget build(BuildContext context) {
return FilledButton(
onPressed: onPressed,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Flexible(child: Text(label, overflow: TextOverflow.ellipsis)),
if (icon != null) ...[
const SizedBox(width: 8),
Icon(icon, size: 18),
],
],
),
);
}
}
/// Quiet secondary text action.
class GhostPillButton extends StatelessWidget {
const GhostPillButton({
super.key,
required this.label,
required this.onPressed,
this.icon,
});
final String label;
final VoidCallback? onPressed;
final IconData? icon;
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: onPressed,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(label),
if (icon != null) ...[
const SizedBox(width: 6),
Icon(icon, size: 18),
],
],
),
);
}
}