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
+360
View File
@@ -0,0 +1,360 @@
/// Welcome screen with stacked sample cards and guide drawer.
library;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/values_sort_provider.dart';
import '../utils/app_theme.dart';
import '../widgets/practice_chrome.dart';
class WelcomeScreen extends StatelessWidget {
const WelcomeScreen({super.key});
@override
Widget build(BuildContext context) {
final text = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
return PracticeScaffold(
header: const Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: BrandMark(),
),
footer: Column(
children: [
GhostPillButton(
label: 'How this practice works',
icon: Icons.info_outline,
onPressed: () => _showGuide(context),
),
PrimaryPillButton(
label: 'Begin Reflection',
icon: Icons.arrow_forward,
onPressed: () =>
context.read<ValuesSortController>().beginPractice(),
),
],
),
child: ListView(
children: [
const SizedBox(height: 8),
const _CardFan(),
const SizedBox(height: 20),
Text(
'Discover what matters most to you',
textAlign: TextAlign.center,
style: text.displaySmall,
),
const SizedBox(height: 10),
Text(
'Through a gentle, guided card-sort practice, clarify your intrinsic '
'values and illuminate the principles that guide your everyday decisions.',
textAlign: TextAlign.center,
style: text.bodyLarge?.copyWith(color: colors.onSurfaceVariant),
),
const SizedBox(height: 18),
Wrap(
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8,
runSpacing: 6,
children: [
_Meta(icon: Icons.schedule, label: '10–15 min'),
Text('•', style: text.labelMedium),
_Meta(icon: Icons.timelapse, label: 'Self-paced'),
Text('•', style: text.labelMedium),
_Meta(icon: Icons.lock_outline, label: 'Private & reflective'),
],
),
],
),
);
}
Future<void> _showGuide(BuildContext context) async {
final controller = context.read<ValuesSortController>();
await showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (ctx) {
final text = Theme.of(ctx).textTheme;
final colors = Theme.of(ctx).colorScheme;
return Padding(
padding: EdgeInsets.only(
left: 24,
right: 24,
top: 8,
bottom: MediaQuery.paddingOf(ctx).bottom + 24,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.diversity_3, color: colors.tertiary),
const SizedBox(width: 8),
Expanded(
child: Text(
'The Practice of Sorting',
style: text.titleLarge,
),
),
IconButton(
onPressed: () => Navigator.pop(ctx),
icon: const Icon(Icons.close),
),
],
),
const SizedBox(height: 8),
Text(
'Grounded in contemplative psychology, this sorting practice '
'helps clarify the core tenets that give your life meaning and direction.',
style: text.bodyLarge?.copyWith(color: colors.onSurfaceVariant),
),
const SizedBox(height: 16),
const _GuideStep(
index: 1,
title: 'One card at a time',
body:
'Review cards with diverse values one at a time, checking in with your intuition without overthinking.',
),
const _GuideStep(
index: 2,
title: 'Sort into reflective piles',
body:
'Place each card into Most, Very, Important, Somewhat, or Not Important — trusting your first honest response.',
),
const _GuideStep(
index: 3,
title: 'Distill your top values',
body:
'Narrow down to your core top values, then reflect so they can guide daily decisions with clarity.',
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: AppTheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppTheme.outlineVariant),
),
child: Row(
children: [
Icon(Icons.spa, color: colors.tertiary, size: 18),
const SizedBox(width: 10),
Expanded(
child: Text(
'There are no right or wrong answers—only your authentic presence.',
style: text.bodyMedium?.copyWith(
fontStyle: FontStyle.italic,
),
),
),
],
),
),
const SizedBox(height: 16),
PrimaryPillButton(
label: "Got it, let's begin",
onPressed: () {
Navigator.pop(ctx);
controller.beginPractice();
},
),
],
),
);
},
);
}
}
class _Meta extends StatelessWidget {
const _Meta({required this.icon, required this.label});
final IconData icon;
final String label;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final text = Theme.of(context).textTheme;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 15, color: colors.tertiary),
const SizedBox(width: 4),
Text(label, style: text.labelMedium),
],
);
}
}
class _GuideStep extends StatelessWidget {
const _GuideStep({
required this.index,
required this.title,
required this.body,
});
final int index;
final String title;
final String body;
@override
Widget build(BuildContext context) {
final text = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.only(bottom: 14),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 12,
backgroundColor: AppTheme.surfaceContainerHigh,
child: Text(
'$index',
style: text.labelMedium?.copyWith(
color: colors.primary,
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: text.titleMedium),
const SizedBox(height: 2),
Text(body, style: text.bodyMedium),
],
),
),
],
),
);
}
}
class _CardFan extends StatelessWidget {
const _CardFan();
@override
Widget build(BuildContext context) {
final text = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
Widget mini({
required String domain,
required String title,
required String body,
required double angle,
required double dx,
required double dy,
bool hero = false,
}) {
return Transform.translate(
offset: Offset(dx, dy),
child: Transform.rotate(
angle: angle,
child: Container(
width: hero ? 210 : 180,
height: hero ? 155 : 135,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: hero ? Colors.white : AppTheme.surfaceContainer,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppTheme.outlineVariant),
boxShadow: [
BoxShadow(
color: const Color(0x142C2825),
blurRadius: hero ? 16 : 8,
offset: const Offset(0, 4),
),
],
),
child: ClipRect(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
domain.toUpperCase(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: text.labelSmall?.copyWith(
color: hero ? colors.secondary : colors.onSurfaceVariant,
letterSpacing: 1.2,
),
),
),
if (hero)
Icon(Icons.spa, size: 16, color: colors.primary),
],
),
const SizedBox(height: 4),
Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: (hero ? text.headlineMedium : text.titleLarge)
?.copyWith(fontSize: hero ? 22 : 18, height: 1.1),
),
const SizedBox(height: 4),
Expanded(
child: Text(
body,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: text.bodySmall,
),
),
],
),
),
),
),
);
}
return SizedBox(
height: 210,
child: Stack(
alignment: Alignment.center,
children: [
mini(
domain: 'Experience',
title: 'Adventure',
body: 'To have new and exciting experiences.',
angle: -0.21,
dx: -48,
dy: 10,
),
mini(
domain: 'Relation',
title: 'Compassion',
body: 'To feel and act on concern for others.',
angle: 0.21,
dx: 48,
dy: 8,
),
mini(
domain: 'Core Ground',
title: 'Genuineness',
body: 'To act in a manner that is true to who I am.',
angle: 0.02,
dx: 0,
dy: 4,
hero: true,
),
],
),
);
}
}