Clinicas Parceiras e curiosidades retiradas.

This commit is contained in:
Carlos Correia
2026-05-22 11:10:49 +01:00
parent 4f57044196
commit ea009af0d3
6 changed files with 1809 additions and 1280 deletions

View File

@@ -33,10 +33,7 @@ class _HomeScreenState extends State<HomeScreen> {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [Color(0xFFFFE6F1), Color(0xFFFFC9DF)],
Color(0xFFFFE6F1),
Color(0xFFFFC9DF),
],
), ),
), ),
), ),
@@ -64,22 +61,67 @@ class _HomeScreenState extends State<HomeScreen> {
), ),
Center( Center(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 28),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: const Color(
0xFF2F9E94,
).withValues(alpha: 0.18),
blurRadius: 24,
offset: const Offset(0, 8),
),
],
),
child: const Icon(
Icons.medical_services_rounded,
size: 38,
color: Color(0xFF2F9E94),
),
),
const SizedBox(height: 18),
const Text( const Text(
'Check-Teeth Kids', 'Check-Teeth Kids',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 30, fontSize: 28,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w900,
color: Color(0xFFFF55A7), color: Color(0xFFFF55A7),
height: 1.0, height: 1.0,
letterSpacing: -0.5,
), ),
), ),
const SizedBox(height: 22), const SizedBox(height: 10),
Text(
'Cuidar do sorriso começa aqui.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: const Color(0xFF2F9E94).withValues(alpha: 0.9),
),
),
const SizedBox(height: 6),
Text(
'Acompanhe a saúde oral do seu filho com\ninformação segura e prevenção inteligente.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
height: 1.35,
color: Colors.black.withValues(alpha: 0.52),
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 32),
SizedBox( SizedBox(
width: size.width * 0.78, width: size.width * 0.78,
child: _PrimaryButton( child: _PrimaryButton(
@@ -87,39 +129,14 @@ class _HomeScreenState extends State<HomeScreen> {
onPressed: _openRegister, onPressed: _openRegister,
), ),
), ),
const SizedBox(height: 14), const SizedBox(height: 12),
SizedBox( SizedBox(
width: size.width * 0.78, width: size.width * 0.78,
child: _PrimaryButton( child: _SecondaryButton(
label: 'Entrar', label: 'Entrar',
onPressed: _openLogin, onPressed: _openLogin,
), ),
), ),
const SizedBox(height: 24),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: TextStyle(
fontSize: 14.0,
height: 1.25,
color: Colors.black.withValues(alpha: 0.55),
fontWeight: FontWeight.w600,
),
children: const [
TextSpan(
text: 'Cuidar do sorriso começa aqui.\n',
style: TextStyle(
color: Color(0xFF2F9E94),
fontWeight: FontWeight.w900,
),
),
TextSpan(
text:
'Acompanhe a saúde oral do seu filho com\ninformação segura e prevenção inteligente.',
),
],
),
),
], ],
), ),
), ),
@@ -150,6 +167,32 @@ class _HomeScreenState extends State<HomeScreen> {
} }
} }
class _SecondaryButton extends StatelessWidget {
const _SecondaryButton({required this.label, required this.onPressed});
final String label;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
const Color teal = Color(0xFF2F9E94);
return SizedBox(
height: 44,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: teal,
side: const BorderSide(color: teal, width: 1.6),
shape: const StadiumBorder(),
backgroundColor: Colors.white.withValues(alpha: 0.5),
textStyle: const TextStyle(fontWeight: FontWeight.w800, fontSize: 15),
),
onPressed: onPressed,
child: Text(label),
),
);
}
}
class _PrimaryButton extends StatelessWidget { class _PrimaryButton extends StatelessWidget {
const _PrimaryButton({required this.label, required this.onPressed}); const _PrimaryButton({required this.label, required this.onPressed});
@@ -162,26 +205,29 @@ class _PrimaryButton extends StatelessWidget {
return SizedBox( return SizedBox(
height: 44, height: 44,
child: FilledButton( child: FilledButton(
style: FilledButton.styleFrom( style:
backgroundColor: teal, FilledButton.styleFrom(
foregroundColor: Colors.white, backgroundColor: teal,
shape: const StadiumBorder(), foregroundColor: Colors.white,
textStyle: const TextStyle(fontWeight: FontWeight.w800, fontSize: 15), shape: const StadiumBorder(),
).copyWith( textStyle: const TextStyle(
animationDuration: const Duration(milliseconds: 180), fontWeight: FontWeight.w800,
splashFactory: InkSparkle.splashFactory, fontSize: 15,
overlayColor: WidgetStateProperty.resolveWith<Color?>( ),
(states) { ).copyWith(
if (states.contains(WidgetState.pressed)) { animationDuration: const Duration(milliseconds: 180),
return Colors.white.withValues(alpha: 0.14); splashFactory: InkSparkle.splashFactory,
} overlayColor: WidgetStateProperty.resolveWith<Color?>((states) {
if (states.contains(WidgetState.hovered) || states.contains(WidgetState.focused)) { if (states.contains(WidgetState.pressed)) {
return Colors.white.withValues(alpha: 0.08); return Colors.white.withValues(alpha: 0.14);
} }
return null; if (states.contains(WidgetState.hovered) ||
}, states.contains(WidgetState.focused)) {
), return Colors.white.withValues(alpha: 0.08);
), }
return null;
}),
),
onPressed: onPressed, onPressed: onPressed,
child: Text(label), child: Text(label),
), ),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,14 +3,25 @@ import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
typedef QuizNextBuilder = Route<void> Function(BuildContext context, int nextScore); typedef QuizNextBuilder =
Route<void> Function(BuildContext context, int nextScore);
enum QuizAnswerType { text, image, number, yesNo }
class QuizAnswer { class QuizAnswer {
const QuizAnswer({required this.title, required this.description, required this.weight}); const QuizAnswer({
required this.title,
required this.description,
required this.weight,
this.imagePath,
this.value,
});
final String title; final String title;
final String description; final String description;
final int weight; final int weight;
final String? imagePath;
final String? value;
} }
class QuizQuestionScreen extends StatefulWidget { class QuizQuestionScreen extends StatefulWidget {
@@ -24,6 +35,7 @@ class QuizQuestionScreen extends StatefulWidget {
this.onFinished, this.onFinished,
this.isFinal = false, this.isFinal = false,
this.showBackButton = false, this.showBackButton = false,
this.answerType = QuizAnswerType.text,
}); });
final String title; final String title;
@@ -34,6 +46,7 @@ class QuizQuestionScreen extends StatefulWidget {
final VoidCallback? onFinished; final VoidCallback? onFinished;
final bool isFinal; final bool isFinal;
final bool showBackButton; final bool showBackButton;
final QuizAnswerType answerType;
@override @override
State<QuizQuestionScreen> createState() => _QuizQuestionScreenState(); State<QuizQuestionScreen> createState() => _QuizQuestionScreenState();
@@ -41,11 +54,30 @@ class QuizQuestionScreen extends StatefulWidget {
class _QuizQuestionScreenState extends State<QuizQuestionScreen> { class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
int? _selected; int? _selected;
TextEditingController? _numberController;
int? _numberValue;
@override
void initState() {
super.initState();
if (widget.answerType == QuizAnswerType.number) {
_numberController = TextEditingController();
}
}
@override
void dispose() {
_numberController?.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final size = MediaQuery.sizeOf(context); final size = MediaQuery.sizeOf(context);
final bool canProceed = _selected != null; bool canProceed = _selected != null;
if (widget.answerType == QuizAnswerType.number) {
canProceed = _numberValue != null && _numberValue! >= 0;
}
return Scaffold( return Scaffold(
body: Stack( body: Stack(
@@ -57,10 +89,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [Color(0xFFFFE6F1), Color(0xFFFFC9DF)],
Color(0xFFFFE6F1),
Color(0xFFFFC9DF),
],
), ),
), ),
), ),
@@ -119,7 +148,11 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Escolha apenas uma opção', widget.answerType == QuizAnswerType.number
? 'Insira o número'
: widget.answerType == QuizAnswerType.yesNo
? 'Escolha uma opção'
: 'Escolha apenas uma opção',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: Colors.black.withValues(alpha: 0.55), color: Colors.black.withValues(alpha: 0.55),
@@ -132,18 +165,21 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: ListView.separated( child: widget.answerType == QuizAnswerType.number
padding: const EdgeInsets.only(bottom: 12), ? _buildNumberInput()
itemCount: widget.answers.length, : ListView.separated(
separatorBuilder: (context, index) => const SizedBox(height: 12), padding: const EdgeInsets.only(bottom: 12),
itemBuilder: (context, i) { itemCount: widget.answers.length,
return _QuizAnswerTile( separatorBuilder: (context, index) =>
answer: widget.answers[i], const SizedBox(height: 12),
selected: _selected == i, itemBuilder: (context, i) {
onTap: () => setState(() => _selected = i), return _QuizAnswerTile(
); answer: widget.answers[i],
}, selected: _selected == i,
), onTap: () => setState(() => _selected = i),
);
},
),
), ),
), ),
Padding( Padding(
@@ -154,41 +190,77 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
width: size.width * 0.62, width: size.width * 0.62,
height: 46, height: 46,
child: FilledButton( child: FilledButton(
style: FilledButton.styleFrom( style:
backgroundColor: const Color(0xFF2F9E94), FilledButton.styleFrom(
foregroundColor: Colors.white, backgroundColor: const Color(0xFF2F9E94),
shape: const StadiumBorder(), foregroundColor: Colors.white,
textStyle: const TextStyle(fontWeight: FontWeight.w900), shape: const StadiumBorder(),
).copyWith( textStyle: const TextStyle(
animationDuration: const Duration(milliseconds: 180), fontWeight: FontWeight.w900,
splashFactory: InkSparkle.splashFactory, ),
overlayColor: WidgetStateProperty.resolveWith<Color?>( ).copyWith(
(states) { animationDuration: const Duration(
if (states.contains(WidgetState.pressed)) { milliseconds: 180,
return Colors.white.withValues(alpha: 0.14); ),
} splashFactory: InkSparkle.splashFactory,
if (states.contains(WidgetState.hovered) || states.contains(WidgetState.focused)) { overlayColor:
return Colors.white.withValues(alpha: 0.08); WidgetStateProperty.resolveWith<Color?>(
} (states) {
return null; 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 onPressed: !canProceed
? null ? null
: () { : () {
final picked = widget.answers[_selected!]; int nextScore = widget.currentScore;
final nextScore = widget.currentScore + picked.weight; if (widget.answerType ==
QuizAnswerType.number) {
nextScore =
widget.currentScore +
(_numberValue ?? 0);
} else {
final picked =
widget.answers[_selected!];
nextScore =
widget.currentScore + picked.weight;
}
if (widget.isFinal) { if (widget.isFinal) {
widget.onFinished?.call(); final finishedRoute = widget.nextRoute(
Navigator.of(context).popUntil((r) => r.isFirst); context,
nextScore,
);
Navigator.of(
context,
).pushReplacement(finishedRoute);
return; return;
} }
Navigator.of(context).push(widget.nextRoute(context, nextScore)); Navigator.of(context).push(
widget.nextRoute(context, nextScore),
);
}, },
child: Text(widget.isFinal ? 'Concluir' : 'Avançar'), child: Text(
widget.isFinal ? 'Concluir' : 'Avançar',
),
), ),
), ),
if (widget.showBackButton) ...[ if (widget.showBackButton) ...[
@@ -197,27 +269,45 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
width: size.width * 0.62, width: size.width * 0.62,
height: 42, height: 42,
child: FilledButton( child: FilledButton(
style: FilledButton.styleFrom( style:
backgroundColor: const Color(0xFF2F9E94), FilledButton.styleFrom(
foregroundColor: Colors.white, backgroundColor: const Color(0xFF2F9E94),
shape: const StadiumBorder(), foregroundColor: Colors.white,
textStyle: const TextStyle(fontWeight: FontWeight.w900), shape: const StadiumBorder(),
).copyWith( textStyle: const TextStyle(
animationDuration: const Duration(milliseconds: 180), fontWeight: FontWeight.w900,
splashFactory: InkSparkle.splashFactory, ),
overlayColor: WidgetStateProperty.resolveWith<Color?>( ).copyWith(
(states) { animationDuration: const Duration(
if (states.contains(WidgetState.pressed)) { milliseconds: 180,
return Colors.white.withValues(alpha: 0.14); ),
} splashFactory: InkSparkle.splashFactory,
if (states.contains(WidgetState.hovered) || states.contains(WidgetState.focused)) { overlayColor:
return Colors.white.withValues(alpha: 0.08); WidgetStateProperty.resolveWith<
} Color?
return null; >((states) {
}, if (states.contains(
), WidgetState.pressed,
), )) {
onPressed: () => Navigator.of(context).maybePop(), return Colors.white.withValues(
alpha: 0.14,
);
}
if (states.contains(
WidgetState.hovered,
) ||
states.contains(
WidgetState.focused,
)) {
return Colors.white.withValues(
alpha: 0.08,
);
}
return null;
}),
),
onPressed: () =>
Navigator.of(context).maybePop(),
child: const Text('Voltar'), child: const Text('Voltar'),
), ),
), ),
@@ -234,10 +324,67 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
), ),
); );
} }
Widget _buildNumberInput() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 150,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.70),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.black.withValues(alpha: 0.12),
width: 1.0,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.06),
blurRadius: 18,
offset: const Offset(0, 10),
),
],
),
child: TextField(
controller: _numberController,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w900,
color: Color(0xFF2F9E94),
),
decoration: const InputDecoration(
border: InputBorder.none,
hintText: '0',
hintStyle: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w900,
color: Colors.grey,
),
contentPadding: EdgeInsets.symmetric(vertical: 20),
),
onChanged: (value) {
setState(() {
_numberValue = int.tryParse(value);
});
},
),
),
],
),
);
}
} }
class _QuizAnswerTile extends StatelessWidget { class _QuizAnswerTile extends StatelessWidget {
const _QuizAnswerTile({required this.answer, required this.selected, required this.onTap}); const _QuizAnswerTile({
required this.answer,
required this.selected,
required this.onTap,
});
final QuizAnswer answer; final QuizAnswer answer;
final bool selected; final bool selected;
@@ -245,8 +392,12 @@ class _QuizAnswerTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final borderColor = selected ? const Color(0xFF2F9E94) : Colors.black.withValues(alpha: 0.12); final borderColor = selected
final bg = selected ? Colors.white.withValues(alpha: 0.88) : Colors.white.withValues(alpha: 0.70); ? const Color(0xFF2F9E94)
: Colors.black.withValues(alpha: 0.12);
final bg = selected
? Colors.white.withValues(alpha: 0.88)
: Colors.white.withValues(alpha: 0.70);
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 220), duration: const Duration(milliseconds: 220),
@@ -310,7 +461,9 @@ class _QuizAnswerTile extends StatelessWidget {
), ),
), ),
), ),
crossFadeState: selected ? CrossFadeState.showSecond : CrossFadeState.showFirst, crossFadeState: selected
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 220), duration: const Duration(milliseconds: 220),
firstCurve: Curves.easeIn, firstCurve: Curves.easeIn,
secondCurve: Curves.easeOut, secondCurve: Curves.easeOut,

