import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; typedef QuizNextBuilder = Route Function(BuildContext context, int nextScore); enum QuizAnswerType { text, image, number, yesNo } class QuizAnswer { const QuizAnswer({ required this.title, required this.description, required this.weight, this.imagePath, this.value, }); final String title; final String description; final int weight; final String? imagePath; final String? value; } class QuizQuestionScreen extends StatefulWidget { const QuizQuestionScreen({ super.key, required this.title, required this.question, required this.answers, required this.nextRoute, this.currentScore = 0, this.onFinished, this.isFinal = false, this.showBackButton = false, this.answerType = QuizAnswerType.text, this.questionImagePaths = const [], }); final String title; final String question; final List answers; final QuizNextBuilder nextRoute; final int currentScore; final VoidCallback? onFinished; final bool isFinal; final bool showBackButton; final QuizAnswerType answerType; final List questionImagePaths; @override State createState() => _QuizQuestionScreenState(); } class _QuizQuestionScreenState extends State { int? _selected; TextEditingController? _numberController; int? _numberValue; @override void initState() { super.initState(); if (widget.answerType == QuizAnswerType.number) { _numberController = TextEditingController(); } } @override void dispose() { _numberController?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final size = MediaQuery.sizeOf(context); bool canProceed = _selected != null; if (widget.answerType == QuizAnswerType.number) { canProceed = _numberValue != null && _numberValue! >= 0; } return Scaffold( body: Stack( clipBehavior: Clip.none, children: [ Positioned.fill( child: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0xFFFFE6F1), Color(0xFFFFC9DF)], ), ), ), ), Positioned( left: -size.width * 0.40, bottom: -size.width * 0.45, child: IgnorePointer( child: SizedBox( width: size.width * 1.05, height: size.width * 1.05, child: Transform.rotate( angle: 35 * math.pi / 180, child: Opacity( opacity: 0.95, child: Lottie.asset( 'lottie/Liquid waves.json', fit: BoxFit.cover, repeat: true, ), ), ), ), ), ), SafeArea( child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 520), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.fromLTRB(20, 18, 20, 10), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( widget.title, textAlign: TextAlign.center, style: TextStyle( color: Colors.black.withValues(alpha: 0.55), fontWeight: FontWeight.w800, ), ), const SizedBox(height: 6), if (widget.questionImagePaths.isNotEmpty) ...[ const SizedBox(height: 6), _QuestionReferenceImages( paths: widget.questionImagePaths, ), const SizedBox(height: 10), ], Text( widget.question, textAlign: TextAlign.center, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w900, color: Color(0xFFFF55A7), height: 1.2, ), ), const SizedBox(height: 8), Text( widget.answerType == QuizAnswerType.number ? 'Insira o número' : widget.answerType == QuizAnswerType.yesNo ? 'Escolha uma opção' : 'Escolha apenas uma opção', textAlign: TextAlign.center, style: TextStyle( color: Colors.black.withValues(alpha: 0.55), fontWeight: FontWeight.w700, ), ), ], ), ), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: widget.answerType == QuizAnswerType.number ? _buildNumberInput() : ListView.separated( padding: const EdgeInsets.only(bottom: 12), itemCount: widget.answers.length, separatorBuilder: (context, index) => const SizedBox(height: 12), itemBuilder: (context, i) { return _QuizAnswerTile( answer: widget.answers[i], selected: _selected == i, onTap: () => setState(() => _selected = i), ); }, ), ), ), Padding( padding: const EdgeInsets.fromLTRB(20, 8, 20, 18), child: Column( children: [ SizedBox( width: size.width * 0.62, height: 46, child: FilledButton( style: FilledButton.styleFrom( backgroundColor: const Color(0xFF2F9E94), foregroundColor: Colors.white, shape: const StadiumBorder(), textStyle: const TextStyle( fontWeight: FontWeight.w900, ), ).copyWith( animationDuration: const Duration( milliseconds: 180, ), splashFactory: InkSparkle.splashFactory, overlayColor: WidgetStateProperty.resolveWith( (states) { if (states.contains( WidgetState.pressed, )) { return Colors.white.withValues( alpha: 0.14, ); } if (states.contains( WidgetState.hovered, ) || states.contains( WidgetState.focused, )) { return Colors.white.withValues( alpha: 0.08, ); } return null; }, ), ), onPressed: !canProceed ? null : () { int nextScore = widget.currentScore; if (widget.answerType == QuizAnswerType.number) { nextScore = widget.currentScore + (_numberValue ?? 0); } else { final picked = widget.answers[_selected!]; nextScore = widget.currentScore + picked.weight; } if (widget.isFinal) { final finishedRoute = widget.nextRoute( context, nextScore, ); Navigator.of( context, ).pushReplacement(finishedRoute); return; } Navigator.of(context).push( widget.nextRoute(context, nextScore), ); }, child: Text( widget.isFinal ? 'Concluir' : 'Avançar', ), ), ), if (widget.showBackButton) ...[ const SizedBox(height: 10), SizedBox( width: size.width * 0.62, height: 42, child: FilledButton( style: FilledButton.styleFrom( backgroundColor: const Color(0xFF2F9E94), foregroundColor: Colors.white, shape: const StadiumBorder(), textStyle: const TextStyle( fontWeight: FontWeight.w900, ), ).copyWith( animationDuration: const Duration( milliseconds: 180, ), splashFactory: InkSparkle.splashFactory, overlayColor: WidgetStateProperty.resolveWith< Color? >((states) { if (states.contains( WidgetState.pressed, )) { return Colors.white.withValues( alpha: 0.14, ); } if (states.contains( WidgetState.hovered, ) || states.contains( WidgetState.focused, )) { return Colors.white.withValues( alpha: 0.08, ); } return null; }), ), onPressed: () => Navigator.of(context).maybePop(), child: const Text('Voltar'), ), ), ], const SizedBox(height: 10), SizedBox( width: size.width * 0.62, height: 42, child: OutlinedButton( style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFF2F9E94), side: const BorderSide( color: Color(0xFF2F9E94), width: 1.3, ), shape: const StadiumBorder(), textStyle: const TextStyle( fontWeight: FontWeight.w900, ), ), onPressed: () => Navigator.of( context, ).popUntil((route) => route.isFirst), child: const Text('Voltar para homepage'), ), ), ], ), ), ], ), ), ), ), ], ), ); } Widget _buildNumberInput() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 150, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.70), borderRadius: BorderRadius.circular(16), border: Border.all( color: Colors.black.withValues(alpha: 0.12), width: 1.0, ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.06), blurRadius: 18, offset: const Offset(0, 10), ), ], ), child: TextField( controller: _numberController, keyboardType: TextInputType.number, textAlign: TextAlign.center, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w900, color: Color(0xFF2F9E94), ), decoration: const InputDecoration( border: InputBorder.none, hintText: '0', hintStyle: TextStyle( fontSize: 24, fontWeight: FontWeight.w900, color: Colors.grey, ), contentPadding: EdgeInsets.symmetric(vertical: 20), ), onChanged: (value) { setState(() { _numberValue = int.tryParse(value); }); }, ), ), ], ), ); } } class _QuizAnswerTile extends StatelessWidget { const _QuizAnswerTile({ required this.answer, required this.selected, required this.onTap, }); final QuizAnswer answer; final bool selected; final VoidCallback onTap; @override Widget build(BuildContext context) { final borderColor = selected ? const Color(0xFF2F9E94) : Colors.black.withValues(alpha: 0.12); final bg = selected ? Colors.white.withValues(alpha: 0.88) : Colors.white.withValues(alpha: 0.70); return AnimatedContainer( duration: const Duration(milliseconds: 220), curve: Curves.easeOutCubic, decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(16), border: Border.all(color: borderColor, width: selected ? 1.4 : 1.0), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.06), blurRadius: 18, offset: const Offset(0, 10), ), ], ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(16), onTap: onTap, splashFactory: InkSparkle.splashFactory, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (answer.imagePath != null) ...[ ClipRRect( borderRadius: BorderRadius.circular(12), child: AspectRatio( aspectRatio: 4 / 3, child: Image.asset( answer.imagePath!, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( color: Colors.black.withValues(alpha: 0.06), child: const Center( child: Icon( Icons.image_not_supported_outlined, color: Colors.black38, ), ), ), ), ), ), const SizedBox(height: 10), ], Row( children: [ Expanded( child: Text( answer.title, style: const TextStyle( fontWeight: FontWeight.w900, fontSize: 15, color: Color(0xFF2F9E94), ), ), ), ], ), ], ), ), ), ), ); } } class _QuestionReferenceImages extends StatelessWidget { const _QuestionReferenceImages({required this.paths}); final List paths; @override Widget build(BuildContext context) { if (paths.length == 1) { return ClipRRect( borderRadius: BorderRadius.circular(14), child: AspectRatio( aspectRatio: 16 / 9, child: Image.asset( paths.first, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => _placeholder(), ), ), ); } return SizedBox( height: 120, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: paths.length, separatorBuilder: (context, index) => const SizedBox(width: 10), itemBuilder: (context, i) { return AspectRatio( aspectRatio: 4 / 3, child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.asset( paths[i], fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => _placeholder(), ), ), ); }, ), ); } Widget _placeholder() { return Container( color: Colors.black.withValues(alpha: 0.06), child: const Center( child: Icon(Icons.image_not_supported_outlined, color: Colors.black38), ), ); } }