import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../theme.dart'; import '../../widgets/primary_button.dart'; import '../../widgets/quiz_option_row.dart'; import '../result_screen.dart'; class QuizData { String name = ''; String skinType = ''; String age = ''; List concerns = []; String goal = ''; } class QuizFlow extends StatefulWidget { const QuizFlow({super.key}); @override State createState() => _QuizFlowState(); } class _QuizFlowState extends State { final _data = QuizData(); int _step = 0; final _pageCtrl = PageController(); final _nameCtrl = TextEditingController(); static const _totalSteps = 5; void _next() { if (_step < _totalSteps - 1) { setState(() => _step++); _pageCtrl.animateToPage( _step, duration: const Duration(milliseconds: 350), curve: Curves.easeOut, ); } else { Navigator.of(context).pushReplacement( PageRouteBuilder( pageBuilder: (_, a, __) => ResultScreen(data: _data), transitionsBuilder: (_, a, __, child) => FadeTransition(opacity: a, child: child), transitionDuration: const Duration(milliseconds: 500), ), ); } } void _back() { if (_step > 0) { setState(() => _step--); _pageCtrl.animateToPage( _step, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } else { Navigator.of(context).pop(); } } bool get _canProceed { return switch (_step) { 0 => _data.name.trim().length >= 2, 1 => _data.skinType.isNotEmpty, 2 => _data.age.isNotEmpty, 3 => _data.concerns.isNotEmpty, 4 => _data.goal.isNotEmpty, _ => false, }; } @override void dispose() { _nameCtrl.dispose(); _pageCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: SColors.bgPage, body: SafeArea( child: Column( children: [ _topBar(), _progressBar(), Expanded( child: PageView( controller: _pageCtrl, physics: const NeverScrollableScrollPhysics(), children: [ _NameStep( ctrl: _nameCtrl, onChanged: (v) => setState(() => _data.name = v), ), _SkinTypeStep( selected: _data.skinType, onSelect: (v) => setState(() => _data.skinType = v), ), _AgeStep( selected: _data.age, onSelect: (v) => setState(() => _data.age = v), ), _ConcernsStep( selected: _data.concerns, onToggle: (v) => setState(() { if (_data.concerns.contains(v)) { _data.concerns.remove(v); } else if (_data.concerns.length < 3) { _data.concerns.add(v); } }), ), _GoalStep( selected: _data.goal, onSelect: (v) => setState(() => _data.goal = v), ), ], ), ), _bottomBar(), ], ), ), ); } Widget _topBar() { return Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), child: Row( children: [ GestureDetector( onTap: _back, child: Container( width: 40, height: 40, decoration: BoxDecoration( color: SColors.surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: SColors.border), ), child: const Icon(Icons.arrow_back_ios_new_rounded, size: 16, color: SColors.textPrimary), ), ), const Spacer(), Text( '${_step + 1} of $_totalSteps', style: GoogleFonts.plusJakartaSans( fontSize: 13, fontWeight: FontWeight.w600, color: SColors.textSecondary, ), ), ], ), ); } Widget _progressBar() { return Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), child: ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: (_step + 1) / _totalSteps, backgroundColor: SColors.border, valueColor: const AlwaysStoppedAnimation(SColors.primary), minHeight: 4, ), ), ); } Widget _bottomBar() { return Padding( padding: const EdgeInsets.fromLTRB(24, 12, 24, 28), child: PrimaryButton( label: _step == _totalSteps - 1 ? 'See my results ✨' : 'Continue', icon: _step == _totalSteps - 1 ? null : Icons.arrow_forward_rounded, enabled: _canProceed, onTap: _next, ), ); } } // ── Step 1: Name ────────────────────────────────────────────────────────────── class _NameStep extends StatelessWidget { final TextEditingController ctrl; final ValueChanged onChanged; const _NameStep({required this.ctrl, required this.onChanged}); @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(24, 32, 24, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Hey! What\'s\nyour name? 👋', style: GoogleFonts.plusJakartaSans( fontSize: 30, fontWeight: FontWeight.w800, color: SColors.textPrimary, height: 1.2, ), ).animate().fadeIn(duration: 400.ms).slideY(begin: 0.1, end: 0), const SizedBox(height: 8), Text( 'I\'ll personalise your routine just for you.', style: GoogleFonts.plusJakartaSans( fontSize: 15, color: SColors.textSecondary, ), ).animate().fadeIn(duration: 400.ms, delay: 100.ms), const SizedBox(height: 36), TextField( controller: ctrl, onChanged: onChanged, autofocus: true, textCapitalization: TextCapitalization.words, style: GoogleFonts.plusJakartaSans( fontSize: 18, fontWeight: FontWeight.w600, color: SColors.textPrimary, ), decoration: InputDecoration( hintText: 'Your first name', hintStyle: GoogleFonts.plusJakartaSans( color: SColors.textSecondary, fontWeight: FontWeight.w400, ), filled: true, fillColor: SColors.surface, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: SColors.border), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: SColors.border, width: 1.5), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: SColors.primary, width: 2), ), ), ).animate().fadeIn(duration: 400.ms, delay: 200.ms), ], ), ); } } // ── Step 2: Skin Type ───────────────────────────────────────────────────────── class _SkinTypeStep extends StatelessWidget { final String selected; final ValueChanged onSelect; const _SkinTypeStep({required this.selected, required this.onSelect}); static const _options = [ (Icons.water_drop_rounded, 'Oily', 'Shiny by midday, large pores'), (Icons.grass_rounded, 'Dry', 'Tight, flaky, rough patches'), (Icons.blur_on_rounded, 'Combination', 'Oily T-zone, dry cheeks'), (Icons.sentiment_satisfied_alt_rounded, 'Normal', 'Balanced, minimal issues'), (Icons.help_outline_rounded, 'Not sure', 'Help me figure it out'), ]; @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(24, 32, 24, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'What\'s your\nskin type?', style: GoogleFonts.plusJakartaSans( fontSize: 30, fontWeight: FontWeight.w800, color: SColors.textPrimary, height: 1.2, ), ).animate().fadeIn(duration: 400.ms), const SizedBox(height: 8), Text( 'Choose the one that best describes your skin.', style: GoogleFonts.plusJakartaSans(fontSize: 15, color: SColors.textSecondary), ).animate().fadeIn(duration: 400.ms, delay: 100.ms), const SizedBox(height: 24), ...List.generate(_options.length, (i) { final (icon, label, sub) = _options[i]; return Padding( padding: const EdgeInsets.only(bottom: 10), child: QuizOptionRow( icon: icon, label: label, sub: sub, isSelected: selected == label, onTap: () => onSelect(label), ).animate().fadeIn(duration: 300.ms, delay: (100 + i * 60).ms) .slideX(begin: 0.05, end: 0, duration: 300.ms), ); }), ], ), ); } } // ── Step 3: Age ─────────────────────────────────────────────────────────────── class _AgeStep extends StatelessWidget { final String selected; final ValueChanged onSelect; const _AgeStep({required this.selected, required this.onSelect}); static const _options = [ (Icons.star_rounded, 'Under 25', 'Young & preventive care'), (Icons.hourglass_bottom_rounded, '25–35', 'Early anti-aging focus'), (Icons.wb_sunny_rounded, '35–45', 'Targeted treatments'), (Icons.nightlight_round, '45–60', 'Deep renewal & firming'), (Icons.diamond_rounded, '60+', 'Intensive care & radiance'), ]; @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(24, 32, 24, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'How old\nare you?', style: GoogleFonts.plusJakartaSans( fontSize: 30, fontWeight: FontWeight.w800, color: SColors.textPrimary, height: 1.2, ), ).animate().fadeIn(duration: 400.ms), const SizedBox(height: 8), Text( 'Skin changes with age — this helps us tailor advice.', style: GoogleFonts.plusJakartaSans(fontSize: 15, color: SColors.textSecondary), ).animate().fadeIn(duration: 400.ms, delay: 100.ms), const SizedBox(height: 24), ...List.generate(_options.length, (i) { final (icon, label, sub) = _options[i]; return Padding( padding: const EdgeInsets.only(bottom: 10), child: QuizOptionRow( icon: icon, label: label, sub: sub, isSelected: selected == label, onTap: () => onSelect(label), ).animate().fadeIn(duration: 300.ms, delay: (100 + i * 60).ms) .slideX(begin: 0.05, end: 0, duration: 300.ms), ); }), ], ), ); } } // ── Step 4: Concerns ────────────────────────────────────────────────────────── class _ConcernsStep extends StatelessWidget { final List selected; final ValueChanged onToggle; const _ConcernsStep({required this.selected, required this.onToggle}); static const _options = [ (Icons.face_retouching_natural, 'Breakouts', 'Acne, blackheads, congestion'), (Icons.cloud_rounded, 'Dullness', 'Tired, uneven, grey skin'), (Icons.air_rounded, 'Dryness', 'Tight, rough, flaking'), (Icons.format_align_justify_rounded, 'Fine lines', 'Wrinkles, loss of firmness'), (Icons.adjust_rounded, 'Dark spots', 'Hyperpigmentation, melasma'), (Icons.local_fire_department_rounded, 'Redness', 'Sensitivity, flushing, rosacea'), ]; @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(24, 32, 24, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Your skin\nconcerns?', style: GoogleFonts.plusJakartaSans( fontSize: 30, fontWeight: FontWeight.w800, color: SColors.textPrimary, height: 1.2, ), ).animate().fadeIn(duration: 400.ms), const SizedBox(height: 8), Text( 'Pick up to 3 that bother you most.', style: GoogleFonts.plusJakartaSans(fontSize: 15, color: SColors.textSecondary), ).animate().fadeIn(duration: 400.ms, delay: 100.ms), const SizedBox(height: 24), ...List.generate(_options.length, (i) { final (icon, label, sub) = _options[i]; final isSelected = selected.contains(label); final disabled = !isSelected && selected.length >= 3; return Padding( padding: const EdgeInsets.only(bottom: 10), child: Opacity( opacity: disabled ? 0.4 : 1.0, child: QuizOptionRow( icon: icon, label: label, sub: sub, isSelected: isSelected, onTap: disabled ? () {} : () => onToggle(label), ), ).animate().fadeIn(duration: 300.ms, delay: (100 + i * 60).ms) .slideX(begin: 0.05, end: 0, duration: 300.ms), ); }), ], ), ); } } // ── Step 5: Goal ────────────────────────────────────────────────────────────── class _GoalStep extends StatelessWidget { final String selected; final ValueChanged onSelect; const _GoalStep({required this.selected, required this.onSelect}); static const _options = [ (Icons.auto_awesome_rounded, 'Healthy glow', 'Radiant, dewy, lit-from-within'), (Icons.shield_rounded, 'Clear skin', 'Blemish-free, smooth texture'), (Icons.access_time_rounded, 'Slow aging', 'Firm, youthful, preventive'), (Icons.palette_rounded, 'Even tone', 'Balanced, bright complexion'), ]; @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(24, 32, 24, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Your skin\ngoal?', style: GoogleFonts.plusJakartaSans( fontSize: 30, fontWeight: FontWeight.w800, color: SColors.textPrimary, height: 1.2, ), ).animate().fadeIn(duration: 400.ms), const SizedBox(height: 8), Text( 'Pick one — we\'ll build your plan around it.', style: GoogleFonts.plusJakartaSans(fontSize: 15, color: SColors.textSecondary), ).animate().fadeIn(duration: 400.ms, delay: 100.ms), const SizedBox(height: 24), ...List.generate(_options.length, (i) { final (icon, label, sub) = _options[i]; return Padding( padding: const EdgeInsets.only(bottom: 10), child: QuizOptionRow( icon: icon, label: label, sub: sub, isSelected: selected == label, onTap: () => onSelect(label), ).animate().fadeIn(duration: 300.ms, delay: (100 + i * 60).ms) .slideX(begin: 0.05, end: 0, duration: 300.ms), ); }), ], ), ); } }