import 'package:flutter/material.dart'; import '../colors/app_colors.dart'; import 'package:flutter/services.dart'; import '../screens/video_screen.dart'; import '../strings/quiz_ui_strings.dart'; import '../widgets/entrance.dart'; import '../widgets/liquid_waves_background.dart'; import '../widgets/tap_bounce.dart'; typedef QuizNextBuilder = Route Function(BuildContext context, QuizScore nextScore); /// Pontuação do quiz, separada em dois contadores independentes em vez de /// uma única soma — cada pergunta soma 1 a exatamente um dos dois, nunca aos /// dois nem a nenhum. Ver [QuizQuestionScreen.isSignQuestion]. class QuizScore { const QuizScore({this.signs = 0, this.factors = 0}); final int signs; final int factors; QuizScore addSign(int amount) => QuizScore(signs: signs + amount, factors: factors); QuizScore addFactor(int amount) => QuizScore(signs: signs, factors: factors + amount); } enum QuizAnswerType { text, image, number, yesNo } class QuizAnswer { const QuizAnswer({ required this.title, required this.description, required this.weight, this.imagePath, this.imageBuilder, this.hideTitle = false, this.value, this.helpVideoId, }); final String title; final String description; final int weight; final String? imagePath; /// Quando definido, substitui [imagePath] na renderização — usado para /// mostrar um recorte/zoom de uma imagem partilhada (ex.: metade /// esquerda/direita de uma foto de comparação) sem precisar de gerar /// novos ficheiros de imagem. final WidgetBuilder? imageBuilder; /// Quando true, esconde o texto [title] por baixo da imagem — usado nas /// perguntas "escolha a imagem" do quiz, onde a própria imagem já é a /// resposta e uma legenda seria redundante. final bool hideTitle; final String? value; /// Quando definido (normalmente só na resposta "Não sei"), identifica um /// vídeo em [videoList] que ajuda a responder a esta pergunta — mostrado /// como um botão que expande quando esta resposta é selecionada. final int? helpVideoId; } class QuizQuestionScreen extends StatefulWidget { const QuizQuestionScreen({ super.key, required this.title, required this.question, required this.answers, required this.nextRoute, this.currentScore = const QuizScore(), this.isSignQuestion = false, this.onFinished, this.isFinal = false, this.showBackButton = false, this.answerType = QuizAnswerType.text, this.questionImagePaths = const [], this.suggestedVideoPath, this.suggestedYoutubeId, this.suggestedVideoTitle, this.fallbackColor, this.answerImageAspectRatio, this.correctBadgeLabel = QuizUiStrings.defaultCorrectBadgeLabel, }); final String title; final String question; final List answers; final QuizNextBuilder nextRoute; final QuizScore currentScore; /// Quando true, uma resposta "de risco" (weight 2) soma a /// [QuizScore.signs] em vez de [QuizScore.factors] — usado só nas 4 /// perguntas que representam sinais de má oclusão já instalados /// (queixo, boca/dentição, apinhamento, céu da boca); todas as outras /// contam como fatores de risco associados. final bool isSignQuestion; final VoidCallback? onFinished; final bool isFinal; final bool showBackButton; final QuizAnswerType answerType; final List questionImagePaths; final String? suggestedVideoPath; final String? suggestedYoutubeId; final String? suggestedVideoTitle; /// Substitui o cálculo automático (baseado no número de opções) do /// aspect ratio dos blocos de imagem em [QuizAnswerType.image] — usado /// quando as fotos já vêm pré-recortadas num formato específico (ex.: /// retrato, para a postura). final double? answerImageAspectRatio; /// Usado só quando [questionImagePaths] está vazio: em vez de o bloco de /// imagem colapsar, mostra-se um bloco colorido com o ícone da app. final Color? fallbackColor; /// Texto do selo mostrado sobre a imagem certa ao revelar a resposta — /// customizável por pergunta (ex.: "Posição saudável" em vez do genérico /// "Posição certa", quando faz mais sentido para o contexto da pergunta). final String correctBadgeLabel; @override State createState() => _QuizQuestionScreenState(); } class _QuizQuestionScreenState extends State { // Uma criança não tem, na prática, mais do que ~20 dentes numa arcada // (dentição decídua + permanente em transição já é um caso extremo). // Serve para barrar valores absurdos como "400". static const int _maxTeethCount = 20; int? _selected; TextEditingController? _numberController; int? _numberValue; bool _numberDontKnow = false; bool _navigating = false; /// Nas perguntas com imagem, ao avançar mostra-se por 2 segundos qual era /// a imagem certa/errada antes de navegar — fins educativos. bool _revealed = false; /// O título ainda chega como texto livre ("Quiz 3/25") em vez de números /// separados — extraímos daqui em vez de acrescentar mais dois parâmetros /// obrigatórios a cada uma das 25 telas de pergunta. static final RegExp _progressPattern = RegExp(r'(\d+)\s*/\s*(\d+)'); int get _questionIndex { final match = _progressPattern.firstMatch(widget.title); return int.tryParse(match?.group(1) ?? '') ?? 1; } int get _totalQuestions { final match = _progressPattern.firstMatch(widget.title); return int.tryParse(match?.group(2) ?? '') ?? 1; } @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 && !_navigating; if (widget.answerType == QuizAnswerType.number) { canProceed = !_navigating && (_numberDontKnow || (_numberValue != null && _numberValue! >= 0 && _numberValue! <= _maxTeethCount)); } else if (widget.answerType == QuizAnswerType.yesNo && _selected != null && widget.answers[_selected!].value == 'nao_sei') { // "Não sei" não é uma resposta válida para avançar — obriga a // criança/responsável a decidir Sim ou Não antes de continuar. canProceed = false; } final bool hasSuggestedVideo = (widget.suggestedVideoPath?.isNotEmpty ?? false) || (widget.suggestedYoutubeId?.isNotEmpty ?? false); return Scaffold( body: Stack( clipBehavior: Clip.none, children: [ Positioned.fill(child: Container(color: AppColors.background)), const LiquidWavesBackground(), SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 4), child: Row( children: [ if (widget.showBackButton) TapBounce( scale: 0.9, child: Material( color: Colors.white.withValues(alpha: 0.85), shape: const CircleBorder(), elevation: 4, shadowColor: Colors.black.withValues(alpha: 0.15), child: InkWell( customBorder: const CircleBorder(), onTap: () => Navigator.of(context).maybePop(), child: const Padding( padding: EdgeInsets.all(9), child: Icon( Icons.arrow_back_rounded, color: AppColors.teal, size: 20, ), ), ), ), ) else const SizedBox(width: 38), const SizedBox(width: 12), Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(999), child: LinearProgressIndicator( value: (_questionIndex / _totalQuestions).clamp( 0.0, 1.0, ), minHeight: 8, backgroundColor: AppColors.pink.withValues( alpha: 0.15, ), valueColor: const AlwaysStoppedAnimation( AppColors.pink, ), ), ), ), const SizedBox(width: 10), Text( '$_questionIndex/$_totalQuestions', style: TextStyle( color: Colors.black.withValues(alpha: 0.55), fontWeight: FontWeight.w800, fontSize: 13, ), ), ], ), ), Expanded( child: LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints( minHeight: constraints.maxHeight, ), child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 520), child: Padding( padding: const EdgeInsets.symmetric( vertical: 16, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ FadeSlideIn( child: Padding( padding: const EdgeInsets.fromLTRB( 20, 4, 20, 10, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 6), widget.questionImagePaths.isNotEmpty ? _QuestionReferenceImages( paths: widget .questionImagePaths, ) : _FallbackIconBlock( color: widget.fallbackColor ?? AppColors.teal, ), const SizedBox(height: 10), if (hasSuggestedVideo) ...[ TextButton.icon( onPressed: () => showVideoPlayerDialog( context, VideoData( id: 0, title: widget .suggestedVideoTitle ?? QuizUiStrings .videoFallbackTitle, description: '', videoPath: widget .suggestedVideoPath, youtubeId: widget .suggestedYoutubeId, ), ), icon: const Icon( Icons .play_circle_outline_rounded, color: AppColors.teal, ), label: Text( widget.suggestedVideoTitle ?? QuizUiStrings.watchOptionalVideo, style: const TextStyle( color: AppColors.teal, fontWeight: FontWeight.w800, ), ), ), const SizedBox(height: 4), ], Text( widget.question, textAlign: TextAlign.center, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w900, color: AppColors.pink, height: 1.2, ), ), const SizedBox(height: 8), Text( widget.answerType == QuizAnswerType.number ? QuizUiStrings.enterNumber : widget.answerType == QuizAnswerType.yesNo ? QuizUiStrings.chooseOneOption : QuizUiStrings.chooseOnlyOneOption, textAlign: TextAlign.center, style: TextStyle( color: Colors.black.withValues( alpha: 0.55, ), fontWeight: FontWeight.w700, ), ), ], ), ), ), SizedBox( height: widget.answerType == QuizAnswerType.image ? 6 : 18, ), Padding( padding: const EdgeInsets.symmetric( horizontal: 20, ), child: widget.answerType == QuizAnswerType.number ? _buildNumberInput() : Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ for ( int i = 0; i < widget.answers.length; i++ ) ...[ if (i > 0) SizedBox( height: widget.answerType == QuizAnswerType .image ? 5 : 12, ), FadeSlideIn( delay: Duration( milliseconds: 60 * i, ), child: widget.answerType == QuizAnswerType .yesNo && widget .answers[i] .imagePath == null ? _QuizAnswerPill( answer: widget .answers[i], selected: _selected == i, onTap: () => setState( () => _selected = i, ), ) : _QuizAnswerTile( answer: widget .answers[i], selected: _selected == i, // Perguntas com mais opções de // imagem precisam de blocos mais // compactos para caber sem rolar // — com 2 opções sobra espaço // para um enquadramento mais // vertical (ex.: postura), a não // ser que a pergunta imponha um // aspect ratio específico. imageAspectRatio: widget .answerImageAspectRatio ?? (widget.answers.length >= 3 ? 2.3 : 1.5), reveal: _revealed, correctLabel: widget .correctBadgeLabel, onTap: _revealed ? null : () => setState( () => _selected = i, ), ), ), ], ], ), ), SizedBox( height: widget.answerType == QuizAnswerType.image ? 8 : 24, ), Padding( padding: const EdgeInsets.fromLTRB( 20, 0, 20, 0, ), child: Column( children: [ TapBounce( child: SizedBox( width: size.width * 0.62, height: widget.answerType == QuizAnswerType.image ? 38 : 46, child: FilledButton( style: FilledButton.styleFrom( backgroundColor: AppColors.pink, 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: !canProceed ? null : () async { setState( () => _navigating = true, ); if (widget.answerType == QuizAnswerType .image) { setState( () => _revealed = true, ); await Future.delayed( const Duration( seconds: 2, ), ); if (!context .mounted) { return; } } QuizScore nextScore = widget.currentScore; if (widget.answerType != QuizAnswerType .number) { final picked = widget .answers[_selected!]; final increment = picked.weight == 2 ? 1 : 0; nextScore = widget.isSignQuestion ? widget .currentScore .addSign( increment, ) : widget .currentScore .addFactor( increment, ); } if (widget.isFinal) { final finishedRoute = widget.nextRoute( context, nextScore, ); Navigator.of( context, ).pushReplacement( finishedRoute, ); return; } await Navigator.of( context, ).push( widget.nextRoute( context, nextScore, ), ); if (mounted) { setState( () => _navigating = false, ); } }, child: Text( widget.isFinal ? QuizUiStrings.finish : QuizUiStrings.advance, ), ), ), ), SizedBox( height: widget.answerType == QuizAnswerType.image ? 0 : 6, ), TextButton( style: TextButton.styleFrom( foregroundColor: AppColors.teal, textStyle: const TextStyle( fontWeight: FontWeight.w800, ), padding: widget.answerType == QuizAnswerType.image ? const EdgeInsets.symmetric( vertical: 4, horizontal: 12, ) : null, ), onPressed: () => Navigator.of(context).popUntil( (route) => route.isFirst, ), child: const Text( QuizUiStrings.backToHomepage, ), ), ], ), ), ], ), ), ), ), ), ); }, ), ), ], ), ), ], ), ); } Widget _buildNumberInput() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Opacity( opacity: _numberDontKnow ? 0.4 : 1, child: IgnorePointer( ignoring: _numberDontKnow, child: 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, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(2), ], style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w900, color: AppColors.teal, ), 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); }); }, ), ), ), ), if (_numberValue != null && _numberValue! > _maxTeethCount && !_numberDontKnow) ...[ const SizedBox(height: 10), Text( QuizUiStrings.teethCountTooHigh(_maxTeethCount), textAlign: TextAlign.center, style: const TextStyle( color: AppColors.pink, fontWeight: FontWeight.w700, fontSize: 12, ), ), ], const SizedBox(height: 14), TapBounce( scale: 0.97, child: InkWell( borderRadius: BorderRadius.circular(999), onTap: () { setState(() { _numberDontKnow = !_numberDontKnow; if (_numberDontKnow) { _numberController?.clear(); _numberValue = null; } }); }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 8, ), decoration: BoxDecoration( color: _numberDontKnow ? AppColors.teal : Colors.white.withValues(alpha: 0.70), borderRadius: BorderRadius.circular(999), border: Border.all( color: _numberDontKnow ? AppColors.teal : Colors.black.withValues(alpha: 0.12), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.help_outline_rounded, size: 16, color: _numberDontKnow ? Colors.white : Colors.black.withValues(alpha: 0.55), ), const SizedBox(width: 6), Text( QuizUiStrings.dontKnow, style: TextStyle( fontWeight: FontWeight.w800, fontSize: 13, color: _numberDontKnow ? Colors.white : Colors.black.withValues(alpha: 0.55), ), ), ], ), ), ), ), ], ), ); } } class _QuizAnswerTile extends StatelessWidget { const _QuizAnswerTile({ required this.answer, required this.selected, required this.onTap, this.imageAspectRatio = 4 / 3, this.reveal = false, this.correctLabel = QuizUiStrings.defaultCorrectBadgeLabel, }); final QuizAnswer answer; final bool selected; final VoidCallback? onTap; final double imageAspectRatio; /// Quando true, mostra um selo indicando se esta era a resposta certa ou /// errada — ver [_QuizQuestionScreenState._revealed]. final bool reveal; /// Texto do selo quando [isCorrect] — customizável por pergunta (ex.: /// "Posição saudável" em vez do genérico "Posição certa"). final String correctLabel; @override Widget build(BuildContext context) { final bool isCorrect = answer.weight == 1; final borderColor = reveal ? (isCorrect ? AppColors.teal : AppColors.pink) : selected ? AppColors.teal : Colors.black.withValues(alpha: 0.12); final bg = selected ? Colors.white.withValues(alpha: 0.88) : Colors.white.withValues(alpha: 0.70); return TapBounce( scale: 0.97, child: Stack( clipBehavior: Clip.none, fit: StackFit.passthrough, children: [ AnimatedContainer( duration: const Duration(milliseconds: 220), curve: Curves.easeOutCubic, decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(16), border: Border.all( color: borderColor, width: (reveal || selected) ? 1.6 : 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: EdgeInsets.symmetric( horizontal: 16, vertical: answer.hideTitle ? 8 : 14, ), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ if (answer.imagePath != null || answer.imageBuilder != null) ...[ ClipRRect( borderRadius: BorderRadius.circular(12), child: AspectRatio( aspectRatio: imageAspectRatio, child: answer.imageBuilder != null ? answer.imageBuilder!(context) : 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, ), ), ), ), ), ), if (!answer.hideTitle) const SizedBox(height: 10), ], if (!answer.hideTitle) Text( answer.title, textAlign: TextAlign.center, style: const TextStyle( fontWeight: FontWeight.w900, fontSize: 15, color: AppColors.teal, ), ), ], ), ), ), ), ), Positioned( top: 8, right: 8, child: IgnorePointer( child: AnimatedScale( scale: selected ? 1.0 : 0.0, duration: const Duration(milliseconds: 220), curve: Curves.easeOutBack, child: Container( width: 22, height: 22, decoration: const BoxDecoration( color: AppColors.teal, shape: BoxShape.circle, ), child: const Icon( Icons.check_rounded, size: 15, color: Colors.white, ), ), ), ), ), if (reveal) Positioned( left: 8, right: 8, bottom: 8, child: IgnorePointer( child: Center( child: Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), decoration: BoxDecoration( color: isCorrect ? AppColors.teal : AppColors.pink, borderRadius: BorderRadius.circular(999), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.18), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( isCorrect ? Icons.check_rounded : Icons.close_rounded, size: 14, color: Colors.white, ), const SizedBox(width: 4), Text( isCorrect ? correctLabel : QuizUiStrings.incorrectBadgeLabel, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, fontSize: 11.5, ), ), ], ), ), ), ), ), ], ), ); } } 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: Stack( fit: StackFit.expand, children: [ Image.asset( paths.first, fit: BoxFit.cover, cacheWidth: 800, errorBuilder: (context, error, stackTrace) => _placeholder(), ), Positioned( left: 8, bottom: 8, child: Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.55), borderRadius: BorderRadius.circular(999), ), child: const Text( QuizUiStrings.referenceImage, style: TextStyle( color: Colors.white, fontWeight: FontWeight.w700, fontSize: 10.5, ), ), ), ), ], ), ), ); } 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], cacheWidth: 300, 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), ), ); } } /// Mostrado quando a pergunta não tem imagem de referência — em vez de o /// bloco colapsar (como acontecia antes), mostra um pequeno ícone colorido /// relevante ao tema, sem ocupar a largura toda (esse destaque é reservado /// para as perguntas que realmente têm imagem). class _FallbackIconBlock extends StatelessWidget { const _FallbackIconBlock({required this.color}); final Color color; @override Widget build(BuildContext context) { return Center( child: Container( width: 72, height: 72, padding: const EdgeInsets.all(11), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(16), ), child: Image.asset('assets/quiz-icon.png', fit: BoxFit.contain), ), ); } } /// Resposta Sim/Não em formato de pílula horizontal: círculo colorido + /// texto + indicador circular à direita que preenche quando selecionado. class _QuizAnswerPill extends StatelessWidget { const _QuizAnswerPill({ required this.answer, required this.selected, required this.onTap, }); final QuizAnswer answer; final bool selected; final VoidCallback onTap; bool get _isYes => (answer.value ?? '').trim().toLowerCase() == 'sim'; bool get _isNo => (answer.value ?? '').trim().toLowerCase() == 'nao'; VideoData? get _helpVideo { final id = answer.helpVideoId; if (id == null) return null; for (final v in videoList) { if (v.id == id) return v; } return null; } @override Widget build(BuildContext context) { final accent = _isYes ? AppColors.teal : _isNo ? AppColors.pink : Colors.black.withValues(alpha: 0.35); final borderColor = selected ? AppColors.teal : Colors.black.withValues(alpha: 0.10); final helpVideo = _helpVideo; final showHelp = selected && helpVideo != null; return TapBounce( scale: 0.97, child: AnimatedContainer( duration: const Duration(milliseconds: 220), curve: Curves.easeOutCubic, decoration: BoxDecoration( color: selected ? Colors.white.withValues(alpha: 0.92) : Colors.white.withValues(alpha: 0.70), borderRadius: BorderRadius.circular(22), border: Border.all(color: borderColor, width: selected ? 1.4 : 1.0), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.06), blurRadius: 14, offset: const Offset(0, 8), ), ], ), child: Material( color: Colors.transparent, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ InkWell( borderRadius: BorderRadius.circular(22), onTap: onTap, splashFactory: InkSparkle.splashFactory, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 10, ), child: Row( children: [ Container( width: 28, height: 28, decoration: BoxDecoration( color: accent, shape: BoxShape.circle, ), child: Icon( _isYes ? Icons.check_rounded : _isNo ? Icons.close_rounded : Icons.help_outline_rounded, color: Colors.white, size: 17, ), ), const SizedBox(width: 12), Expanded( child: Text( answer.title, style: const TextStyle( fontWeight: FontWeight.w800, fontSize: 15, color: AppColors.teal, ), ), ), AnimatedContainer( duration: const Duration(milliseconds: 220), width: 22, height: 22, decoration: BoxDecoration( shape: BoxShape.circle, color: selected ? AppColors.teal : Colors.transparent, border: Border.all( color: selected ? AppColors.teal : Colors.black.withValues(alpha: 0.25), width: 1.6, ), ), child: selected ? const Icon( Icons.check_rounded, size: 14, color: Colors.white, ) : null, ), ], ), ), ), AnimatedSize( duration: const Duration(milliseconds: 220), curve: Curves.easeOutCubic, alignment: Alignment.topCenter, child: !showHelp ? const SizedBox.shrink() : Padding( padding: const EdgeInsets.fromLTRB(14, 0, 14, 12), child: _HelpVideoButton(video: helpVideo), ), ), ], ), ), ), ); } } /// Botão que aparece quando a resposta "Não sei" é selecionada, sugerindo o /// episódio que pode ajudar a esclarecer a dúvida antes de responder. class _HelpVideoButton extends StatelessWidget { const _HelpVideoButton({required this.video}); final VideoData video; @override Widget build(BuildContext context) { return TapBounce( scale: 0.97, child: Material( color: AppColors.teal.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(14), child: InkWell( borderRadius: BorderRadius.circular(14), onTap: () => showVideoPlayerDialog(context, video), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Row( children: [ const Icon( Icons.play_circle_fill_rounded, color: AppColors.teal, size: 22, ), const SizedBox(width: 10), Expanded( child: Text( QuizUiStrings.notSureSeeVideo(video.title), style: const TextStyle( fontWeight: FontWeight.w700, fontSize: 12.5, color: AppColors.teal, ), ), ), const Icon( Icons.chevron_right_rounded, color: AppColors.teal, size: 20, ), ], ), ), ), ), ); } }