Files
shaynee-flutter/lib/screens/onboarding/s17_routine_pm.dart
mav 93042cbb4c feat: complete 20-screen onboarding flow
Full quiz flow s01-s20: splash, intro, value prop, social proof,
name/skin-type/age/concerns/goal quiz with insights, analysing loader,
result with skin score, AM/PM routines, ingredient science, signup, welcome.

Duolingo-style design: flat cards, pressable SButton (Stack trick),
4px progress bar, mascot moods (wave/think/analyze/celebrate),
confetti on result+welcome, personalized content throughout.
2026-05-28 17:11:27 +02:00

129 lines
4.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import '../../theme.dart';
import '../../widgets/s_button.dart';
import '../../model/quiz_state.dart';
import '../../router.dart';
import 's18_ingredient_science.dart';
class RoutinePmScreen extends StatelessWidget {
final QuizState state;
const RoutinePmScreen({super.key, required this.state});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: SColors.bgSurface,
body: Column(
children: [
SizedBox(
height: MediaQuery.of(context).size.height * 0.30,
child: _IllustrationZone().animate().fadeIn(duration: 500.ms),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(24, 8, 24, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_badge('🌙 Evening routine')
.animate().fadeIn(duration: 350.ms, delay: 100.ms),
const SizedBox(height: 14),
Text('Your PM\nroutine 🌙', style: SText.headline2(SColors.textHead))
.animate().fadeIn(duration: 400.ms, delay: 180.ms)
.slideY(begin: 0.05, end: 0),
const SizedBox(height: 6),
Text('4 steps · ~7 minutes · every night', style: SText.subtitle(SColors.textSub))
.animate().fadeIn(duration: 400.ms, delay: 260.ms),
const SizedBox(height: 20),
..._pmSteps(state).asMap().entries.map((e) =>
_stepRow(e.value.$1, e.value.$2, e.value.$3, e.key)),
const SizedBox(height: 8),
],
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(24, 8, 24, 32),
child: SButton(
label: 'LEARN THE SCIENCE',
onTap: () => Navigator.of(context).push(slideRoute(IngredientScienceScreen(state: state))),
).animate().fadeIn(duration: 400.ms, delay: 600.ms),
),
],
),
);
}
List<(String, String, String)> _pmSteps(QuizState state) {
final hasLines = state.concerns.contains('Fine lines');
final hasAcne = state.concerns.contains('Breakouts');
return [
('1', 'Double cleanse', 'Oil cleanser → gentle cleanser'),
('2', hasLines ? 'Retinol 0.3%' : 'Niacinamide serum', hasLines ? 'Start 2x/week, build up' : '10% — pores, texture, tone'),
('3', 'Moisturiser', hasAcne ? 'Non-comedogenic, lightweight' : 'Rich barrier-repair cream'),
('4', 'Eye cream', 'Optional — target fine lines'),
];
}
Widget _stepRow(String num, String name, String sub, int i) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: SColors.bgSurface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: SColors.borderLight, width: 1.5),
),
child: Row(
children: [
Container(
width: 32, height: 32,
decoration: const BoxDecoration(color: Color(0xFFEEE8FF), shape: BoxShape.circle),
child: Center(child: Text(num, style: SText.cardLabel(SColors.primary).copyWith(fontSize: 14))),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name, style: SText.cardLabel(SColors.textHead)),
Text(sub, style: SText.cardSub(SColors.textSub)),
],
),
),
const Icon(Icons.chevron_right_rounded, color: SColors.textSub, size: 20),
],
),
)
.animate().fadeIn(duration: 280.ms, delay: (300 + i * 80).ms)
.slideX(begin: 0.03, end: 0, duration: 280.ms);
}
Widget _badge(String label) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: const Color(0xFFEEE8FF),
borderRadius: BorderRadius.circular(20),
),
child: Text(label, style: SText.caption(SColors.primary).copyWith(fontWeight: FontWeight.w700)),
);
}
}
class _IllustrationZone extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: const Color(0xFFF0EDFF),
width: double.infinity,
child: Center(
child: const Text('🌙', style: TextStyle(fontSize: 80))
.animate(onPlay: (c) => c.repeat(reverse: true))
.moveY(begin: 0, end: -8, duration: 2200.ms, curve: Curves.easeInOut),
),
);
}
}