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