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.
101 lines
2.4 KiB
Dart
101 lines
2.4 KiB
Dart
import 'dart:math';
|
|
import 'package:flutter/material.dart';
|
|
|
|
class ConfettiOverlay extends StatefulWidget {
|
|
const ConfettiOverlay({super.key});
|
|
|
|
@override
|
|
State<ConfettiOverlay> createState() => _ConfettiOverlayState();
|
|
}
|
|
|
|
class _ConfettiOverlayState extends State<ConfettiOverlay>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _ctrl;
|
|
final _rand = Random();
|
|
late List<_Particle> _particles;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_particles = List.generate(40, (_) => _Particle(_rand));
|
|
_ctrl = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 2400),
|
|
)..forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_ctrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedBuilder(
|
|
animation: _ctrl,
|
|
builder: (_, __) => CustomPaint(
|
|
painter: _ConfettiPainter(_particles, _ctrl.value),
|
|
size: Size.infinite,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _Particle {
|
|
final double x;
|
|
double y;
|
|
final double speed;
|
|
final double size;
|
|
final Color color;
|
|
final double rotation;
|
|
final double rotSpeed;
|
|
|
|
_Particle(Random r)
|
|
: x = r.nextDouble(),
|
|
y = -0.1 - r.nextDouble() * 0.3,
|
|
speed = 0.3 + r.nextDouble() * 0.5,
|
|
size = 6 + r.nextDouble() * 8,
|
|
color = [
|
|
const Color(0xFF5B4FCF),
|
|
const Color(0xFFFF7EB3),
|
|
const Color(0xFFFFD700),
|
|
const Color(0xFF4CAF50),
|
|
const Color(0xFF1CB0F6),
|
|
const Color(0xFFFF7043),
|
|
][r.nextInt(6)],
|
|
rotation = r.nextDouble() * 2 * pi,
|
|
rotSpeed = (r.nextDouble() - 0.5) * 6;
|
|
}
|
|
|
|
class _ConfettiPainter extends CustomPainter {
|
|
final List<_Particle> particles;
|
|
final double t;
|
|
|
|
_ConfettiPainter(this.particles, this.t);
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
for (final p in particles) {
|
|
final y = (p.y + t * p.speed) * size.height;
|
|
if (y > size.height) continue;
|
|
final x = p.x * size.width;
|
|
final rot = p.rotation + t * p.rotSpeed;
|
|
|
|
canvas.save();
|
|
canvas.translate(x, y);
|
|
canvas.rotate(rot);
|
|
|
|
final paint = Paint()..color = p.color;
|
|
canvas.drawRect(
|
|
Rect.fromCenter(center: Offset.zero, width: p.size, height: p.size * 0.5),
|
|
paint,
|
|
);
|
|
canvas.restore();
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(_ConfettiPainter old) => old.t != t;
|
|
}
|