/// Bird's-eye overview of all five piles with demote / refine actions. library; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/value_card.dart'; import '../models/value_pile.dart'; import '../providers/values_sort_provider.dart'; import '../widgets/practice_chrome.dart'; import '../widgets/value_card_tile.dart'; class SortedOverviewScreen extends StatefulWidget { const SortedOverviewScreen({super.key}); @override State createState() => _SortedOverviewScreenState(); } class _SortedOverviewScreenState extends State { String _query = ''; ValuePile? _filter; @override Widget build(BuildContext context) { final sort = context.watch(); final text = Theme.of(context).textTheme; final colors = Theme.of(context).colorScheme; return PracticeScaffold( header: Padding( padding: const EdgeInsets.fromLTRB(8, 4, 12, 0), child: Row( children: [ IconButton( onPressed: sort.goToHub, icon: const Icon(Icons.arrow_back), ), Expanded( child: Text( 'All Sorted Values', style: text.labelMedium, ), ), ], ), ), child: ListView( children: [ Text('All Sorted Values', style: text.displaySmall), const SizedBox(height: 16), TextField( onChanged: (v) => setState(() => _query = v.trim().toLowerCase()), decoration: const InputDecoration( hintText: 'Find a value (e.g., Creativity)', prefixIcon: Icon(Icons.search), ), ), const SizedBox(height: 10), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _FilterChip( label: 'All (${sort.totalCount})', selected: _filter == null, onTap: () => setState(() => _filter = null), ), ...ValuePile.values.map((pile) { return _FilterChip( label: '${pile.shortLabel} (${sort.countInPile(pile)})', selected: _filter == pile, accent: pile.accent, onTap: () => setState(() => _filter = pile), ); }), ], ), ), const SizedBox(height: 18), ...ValuePile.values .where((p) => _filter == null || _filter == p) .map((pile) { final cards = sort.cardsInPile(pile).where((c) { if (_query.isEmpty) return true; return c.title.toLowerCase().contains(_query) || c.description.toLowerCase().contains(_query); }).toList(); return _PileSection( pile: pile, cards: cards, onDemote: (card) => _showDemoteSheet(context, sort, card), ); }), const SizedBox(height: 12), Text( 'Tip: refine any pile before ranking your core values.', style: text.bodySmall?.copyWith(color: colors.onSurfaceVariant), textAlign: TextAlign.center, ), ], ), ); } Future _showDemoteSheet( BuildContext context, ValuesSortController sort, ValueCard card, ) async { await showModalBottomSheet( context: context, isScrollControlled: true, builder: (ctx) { final text = Theme.of(ctx).textTheme; final colors = Theme.of(ctx).colorScheme; final maxHeight = MediaQuery.sizeOf(ctx).height * 0.75; return SafeArea( child: ConstrainedBox( constraints: BoxConstraints(maxHeight: maxHeight), child: SingleChildScrollView( padding: EdgeInsets.fromLTRB( 24, 8, 24, MediaQuery.paddingOf(ctx).bottom + 24, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( 'Reorder ${card.title}', style: text.titleLarge, ), ), IconButton( onPressed: () => Navigator.pop(ctx), icon: const Icon(Icons.close), ), ], ), Text('Move to pile:', style: text.bodyMedium), const SizedBox(height: 8), ...ValuePile.values.map((pile) { final current = sort.assignments[card.id] == pile; return ListTile( contentPadding: EdgeInsets.zero, enabled: !current, leading: CircleAvatar( backgroundColor: pile.accent.withValues(alpha: 0.2), child: Text( '${pile.rank}', style: text.labelMedium?.copyWith(color: pile.accent), ), ), title: Text(pile.label), subtitle: Text(pile.subtitle), trailing: current ? Icon(Icons.check, color: colors.primary) : Icon(Icons.arrow_forward, color: colors.onSurfaceVariant), onTap: current ? null : () { sort.moveCard(card.id, pile); Navigator.pop(ctx); }, ); }), ], ), ), ), ); }, ); } } class _FilterChip extends StatelessWidget { const _FilterChip({ required this.label, required this.selected, required this.onTap, this.accent, }); final String label; final bool selected; final VoidCallback onTap; final Color? accent; @override Widget build(BuildContext context) { final colors = Theme.of(context).colorScheme; final labelColor = selected ? colors.onPrimary : colors.onSurface; return Padding( padding: const EdgeInsets.only(right: 8), child: ChoiceChip( label: Text(label), selected: selected, onSelected: (_) => onTap(), showCheckmark: true, checkmarkColor: colors.onPrimary, selectedColor: colors.primary, backgroundColor: colors.surfaceContainer, labelStyle: Theme.of(context).textTheme.labelMedium?.copyWith( color: labelColor, fontWeight: selected ? FontWeight.w700 : FontWeight.w500, ), side: BorderSide( color: selected ? colors.primary : (accent ?? colors.outlineVariant), ), ), ); } } class _PileSection extends StatelessWidget { const _PileSection({ required this.pile, required this.cards, required this.onDemote, }); final ValuePile pile; final List cards; final void Function(ValueCard card) onDemote; @override Widget build(BuildContext context) { final text = Theme.of(context).textTheme; return Padding( padding: const EdgeInsets.only(bottom: 22), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.circle, size: 12, color: pile.accent), const SizedBox(width: 8), Expanded( child: Text( '${pile.label} ยท ${cards.length} cards', style: text.titleLarge, ), ), ], ), const SizedBox(height: 8), if (pile == ValuePile.mostImportant) ...cards.map( (card) => Padding( padding: const EdgeInsets.only(bottom: 10), child: ValueCardTile( card: card, compact: true, trailing: TextButton( onPressed: () => onDemote(card), child: const Text('Reorder'), ), ), ), ) else Wrap( spacing: 8, runSpacing: 8, children: cards .map( (c) => ValueChip( title: c.title, onTap: () => onDemote(c), ), ) .toList(), ), ], ), ); } }