Files
shaynee-flutter/lib/screens/onboarding/s12_almost_there.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

108 lines
4.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import '../../theme.dart';
import '../../widgets/mascot.dart';
import '../../widgets/s_button.dart';
import '../../model/quiz_state.dart';
import '../../router.dart';
import 's13_referral.dart';
class AlmostThereScreen extends StatelessWidget {
final QuizState state;
const AlmostThereScreen({super.key, required this.state});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: SColors.bgSurface,
body: SafeArea(
child: Column(
children: [
Expanded(
flex: 45,
child: Stack(
alignment: Alignment.center,
children: [
Mascot(mood: MascotMood.celebrate, size: 180)
.animate().fadeIn(duration: 600.ms).slideY(begin: 0.06, end: 0),
Positioned(
top: 20, left: 50,
child: const Text('🎉', style: TextStyle(fontSize: 32))
.animate(onPlay: (c) => c.repeat(reverse: true), delay: 400.ms)
.moveY(begin: 0, end: -8, duration: 1200.ms),
),
Positioned(
top: 30, right: 45,
child: const Text('', style: TextStyle(fontSize: 24))
.animate(onPlay: (c) => c.repeat(reverse: true), delay: 200.ms)
.moveY(begin: 0, end: -10, duration: 1000.ms),
),
],
),
),
Expanded(
flex: 55,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: const Color(0xFFFFF3CD),
borderRadius: BorderRadius.circular(20),
),
child: Text('Almost there! 🔥', style: SText.caption(const Color(0xFF856404)).copyWith(fontWeight: FontWeight.w700)),
).animate().fadeIn(duration: 350.ms, delay: 200.ms),
const SizedBox(height: 14),
Text("You're doing\ngreat, ${state.name.split(' ').first}! 💪",
style: SText.headline2(SColors.textHead))
.animate().fadeIn(duration: 400.ms, delay: 300.ms)
.slideY(begin: 0.05, end: 0),
const SizedBox(height: 10),
Text("Just one more question and your personalised skin report will be ready.",
style: SText.subtitle(SColors.textSub))
.animate().fadeIn(duration: 400.ms, delay: 400.ms),
const SizedBox(height: 20),
_summaryChips(state)
.animate().fadeIn(duration: 400.ms, delay: 500.ms),
const Spacer(),
SButton(
label: "ONE MORE THING",
onTap: () => Navigator.of(context).push(slideRoute(ReferralScreen(state: state))),
).animate().fadeIn(duration: 400.ms, delay: 600.ms),
const SizedBox(height: 20),
],
),
),
),
],
),
),
);
}
Widget _summaryChips(QuizState state) {
final items = [
state.skinType, state.age, state.goal,
...state.concerns,
].where((s) => s.isNotEmpty).take(4);
return Wrap(
spacing: 8,
runSpacing: 8,
children: items.map((item) => Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: SColors.primaryLight,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: SColors.borderLight),
),
child: Text(item, style: SText.caption(SColors.primary).copyWith(fontWeight: FontWeight.w600)),
)).toList(),
);
}
}