Files
valuecards/lib/screens/card_sorting_screen.dart
2026-09-18 00:01:42 +02:00

191 lines
6.0 KiB
Dart

/// Stage 1 — assign each value card into one of five reflective piles.
library;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/value_pile.dart';
import '../providers/values_sort_provider.dart';
import '../widgets/practice_chrome.dart';
import '../widgets/value_card_tile.dart';
class CardSortingScreen extends StatelessWidget {
const CardSortingScreen({super.key});
@override
Widget build(BuildContext context) {
final sort = context.watch<ValuesSortController>();
final text = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
final card = sort.currentCard;
return PracticeScaffold(
padding: const EdgeInsets.fromLTRB(20, 4, 20, 8),
header: Padding(
padding: const EdgeInsets.fromLTRB(12, 4, 12, 0),
child: Row(
children: [
TextButton.icon(
onPressed: sort.sortedCount == 0 ? null : sort.undoLastSort,
icon: const Icon(Icons.undo, size: 18),
label: const Text('Undo'),
),
const Spacer(),
Text(
'Stage 1 · Initial Sort',
style: text.labelMedium,
),
],
),
),
child: Column(
children: [
Row(
children: [
Icon(Icons.bookmark_border, size: 18, color: colors.onSurfaceVariant),
const SizedBox(width: 6),
Expanded(
child: Text(
'${sort.sortedCount} of ${sort.totalCount} values sorted',
style: text.labelMedium,
),
),
Text(
'${(sort.sortProgress * 100).round()}%',
style: text.labelMedium?.copyWith(
color: colors.primary,
fontWeight: FontWeight.w700,
),
),
],
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(999),
child: LinearProgressIndicator(
value: sort.sortProgress,
minHeight: 6,
backgroundColor: colors.surfaceContainerHigh,
color: colors.secondary,
),
),
const SizedBox(height: 12),
Expanded(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 280),
child: card == null
? Center(
key: const ValueKey('empty'),
child: Text('All sorted', style: text.headlineMedium),
)
: SingleChildScrollView(
key: ValueKey(card.id),
child: Column(
children: [
ValueCardTile(card: card, elevation: 2),
const SizedBox(height: 14),
Text(
'Tap a pile to sort',
style: text.labelMedium?.copyWith(
color: colors.onSurfaceVariant,
),
),
const SizedBox(height: 8),
...ValuePile.values.map((pile) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: _PileButton(
pile: pile,
count: sort.countInPile(pile),
onTap: () => sort.assignCurrent(pile),
),
);
}),
],
),
),
),
),
],
),
);
}
}
class _PileButton extends StatelessWidget {
const _PileButton({
required this.pile,
required this.count,
required this.onTap,
});
final ValuePile pile;
final int count;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final text = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
return Material(
color: colors.surfaceContainerLow,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
side: BorderSide(color: pile.accent.withValues(alpha: 0.35)),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(18),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
child: Row(
children: [
Container(
width: 28,
height: 28,
alignment: Alignment.center,
decoration: BoxDecoration(
color: pile.accent.withValues(alpha: 0.15),
shape: BoxShape.circle,
),
child: Text(
'${pile.rank}',
style: text.labelMedium?.copyWith(
color: pile.accent,
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
pile.label,
style: text.titleMedium?.copyWith(
color: colors.onSurface,
),
),
Text(
pile.subtitle,
style: text.bodySmall?.copyWith(
color: colors.onSurfaceVariant,
),
),
],
),
),
Text(
'$count',
style: text.labelMedium?.copyWith(color: pile.accent),
),
],
),
),
),
);
}
}