View File

@@ -19,331 +19,580 @@ class _QuizRandomScreenState extends State<QuizRandomScreen> {
final List<QuizQuestion> _allQuestions = [ final List<QuizQuestion> _allQuestions = [
QuizQuestion( QuizQuestion(
id: 1, id: 1,
title: 'Quiz 1/15', title: 'Quiz 1/26',
question: 'Qual é o tempo ideal para escovar os dentes?', question:
'Qual das seguintes imagens se assemelha à face do seu filho/a?',
answerType: QuizAnswerType.image,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Cerca de 2 minutos', title: 'Opção A',
description: 'O recomendado é escovar por aproximadamente 2 minutos, cobrindo todas as superfícies dos dentes e a linha da gengiva sem pressa.', description:
'Selecione se a imagem se assemelha à face do seu filho/a',
weight: 2, weight: 2,
imagePath: 'assets/images/face_a.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Só 30 segundos, se fizer rápido', title: 'Opção B',
description: 'Muito pouco tempo costuma deixar placa bacteriana para trás, principalmente nos dentes de trás e perto da gengiva.', description:
weight: 5, 'Selecione se a imagem se assemelha à face do seu filho/a',
weight: 2,
imagePath: 'assets/images/face_b.png',
), ),
QuizAnswer( QuizAnswer(
title: '5 minutos com força para "limpar bem"', title: 'Opção C',
description: 'Tempo demais e força excessiva podem irritar a gengiva e desgastar o esmalte. Prefira movimentos suaves e tempo adequado.', description:
weight: 3, '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',
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 2, id: 2,
title: 'Quiz 2/15', title: 'Quiz 2/26',
question: 'Quando devo trocar a escova de dentes?', question:
'Qual das seguintes imagens se assemelha à boca do seu filho/a?',
answerType: QuizAnswerType.image,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'A cada 3 meses (ou antes se estragar)', title: 'Opção A',
description: 'O ideal é trocar a cada ~3 meses. Se as cerdas abrirem antes, troque antes. Cerdas abertas limpam pior.', description:
'Selecione se a imagem se assemelha à boca do seu filho/a',
weight: 2, weight: 2,
imagePath: 'assets/images/mouth_a.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Só quando a escova "quebrar"', title: 'Opção B',
description: 'Esperar demais reduz a eficiência da escovação e pode acumular microrganismos na escova.', description:
weight: 5, 'Selecione se a imagem se assemelha à boca do seu filho/a',
weight: 2,
imagePath: 'assets/images/mouth_b.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Todo mês, obrigatoriamente', title: 'Opção C',
description: 'Não é regra fixa. Um mês pode ser cedo demais se a escova estiver em bom estado. O principal é o estado das cerdas.', description:
weight: 3, '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',
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 3, id: 3,
title: 'Quiz 3/15', title: 'Quiz 3/26',
question: 'Qual a quantidade ideal de pasta de dente para crianças?', question:
'Qual das seguintes imagens se assemelha às olheiras do seu filho/a?',
answerType: QuizAnswerType.image,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Um grão de arroz (pequenos) / ervilha (maiores)', title: 'Opção A',
description: 'Para crianças pequenas, um "grão de arroz" já basta. Conforme cresce, pode ser do tamanho de uma ervilha. Isso ajuda a evitar excesso de flúor ingerido.', description:
'Selecione se a imagem se assemelha às olheiras do seu filho/a',
weight: 2, weight: 2,
imagePath: 'assets/images/dark_circles_a.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Cobrir toda a escova com pasta', title: 'Opção B',
description: 'Muito produto não significa melhor limpeza. Em crianças, aumenta o risco de engolir pasta em excesso.', description:
weight: 5, 'Selecione se a imagem se assemelha às olheiras do seu filho/a',
weight: 2,
imagePath: 'assets/images/dark_circles_b.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Nenhuma pasta, só água', title: 'Opção C',
description: 'A pasta com flúor (na quantidade correta) ajuda a prevenir cáries. Em geral, água sozinha não oferece a mesma proteção.', description:
weight: 3, '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',
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 4, id: 4,
title: 'Quiz 4/15', title: 'Quiz 4/26',
question: 'Qual é o melhor horário para usar fio dental?', question:
'Qual das seguintes imagens se assemelha ao queixo do seu filho/a com a boca fechada?',
answerType: QuizAnswerType.image,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Uma vez ao dia, com calma (geralmente à noite)', title: 'Opção A',
description: 'O importante é a frequência diária. À noite costuma ser mais fácil, pois remove restos e placa antes de dormir.', description:
'Selecione se a imagem se assemelha ao queixo do seu filho/a',
weight: 2, weight: 2,
imagePath: 'assets/images/chin_a.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Só quando algo fica preso', title: 'Opção B',
description: 'O fio dental não serve apenas para tirar restos visíveis; ele remove placa bacteriana entre os dentes onde a escova não alcança.', description:
weight: 5, 'Selecione se a imagem se assemelha ao queixo do seu filho/a',
weight: 2,
imagePath: 'assets/images/chin_b.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Depois de toda refeição (obrigatório)', title: 'Opção C',
description: 'Pode ser útil em alguns casos, mas não é obrigatório para todos. O essencial é fazer bem feito ao menos 1x ao dia.', description:
weight: 3, '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',
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 5, id: 5,
title: 'Quiz 5/15', title: 'Quiz 5/26',
question: 'O que ajuda mais a prevenir cáries no dia a dia?', question: 'Quantos dentes tem o seu filho/a em cima na boca?',
answers: const [ answerType: QuizAnswerType.number,
QuizAnswer( answers: const [],
title: 'Escovar + flúor + reduzir açúcar frequente',
description: 'A prevenção é um conjunto: boa higiene com flúor e menos "beliscos" açucarados ao longo do dia.',
weight: 2,
),
QuizAnswer(
title: 'Só enxaguante bucal',
description: 'Enxaguante pode ajudar em alguns casos, mas não substitui escovação e fio dental.',
weight: 3,
),
QuizAnswer(
title: 'Evitar completamente dentista',
description: 'Consultas regulares são importantes para prevenção e orientação. O dentista também identifica problemas bem no começo.',
weight: 5,
),
],
), ),
QuizQuestion( QuizQuestion(
id: 6, id: 6,
title: 'Quiz 6/15', title: 'Quiz 6/26',
question: 'Qual tipo de escova é mais recomendada para crianças?', question: 'Quantos dentes tem o seu filho/a em baixo na boca?',
answers: const [ answerType: QuizAnswerType.number,
QuizAnswer( answers: const [],
title: 'Escova macia com cabeça pequena',
description: 'Escovas macias protegem a gengiva sensível das crianças e a cabeça pequena alcança melhor todos os dentes.',
weight: 2,
),
QuizAnswer(
title: 'Escova dura para limpar melhor',
description: 'Escovas duras podem machucar a gengiva e desgastar o esmalte dos dentes das crianças.',
weight: 5,
),
QuizAnswer(
title: 'Escova elétrica sempre é melhor',
description: 'Escova elétrica pode ajudar, mas não é essencial. O mais importante é a técnica e frequência.',
weight: 3,
),
],
), ),
QuizQuestion( QuizQuestion(
id: 7, id: 7,
title: 'Quiz 7/15', title: 'Quiz 7/26',
question: 'Qual alimento é mais prejudicial para os dentes?', question:
'Qual das seguintes imagens se assemelha à boca do seu filho/a?',
answerType: QuizAnswerType.image,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Balas e chicletes pegajosos', title: 'Opção A',
description: 'Alimentos pegajosos ficam presos nos dentes por mais tempo, aumentando o risco de cáries.', description:
'Selecione se a imagem se assemelha à boca do seu filho/a',
weight: 2, weight: 2,
imagePath: 'assets/images/mouth2_a.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Maçã e cenoura', title: 'Opção B',
description: 'Frutas e vegetais crus ajudam a limpar os dentes naturalmente e são saudáveis.', description:
weight: 5, 'Selecione se a imagem se assemelha à boca do seu filho/a',
weight: 2,
imagePath: 'assets/images/mouth2_b.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Água e leite', title: 'Opção C',
description: 'Água ajuda a limpar e leite tem cálcio. São opções saudáveis para os dentes.', description:
weight: 3, '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',
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 8, id: 8,
title: 'Quiz 8/15', title: 'Quiz 8/26',
question: 'Quando deve ser a primeira visita ao dentista?', question:
'Qual das seguintes imagens se assemelha ao freio do seu filho/a?',
answerType: QuizAnswerType.image,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Por volta dos 1 ano de idade', title: 'Opção A',
description: 'A primeira visita deve ser assim que o primeiro dentinho nascer ou até o primeiro aniversário.', description:
'Selecione se a imagem se assemelha ao freio do seu filho/a',
weight: 2, weight: 2,
imagePath: 'assets/images/frenulum_a.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Só quando tiver todos os dentes', title: 'Opção B',
description: 'Esperar demais pode permitir que problemas comecem sem detecção precoce.', description:
weight: 5, 'Selecione se a imagem se assemelha ao freio do seu filho/a',
weight: 2,
imagePath: 'assets/images/frenulum_b.png',
), ),
QuizAnswer( QuizAnswer(
title: 'Apenas se sentir dor', title: 'Opção C',
description: 'Dor geralmente indica que o problema já está avançado. Prevenção é melhor que tratamento.', description:
weight: 5, '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',
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 9, id: 9,
title: 'Quiz 9/15', title: 'Quiz 9/26',
question: 'Até que idade é aceitável usar chupeta?', question: 'O seu filho/a tem problemas respiratórios diagnosticados?',
answerType: QuizAnswerType.yesNo,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Até 2-3 anos no máximo', title: 'Sim',
description: 'Após 2-3 anos, chupeta pode causar problemas na dentição e no desenvolvimento da fala.', description: 'Problemas respiratórios diagnosticados',
weight: 2, weight: 2,
value: 'sim',
), ),
QuizAnswer( QuizAnswer(
title: 'Até 6-7 anos', title: 'Não',
description: 'Essa idade já é muito tarde e pode causar problemas sérios na arcada dentária.', description: 'Sem problemas respiratórios diagnosticados',
weight: 5, weight: 1,
), value: 'nao',
QuizAnswer(
title: 'Não tem problema usar sempre',
description: 'Uso prolongado pode causar má oclusão, problemas na fala e alterações faciais.',
weight: 5,
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 10, id: 10,
title: 'Quiz 10/15', title: 'Quiz 10/26',
question: 'O flúor na água de abastecimento ajuda?', question: 'O seu filho/a respira habitualmente pela boca?',
answerType: QuizAnswerType.yesNo,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Sim, reduz cáries em até 60%', title: 'Sim',
description: 'Flúor na água é uma das medidas de saúde pública mais eficazes na prevenção de cáries.', description: 'Respira habitualmente pela boca',
weight: 2, weight: 2,
value: 'sim',
), ),
QuizAnswer( QuizAnswer(
title: 'Não faz diferença nenhuma', title: 'Não',
description: 'Estudos comprovam que flúor na água reduz significativamente a incidência de cáries.', description: 'Não respira habitualmente pela boca',
weight: 5, weight: 1,
), value: 'nao',
QuizAnswer(
title: 'É perigoso e causa problemas',
description: 'Nas concentrações corretas, flúor é seguro. O problema é o excesso, não o uso adequado.',
weight: 4,
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 11, id: 11,
title: 'Quiz 11/15', title: 'Quiz 11/26',
question: 'Por que a escovação noturna é tão importante?', question: 'O seu filho/a ressona habitualmente durante a noite?',
answerType: QuizAnswerType.yesNo,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Menos saliva durante o sono', title: 'Sim',
description: 'Durante a noite produzimos menos saliva, que protege os dentes. Escovação remove placa antes desse período vulnerável.', description: 'Ressonar habitualmente durante a noite',
weight: 2, weight: 2,
value: 'sim',
), ),
QuizAnswer( QuizAnswer(
title: 'É igual aos outros horários', title: 'Não',
description: 'A noite é especial porque a produção de saliva diminui, aumentando o risco de cáries.', description: 'Não ressona habitualmente',
weight: 4, weight: 1,
), value: 'nao',
QuizAnswer(
title: 'Só por tradição',
description: 'Tem fundamento científico. A noite é o período mais crítico para formação de cáries.',
weight: 5,
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 12, id: 12,
title: 'Quiz 12/15', title: 'Quiz 12/26',
question: 'Qual bebida é mais ácida para os dentes?', question: 'O seu filho/a sente habitualmente o nariz "tapado"?',
answerType: QuizAnswerType.yesNo,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Refrigerantes e sucos industrializados', title: 'Sim',
description: 'Refrigerantes e sucos artificiais têm pH muito baixo, corroem o esmalte e causam erosão dental.', description: 'Sente habitualmente o nariz tapado',
weight: 2, weight: 2,
value: 'sim',
), ),
QuizAnswer( QuizAnswer(
title: 'Água e leite', title: 'Não',
description: 'Água tem pH neutro e leite é levemente ácido mas protege os dentes com cálcio.', description: 'Não sente habitualmente o nariz tapado',
weight: 5, weight: 1,
), value: 'nao',
QuizAnswer(
title: 'Chá sem açúcar',
description: 'Chá pode manchar mas é muito menos ácido que refrigerantes e sucos artificiais.',
weight: 3,
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 13, id: 13,
title: 'Quiz 13/15', title: 'Quiz 13/26',
question: 'É importante cuidar dos dentes de leite?', question:
'Durante o sono, o seu filho/a tem habitualmente interrupções da respiração?',
answerType: QuizAnswerType.yesNo,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Sim, são fundamentais para o desenvolvimento', title: 'Sim',
description: 'Dentes de leite mantêm espaço para os permanentes, auxiliam na fala e mastigação.', description:
'Tem habitualmente interrupções da respiração durante o sono',
weight: 2, weight: 2,
value: 'sim',
), ),
QuizAnswer( QuizAnswer(
title: 'Não, vão cair de qualquer jeito', title: 'Não',
description: 'Dentes de leite doentes podem afetar os permanentes e causar problemas no desenvolvimento.', description: 'Não tem interrupções da respiração durante o sono',
weight: 5, weight: 1,
), value: 'nao',
QuizAnswer(
title: 'Só se doerem',
description: 'Mesmo sem dor, problemas nos dentes de leite podem ter consequências sérias futuras.',
weight: 4,
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 14, id: 14,
title: 'Quiz 14/15', title: 'Quiz 14/26',
question: 'Qual é a técnica correta de escovação?', question: 'O seu filho/a range os dentes com frequência?',
answerType: QuizAnswerType.yesNo,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Movimentos circulares suaves', title: 'Sim',
description: 'Movimentos circulares ou vibratórios suaves limpam sem machucar a gengiva e removem a placa eficientemente.', description: 'Range os dentes com frequência',
weight: 2, weight: 2,
value: 'sim',
), ),
QuizAnswer( QuizAnswer(
title: 'Força de um lado para o outro', title: 'o',
description: 'Movimentos horizontais fortes podem machucar a gengiva e causar recessão gengival.', description: 'Não range os dentes com frequência',
weight: 5, weight: 1,
), value: 'nao',
QuizAnswer(
title: 'Só na frente dos dentes',
description: 'Precisa escovar todas as faces: frente, atrás e superfície de mastigação.',
weight: 4,
), ),
], ],
), ),
QuizQuestion( QuizQuestion(
id: 15, id: 15,
title: 'Quiz 15/15', title: 'Quiz 15/26',
question: 'Para que servem os selantes dentários?', question: 'O seu filho/a habitualmente tem alergias sazonais?',
answerType: QuizAnswerType.yesNo,
answers: const [ answers: const [
QuizAnswer( QuizAnswer(
title: 'Proteger sulcos dos dentes contra cáries', title: 'Sim',
description: 'Selantes são uma resina que preenche sulcos e fissuras dos dentes, protegendo contra cáries.', description: 'Habitualmente tem alergias sazonais',
weight: 2, weight: 2,
value: 'sim',
), ),
QuizAnswer( QuizAnswer(
title: 'Clarear os dentes', title: 'Não',
description: 'Selantes não têm função estética de clareamento, apenas protetiva contra cáries.', description: 'Não tem alergias sazonais',
weight: 5, weight: 1,
value: 'nao',
),
],
),
QuizQuestion(
id: 16,
title: 'Quiz 16/26',
question: 'O seu filho/a acorda com saliva seca na cara ou na almofada?',
answerType: QuizAnswerType.yesNo,
answers: const [
QuizAnswer(
title: 'Sim',
description: 'Acorda com saliva seca na cara ou na almofada',
weight: 2,
value: 'sim',
), ),
QuizAnswer( QuizAnswer(
title: 'Substituir a escovação', title: 'Não',
description: 'Selantes complementam a higiene, não substituem a escovação e o fio dental.', description: 'Não acorda com saliva seca',
weight: 4, weight: 1,
value: 'nao',
),
],
),
QuizQuestion(
id: 17,
title: 'Quiz 17/26',
question: 'O seu filho/a teve ou costuma ter com frequência otites?',
answerType: QuizAnswerType.yesNo,
answers: const [
QuizAnswer(
title: 'Sim',
description: 'Teve ou costuma ter com frequência otites',
weight: 2,
value: 'sim',
),
QuizAnswer(
title: 'Não',
description: 'Não teve ou não costuma ter otites',
weight: 1,
value: 'nao',
),
],
),
QuizQuestion(
id: 18,
title: 'Quiz 18/26',
question: 'O seu filho/a teve ou costuma ter com frequência amigdalites?',
answerType: QuizAnswerType.yesNo,
answers: const [
QuizAnswer(
title: 'Sim',
description: 'Teve ou costuma ter com frequência amigdalites',
weight: 2,
value: 'sim',
),
QuizAnswer(
title: 'Não',
description: 'Não teve ou não costuma ter amigdalites',
weight: 1,
value: 'nao',
),
],
),
QuizQuestion(
id: 19,
title: 'Quiz 19/26',
question:
'O seu filho/a teve ou costuma ter com frequência bronquiolites?',
answerType: QuizAnswerType.yesNo,
answers: const [
QuizAnswer(
title: 'Sim',
description: 'Teve ou costuma ter com frequência bronquiolites',
weight: 2,
value: 'sim',
),
QuizAnswer(
title: 'Não',
description: 'Não teve ou não costuma ter bronquiolites',
weight: 1,
value: 'nao',
),
],
),
QuizQuestion(
id: 20,
title: 'Quiz 20/26',
question: 'O seu filho/a apresenta dificuldades a mastigar?',
answerType: QuizAnswerType.yesNo,
answers: const [
QuizAnswer(
title: 'Sim',
description: 'Apresenta dificuldades a mastigar',
weight: 2,
value: 'sim',
),
QuizAnswer(
title: 'Não',
description: 'Não apresenta dificuldades a mastigar',
weight: 1,
value: 'nao',
),
],
),
QuizQuestion(
id: 21,
title: 'Quiz 21/26',
question: 'O seu filho/a habitualmente é lento a comer?',
answerType: QuizAnswerType.yesNo,
answers: const [
QuizAnswer(
title: 'Sim',
description: 'Habitualmente é lento a comer',
weight: 2,
value: 'sim',
),
QuizAnswer(
title: 'Não',
description: 'Não é lento a comer',
weight: 1,
value: 'nao',
),
],
),
QuizQuestion(
id: 22,
title: 'Quiz 22/26',
question: 'O seu filho/a habitualmente prefere comer alimentos moles?',
answerType: QuizAnswerType.yesNo,
answers: const [
QuizAnswer(
title: 'Sim',
description: 'Habitualmente prefere comer alimentos moles',
weight: 2,
value: 'sim',
),
QuizAnswer(
title: 'Não',
description: 'Não prefere alimentos moles',
weight: 1,
value: 'nao',
),
],
),
QuizQuestion(
id: 23,
title: 'Quiz 23/26',
question: 'Em bebé apenas foi alimentado por biberão?',
answerType: QuizAnswerType.yesNo,
answers: const [
QuizAnswer(
title: 'Sim',
description: 'Em bebé apenas foi alimentado por biberão',
weight: 2,
value: 'sim',
),
QuizAnswer(
title: 'Não',
description: 'Não foi apenas alimentado por biberão',
weight: 1,
value: 'nao',
),
],
),
QuizQuestion(
id: 24,
title: 'Quiz 24/26',
question: 'O seu filho/a usa ou usou chupeta com frequência?',
answerType: QuizAnswerType.yesNo,
answers: const [
QuizAnswer(
title: 'Sim',
description: 'Usa ou usou chupeta com frequência',
weight: 2,
value: 'sim',
),
QuizAnswer(
title: 'Não',
description: 'Não usa ou não usou chupeta com frequência',
weight: 1,
value: 'nao',
),
],
),
QuizQuestion(
id: 25,
title: 'Quiz 25/26',
question: 'O seu filho/a chucha ou já chuchou o dedo com frequência?',
answerType: QuizAnswerType.yesNo,
answers: const [
QuizAnswer(
title: 'Sim',
description: 'Chucha ou já chuchou o dedo com frequência',
weight: 2,
value: 'sim',
),
QuizAnswer(
title: 'Não',
description: 'Não chucha ou não chuchou o dedo com frequência',
weight: 1,
value: 'nao',
), ),
], ],
), ),
@@ -388,7 +637,8 @@ class _QuizRandomScreenState extends State<QuizRandomScreen> {
} }
final currentQuestion = _shuffledQuestions[_currentQuestionIndex]; final currentQuestion = _shuffledQuestions[_currentQuestionIndex];
final isLastQuestion = _currentQuestionIndex == _shuffledQuestions.length - 1; final isLastQuestion =
_currentQuestionIndex == _shuffledQuestions.length - 1;
return QuizQuestionScreen( return QuizQuestionScreen(
title: currentQuestion.title, title: currentQuestion.title,
@@ -397,23 +647,24 @@ class _QuizRandomScreenState extends State<QuizRandomScreen> {
currentScore: _currentScore, currentScore: _currentScore,
nextRoute: (context, nextScore) { nextRoute: (context, nextScore) {
_nextQuestion(nextScore - _currentScore); _nextQuestion(nextScore - _currentScore);
return MaterialPageRoute<void>( return MaterialPageRoute<void>(builder: (_) => const SizedBox.shrink());
builder: (_) => const SizedBox.shrink(),
);
}, },
isFinal: isLastQuestion, isFinal: isLastQuestion,
showBackButton: _currentQuestionIndex > 0, showBackButton: _currentQuestionIndex > 0,
onFinished: isLastQuestion ? () { answerType: currentQuestion.answerType,
Navigator.of(context).pushReplacement( onFinished: isLastQuestion
MaterialPageRoute<void>( ? () {
builder: (_) => QuizResultScreen( Navigator.of(context).pushReplacement(
finalScore: _currentScore, MaterialPageRoute<void>(
maxScore: 75, builder: (_) => QuizResultScreen(
scopeId: widget.scopeId, finalScore: _currentScore,
), maxScore: 75,
), scopeId: widget.scopeId,
); ),
} : null, ),
);
}
: null,
); );
} }
} }
@@ -423,11 +674,13 @@ class QuizQuestion {
final String title; final String title;
final String question; final String question;
final List<QuizAnswer> answers; final List<QuizAnswer> answers;
final QuizAnswerType answerType;
QuizQuestion({ QuizQuestion({
required this.id, required this.id,
required this.title, required this.title,
required this.question, required this.question,
required this.answers, required this.answers,
this.answerType = QuizAnswerType.text,
}); });
} }

