full app done

This commit is contained in:
davmar
2026-09-18 00:01:42 +02:00
parent 8475abeddb
commit 78fd23f69f
74 changed files with 8104 additions and 1963 deletions
+447
View File
@@ -0,0 +1,447 @@
/// Post-sort home — landscape summary and gateways (Stitch Practice Hub).
library;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/value_card.dart';
import '../providers/values_sort_provider.dart';
import '../widgets/practice_chrome.dart';
class PracticeHubScreen extends StatelessWidget {
const PracticeHubScreen({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 answered = sort.totalAnsweredReflections;
final slots = sort.totalReflectionSlots;
final progress = slots == 0 ? 0.0 : answered / slots;
final core = sort.rankedCards.take(5).toList(growable: false);
return PracticeScaffold(
padding: const EdgeInsets.fromLTRB(20, 4, 20, 8),
footer: FilledButton.tonal(
onPressed: () => _confirmReset(context, sort),
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(52),
backgroundColor: colors.surfaceContainerHigh,
foregroundColor: colors.onSurface,
shape: const StadiumBorder(),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.restart_alt, size: 18, color: colors.outline),
const SizedBox(width: 8),
Text(
'Finish Practice (Reset All)',
style: text.labelLarge?.copyWith(color: colors.onSurface),
),
],
),
),
child: ListView(
children: [
Text(
'Your Values Landscape',
style: text.displaySmall?.copyWith(color: colors.onSurface),
),
const SizedBox(height: 18),
_CoreValuesCard(
values: core,
onValueTap: (card) => sort.goToReflection(valueId: card.id),
),
const SizedBox(height: 20),
_GatewayCard(
icon: Icons.layers_outlined,
iconColor: colors.primary,
title: 'All Sorted Values',
onTap: sort.goToOverview,
),
const SizedBox(height: 12),
_GatewayCard(
icon: Icons.format_list_numbered,
iconColor: colors.secondary,
title: 'Order Top Values',
onTap: sort.goToRanking,
),
const SizedBox(height: 12),
_GatewayCard(
icon: Icons.edit_note,
iconColor: colors.tertiary,
title: 'Reflect & Inquire',
meta: slots == 0
? 'Rank top values first'
: '$answered of $slots Answered (${(progress * 100).round()}%)',
metaBackground: colors.tertiaryContainer.withValues(alpha: 0.45),
metaForeground: colors.onTertiaryContainer,
progress: slots == 0 ? null : progress,
onTap: sort.coreValues.isEmpty ? null : sort.goToReflection,
),
const SizedBox(height: 8),
],
),
);
}
Future<void> _confirmReset(
BuildContext context,
ValuesSortController sort,
) async {
final ok = await showModalBottomSheet<bool>(
context: context,
isScrollControlled: true,
builder: (ctx) {
final text = Theme.of(ctx).textTheme;
final c = Theme.of(ctx).colorScheme;
return SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(24, 8, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
backgroundColor: c.errorContainer.withValues(alpha: 0.5),
child: Icon(Icons.hourglass_empty, color: c.error),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Reset Entire Practice?', style: text.titleLarge),
Text(
'Clean slate contemplation',
style: text.labelSmall,
),
],
),
),
],
),
const SizedBox(height: 14),
Text(
'This deliberate action will clear all 5 categorized piles, '
'your prioritized hierarchy, and stored reflection notes. '
'You will begin this mindful exploration from the initial deck.',
style: text.bodyMedium,
),
const SizedBox(height: 14),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: c.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.lightbulb_outline,
size: 18, color: c.secondary),
const SizedBox(width: 8),
Expanded(
child: Text(
'Tip: You can pause here and revisit your values whenever you need clarity in daily choices without resetting.',
style: text.bodySmall,
),
),
],
),
),
const SizedBox(height: 18),
PrimaryPillButton(
label: 'Yes, Reset All ${sort.totalCount} Cards',
onPressed: () => Navigator.pop(ctx, true),
),
const SizedBox(height: 8),
GhostPillButton(
label: 'Keep My Current Landscape',
onPressed: () => Navigator.pop(ctx, false),
),
],
),
),
);
},
);
if (ok == true) sort.restart();
}
}
class _CoreValuesCard extends StatelessWidget {
const _CoreValuesCard({
required this.values,
required this.onValueTap,
});
final List<ValueCard> values;
final void Function(ValueCard card) onValueTap;
@override
Widget build(BuildContext context) {
final text = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
final accent = colors.secondary;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: colors.surfaceContainerLow,
borderRadius: BorderRadius.circular(22),
border: Border.all(color: colors.outlineVariant.withValues(alpha: 0.7)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: accent.withValues(alpha: 0.18),
shape: BoxShape.circle,
),
child: Icon(Icons.star, size: 15, color: accent),
),
const SizedBox(width: 10),
Expanded(
child: Text(
'Your Core Values',
style: text.titleLarge?.copyWith(
color: colors.onSurface,
fontWeight: FontWeight.w400,
),
),
),
],
),
const SizedBox(height: 14),
if (values.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(
'Rank your Most Important values to distill your core set here.',
style: text.bodyMedium?.copyWith(
color: colors.onSurfaceVariant,
),
),
)
else
LayoutBuilder(
builder: (context, constraints) {
final gap = 8.0;
final chipWidth = (constraints.maxWidth - gap) / 2;
return Wrap(
spacing: gap,
runSpacing: gap,
children: [
for (var i = 0; i < values.length; i++)
SizedBox(
width: chipWidth,
child: _CoreValueChip(
rank: i + 1,
title: values[i].title,
accent: accent,
onTap: () => onValueTap(values[i]),
),
),
],
);
},
),
],
),
);
}
}
class _CoreValueChip extends StatelessWidget {
const _CoreValueChip({
required this.rank,
required this.title,
required this.accent,
required this.onTap,
});
final int rank;
final String title;
final Color accent;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final text = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
return Material(
color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(14),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(14),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: Row(
children: [
Container(
width: 22,
height: 22,
alignment: Alignment.center,
decoration: BoxDecoration(
color: accent.withValues(alpha: 0.18),
shape: BoxShape.circle,
),
child: Text(
'$rank',
style: text.labelSmall?.copyWith(
color: accent,
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: text.labelMedium?.copyWith(
color: colors.onSurface,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
),
);
}
}
class _GatewayCard extends StatelessWidget {
const _GatewayCard({
required this.icon,
required this.iconColor,
required this.title,
required this.onTap,
this.meta,
this.metaBackground,
this.metaForeground,
this.progress,
});
final IconData icon;
final Color iconColor;
final String title;
final String? meta;
final Color? metaBackground;
final Color? metaForeground;
final VoidCallback? onTap;
final double? progress;
@override
Widget build(BuildContext context) {
final text = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
final enabled = onTap != null;
final hasMeta = meta != null && meta!.isNotEmpty;
return Opacity(
opacity: enabled ? 1 : 0.55,
child: Material(
color: colors.surfaceContainerLow,
elevation: 0,
shadowColor: colors.shadow.withValues(alpha: 0.08),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
child: Padding(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: colors.surfaceContainerHigh,
shape: BoxShape.circle,
),
child: Icon(icon, color: iconColor, size: 22),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
style: text.titleLarge?.copyWith(
color: colors.onSurface,
fontWeight: FontWeight.w400,
),
),
if (hasMeta) ...[
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 3,
),
decoration: BoxDecoration(
color: metaBackground ?? colors.surfaceContainer,
borderRadius: BorderRadius.circular(999),
),
child: Text(
meta!,
style: text.labelSmall?.copyWith(
color: metaForeground ?? colors.onSurfaceVariant,
),
),
),
],
],
),
),
Icon(
Icons.arrow_forward,
color: colors.primary,
size: 22,
),
],
),
if (progress != null) ...[
const SizedBox(height: 14),
ClipRRect(
borderRadius: BorderRadius.circular(999),
child: LinearProgressIndicator(
value: progress,
minHeight: 6,
backgroundColor: colors.surfaceContainerHighest,
color: colors.tertiary,
),
),
],
],
),
),
),
),
);
}
}