CTK 1.0.2

This commit is contained in:
Carlos Correia
2026-07-15 17:46:14 +01:00
parent 9c21887fc7
commit 3214a07779
9 changed files with 211 additions and 150 deletions

View File

@@ -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,

View File

@@ -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 [

View File

@@ -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,

View File

@@ -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),
),