View File

@@ -6,7 +6,12 @@ import 'dart:async';
import 'quiz_prefs.dart'; import 'quiz_prefs.dart';
class QuizResultScreen extends StatefulWidget { class QuizResultScreen extends StatefulWidget {
const QuizResultScreen({super.key, required this.finalScore, required this.maxScore, this.scopeId}); const QuizResultScreen({
super.key,
required this.finalScore,
required this.maxScore,
this.scopeId,
});
final int finalScore; final int finalScore;
final int maxScore; final int maxScore;
@@ -17,39 +22,57 @@ class QuizResultScreen extends StatefulWidget {
} }
class _QuizResultScreenState extends State<QuizResultScreen> { class _QuizResultScreenState extends State<QuizResultScreen> {
late final Future<void> _saveResultFuture;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_saveResultFuture = _saveResult();
}
Future<void> _saveResult() async {
QuizPrefs.markQuizSeen(); QuizPrefs.markQuizSeen();
final scope = (widget.scopeId ?? '').trim(); final scope = (widget.scopeId ?? '').trim();
if (scope.isNotEmpty) { if (scope.isNotEmpty) {
QuizPrefs.saveLastResultForScope(scopeId: scope, score: widget.finalScore, maxScore: widget.maxScore); await QuizPrefs.saveLastResultForScope(
scopeId: scope,
score: widget.finalScore,
maxScore: widget.maxScore,
);
} else { } else {
final uid = FirebaseAuth.instance.currentUser?.uid; final uid = FirebaseAuth.instance.currentUser?.uid;
if (uid != null && uid.trim().isNotEmpty) { if (uid != null && uid.trim().isNotEmpty) {
QuizPrefs.saveLastResultForUser(userId: uid, score: widget.finalScore, maxScore: widget.maxScore); await QuizPrefs.saveLastResultForUser(
userId: uid,
score: widget.finalScore,
maxScore: widget.maxScore,
);
} else { } else {
QuizPrefs.saveLastResult(score: widget.finalScore, maxScore: widget.maxScore); await QuizPrefs.saveLastResult(
score: widget.finalScore,
maxScore: widget.maxScore,
);
} }
} }
final uid = FirebaseAuth.instance.currentUser?.uid; final uid = FirebaseAuth.instance.currentUser?.uid;
final userId = (uid ?? '').trim(); final userId = (uid ?? '').trim();
if (userId.isNotEmpty && scope.isNotEmpty && scope.startsWith('${userId}_')) { if (userId.isNotEmpty &&
scope.isNotEmpty &&
scope.startsWith('${userId}_')) {
final childId = scope.substring(userId.length + 1).trim(); final childId = scope.substring(userId.length + 1).trim();
if (childId.isNotEmpty) { if (childId.isNotEmpty) {
unawaited( await FirebaseFirestore.instance
FirebaseFirestore.instance .collection('users')
.collection('users') .doc(userId)
.doc(userId) .collection('children')
.collection('children') .doc(childId)
.doc(childId) .set({
.set({ 'lastScore': widget.finalScore,
'lastScore': widget.finalScore, 'lastMaxScore': widget.maxScore,
'lastMaxScore': widget.maxScore, 'lastQuizAt': FieldValue.serverTimestamp(),
'lastQuizAt': FieldValue.serverTimestamp(), }, SetOptions(merge: true))
}, SetOptions(merge: true)).catchError((_) {}), .catchError((_) {});
);
} }
} }
} }
@@ -66,10 +89,7 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [Color(0xFFFFE6F1), Color(0xFFFFC9DF)],
Color(0xFFFFE6F1),
Color(0xFFFFC9DF),
],
), ),
), ),
child: SafeArea( child: SafeArea(
@@ -84,7 +104,8 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: TextButton( child: TextButton(
onPressed: () => Navigator.of(context).popUntil((r) => r.isFirst), onPressed: () =>
Navigator.of(context).popUntil((r) => r.isFirst),
child: const Text(''), child: const Text(''),
), ),
), ),
@@ -118,8 +139,12 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
child: CircularProgressIndicator( child: CircularProgressIndicator(
value: progress, value: progress,
strokeWidth: 12, strokeWidth: 12,
backgroundColor: Colors.black.withValues(alpha: 0.10), backgroundColor: Colors.black
valueColor: const AlwaysStoppedAnimation(Color(0xFF2F9E94)), .withValues(alpha: 0.10),
valueColor:
const AlwaysStoppedAnimation(
Color(0xFF2F9E94),
),
), ),
), ),
Column( Column(
@@ -137,7 +162,9 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
Text( Text(
'${clamped.toInt()}/${widget.maxScore}', '${clamped.toInt()}/${widget.maxScore}',
style: TextStyle( style: TextStyle(
color: Colors.black.withValues(alpha: 0.60), color: Colors.black.withValues(
alpha: 0.60,
),
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
), ),
), ),
@@ -171,7 +198,9 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
child: Text( child: Text(
'Descarregar relatório (em breve)', 'Descarregar relatório (em breve)',
style: TextStyle( style: TextStyle(
color: const Color(0xFFFF55A7).withValues(alpha: 0.95), color: const Color(
0xFFFF55A7,
).withValues(alpha: 0.95),
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
), ),
), ),
@@ -189,9 +218,12 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
backgroundColor: const Color(0xFF2F9E94), backgroundColor: const Color(0xFF2F9E94),
foregroundColor: Colors.white, foregroundColor: Colors.white,
shape: const StadiumBorder(), shape: const StadiumBorder(),
textStyle: const TextStyle(fontWeight: FontWeight.w900), textStyle: const TextStyle(
fontWeight: FontWeight.w900,
),
), ),
onPressed: () => Navigator.of(context).popUntil((r) => r.isFirst), onPressed: () =>
Navigator.of(context).popUntil((r) => r.isFirst),
child: const Text('Avançar'), child: const Text('Avançar'),
), ),
), ),