diff --git a/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ChatGPT Image 15_05_2026, 18_43_47.png b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ChatGPT Image 15_05_2026, 18_43_47.png new file mode 100644 index 0000000..748f0cd Binary files /dev/null and b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ChatGPT Image 15_05_2026, 18_43_47.png differ diff --git a/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ChatGPT Image 15_05_2026, 18_52_57.png b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ChatGPT Image 15_05_2026, 18_52_57.png new file mode 100644 index 0000000..a496a17 Binary files /dev/null and b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ChatGPT Image 15_05_2026, 18_52_57.png differ diff --git a/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/Gemini_Generated_Image_4igv2b4igv2b4igv.png b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/Gemini_Generated_Image_4igv2b4igv2b4igv.png new file mode 100644 index 0000000..0785c2e Binary files /dev/null and b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/Gemini_Generated_Image_4igv2b4igv2b4igv.png differ diff --git a/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/Gemini_Generated_Image_c12xbmc12xbmc12x.png b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/Gemini_Generated_Image_c12xbmc12xbmc12x.png new file mode 100644 index 0000000..f5904b6 Binary files /dev/null and b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/Gemini_Generated_Image_c12xbmc12xbmc12x.png differ diff --git a/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/Gemini_Generated_Image_h6nzpwh6nzpwh6nz.png b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/Gemini_Generated_Image_h6nzpwh6nzpwh6nz.png new file mode 100644 index 0000000..df0871e Binary files /dev/null and b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/Gemini_Generated_Image_h6nzpwh6nzpwh6nz.png differ diff --git a/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/IMG_2421.jpg b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/IMG_2421.jpg new file mode 100644 index 0000000..4f5e2df Binary files /dev/null and b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/IMG_2421.jpg differ diff --git a/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ScreenRecording_04-01-2026 22-28-03_1.MP4 b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ScreenRecording_04-01-2026 22-28-03_1.MP4 new file mode 100644 index 0000000..2061263 Binary files /dev/null and b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ScreenRecording_04-01-2026 22-28-03_1.MP4 differ diff --git a/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ScreenRecording_12-25-2025 23-30-10_1.mov b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ScreenRecording_12-25-2025 23-30-10_1.mov new file mode 100644 index 0000000..63c8c04 Binary files /dev/null and b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Outros conteúdos utilizados/ScreenRecording_12-25-2025 23-30-10_1.mov differ diff --git a/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Vídeos de orientação - dentro do questionário/1º Episódio GUIA OFICIAL.mp4 b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Vídeos de orientação - dentro do questionário/1º Episódio GUIA OFICIAL.mp4 new file mode 100644 index 0000000..1dea8bd Binary files /dev/null and b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Vídeos de orientação - dentro do questionário/1º Episódio GUIA OFICIAL.mp4 differ diff --git a/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Vídeos de orientação - dentro do questionário/2º Episódio GUIA OFICIAL.mp4 b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Vídeos de orientação - dentro do questionário/2º Episódio GUIA OFICIAL.mp4 new file mode 100644 index 0000000..d8c1ddc Binary files /dev/null and b/assets/Vídeos_APP_Check-Teeth-Kids/Videos/Vídeos de orientação - dentro do questionário/2º Episódio GUIA OFICIAL.mp4 differ diff --git a/assets/mockup_images/0.png b/assets/mockup_images/0.png new file mode 100644 index 0000000..3e4ad99 Binary files /dev/null and b/assets/mockup_images/0.png differ diff --git a/assets/mockup_images/1.jpeg b/assets/mockup_images/1.jpeg new file mode 100644 index 0000000..78344a8 Binary files /dev/null and b/assets/mockup_images/1.jpeg differ diff --git a/assets/mockup_images/10.png b/assets/mockup_images/10.png new file mode 100644 index 0000000..9784040 Binary files /dev/null and b/assets/mockup_images/10.png differ diff --git a/assets/mockup_images/11.png b/assets/mockup_images/11.png new file mode 100644 index 0000000..ec34157 Binary files /dev/null and b/assets/mockup_images/11.png differ diff --git a/assets/mockup_images/12.png b/assets/mockup_images/12.png new file mode 100644 index 0000000..73302ae Binary files /dev/null and b/assets/mockup_images/12.png differ diff --git a/assets/mockup_images/13.png b/assets/mockup_images/13.png new file mode 100644 index 0000000..49970f5 Binary files /dev/null and b/assets/mockup_images/13.png differ diff --git a/assets/mockup_images/14.jpeg b/assets/mockup_images/14.jpeg new file mode 100644 index 0000000..93f4de5 Binary files /dev/null and b/assets/mockup_images/14.jpeg differ diff --git a/assets/mockup_images/15.png b/assets/mockup_images/15.png new file mode 100644 index 0000000..aceefca Binary files /dev/null and b/assets/mockup_images/15.png differ diff --git a/assets/mockup_images/16.png b/assets/mockup_images/16.png new file mode 100644 index 0000000..48960e5 Binary files /dev/null and b/assets/mockup_images/16.png differ diff --git a/assets/mockup_images/17.png b/assets/mockup_images/17.png new file mode 100644 index 0000000..28fe4bd Binary files /dev/null and b/assets/mockup_images/17.png differ diff --git a/assets/mockup_images/18.jpeg b/assets/mockup_images/18.jpeg new file mode 100644 index 0000000..7a848f9 Binary files /dev/null and b/assets/mockup_images/18.jpeg differ diff --git a/assets/mockup_images/19.jpeg b/assets/mockup_images/19.jpeg new file mode 100644 index 0000000..483753f Binary files /dev/null and b/assets/mockup_images/19.jpeg differ diff --git a/assets/mockup_images/2.jpeg b/assets/mockup_images/2.jpeg new file mode 100644 index 0000000..9911cfa Binary files /dev/null and b/assets/mockup_images/2.jpeg differ diff --git a/assets/mockup_images/20.png b/assets/mockup_images/20.png new file mode 100644 index 0000000..74a4275 Binary files /dev/null and b/assets/mockup_images/20.png differ diff --git a/assets/mockup_images/21.jpeg b/assets/mockup_images/21.jpeg new file mode 100644 index 0000000..000f0d5 Binary files /dev/null and b/assets/mockup_images/21.jpeg differ diff --git a/assets/mockup_images/22.png b/assets/mockup_images/22.png new file mode 100644 index 0000000..367f11e Binary files /dev/null and b/assets/mockup_images/22.png differ diff --git a/assets/mockup_images/23.jpeg b/assets/mockup_images/23.jpeg new file mode 100644 index 0000000..3e0a17a Binary files /dev/null and b/assets/mockup_images/23.jpeg differ diff --git a/assets/mockup_images/24.png b/assets/mockup_images/24.png new file mode 100644 index 0000000..d51d6a8 Binary files /dev/null and b/assets/mockup_images/24.png differ diff --git a/assets/mockup_images/25.jpg b/assets/mockup_images/25.jpg new file mode 100644 index 0000000..d5e4806 Binary files /dev/null and b/assets/mockup_images/25.jpg differ diff --git a/assets/mockup_images/26.png b/assets/mockup_images/26.png new file mode 100644 index 0000000..7791789 Binary files /dev/null and b/assets/mockup_images/26.png differ diff --git a/assets/mockup_images/27.png b/assets/mockup_images/27.png new file mode 100644 index 0000000..c5657d8 Binary files /dev/null and b/assets/mockup_images/27.png differ diff --git a/assets/mockup_images/3.jpeg b/assets/mockup_images/3.jpeg new file mode 100644 index 0000000..ace0adc Binary files /dev/null and b/assets/mockup_images/3.jpeg differ diff --git a/assets/mockup_images/4.jpeg b/assets/mockup_images/4.jpeg new file mode 100644 index 0000000..f2c5161 Binary files /dev/null and b/assets/mockup_images/4.jpeg differ diff --git a/assets/mockup_images/5.png b/assets/mockup_images/5.png new file mode 100644 index 0000000..16863cb Binary files /dev/null and b/assets/mockup_images/5.png differ diff --git a/assets/mockup_images/6.jpeg b/assets/mockup_images/6.jpeg new file mode 100644 index 0000000..1dd8d2c Binary files /dev/null and b/assets/mockup_images/6.jpeg differ diff --git a/assets/mockup_images/7.png b/assets/mockup_images/7.png new file mode 100644 index 0000000..16863cb Binary files /dev/null and b/assets/mockup_images/7.png differ diff --git a/assets/mockup_images/8.jpeg b/assets/mockup_images/8.jpeg new file mode 100644 index 0000000..ee57d44 Binary files /dev/null and b/assets/mockup_images/8.jpeg differ diff --git a/assets/mockup_images/9.png b/assets/mockup_images/9.png new file mode 100644 index 0000000..7e27357 Binary files /dev/null and b/assets/mockup_images/9.png differ diff --git a/assets/videos/episodio_01.mp4 b/assets/videos/episodio_01.mp4 new file mode 100644 index 0000000..8f4fa64 Binary files /dev/null and b/assets/videos/episodio_01.mp4 differ diff --git a/assets/videos/episodio_02.mp4 b/assets/videos/episodio_02.mp4 new file mode 100644 index 0000000..56ef96e Binary files /dev/null and b/assets/videos/episodio_02.mp4 differ diff --git a/assets/videos/episodio_03.mp4 b/assets/videos/episodio_03.mp4 new file mode 100644 index 0000000..dfefd8b Binary files /dev/null and b/assets/videos/episodio_03.mp4 differ diff --git a/assets/videos/episodio_04.mp4 b/assets/videos/episodio_04.mp4 new file mode 100644 index 0000000..b411840 Binary files /dev/null and b/assets/videos/episodio_04.mp4 differ diff --git a/assets/videos/episodio_05.mp4 b/assets/videos/episodio_05.mp4 new file mode 100644 index 0000000..8490943 Binary files /dev/null and b/assets/videos/episodio_05.mp4 differ diff --git a/assets/videos/episodio_06.mp4 b/assets/videos/episodio_06.mp4 new file mode 100644 index 0000000..cd95ece Binary files /dev/null and b/assets/videos/episodio_06.mp4 differ diff --git a/assets/videos/episodio_07.mp4 b/assets/videos/episodio_07.mp4 new file mode 100644 index 0000000..76bc8b4 Binary files /dev/null and b/assets/videos/episodio_07.mp4 differ diff --git a/assets/videos/episodio_08.mp4 b/assets/videos/episodio_08.mp4 new file mode 100644 index 0000000..a426bf4 Binary files /dev/null and b/assets/videos/episodio_08.mp4 differ diff --git a/assets/videos/episodio_09.mp4 b/assets/videos/episodio_09.mp4 new file mode 100644 index 0000000..3c3943f Binary files /dev/null and b/assets/videos/episodio_09.mp4 differ diff --git a/assets/videos/episodio_10.mp4 b/assets/videos/episodio_10.mp4 new file mode 100644 index 0000000..b41b73e Binary files /dev/null and b/assets/videos/episodio_10.mp4 differ diff --git a/assets/videos/episodio_11.mp4 b/assets/videos/episodio_11.mp4 new file mode 100644 index 0000000..6cfe5b4 Binary files /dev/null and b/assets/videos/episodio_11.mp4 differ diff --git a/assets/videos/episodio_12.mp4 b/assets/videos/episodio_12.mp4 new file mode 100644 index 0000000..2f83074 Binary files /dev/null and b/assets/videos/episodio_12.mp4 differ diff --git a/assets/videos/episodio_13.mp4 b/assets/videos/episodio_13.mp4 new file mode 100644 index 0000000..64b52f1 Binary files /dev/null and b/assets/videos/episodio_13.mp4 differ diff --git a/lib/logged_home.dart b/lib/logged_home.dart index 5971b62..e105a99 100644 --- a/lib/logged_home.dart +++ b/lib/logged_home.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:lottie/lottie.dart'; import 'dart:async'; -import 'package:youtube_player_flutter/youtube_player_flutter.dart'; import 'dart:math' as math; import 'dart:io'; import 'package:shared_preferences/shared_preferences.dart'; @@ -413,8 +412,8 @@ class _RiskArcGauge extends StatelessWidget { builder: (context, value, _) { final shown = (value * 100).round(); return SizedBox( - width: 178, - height: 94, + width: 120, + height: 60, child: Stack( alignment: Alignment.center, children: [ @@ -424,7 +423,7 @@ class _RiskArcGauge extends StatelessWidget { ), ), Positioned( - bottom: 8, + bottom: 4, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -432,17 +431,17 @@ class _RiskArcGauge extends StatelessWidget { '$shown%', style: const TextStyle( color: Colors.white, - fontSize: 26, + fontSize: 20, fontWeight: FontWeight.w900, height: 1, ), ), - const SizedBox(height: 4), + const SizedBox(height: 2), Text( - 'Risco de Má Oclusão', + '', style: TextStyle( color: Colors.white.withValues(alpha: 0.92), - fontSize: 9, + fontSize: 8, fontWeight: FontWeight.w900, ), ), @@ -464,10 +463,10 @@ class _RiskArcGaugePainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - final rect = Rect.fromLTWH(16, 12, size.width - 32, size.height * 1.55); + final rect = Rect.fromLTWH(10, 8, size.width - 20, size.height * 1.7); const startAngle = math.pi; const sweepAngle = math.pi; - final strokeWidth = size.width * 0.14; + final strokeWidth = size.width * 0.12; final backgroundPaint = Paint() ..color = Colors.white.withValues(alpha: 0.72) @@ -652,7 +651,7 @@ class _VideoLibraryCard extends StatelessWidget { @override Widget build(BuildContext context) { - final item = VideoScreen.library.isEmpty ? null : VideoScreen.library.first; + const Object? item = null; return Material( color: Colors.white, @@ -685,7 +684,9 @@ class _VideoLibraryCard extends StatelessWidget { child: Stack( fit: StackFit.expand, children: [ - _VideoThumbnail(url: item.url), + Container( + color: const Color(0xFF2F9E94).withValues(alpha: 0.14), + ), Container( decoration: BoxDecoration( gradient: LinearGradient( @@ -767,27 +768,6 @@ class _VideoLibraryCard extends StatelessWidget { } } -class _VideoThumbnail extends StatelessWidget { - const _VideoThumbnail({required this.url}); - - final String url; - - @override - Widget build(BuildContext context) { - final id = YoutubePlayer.convertUrlToId(url); - final thumb = id == null ? null : 'https://img.youtube.com/vi/$id/0.jpg'; - if (thumb == null) { - return Container(color: Colors.white.withValues(alpha: 0.12)); - } - return Image.network( - thumb, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) => - Container(color: Colors.white.withValues(alpha: 0.12)), - ); - } -} - class _PerfilTab extends StatefulWidget { const _PerfilTab({ required this.selectedChildIndex, diff --git a/lib/quiz/quiz_question_screen.dart b/lib/quiz/quiz_question_screen.dart index 64e1a1a..c41eb6a 100644 --- a/lib/quiz/quiz_question_screen.dart +++ b/lib/quiz/quiz_question_screen.dart @@ -36,6 +36,7 @@ class QuizQuestionScreen extends StatefulWidget { this.isFinal = false, this.showBackButton = false, this.answerType = QuizAnswerType.text, + this.questionImagePaths = const [], }); final String title; @@ -47,6 +48,7 @@ class QuizQuestionScreen extends StatefulWidget { final bool isFinal; final bool showBackButton; final QuizAnswerType answerType; + final List questionImagePaths; @override State createState() => _QuizQuestionScreenState(); @@ -136,6 +138,13 @@ class _QuizQuestionScreenState extends State { ), ), 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, @@ -312,6 +321,28 @@ class _QuizQuestionScreenState extends State { ), ), ], + 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'), + ), + ), ], ), ), @@ -425,6 +456,28 @@ class _QuizAnswerTile extends StatelessWidget { 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( @@ -437,38 +490,8 @@ class _QuizAnswerTile extends StatelessWidget { ), ), ), - AnimatedRotation( - turns: selected ? 0.5 : 0.0, - duration: const Duration(milliseconds: 220), - curve: Curves.easeOutCubic, - child: Icon( - Icons.expand_more_rounded, - color: Colors.black.withValues(alpha: 0.55), - ), - ), ], ), - AnimatedCrossFade( - firstChild: const SizedBox.shrink(), - secondChild: Padding( - padding: const EdgeInsets.only(top: 10), - child: Text( - answer.description, - style: TextStyle( - color: Colors.black.withValues(alpha: 0.72), - fontWeight: FontWeight.w600, - height: 1.25, - ), - ), - ), - crossFadeState: selected - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, - duration: const Duration(milliseconds: 220), - firstCurve: Curves.easeIn, - secondCurve: Curves.easeOut, - sizeCurve: Curves.easeOutCubic, - ), ], ), ), @@ -477,3 +500,56 @@ class _QuizAnswerTile extends StatelessWidget { ); } } + +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), + ), + ); + } +} diff --git a/lib/quiz/quiz_random.dart b/lib/quiz/quiz_random.dart index d3c0d07..787636a 100644 --- a/lib/quiz/quiz_random.dart +++ b/lib/quiz/quiz_random.dart @@ -25,32 +25,28 @@ class _QuizRandomScreenState extends State { answerType: QuizAnswerType.image, answers: const [ QuizAnswer( + //1.jpeg title: 'Opção A', description: 'Selecione se a imagem se assemelha à face do seu filho/a', weight: 2, - imagePath: 'assets/images/face_a.png', + imagePath: 'assets/mockup_images/1.jpeg', ), QuizAnswer( + //2.jpeg title: 'Opção B', description: 'Selecione se a imagem se assemelha à face do seu filho/a', weight: 2, - imagePath: 'assets/images/face_b.png', + imagePath: 'assets/mockup_images/2.jpeg', ), QuizAnswer( + //3.jpeg title: 'Opção C', description: 'Selecione se a imagem se assemelha à face do seu filho/a', weight: 2, - imagePath: 'assets/images/face_c.png', - ), - QuizAnswer( - title: 'Opção D', - description: - 'Selecione se a imagem se assemelha à face do seu filho/a', - weight: 2, - imagePath: 'assets/images/face_d.png', + imagePath: 'assets/mockup_images/3.jpeg', ), ], ), @@ -58,36 +54,24 @@ class _QuizRandomScreenState extends State { id: 2, title: 'Quiz 2/26', question: - 'Qual das seguintes imagens se assemelha à boca do seu filho/a?', + 'Qual das seguintes imagens se assemelha à posição boca do seu filho/a habitualmente?', answerType: QuizAnswerType.image, answers: const [ QuizAnswer( + //4.jpeg title: 'Opção A', description: 'Selecione se a imagem se assemelha à boca do seu filho/a', weight: 2, - imagePath: 'assets/images/mouth_a.png', + imagePath: 'assets/mockup_images/4.jpeg', ), QuizAnswer( + //5.png title: 'Opção B', description: 'Selecione se a imagem se assemelha à boca do seu filho/a', weight: 2, - imagePath: 'assets/images/mouth_b.png', - ), - QuizAnswer( - title: 'Opção C', - description: - 'Selecione se a imagem se assemelha à boca do seu filho/a', - weight: 2, - imagePath: 'assets/images/mouth_c.png', - ), - QuizAnswer( - title: 'Opção D', - description: - 'Selecione se a imagem se assemelha à boca do seu filho/a', - weight: 2, - imagePath: 'assets/images/mouth_d.png', + imagePath: 'assets/mockup_images/5.png', ), ], ), @@ -99,32 +83,20 @@ class _QuizRandomScreenState extends State { answerType: QuizAnswerType.image, answers: const [ QuizAnswer( + //8.jpeg title: 'Opção A', description: 'Selecione se a imagem se assemelha às olheiras do seu filho/a', weight: 2, - imagePath: 'assets/images/dark_circles_a.png', + imagePath: 'assets/mockup_images/8.jpeg', ), QuizAnswer( + //9.png title: 'Opção B', description: 'Selecione se a imagem se assemelha às olheiras do seu filho/a', weight: 2, - imagePath: 'assets/images/dark_circles_b.png', - ), - QuizAnswer( - title: 'Opção C', - description: - 'Selecione se a imagem se assemelha às olheiras do seu filho/a', - weight: 2, - imagePath: 'assets/images/dark_circles_c.png', - ), - QuizAnswer( - title: 'Opção D', - description: - 'Selecione se a imagem se assemelha às olheiras do seu filho/a', - weight: 2, - imagePath: 'assets/images/dark_circles_d.png', + imagePath: 'assets/mockup_images/9.png', ), ], ), @@ -136,48 +108,55 @@ class _QuizRandomScreenState extends State { answerType: QuizAnswerType.image, answers: const [ QuizAnswer( + //6.jpeg title: 'Opção A', description: 'Selecione se a imagem se assemelha ao queixo do seu filho/a', weight: 2, - imagePath: 'assets/images/chin_a.png', + imagePath: 'assets/mockup_images/6.jpeg', ), QuizAnswer( + //7.png title: 'Opção B', description: 'Selecione se a imagem se assemelha ao queixo do seu filho/a', weight: 2, - imagePath: 'assets/images/chin_b.png', - ), - QuizAnswer( - title: 'Opção C', - description: - 'Selecione se a imagem se assemelha ao queixo do seu filho/a', - weight: 2, - imagePath: 'assets/images/chin_c.png', - ), - QuizAnswer( - title: 'Opção D', - description: - 'Selecione se a imagem se assemelha ao queixo do seu filho/a', - weight: 2, - imagePath: 'assets/images/chin_d.png', + imagePath: 'assets/mockup_images/7.png', ), ], ), - QuizQuestion( - id: 5, - title: 'Quiz 5/26', - question: 'Quantos dentes tem o seu filho/a em cima na boca?', - answerType: QuizAnswerType.number, - answers: const [], - ), QuizQuestion( id: 6, title: 'Quiz 6/26', - question: 'Quantos dentes tem o seu filho/a em baixo na boca?', - answerType: QuizAnswerType.number, - answers: const [], + question: + 'Qual das seguintes imagens se assemelha à boca do seu filho/a?', + answerType: QuizAnswerType.image, + answers: const [ + QuizAnswer( + //14.jpeg + title: 'Opção A', + description: + 'Selecione se a imagem se assemelha à boca do seu filho/a', + weight: 2, + imagePath: 'assets/mockup_images/14.jpeg', + ), + QuizAnswer( + //15.png + title: 'Opção B', + description: + 'Selecione se a imagem se assemelha à boca do seu filho/a', + weight: 2, + imagePath: 'assets/mockup_images/15.png', + ), + QuizAnswer( + //16.png + title: 'Opção C', + description: + 'Selecione se a imagem se assemelha à boca do seu filho/a', + weight: 2, + imagePath: 'assets/mockup_images/16.png', + ), + ], ), QuizQuestion( id: 7, @@ -187,32 +166,28 @@ class _QuizRandomScreenState extends State { answerType: QuizAnswerType.image, answers: const [ QuizAnswer( + //10.png title: 'Opção A', description: 'Selecione se a imagem se assemelha à boca do seu filho/a', weight: 2, - imagePath: 'assets/images/mouth2_a.png', + imagePath: 'assets/mockup_images/10.png', ), QuizAnswer( + //11.png title: 'Opção B', description: 'Selecione se a imagem se assemelha à boca do seu filho/a', weight: 2, - imagePath: 'assets/images/mouth2_b.png', + imagePath: 'assets/mockup_images/11.png', ), QuizAnswer( + //13.png title: 'Opção C', description: 'Selecione se a imagem se assemelha à boca do seu filho/a', weight: 2, - imagePath: 'assets/images/mouth2_c.png', - ), - QuizAnswer( - title: 'Opção D', - description: - 'Selecione se a imagem se assemelha à boca do seu filho/a', - weight: 2, - imagePath: 'assets/images/mouth2_d.png', + imagePath: 'assets/mockup_images/13.png', ), ], ), @@ -224,32 +199,45 @@ class _QuizRandomScreenState extends State { answerType: QuizAnswerType.image, answers: const [ QuizAnswer( + //17.png title: 'Opção A', description: 'Selecione se a imagem se assemelha ao freio do seu filho/a', weight: 2, - imagePath: 'assets/images/frenulum_a.png', + imagePath: 'assets/mockup_images/17.png', ), QuizAnswer( + //18.jpeg title: 'Opção B', description: 'Selecione se a imagem se assemelha ao freio do seu filho/a', weight: 2, - imagePath: 'assets/images/frenulum_b.png', + imagePath: 'assets/mockup_images/18.jpeg', ), + ], + ), + QuizQuestion( + id: 8, + title: 'Quiz 8/26', + question: + 'Qual das seguintes imagens se assemelha ao freio do seu filho/a?', + answerType: QuizAnswerType.image, + answers: const [ QuizAnswer( - title: 'Opção C', + //19.jpeg + title: 'Opção A', description: 'Selecione se a imagem se assemelha ao freio do seu filho/a', weight: 2, - imagePath: 'assets/images/frenulum_c.png', + imagePath: 'assets/mockup_images/19.jpeg', ), QuizAnswer( - title: 'Opção D', + //20.png + title: 'Opção B', description: 'Selecione se a imagem se assemelha ao freio do seu filho/a', weight: 2, - imagePath: 'assets/images/frenulum_d.png', + imagePath: 'assets/mockup_images/20.png', ), ], ), @@ -652,6 +640,7 @@ class _QuizRandomScreenState extends State { isFinal: isLastQuestion, showBackButton: _currentQuestionIndex > 0, answerType: currentQuestion.answerType, + questionImagePaths: currentQuestion.questionImagePaths, onFinished: isLastQuestion ? () { Navigator.of(context).pushReplacement( @@ -675,6 +664,8 @@ class QuizQuestion { final String question; final List answers; final QuizAnswerType answerType; + // Reference images shown ABOVE the question text (visualization only). + final List questionImagePaths; QuizQuestion({ required this.id, @@ -682,5 +673,41 @@ class QuizQuestion { required this.question, required this.answers, this.answerType = QuizAnswerType.text, + this.questionImagePaths = const [], }); } + +// Helper: returns the asset path for a numbered mockup image (0..27). +// Use in QuizAnswer.imagePath or QuizQuestion.questionImagePaths. +String mockup(int n) => 'assets/mockup_images/$n${_mockupExt[n] ?? '.png'}'; + +const Map _mockupExt = { + 0: '.png', + 1: '.jpeg', + 2: '.jpeg', + 3: '.jpeg', + 4: '.jpeg', + 5: '.png', + 6: '.jpeg', + 7: '.png', + 8: '.jpeg', + 9: '.png', + 10: '.png', + 11: '.png', + 12: '.png', + 13: '.png', + 14: '.jpeg', + 15: '.png', + 16: '.png', + 17: '.png', + 18: '.jpeg', + 19: '.jpeg', + 20: '.png', + 21: '.jpeg', + 22: '.png', + 23: '.jpeg', + 24: '.png', + 25: '.jpg', + 26: '.png', + 27: '.png', +}; diff --git a/lib/quiz/quiz_result.dart b/lib/quiz/quiz_result.dart index 6f89672..c49ab23 100644 --- a/lib/quiz/quiz_result.dart +++ b/lib/quiz/quiz_result.dart @@ -62,17 +62,20 @@ class _QuizResultScreenState extends State { scope.startsWith('${userId}_')) { final childId = scope.substring(userId.length + 1).trim(); if (childId.isNotEmpty) { - await FirebaseFirestore.instance - .collection('users') - .doc(userId) - .collection('children') - .doc(childId) - .set({ - 'lastScore': widget.finalScore, - 'lastMaxScore': widget.maxScore, - 'lastQuizAt': FieldValue.serverTimestamp(), - }, SetOptions(merge: true)) - .catchError((_) {}); + // Fire-and-forget: avoid blocking UI on Firestore (may hang offline). + unawaited( + FirebaseFirestore.instance + .collection('users') + .doc(userId) + .collection('children') + .doc(childId) + .set({ + 'lastScore': widget.finalScore, + 'lastMaxScore': widget.maxScore, + 'lastQuizAt': FieldValue.serverTimestamp(), + }, SetOptions(merge: true)) + .catchError((_) {}), + ); } } } @@ -222,8 +225,11 @@ class _QuizResultScreenState extends State { fontWeight: FontWeight.w900, ), ), - onPressed: () => - Navigator.of(context).popUntil((r) => r.isFirst), + onPressed: () async { + await _saveResultFuture; + if (!context.mounted) return; + Navigator.of(context).popUntil((r) => r.isFirst); + }, child: const Text('Avançar'), ), ), diff --git a/lib/screens/video_screen.dart b/lib/screens/video_screen.dart index 060eceb..a2b8127 100644 --- a/lib/screens/video_screen.dart +++ b/lib/screens/video_screen.dart @@ -2,14 +2,6 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; -import 'package:youtube_player_flutter/youtube_player_flutter.dart'; - -class VideoItem { - const VideoItem({required this.title, required this.url}); - - final String title; - final String url; -} class VideoScreen extends StatelessWidget { const VideoScreen({super.key}); @@ -17,10 +9,6 @@ class VideoScreen extends StatelessWidget { static const Color _teal = Color(0xFF2F9E94); static const Color _accentPink = Color(0xFFFF55A7); - static const List library = [ - VideoItem(title: 'Como escovar da maneira certa', url: 'https://www.youtube.com/watch?v=uH8dBWkD__0'), - ]; - @override Widget build(BuildContext context) { final size = MediaQuery.sizeOf(context); @@ -29,9 +17,8 @@ class VideoScreen extends StatelessWidget { backgroundColor: _teal, foregroundColor: Colors.white, elevation: 0, - title: const Text( - 'Vídeos Educativos', + 'Videos Educativos', style: TextStyle(fontWeight: FontWeight.w900), ), ), @@ -44,10 +31,7 @@ class VideoScreen extends StatelessWidget { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Color(0xFFFFE6F1), - Color(0xFFFFC9DF), - ], + colors: [Color(0xFFFFE6F1), Color(0xFFFFC9DF)], ), ), ), @@ -74,63 +58,48 @@ class VideoScreen extends StatelessWidget { ), ), SafeArea( - child: Align( - alignment: Alignment.topCenter, + child: Center( child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 560), + constraints: const BoxConstraints(maxWidth: 480), child: Padding( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.65), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.black.withValues(alpha: 0.08)), - ), - child: const Row( - children: [ - Icon(Icons.play_circle_fill_rounded, color: _accentPink), - SizedBox(width: 10), - Expanded( - child: Text( - 'Vídeos Educativos', - style: TextStyle( - fontWeight: FontWeight.w900, - color: _accentPink, - ), - ), - ), - ], - ), + padding: const EdgeInsets.all(24), + child: Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.85), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.black.withValues(alpha: 0.08), ), - const SizedBox(height: 14), - Expanded( - child: GridView.count( - crossAxisCount: 2, - mainAxisSpacing: 12, - crossAxisSpacing: 12, - childAspectRatio: 0.92, - children: library.map((item) { - final videoId = YoutubePlayer.convertUrlToId(item.url); - final thumb = videoId == null ? null : 'https://img.youtube.com/vi/$videoId/0.jpg'; - return _VideoCard( - title: item.title, - thumbnailUrl: thumb, - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => VideoPlayerScreen(url: item.url, title: item.title), - ), - ); - }, - ); - }).toList(), + ), + child: const Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.play_circle_fill_rounded, + size: 64, + color: _accentPink, ), - ), - ], + SizedBox(height: 12), + Text( + 'Em breve', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.w900, + color: _teal, + ), + ), + SizedBox(height: 8), + Text( + 'Os videos educativos serao disponibilizados em breve.', + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ], + ), ), ), ), @@ -141,175 +110,3 @@ class VideoScreen extends StatelessWidget { ); } } - -class _VideoCard extends StatelessWidget { - const _VideoCard({required this.title, required this.thumbnailUrl, required this.onTap}); - - final String title; - final String? thumbnailUrl; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return Material( - color: Colors.white.withValues(alpha: 0.80), - borderRadius: BorderRadius.circular(16), - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: onTap, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: ClipRRect( - borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), - child: Stack( - fit: StackFit.expand, - children: [ - if (thumbnailUrl != null) - Image.network( - thumbnailUrl!, - fit: BoxFit.cover, - ) - else - Container(color: Colors.black.withValues(alpha: 0.06)), - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withValues(alpha: 0.10), - Colors.black.withValues(alpha: 0.42), - ], - ), - ), - ), - const Align( - alignment: Alignment.center, - child: Icon(Icons.play_circle_fill_rounded, size: 54, color: Colors.white), - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(12, 10, 12, 12), - child: Text( - title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.w900, - color: Color(0xFF2F9E94), - ), - ), - ), - ], - ), - ), - ); - } -} - -class VideoPlayerScreen extends StatefulWidget { - const VideoPlayerScreen({super.key, required this.url, required this.title}); - - final String url; - final String title; - - @override - State createState() => _VideoPlayerScreenState(); -} - -class _VideoPlayerScreenState extends State { - static const Color _teal = Color(0xFF2F9E94); - static const Color _bg = Color(0xFFFFE6F1); - - late final YoutubePlayerController _controller; - - @override - void initState() { - super.initState(); - final id = YoutubePlayer.convertUrlToId(widget.url); - _controller = YoutubePlayerController( - initialVideoId: id ?? '', - flags: const YoutubePlayerFlags( - autoPlay: true, - mute: false, - enableCaption: true, - ), - ); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final hasVideo = _controller.initialVideoId.isNotEmpty; - - return Scaffold( - appBar: AppBar( - backgroundColor: _teal, - foregroundColor: Colors.white, - elevation: 0, - title: Text( - widget.title, - style: const TextStyle(fontWeight: FontWeight.w900), - ), - ), - body: Container( - color: _bg, - child: SafeArea( - child: ListView( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 16), - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(16), - child: AspectRatio( - aspectRatio: 16 / 9, - child: hasVideo - ? YoutubePlayer( - controller: _controller, - showVideoProgressIndicator: true, - progressIndicatorColor: Colors.white, - ) - : Container( - color: Colors.black.withValues(alpha: 0.10), - child: const Center( - child: Text( - 'Link de vídeo inválido', - style: TextStyle(fontWeight: FontWeight.w800), - ), - ), - ), - ), - ), - const SizedBox(height: 14), - Text( - widget.title, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w900, - color: Color(0xFFFF55A7), - ), - ), - const SizedBox(height: 6), - Text( - 'Assista ao vídeo e aprenda mais sobre saúde bucal.', - style: TextStyle( - color: Colors.black.withValues(alpha: 0.70), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 6a54840..765592c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,6 +12,7 @@ import firebase_core import firebase_storage import flutter_inappwebview_macos import shared_preferences_foundation +import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) @@ -21,4 +22,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index b61fc3e..1dec370 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -121,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -360,6 +368,14 @@ packages: description: flutter source: sdk version: "0.0.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: transitive description: @@ -717,6 +733,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "48a7bdaa38a3d50ec10c78627abdbfad863fdf6f0d6e08c7c3c040cfd80ae36f" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "877a6c7ba772456077d7bfd71314629b3fe2b73733ce503fc77c3314d43a0ca0" + url: "https://pub.dev" + source: hosted + version: "2.9.5" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: "9338f3ec22774f88146b22f13273a446719b1da010fd200c4d1d97802156ac58" + url: "https://pub.dev" + source: hosted + version: "2.9.7" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "16eaed5268c571c31840dc58ef8da5f0cd4db2a98490c3b8f1cf70122546c6e0" + url: "https://pub.dev" + source: hosted + version: "6.7.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" + url: "https://pub.dev" + source: hosted + version: "2.4.0" vm_service: dependency: transitive description: @@ -767,4 +823,4 @@ packages: version: "9.1.3" sdks: dart: ">=3.10.4 <4.0.0" - flutter: ">=3.35.0" + flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index 856ff09..f4a57e9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: lottie: ^3.3.1 shared_preferences: ^2.3.2 youtube_player_flutter: ^9.0.0 + video_player: ^2.9.2 dev_dependencies: flutter_test: @@ -72,6 +73,7 @@ flutter: # - images/a_dot_ham.jpeg - lottie/ - assets/ + - assets/mockup_images/ flutter_launcher_icons: android: true