full app done
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
/// 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),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user