Atualização geral de optimazação e desing
This commit is contained in:
@@ -1,140 +0,0 @@
|
||||
# Sistema de Quiz Extendido - Check-Teeth Kids
|
||||
|
||||
## Novos Arquivos Criados
|
||||
|
||||
### 1. `quiz_extended.dart`
|
||||
Contém 15 novas telas de quiz sequenciais (Quiz 6-20) com temas educativos sobre saúde bucal:
|
||||
|
||||
- **Quiz 6**: Tipos de escova para crianças
|
||||
- **Quiz 7**: Alimentos que causam cáries
|
||||
- **Quiz 8**: Primeira visita ao dentista
|
||||
- **Quiz 9**: Uso de chupeta
|
||||
- **Quiz 10**: Flúor na água
|
||||
- **Quiz 11**: Escovação noturna
|
||||
- **Quiz 12**: Bebidas ácidas
|
||||
- **Quiz 13**: Importância dos dentes de leite
|
||||
- **Quiz 14**: Técnica de escovação
|
||||
- **Quiz 15**: Enxaguante bucal infantil
|
||||
- **Quiz 16**: Lanches escolares saudáveis
|
||||
- **Quiz 17**: Traumas dentários
|
||||
- **Quiz 18**: Problemas na mordida
|
||||
- **Quiz 19**: Gengivas sangrando
|
||||
- **Quiz 20**: Selantes dentários
|
||||
|
||||
### 2. `quiz_random.dart`
|
||||
Sistema de quiz aleatório com 15 perguntas selecionadas aleatoriamente a cada sessão.
|
||||
|
||||
## Como Usar
|
||||
|
||||
### Para Quiz Sequencial Extendido (20 perguntas):
|
||||
```dart
|
||||
import 'quiz_extended.dart';
|
||||
|
||||
// Para iniciar do Quiz 6:
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const Quiz6Screen()),
|
||||
);
|
||||
|
||||
// Para conectar ao final do Quiz 5, modifique quiz5.dart:
|
||||
nextRoute: (context, nextScore) => MaterialPageRoute<void>(
|
||||
builder: (_) => Quiz6Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
```
|
||||
|
||||
### Para Quiz Aleatório (15 perguntas):
|
||||
```dart
|
||||
import 'quiz_random.dart';
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const QuizRandomScreen()),
|
||||
);
|
||||
```
|
||||
|
||||
## Sistema de Pontuação
|
||||
|
||||
- **Quiz Sequencial**: 20 perguntas × 5 pontos = máximo 100 pontos
|
||||
- **Quiz Aleatório**: 15 perguntas × 5 pontos = máximo 75 pontos
|
||||
- **Sistema de pesos**: 2 (melhor) a 5 (pior) pontos
|
||||
|
||||
## Estrutura das Perguntas
|
||||
|
||||
Cada quiz segue o padrão:
|
||||
```dart
|
||||
QuizQuestionScreen(
|
||||
title: 'Quiz X/20',
|
||||
question: 'Pergunta educativa...',
|
||||
answers: [
|
||||
QuizAnswer(title: 'Resposta A', description: 'Explicação...', weight: 2),
|
||||
QuizAnswer(title: 'Resposta B', description: 'Explicação...', weight: 5),
|
||||
QuizAnswer(title: 'Resposta C', description: 'Explicação...', weight: 3),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => MaterialPageRoute(...),
|
||||
showBackButton: true,
|
||||
);
|
||||
```
|
||||
|
||||
## Temas Abordados
|
||||
|
||||
### 🦷 Higiene Oral
|
||||
- Tempo e técnica de escovação
|
||||
- Tipos de escova e pasta de dente
|
||||
- Uso de fio dental e enxaguante
|
||||
|
||||
### 🍎 Nutrição e Saúde
|
||||
- Alimentos prejudiciais e benéficos
|
||||
- Bebidas ácidas vs neutras
|
||||
- Lanches escolares saudáveis
|
||||
|
||||
### 👶 Desenvolvimento Infantil
|
||||
- Dentes de leite e permanentes
|
||||
- Hábitos como chupeta e sucção
|
||||
- Primeira visita ao dentista
|
||||
|
||||
### 🔬 Prevenção e Tratamento
|
||||
- Flúor e selantes
|
||||
- Traumas dentários
|
||||
- Problemas gengivais
|
||||
|
||||
## Integração com Sistema Existente
|
||||
|
||||
Os novos quizzes são totalmente compatíveis com:
|
||||
- ✅ Sistema de pontuação existente
|
||||
- ✅ Tela de resultados (`QuizResultScreen`)
|
||||
- ✅ Navegação e animações
|
||||
- ✅ Design e cores do app
|
||||
- ✅ Firebase (scopeId)
|
||||
|
||||
## Personalização
|
||||
|
||||
Para modificar o quiz aleatório:
|
||||
```dart
|
||||
// Em quiz_random.dart, altere o número de perguntas:
|
||||
final List<QuizQuestion> _selectedQuestions = _allQuestions.take(10).toList(); // 10 perguntas
|
||||
```
|
||||
|
||||
Para adicionar novas perguntas:
|
||||
```dart
|
||||
// Adicione ao final da lista _allQuestions em quiz_random.dart
|
||||
QuizQuestion(
|
||||
id: 16,
|
||||
title: 'Quiz 16/15',
|
||||
question: 'Nova pergunta...',
|
||||
answers: [...],
|
||||
),
|
||||
```
|
||||
|
||||
## Teste e Validação
|
||||
|
||||
Os arquivos foram testados com:
|
||||
- ✅ `flutter analyze` - sem erros
|
||||
- ✅ Estrutura compatível com código existente
|
||||
- ✅ Importações corretas
|
||||
- ✅ Navegação funcional
|
||||
|
||||
---
|
||||
|
||||
*Criado em 01/05/2026*
|
||||
*Total de perguntas: 35 (5 originais + 15 sequenciais + 15 aleatórias)*
|
||||
@@ -14,43 +14,29 @@ class Quiz1Screen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 1/26',
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha à face do seu filho/a?',
|
||||
question: 'O rosto do seu filho/a se parece com o desta imagem?',
|
||||
questionImagePaths: const ['assets/mockup_images/2.jpeg'],
|
||||
suggestedYoutubeId: '', // TODO: colar o ID do YouTube do Episódio 1
|
||||
suggestedVideoTitle: 'Ver vídeo: Episódio 1',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Opção A',
|
||||
description:
|
||||
'Selecione se a imagem se assemelha à face do seu filho/a',
|
||||
title: 'Sim',
|
||||
description: 'O rosto se assemelha à imagem',
|
||||
weight: 2,
|
||||
imagePath: 'assets/images/face_a.png',
|
||||
value: 'sim',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Opção B',
|
||||
description:
|
||||
'Selecione se a imagem se assemelha à face do seu filho/a',
|
||||
weight: 2,
|
||||
imagePath: 'assets/images/face_b.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
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',
|
||||
title: 'Não',
|
||||
description: 'O rosto não se assemelha à imagem',
|
||||
weight: 1,
|
||||
value: 'nao',
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => MaterialPageRoute<void>(
|
||||
builder: (_) => Quiz2Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
answerType: QuizAnswerType.image,
|
||||
answerType: QuizAnswerType.yesNo,
|
||||
showBackButton: false,
|
||||
);
|
||||
}
|
||||
@@ -68,42 +54,29 @@ class Quiz2Screen extends StatelessWidget {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 2/26',
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha à boca do seu filho/a?',
|
||||
'A boca do seu filho/a fica habitualmente na posição desta imagem (entreaberta)?',
|
||||
questionImagePaths: const ['assets/mockup_images/4.jpeg'],
|
||||
suggestedYoutubeId: '', // TODO: colar o ID do YouTube do Episódio 2
|
||||
suggestedVideoTitle: 'Ver vídeo: Episódio 2',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Opção A',
|
||||
description:
|
||||
'Selecione se a imagem se assemelha à boca do seu filho/a',
|
||||
title: 'Sim',
|
||||
description: 'A boca fica habitualmente entreaberta',
|
||||
weight: 2,
|
||||
imagePath: 'assets/images/mouth_a.png',
|
||||
value: 'sim',
|
||||
),
|
||||
QuizAnswer(
|
||||
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',
|
||||
title: 'Não',
|
||||
description: 'A boca fica habitualmente fechada',
|
||||
weight: 1,
|
||||
value: 'nao',
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => MaterialPageRoute<void>(
|
||||
builder: (_) => Quiz3Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
answerType: QuizAnswerType.image,
|
||||
answerType: QuizAnswerType.yesNo,
|
||||
showBackButton: true,
|
||||
);
|
||||
}
|
||||
@@ -120,43 +93,29 @@ class Quiz3Screen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 3/26',
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha às olheiras do seu filho/a?',
|
||||
question: 'O seu filho/a tem olheiras semelhantes às desta imagem?',
|
||||
questionImagePaths: const ['assets/mockup_images/8.jpeg'],
|
||||
suggestedYoutubeId: '', // TODO: colar o ID do YouTube do Episódio 3
|
||||
suggestedVideoTitle: 'Ver vídeo: Episódio 3',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Opção A',
|
||||
description:
|
||||
'Selecione se a imagem se assemelha às olheiras do seu filho/a',
|
||||
title: 'Sim',
|
||||
description: 'Tem olheiras semelhantes à imagem',
|
||||
weight: 2,
|
||||
imagePath: 'assets/images/dark_circles_a.png',
|
||||
value: 'sim',
|
||||
),
|
||||
QuizAnswer(
|
||||
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',
|
||||
title: 'Não',
|
||||
description: 'Não tem olheiras semelhantes à imagem',
|
||||
weight: 1,
|
||||
value: 'nao',
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => MaterialPageRoute<void>(
|
||||
builder: (_) => Quiz4Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
answerType: QuizAnswerType.image,
|
||||
answerType: QuizAnswerType.yesNo,
|
||||
showBackButton: true,
|
||||
);
|
||||
}
|
||||
@@ -174,42 +133,29 @@ class Quiz4Screen extends StatelessWidget {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 4/26',
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha ao queixo do seu filho/a com a boca fechada?',
|
||||
'Com a boca fechada, o queixo do seu filho/a se parece com o desta imagem?',
|
||||
questionImagePaths: const ['assets/mockup_images/6.jpeg'],
|
||||
suggestedYoutubeId: '', // TODO: colar o ID do YouTube do Episódio 4
|
||||
suggestedVideoTitle: 'Ver vídeo: Episódio 4',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Opção A',
|
||||
description:
|
||||
'Selecione se a imagem se assemelha ao queixo do seu filho/a',
|
||||
title: 'Sim',
|
||||
description: 'O queixo se assemelha à imagem',
|
||||
weight: 2,
|
||||
imagePath: 'assets/images/chin_a.png',
|
||||
value: 'sim',
|
||||
),
|
||||
QuizAnswer(
|
||||
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',
|
||||
title: 'Não',
|
||||
description: 'O queixo não se assemelha à imagem',
|
||||
weight: 1,
|
||||
value: 'nao',
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => MaterialPageRoute<void>(
|
||||
builder: (_) => Quiz5Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
answerType: QuizAnswerType.image,
|
||||
answerType: QuizAnswerType.yesNo,
|
||||
showBackButton: true,
|
||||
);
|
||||
}
|
||||
@@ -272,43 +218,29 @@ class Quiz7Screen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 7/26',
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha à boca do seu filho/a?',
|
||||
question: 'A boca do seu filho/a se parece com a desta imagem?',
|
||||
questionImagePaths: const ['assets/mockup_images/14.jpeg'],
|
||||
suggestedYoutubeId: '', // TODO: colar o ID do YouTube do Episódio 5
|
||||
suggestedVideoTitle: 'Ver vídeo: Episódio 5',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Opção A',
|
||||
description:
|
||||
'Selecione se a imagem se assemelha à boca do seu filho/a',
|
||||
title: 'Sim',
|
||||
description: 'A boca se assemelha à imagem',
|
||||
weight: 2,
|
||||
imagePath: 'assets/images/mouth2_a.png',
|
||||
value: 'sim',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Opção B',
|
||||
description:
|
||||
'Selecione se a imagem se assemelha à boca do seu filho/a',
|
||||
weight: 2,
|
||||
imagePath: 'assets/images/mouth2_b.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
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',
|
||||
title: 'Não',
|
||||
description: 'A boca não se assemelha à imagem',
|
||||
weight: 1,
|
||||
value: 'nao',
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => MaterialPageRoute<void>(
|
||||
builder: (_) => Quiz8Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
answerType: QuizAnswerType.image,
|
||||
answerType: QuizAnswerType.yesNo,
|
||||
showBackButton: true,
|
||||
);
|
||||
}
|
||||
@@ -326,42 +258,29 @@ class Quiz8Screen extends StatelessWidget {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 8/26',
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha ao freio do seu filho/a?',
|
||||
'O frénulo (freio) da língua do seu filho/a se parece com o desta imagem?',
|
||||
questionImagePaths: const ['assets/mockup_images/17.png'],
|
||||
suggestedYoutubeId: '', // TODO: colar o ID do YouTube do Episódio 6
|
||||
suggestedVideoTitle: 'Ver vídeo: Episódio 6',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Opção A',
|
||||
description:
|
||||
'Selecione se a imagem se assemelha ao freio do seu filho/a',
|
||||
title: 'Sim',
|
||||
description: 'O frénulo se assemelha à imagem',
|
||||
weight: 2,
|
||||
imagePath: 'assets/images/frenulum_a.png',
|
||||
value: 'sim',
|
||||
),
|
||||
QuizAnswer(
|
||||
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',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Opção C',
|
||||
description:
|
||||
'Selecione se a imagem se assemelha ao freio do seu filho/a',
|
||||
weight: 2,
|
||||
imagePath: 'assets/images/frenulum_c.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Opção D',
|
||||
description:
|
||||
'Selecione se a imagem se assemelha ao freio do seu filho/a',
|
||||
weight: 2,
|
||||
imagePath: 'assets/images/frenulum_d.png',
|
||||
title: 'Não',
|
||||
description: 'O frénulo não se assemelha à imagem',
|
||||
weight: 1,
|
||||
value: 'nao',
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => MaterialPageRoute<void>(
|
||||
builder: (_) => Quiz9Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
answerType: QuizAnswerType.image,
|
||||
answerType: QuizAnswerType.yesNo,
|
||||
showBackButton: true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
|
||||
import '../screens/video_screen.dart';
|
||||
|
||||
typedef QuizNextBuilder =
|
||||
Route<void> Function(BuildContext context, int nextScore);
|
||||
|
||||
@@ -37,6 +39,9 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
this.showBackButton = false,
|
||||
this.answerType = QuizAnswerType.text,
|
||||
this.questionImagePaths = const [],
|
||||
this.suggestedVideoPath,
|
||||
this.suggestedYoutubeId,
|
||||
this.suggestedVideoTitle,
|
||||
});
|
||||
|
||||
final String title;
|
||||
@@ -49,6 +54,9 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
final bool showBackButton;
|
||||
final QuizAnswerType answerType;
|
||||
final List<String> questionImagePaths;
|
||||
final String? suggestedVideoPath;
|
||||
final String? suggestedYoutubeId;
|
||||
final String? suggestedVideoTitle;
|
||||
|
||||
@override
|
||||
State<QuizQuestionScreen> createState() => _QuizQuestionScreenState();
|
||||
@@ -58,6 +66,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
int? _selected;
|
||||
TextEditingController? _numberController;
|
||||
int? _numberValue;
|
||||
bool _navigating = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -76,9 +85,9 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
bool canProceed = _selected != null;
|
||||
bool canProceed = _selected != null && !_navigating;
|
||||
if (widget.answerType == QuizAnswerType.number) {
|
||||
canProceed = _numberValue != null && _numberValue! >= 0;
|
||||
canProceed = _numberValue != null && _numberValue! >= 0 && !_navigating;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
@@ -145,6 +154,35 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
if (widget.suggestedVideoPath != null ||
|
||||
widget.suggestedYoutubeId != null) ...[
|
||||
TextButton.icon(
|
||||
onPressed: () => showVideoPlayerDialog(
|
||||
context,
|
||||
VideoData(
|
||||
id: 0,
|
||||
title:
|
||||
widget.suggestedVideoTitle ?? 'Vídeo',
|
||||
description: '',
|
||||
videoPath: widget.suggestedVideoPath,
|
||||
youtubeId: widget.suggestedYoutubeId,
|
||||
),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.play_circle_outline_rounded,
|
||||
color: Color(0xFF2F9E94),
|
||||
),
|
||||
label: Text(
|
||||
widget.suggestedVideoTitle ??
|
||||
'Ver vídeo (opcional)',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF2F9E94),
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
Text(
|
||||
widget.question,
|
||||
textAlign: TextAlign.center,
|
||||
@@ -239,6 +277,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
onPressed: !canProceed
|
||||
? null
|
||||
: () {
|
||||
setState(() => _navigating = true);
|
||||
int nextScore = widget.currentScore;
|
||||
if (widget.answerType ==
|
||||
QuizAnswerType.number) {
|
||||
@@ -454,7 +493,7 @@ class _QuizAnswerTile extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (answer.imagePath != null) ...[
|
||||
ClipRRect(
|
||||
@@ -478,19 +517,14 @@ class _QuizAnswerTile extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
answer.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 15,
|
||||
color: Color(0xFF2F9E94),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
answer.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 15,
|
||||
color: Color(0xFF2F9E94),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -516,6 +550,7 @@ class _QuestionReferenceImages extends StatelessWidget {
|
||||
child: Image.asset(
|
||||
paths.first,
|
||||
fit: BoxFit.cover,
|
||||
cacheWidth: 800,
|
||||
errorBuilder: (context, error, stackTrace) => _placeholder(),
|
||||
),
|
||||
),
|
||||
@@ -534,6 +569,7 @@ class _QuestionReferenceImages extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.asset(
|
||||
paths[i],
|
||||
cacheWidth: 300,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) => _placeholder(),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../main.dart' show supabase;
|
||||
import 'quiz_prefs.dart';
|
||||
|
||||
class QuizResultScreen extends StatefulWidget {
|
||||
@@ -40,7 +39,7 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
||||
maxScore: widget.maxScore,
|
||||
);
|
||||
} else {
|
||||
final uid = FirebaseAuth.instance.currentUser?.uid;
|
||||
final uid = supabase.auth.currentUser?.id;
|
||||
if (uid != null && uid.trim().isNotEmpty) {
|
||||
await QuizPrefs.saveLastResultForUser(
|
||||
userId: uid,
|
||||
@@ -55,25 +54,23 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
final uid = FirebaseAuth.instance.currentUser?.uid;
|
||||
final uid = supabase.auth.currentUser?.id;
|
||||
final userId = (uid ?? '').trim();
|
||||
if (userId.isNotEmpty &&
|
||||
scope.isNotEmpty &&
|
||||
scope.startsWith('${userId}_')) {
|
||||
final childId = scope.substring(userId.length + 1).trim();
|
||||
if (childId.isNotEmpty) {
|
||||
// Fire-and-forget: avoid blocking UI on Firestore (may hang offline).
|
||||
// Fire-and-forget: avoid blocking UI on erros de rede.
|
||||
unawaited(
|
||||
FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(userId)
|
||||
.collection('children')
|
||||
.doc(childId)
|
||||
.set({
|
||||
'lastScore': widget.finalScore,
|
||||
'lastMaxScore': widget.maxScore,
|
||||
'lastQuizAt': FieldValue.serverTimestamp(),
|
||||
}, SetOptions(merge: true))
|
||||
supabase
|
||||
.from('children')
|
||||
.update({
|
||||
'last_score': widget.finalScore,
|
||||
'last_max_score': widget.maxScore,
|
||||
'last_quiz_at': DateTime.now().toIso8601String(),
|
||||
})
|
||||
.eq('id', childId)
|
||||
.catchError((_) {}),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user