import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import 'package:youtube_player_flutter/youtube_player_flutter.dart'; import '../widgets/entrance.dart'; import '../widgets/tap_bounce.dart'; const Color _pink = Color(0xFFFF55A7); const Color _teal = Color(0xFF2F9E94); /// Player do YouTube incorporado (sem AppBar/tela cheia própria) — usado /// tanto no ecrã intersticial [QuizVideoGuideScreen] como embutido /// diretamente no ecrã de resultado do quiz. class QuizGuideVideoCard extends StatefulWidget { const QuizGuideVideoCard({super.key, required this.youtubeId, this.onEnded}); final String youtubeId; /// Chamado uma única vez quando o vídeo chega ao fim. final VoidCallback? onEnded; @override State createState() => _QuizGuideVideoCardState(); } class _QuizGuideVideoCardState extends State { late final YoutubePlayerController _controller = YoutubePlayerController( initialVideoId: widget.youtubeId, flags: const YoutubePlayerFlags(autoPlay: true, mute: false), ); bool _endedNotified = false; @override void initState() { super.initState(); _controller.addListener(_handleValueChange); } void _handleValueChange() { if (_endedNotified) return; if (_controller.value.playerState == PlayerState.ended) { _endedNotified = true; widget.onEnded?.call(); } } @override void dispose() { _controller.removeListener(_handleValueChange); _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(20), child: AspectRatio( aspectRatio: 16 / 9, child: YoutubePlayer( controller: _controller, showVideoProgressIndicator: true, progressColors: const ProgressBarColors( playedColor: _pink, handleColor: _pink, ), ), ), ); } } /// Ecrã intersticial mostrado a meio do quiz, convidando a ver um vídeo /// educativo antes de continuar. Tanto "Avançar" como "Pular" seguem para /// [onAdvance] — o vídeo é um convite, não um bloqueio. class QuizVideoGuideScreen extends StatefulWidget { const QuizVideoGuideScreen({ super.key, required this.youtubeId, required this.onAdvance, this.caption = 'Assista para compreender melhor as próximas perguntas do questionário.', this.extraHeading, this.extraSubtitle, }); final String youtubeId; final void Function(BuildContext context) onAdvance; final String caption; /// Quando definidos, mostram um bloco de destaque extra por baixo da /// legenda do vídeo (ex.: um aviso a preparar o utilizador para as /// próximas perguntas de observação). final String? extraHeading; final String? extraSubtitle; @override State createState() => _QuizVideoGuideScreenState(); } class _QuizVideoGuideScreenState extends State { // Evita que um duplo-toque (Pular + Avançar, ou o mesmo botão duas vezes // seguidas antes da navegação trocar de ecrã) dispare onAdvance duas // vezes sobre uma rota que já foi substituída. bool _advanced = false; // "Avançar" só fica disponível depois de o vídeo chegar ao fim; "Pular" // continua livre a qualquer momento. bool _videoWatched = false; void _advance() { if (_advanced) return; _advanced = true; widget.onAdvance(context); } @override Widget build(BuildContext context) { final size = MediaQuery.sizeOf(context); return Scaffold( body: Stack( clipBehavior: Clip.none, children: [ Positioned.fill( child: Container(color: const Color(0xFFFAFAF7)), ), 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: Padding( padding: const EdgeInsets.fromLTRB(24, 4, 24, 24), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Align( alignment: Alignment.topRight, child: TextButton( onPressed: _advance, child: const Text( 'Pular', style: TextStyle( color: Colors.black45, fontWeight: FontWeight.w700, ), ), ), ), const SizedBox(height: 4), const FadeSlideIn( child: Text( 'Antes de continuar, assista ao vídeo educativo...', style: TextStyle( fontSize: 21, fontWeight: FontWeight.w900, color: Colors.black87, height: 1.25, ), ), ), const SizedBox(height: 24), Expanded( child: Center( child: FadeSlideIn( delay: const Duration(milliseconds: 80), child: QuizGuideVideoCard( youtubeId: widget.youtubeId, onEnded: () => setState(() => _videoWatched = true), ), ), ), ), const SizedBox(height: 16), FadeSlideIn( delay: const Duration(milliseconds: 120), child: Text( widget.caption, textAlign: TextAlign.center, style: TextStyle( fontSize: 13.5, fontWeight: FontWeight.w600, color: Colors.black.withValues(alpha: 0.6), height: 1.4, ), ), ), if (widget.extraHeading != null) ...[ const SizedBox(height: 16), FadeSlideIn( delay: const Duration(milliseconds: 140), child: Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: _pink.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(16), ), child: Column( children: [ Text( widget.extraHeading!, textAlign: TextAlign.center, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w900, color: _pink, height: 1.25, ), ), if (widget.extraSubtitle != null) ...[ const SizedBox(height: 6), Text( widget.extraSubtitle!, textAlign: TextAlign.center, style: TextStyle( fontSize: 12.5, fontWeight: FontWeight.w600, color: Colors.black.withValues(alpha: 0.6), height: 1.4, ), ), ], ], ), ), ), ], const SizedBox(height: 20), FadeSlideIn( delay: const Duration(milliseconds: 160), child: TapBounce( child: SizedBox( width: double.infinity, height: 52, child: FilledButton( style: FilledButton.styleFrom( backgroundColor: _teal, disabledBackgroundColor: _teal.withValues( alpha: 0.35, ), foregroundColor: Colors.white, shape: const StadiumBorder(), textStyle: const TextStyle( fontWeight: FontWeight.w800, fontSize: 16, ), ), onPressed: _videoWatched ? _advance : null, child: const Text('Avançar'), ), ), ), ), ], ), ), ), ], ), ); } }