CTK 1.0.2
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
android:label="Check Theeth Kids"
|
||||
android:label="Check Teeth Kids"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
|
||||
BIN
assets/mockup_images/23.JPEG
Normal file
BIN
assets/mockup_images/23.JPEG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
BIN
assets/mockup_images/24.png
Normal file
BIN
assets/mockup_images/24.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 895 KiB |
BIN
assets/mockup_images/25.jpg
Normal file
BIN
assets/mockup_images/25.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Check Theeth Kids</string>
|
||||
<string>Check Teeth Kids</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -13,7 +13,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Check Theeth Kids</string>
|
||||
<string>Check Teeth Kids</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
|
||||
@@ -62,8 +62,7 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
||||
static const String _kPendingQuizScopeKey = 'pending_quiz_scope_v1';
|
||||
|
||||
static const double _collapsedAppBarHeight = kToolbarHeight;
|
||||
static const double _expandedAppBarHeight = 226;
|
||||
static const double _nameOnlyAppBarHeight = 160;
|
||||
static const double _expandedAppBarHeight = 256;
|
||||
|
||||
int _index = 0;
|
||||
|
||||
@@ -349,9 +348,11 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
||||
// um NestedScrollView em vez do AppBar fixo usado nas outras abas,
|
||||
// para que a altura acompanhe o scroll em vez de ficar sempre cheia
|
||||
// (o que cortava o conteúdo do card do quiz contra a barra ao rolar).
|
||||
final double expandedHeight = hasScore
|
||||
? _expandedAppBarHeight
|
||||
: _nameOnlyAppBarHeight;
|
||||
// A app bar mantém sempre a mesma altura e mostra sempre os dois
|
||||
// gauges — vazios (sem número, sem rótulo) quando ainda não há
|
||||
// resultado, para o utilizador já perceber que aquele espaço é para
|
||||
// o resultado do quiz mesmo antes de o fazer.
|
||||
const double expandedHeight = _expandedAppBarHeight;
|
||||
|
||||
return Scaffold(
|
||||
body: NestedScrollView(
|
||||
@@ -385,7 +386,7 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: kToolbarHeight + 34,
|
||||
top: kToolbarHeight + 50,
|
||||
child: Center(
|
||||
child: Text(
|
||||
(_selectedChildName ?? '').trim(),
|
||||
@@ -397,54 +398,35 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else if ((_selectedChildName ?? '').trim().isNotEmpty)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: kToolbarHeight,
|
||||
bottom: 0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
_selectedChildName!.trim(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white.withValues(alpha: 0.92),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
//posição da app bar relativamente ao nome
|
||||
if (hasScore)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: kToolbarHeight + 60,
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_RiskArcGauge(
|
||||
// Usa sempre o máximo atual do quiz (não
|
||||
// o que foi gravado na última avaliação)
|
||||
// para não mostrar um denominador antigo
|
||||
// quando o número de perguntas muda.
|
||||
value: result.signs,
|
||||
max: kSignsMax,
|
||||
label: 'Sinais',
|
||||
),
|
||||
const SizedBox(width: 18),
|
||||
_RiskArcGauge(
|
||||
value: result.factors,
|
||||
max: kFactorsMax,
|
||||
label: 'Fatores de risco',
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: kToolbarHeight + 96,
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_RiskArcGauge(
|
||||
// Usa sempre o máximo atual do quiz (não
|
||||
// o que foi gravado na última avaliação)
|
||||
// para não mostrar um denominador antigo
|
||||
// quando o número de perguntas muda.
|
||||
value: hasScore ? result.signs : null,
|
||||
max: hasScore ? kSignsMax : null,
|
||||
label: 'Sinais de má\noclusão',
|
||||
),
|
||||
const SizedBox(width: 18),
|
||||
_RiskArcGauge(
|
||||
value: hasScore ? result.factors : null,
|
||||
max: hasScore ? kFactorsMax : null,
|
||||
label: 'Fatores de risco',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -587,19 +569,23 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
|
||||
}
|
||||
|
||||
class _RiskArcGauge extends StatelessWidget {
|
||||
const _RiskArcGauge({
|
||||
required this.value,
|
||||
required this.max,
|
||||
required this.label,
|
||||
});
|
||||
const _RiskArcGauge({required this.value, required this.max, this.label});
|
||||
|
||||
final int value;
|
||||
final int max;
|
||||
final String label;
|
||||
/// Quando [value]/[max] são nulos (sem criança selecionada, ou criança
|
||||
/// ainda sem quiz feito), o gauge mostra-se vazio — só o anel em branco,
|
||||
/// sem número nem rótulo — em vez de desaparecer, para o espaço já
|
||||
/// indicar onde o resultado vai aparecer assim que existir.
|
||||
final int? value;
|
||||
final int? max;
|
||||
final String? label;
|
||||
|
||||
bool get _isEmpty => value == null || max == null;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final progress = max > 0 ? (value / max).clamp(0, 1).toDouble() : 0.0;
|
||||
final progress = _isEmpty || max! <= 0
|
||||
? 0.0
|
||||
: (value! / max!).clamp(0, 1).toDouble();
|
||||
|
||||
return TweenAnimationBuilder<double>(
|
||||
duration: const Duration(milliseconds: 700),
|
||||
@@ -621,38 +607,40 @@ class _RiskArcGauge extends StatelessWidget {
|
||||
painter: _RiskArcGaugePainter(progress: animatedProgress),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 38,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'$value/$max',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w900,
|
||||
height: 1,
|
||||
if (!_isEmpty) ...[
|
||||
Positioned(
|
||||
top: 38,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'$value/$max',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w900,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 68,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.85),
|
||||
fontSize: 10.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.15,
|
||||
letterSpacing: 0.1,
|
||||
Positioned(
|
||||
top: 68,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Text(
|
||||
label ?? '',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.85),
|
||||
fontSize: 10.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.15,
|
||||
letterSpacing: 0.1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -1350,7 +1338,7 @@ class _HeroQuizCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
'28 perguntas rápidas · menos de 3 minutos',
|
||||
'29 perguntas rápidas · menos de 3 minutos',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.92),
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
@@ -83,7 +83,7 @@ class Quiz1Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 1/28',
|
||||
title: 'Quiz 1/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question: 'O seu filho/a tem problemas respiratórios diagnosticados?',
|
||||
answers: const [
|
||||
@@ -127,7 +127,7 @@ class Quiz2Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 2/28',
|
||||
title: 'Quiz 2/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
question: 'O seu filho/a respira habitualmente pela boca?',
|
||||
answers: const [
|
||||
@@ -171,7 +171,7 @@ class Quiz3Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 3/28',
|
||||
title: 'Quiz 3/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question: 'O seu filho/a ressona habitualmente durante a noite?',
|
||||
answers: const [
|
||||
@@ -215,7 +215,7 @@ class Quiz4Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 4/28',
|
||||
title: 'Quiz 4/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question: 'O seu filho/a sente habitualmente o nariz "tapado"?',
|
||||
answers: const [
|
||||
@@ -259,7 +259,7 @@ class Quiz5Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 5/28',
|
||||
title: 'Quiz 5/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question:
|
||||
'Durante o sono, o seu filho/a tem habitualmente interrupções da respiração?',
|
||||
@@ -305,7 +305,7 @@ class Quiz6Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 6/28',
|
||||
title: 'Quiz 6/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
question: 'O seu filho/a range os dentes com frequência?',
|
||||
answers: const [
|
||||
@@ -342,7 +342,7 @@ class Quiz7Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 7/28',
|
||||
title: 'Quiz 7/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question: 'O seu filho/a habitualmente tem alergias sazonais?',
|
||||
answers: const [
|
||||
@@ -386,7 +386,7 @@ class Quiz8Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 8/28',
|
||||
title: 'Quiz 8/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question: 'O seu filho/a acorda com saliva seca na cara ou na almofada?',
|
||||
answers: const [
|
||||
@@ -430,7 +430,7 @@ class Quiz9Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 9/28',
|
||||
title: 'Quiz 9/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
question: 'O seu filho/a teve ou costuma ter com frequência otites?',
|
||||
answers: const [
|
||||
@@ -474,7 +474,7 @@ class Quiz10Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 10/28',
|
||||
title: 'Quiz 10/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question: 'O seu filho/a teve ou costuma ter com frequência amigdalites?',
|
||||
answers: const [
|
||||
@@ -518,7 +518,7 @@ class Quiz11Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 11/28',
|
||||
title: 'Quiz 11/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
question:
|
||||
'O seu filho/a teve ou costuma ter com frequência bronquiolites?',
|
||||
@@ -563,7 +563,7 @@ class Quiz12Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 12/28',
|
||||
title: 'Quiz 12/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question: 'O seu filho/a apresenta dificuldades a mastigar?',
|
||||
answers: const [
|
||||
@@ -600,7 +600,7 @@ class Quiz13Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 13/28',
|
||||
title: 'Quiz 13/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
question: 'O seu filho/a habitualmente é lento a comer?',
|
||||
answers: const [
|
||||
@@ -637,7 +637,7 @@ class Quiz14Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 14/28',
|
||||
title: 'Quiz 14/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question: 'O seu filho/a habitualmente prefere comer alimentos moles?',
|
||||
answers: const [
|
||||
@@ -674,7 +674,7 @@ class Quiz15Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 15/28',
|
||||
title: 'Quiz 15/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question: 'Em bebé apenas foi alimentado por biberão?',
|
||||
answers: const [
|
||||
@@ -718,7 +718,7 @@ class Quiz16Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 16/28',
|
||||
title: 'Quiz 16/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
question: 'O seu filho/a usa ou usou chupeta com frequência?',
|
||||
answers: const [
|
||||
@@ -762,7 +762,7 @@ class Quiz17Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 17/28',
|
||||
title: 'Quiz 17/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question: 'O seu filho/a chucha ou já chuchou o dedo com frequência?',
|
||||
answers: const [
|
||||
@@ -814,7 +814,7 @@ class Quiz18Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 18/28',
|
||||
title: 'Quiz 18/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
answerImageAspectRatio: 1.5,
|
||||
question:
|
||||
@@ -857,7 +857,7 @@ class Quiz19Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 19/28',
|
||||
title: 'Quiz 19/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question:
|
||||
'Qual das seguintes imagens é mais parecida com o perfil do seu filho/a?',
|
||||
@@ -919,7 +919,7 @@ class Quiz20Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 20/28',
|
||||
title: 'Quiz 20/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
question: 'Qual é a posição da boca do seu filho/a habitualmente?',
|
||||
answers: [
|
||||
@@ -964,7 +964,7 @@ class Quiz21Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 21/28',
|
||||
title: 'Quiz 21/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question:
|
||||
'Qual das imagens, na zona abaixo dos olhos, se assemelha mais ao seu filho/a?',
|
||||
@@ -1014,7 +1014,7 @@ class Quiz22Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 22/28',
|
||||
title: 'Quiz 22/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
isSignQuestion: true,
|
||||
question:
|
||||
@@ -1070,12 +1070,20 @@ class Quiz23Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 23/28',
|
||||
title: 'Quiz 23/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
isSignQuestion: true,
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha à boca do seu filho/a?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Dentição sobreposta',
|
||||
description: 'Dentes sobrepostos/tortos',
|
||||
weight: 2,
|
||||
hideTitle: true,
|
||||
value: 'dentes_sobrepostos',
|
||||
imagePath: 'assets/mockup_images/10.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Dentição alinhada',
|
||||
description: 'Dentição bem alinhada, sem apinhamento',
|
||||
@@ -1092,14 +1100,6 @@ class Quiz23Screen extends StatelessWidget {
|
||||
value: 'dentes_desalinhados',
|
||||
imagePath: 'assets/mockup_images/13.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Dentição sobreposta',
|
||||
description: 'Dentes sobrepostos/tortos',
|
||||
weight: 2,
|
||||
hideTitle: true,
|
||||
value: 'dentes_sobrepostos',
|
||||
imagePath: 'assets/mockup_images/10.png',
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
@@ -1121,7 +1121,7 @@ class Quiz24Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 24/28',
|
||||
title: 'Quiz 24/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
isSignQuestion: true,
|
||||
question:
|
||||
@@ -1172,7 +1172,7 @@ class Quiz25Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 25/28',
|
||||
title: 'Quiz 25/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha ao freio labial do seu filho/a?',
|
||||
@@ -1214,7 +1214,7 @@ class Quiz26Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 26/28',
|
||||
title: 'Quiz 26/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha ao freio lingual do seu filho/a?',
|
||||
@@ -1256,7 +1256,58 @@ class Quiz27Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 27/28',
|
||||
title: 'Quiz 27/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
isSignQuestion: true,
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha com a boca do seu filho/a?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Dentição desalinhada',
|
||||
description: 'Dentição desalinhada/apinhada',
|
||||
weight: 2,
|
||||
hideTitle: true,
|
||||
value: 'dentes_desalinhados_3',
|
||||
imagePath: 'assets/mockup_images/23.JPEG',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Dentição sobreposta',
|
||||
description: 'Dentes sobrepostos/tortos',
|
||||
weight: 2,
|
||||
hideTitle: true,
|
||||
value: 'dentes_sobrepostos_3',
|
||||
imagePath: 'assets/mockup_images/24.png',
|
||||
),
|
||||
QuizAnswer(
|
||||
title: 'Dentição alinhada',
|
||||
description: 'Dentição bem alinhada, sem apinhamento',
|
||||
weight: 1,
|
||||
hideTitle: true,
|
||||
value: 'dentes_alinhados_3',
|
||||
imagePath: 'assets/mockup_images/25.jpg',
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz28Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
answerType: QuizAnswerType.image,
|
||||
showBackButton: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Quiz 28: Boca / dentição 2 (Image-choice)
|
||||
class Quiz28Screen extends StatelessWidget {
|
||||
const Quiz28Screen({super.key, required this.currentScore, this.scopeId});
|
||||
|
||||
final QuizScore currentScore;
|
||||
final String? scopeId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 28/29',
|
||||
fallbackColor: const Color(0xFF2F9E94),
|
||||
isSignQuestion: true,
|
||||
question:
|
||||
@@ -1281,7 +1332,7 @@ class Quiz27Screen extends StatelessWidget {
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz28Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
builder: (_) => Quiz29Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
answerType: QuizAnswerType.image,
|
||||
showBackButton: true,
|
||||
@@ -1289,9 +1340,9 @@ class Quiz27Screen extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Quiz 28: Céu da boca (Image-choice, final)
|
||||
class Quiz28Screen extends StatelessWidget {
|
||||
const Quiz28Screen({super.key, required this.currentScore, this.scopeId});
|
||||
// Quiz 29: Céu da boca (Image-choice, final)
|
||||
class Quiz29Screen extends StatelessWidget {
|
||||
const Quiz29Screen({super.key, required this.currentScore, this.scopeId});
|
||||
|
||||
final QuizScore currentScore;
|
||||
final String? scopeId;
|
||||
@@ -1299,9 +1350,10 @@ class Quiz28Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 28/28',
|
||||
title: 'Quiz 29/29',
|
||||
fallbackColor: const Color(0xFFFF55A7),
|
||||
isSignQuestion: true,
|
||||
correctBadgeLabel: 'Posição saudável',
|
||||
question:
|
||||
'Qual das seguintes imagens se assemelha ao céu da boca do seu filho/a?',
|
||||
answers: const [
|
||||
|
||||
@@ -82,6 +82,7 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
this.suggestedVideoTitle,
|
||||
this.fallbackColor,
|
||||
this.answerImageAspectRatio,
|
||||
this.correctBadgeLabel = 'Posição certa',
|
||||
});
|
||||
|
||||
final String title;
|
||||
@@ -115,6 +116,11 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
/// imagem colapsar, mostra-se um bloco colorido com o ícone da app.
|
||||
final Color? fallbackColor;
|
||||
|
||||
/// Texto do selo mostrado sobre a imagem certa ao revelar a resposta —
|
||||
/// customizável por pergunta (ex.: "Posição saudável" em vez do genérico
|
||||
/// "Posição certa", quando faz mais sentido para o contexto da pergunta).
|
||||
final String correctBadgeLabel;
|
||||
|
||||
@override
|
||||
State<QuizQuestionScreen> createState() => _QuizQuestionScreenState();
|
||||
}
|
||||
@@ -445,6 +451,8 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
? 2.3
|
||||
: 1.5),
|
||||
reveal: _revealed,
|
||||
correctLabel: widget
|
||||
.correctBadgeLabel,
|
||||
onTap: _revealed
|
||||
? null
|
||||
: () =>
|
||||
@@ -820,6 +828,7 @@ class _QuizAnswerTile extends StatelessWidget {
|
||||
required this.onTap,
|
||||
this.imageAspectRatio = 4 / 3,
|
||||
this.reveal = false,
|
||||
this.correctLabel = 'Posição certa',
|
||||
});
|
||||
|
||||
final QuizAnswer answer;
|
||||
@@ -831,6 +840,10 @@ class _QuizAnswerTile extends StatelessWidget {
|
||||
/// errada — ver [_QuizQuestionScreenState._revealed].
|
||||
final bool reveal;
|
||||
|
||||
/// Texto do selo quando [isCorrect] — customizável por pergunta (ex.:
|
||||
/// "Posição saudável" em vez do genérico "Posição certa").
|
||||
final String correctLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool isCorrect = answer.weight == 1;
|
||||
@@ -991,7 +1004,7 @@ class _QuizAnswerTile extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
isCorrect ? 'Resposta certa' : 'Resposta inadequada',
|
||||
isCorrect ? correctLabel : 'Posição inadequada',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
|
||||
@@ -13,11 +13,12 @@ import 'quiz_video_guide.dart';
|
||||
|
||||
const String _resultGuideYoutubeId = '3q7C7txH1dE';
|
||||
|
||||
// Fase 3 tem 28 perguntas no total: 5 são sinais de má oclusão já
|
||||
// instalados (queixo, boca/dentição, apinhamento, boca/dentição 2, céu da
|
||||
// boca) — as restantes 23 são fatores de risco associados. Ver o diagrama
|
||||
// de planeamento partilhado com o utilizador para o racional completo.
|
||||
const int kSignsMax = 5;
|
||||
// Fase 3 tem 29 perguntas no total: 6 são sinais de má oclusão já
|
||||
// instalados (queixo, boca/dentição, apinhamento, boca/dentição 2,
|
||||
// boca/dentição 3, céu da boca) — as restantes 23 são fatores de risco
|
||||
// associados. Ver o diagrama de planeamento partilhado com o utilizador
|
||||
// para o racional completo.
|
||||
const int kSignsMax = 6;
|
||||
const int kFactorsMax = 23;
|
||||
|
||||
/// A partir de quantos sinais/fatores presentes se recomenda avaliação.
|
||||
@@ -73,13 +74,16 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
||||
scope.startsWith('${userId}_')) {
|
||||
final childId = scope.substring(userId.length + 1).trim();
|
||||
if (childId.isNotEmpty) {
|
||||
// Fire-and-forget: avoid blocking UI on erros de rede. Requer as
|
||||
// Aguarda a gravação (em vez de fire-and-forget) para que, ao voltar
|
||||
// à Home logo a seguir, _loadQuizResult já encontre o valor novo na
|
||||
// base de dados — caso contrário havia uma corrida em que a Home
|
||||
// consultava a tabela antes desta escrita terminar, mostrando o
|
||||
// resultado antigo até trocar de aba ou reabrir a app. Requer as
|
||||
// colunas last_signs/last_signs_max/last_factors/last_factors_max
|
||||
// na tabela children — se ainda não existirem na base de dados,
|
||||
// esta chamada falha silenciosamente e o resultado fica só local
|
||||
// (SharedPreferences, acima), tal como já acontecia antes.
|
||||
unawaited(
|
||||
supabase
|
||||
// na tabela children — se ainda não existirem, falha silenciosamente
|
||||
// e o resultado fica só local (SharedPreferences, acima).
|
||||
try {
|
||||
await supabase
|
||||
.from('children')
|
||||
.update({
|
||||
'last_signs': widget.finalScore.signs,
|
||||
@@ -87,13 +91,12 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
||||
'last_factors': widget.finalScore.factors,
|
||||
'last_factors_max': kFactorsMax,
|
||||
})
|
||||
.eq('id', childId)
|
||||
.catchError((e) {
|
||||
debugPrint(
|
||||
'[QuizResult] Falha ao gravar score na base de dados: $e',
|
||||
);
|
||||
}),
|
||||
);
|
||||
.eq('id', childId);
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'[QuizResult] Falha ao gravar score na base de dados: $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,7 +115,11 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
||||
'de leite como de permanentes).\n\nO seu propósito é '
|
||||
'identificar, prevenir ou reduzir a gravidade de maloclusões '
|
||||
'e problemas no desenvolvimento dos maxilares enquanto os '
|
||||
'ossos e dentes ainda estão em crescimento.',
|
||||
'ossos e dentes ainda estão em crescimento.\n\nEsta '
|
||||
'intervenção aproveita a fase de desenvolvimento craniofacial '
|
||||
'para influenciar o crescimento ósseo, redirecionar a '
|
||||
'mandíbula, corrigir o alinhamento dos maxilares ou criar '
|
||||
'espaço para a erupção correta dos dentes permanentes.',
|
||||
onAdvance: (context) =>
|
||||
Navigator.of(context).popUntil((r) => r.isFirst),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user