CKT 1.0.2 | otorrinolaringologista
This commit is contained in:
@@ -65,13 +65,73 @@ Route<void> quizStartRoute({String? scopeId}) {
|
||||
QuizStrings.checklistItem2,
|
||||
QuizStrings.checklistItem3,
|
||||
],
|
||||
onAdvance: (context) => Navigator.of(context).pushReplacement(
|
||||
// Importante: usa push (não pushReplacement) — _startQuiz(), em
|
||||
// logged_home.dart, faz `await Navigator.push(quizStartRoute(...))`
|
||||
// (esta rota, a checklist) para saber quando o quiz inteiro terminou
|
||||
// e recarregar o resultado na Home. Se esta rota fosse substituída
|
||||
// aqui, o seu future completava logo ao entrar na Quiz1 — muito antes
|
||||
// do fim real do quiz — e a Home deixava de saber quando recarregar,
|
||||
// só voltando a mostrar o resultado novo depois de fechar e reabrir
|
||||
// a app.
|
||||
onAdvance: (context) => Navigator.of(context).push(
|
||||
quizPageRoute(builder: (_) => Quiz1Screen(scopeId: scopeId)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Um construtor por pergunta (índice 0 = Quiz1, ..., 28 = Quiz29), usado
|
||||
/// por [quizResumeRoute] para saltar diretamente para a pergunta onde um
|
||||
/// quiz anterior ficou por terminar.
|
||||
final List<Widget Function(QuizScore score, String? scopeId)>
|
||||
_kQuizScreenBuilders = [
|
||||
(score, scopeId) => Quiz1Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz2Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz3Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz4Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz5Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz6Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz7Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz8Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz9Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz10Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz11Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz12Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz13Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz14Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz15Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz16Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz17Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz18Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz19Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz20Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz21Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz22Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz23Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz24Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz25Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz26Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz27Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz28Screen(currentScore: score, scopeId: scopeId),
|
||||
(score, scopeId) => Quiz29Screen(currentScore: score, scopeId: scopeId),
|
||||
];
|
||||
|
||||
/// Salta diretamente para a pergunta [questionIndex] (1-based) do quiz, com
|
||||
/// a pontuação já acumulada — usado para "Continuar de onde parou".
|
||||
Route<void> quizResumeRoute({
|
||||
required int questionIndex,
|
||||
required QuizScore score,
|
||||
String? scopeId,
|
||||
}) {
|
||||
final index = (questionIndex - 1).clamp(
|
||||
0,
|
||||
_kQuizScreenBuilders.length - 1,
|
||||
);
|
||||
return quizPageRoute(
|
||||
builder: (_) => _kQuizScreenBuilders[index](score, scopeId),
|
||||
);
|
||||
}
|
||||
|
||||
// Quiz 1: Problemas respiratórios (Yes/No)
|
||||
class Quiz1Screen extends StatelessWidget {
|
||||
const Quiz1Screen({
|
||||
@@ -111,6 +171,7 @@ class Quiz1Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz2Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -155,6 +216,7 @@ class Quiz2Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz3Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -199,6 +261,7 @@ class Quiz3Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz4Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -243,6 +306,7 @@ class Quiz4Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz5Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -287,6 +351,7 @@ class Quiz5Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz6Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -324,6 +389,7 @@ class Quiz6Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz7Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -368,6 +434,7 @@ class Quiz7Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz8Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -412,6 +479,7 @@ class Quiz8Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz9Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -456,6 +524,7 @@ class Quiz9Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz10Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -500,6 +569,7 @@ class Quiz10Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz11Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -544,6 +614,7 @@ class Quiz11Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz12Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -581,6 +652,7 @@ class Quiz12Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz13Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -618,6 +690,7 @@ class Quiz13Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz14Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -655,6 +728,7 @@ class Quiz14Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz15Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -699,6 +773,7 @@ class Quiz15Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz16Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -743,6 +818,7 @@ class Quiz16Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz17Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -787,6 +863,7 @@ class Quiz17Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => QuizVideoGuideScreen(
|
||||
youtubeId: 'W2BcK9nSyt0',
|
||||
@@ -837,6 +914,7 @@ class Quiz18Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz19Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -898,6 +976,7 @@ class Quiz19Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz20Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -943,6 +1022,7 @@ class Quiz20Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz21Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -992,6 +1072,7 @@ class Quiz21Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz22Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -1039,6 +1120,7 @@ class Quiz22Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => QuizVideoGuideScreen(
|
||||
youtubeId: 'msKYr7nPxcw',
|
||||
@@ -1097,6 +1179,7 @@ class Quiz23Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz24Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -1147,6 +1230,7 @@ class Quiz24Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz25Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -1188,6 +1272,7 @@ class Quiz25Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz26Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -1229,6 +1314,7 @@ class Quiz26Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz27Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -1279,6 +1365,7 @@ class Quiz27Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz28Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -1321,6 +1408,7 @@ class Quiz28Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => Quiz29Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
@@ -1364,6 +1452,7 @@ class Quiz29Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
scopeId: scopeId,
|
||||
nextRoute: (context, nextScore) => quizPageRoute(
|
||||
builder: (_) => QuizResultScreen(finalScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
|
||||
61
lib/quiz/quiz_progress_prefs.dart
Normal file
61
lib/quiz/quiz_progress_prefs.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'quiz_question_screen.dart' show QuizScore;
|
||||
|
||||
/// Progresso de um quiz por terminar: em que pergunta ficou e a pontuação
|
||||
/// acumulada até aí.
|
||||
class QuizProgressData {
|
||||
const QuizProgressData({required this.questionIndex, required this.score});
|
||||
|
||||
/// Número (1-based) da pergunta onde retomar.
|
||||
final int questionIndex;
|
||||
final QuizScore score;
|
||||
}
|
||||
|
||||
/// Guarda o progresso de um quiz abandonado a meio (ex.: "Voltar para
|
||||
/// homepage"), para oferecer "Continuar de onde parou" da próxima vez que o
|
||||
/// mesmo utilizador iniciar o quiz para a mesma criança. Só existe uma
|
||||
/// ranhura (não uma por criança): ao guardar progresso para uma criança
|
||||
/// diferente, substitui o anterior — como só se mostra a opção de continuar
|
||||
/// quando o [scopeId] guardado corresponde exatamente à criança escolhida,
|
||||
/// isto já cumpre "só se for a mesma criança" sem precisar de armazenamento
|
||||
/// por criança.
|
||||
class QuizProgressPrefs {
|
||||
static const String _kScopeKey = 'quiz_progress_scope';
|
||||
static const String _kIndexKey = 'quiz_progress_index';
|
||||
static const String _kSignsKey = 'quiz_progress_signs';
|
||||
static const String _kFactorsKey = 'quiz_progress_factors';
|
||||
|
||||
static Future<void> saveProgress({
|
||||
required String scopeId,
|
||||
required int questionIndex,
|
||||
required QuizScore score,
|
||||
}) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_kScopeKey, scopeId);
|
||||
await prefs.setInt(_kIndexKey, questionIndex);
|
||||
await prefs.setInt(_kSignsKey, score.signs);
|
||||
await prefs.setInt(_kFactorsKey, score.factors);
|
||||
}
|
||||
|
||||
static Future<QuizProgressData?> getProgress(String scopeId) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getString(_kScopeKey) != scopeId) return null;
|
||||
final index = prefs.getInt(_kIndexKey);
|
||||
final signs = prefs.getInt(_kSignsKey);
|
||||
final factors = prefs.getInt(_kFactorsKey);
|
||||
if (index == null || signs == null || factors == null) return null;
|
||||
return QuizProgressData(
|
||||
questionIndex: index,
|
||||
score: QuizScore(signs: signs, factors: factors),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> clearProgress() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_kScopeKey);
|
||||
await prefs.remove(_kIndexKey);
|
||||
await prefs.remove(_kSignsKey);
|
||||
await prefs.remove(_kFactorsKey);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import '../strings/quiz_ui_strings.dart';
|
||||
import '../widgets/entrance.dart';
|
||||
import '../widgets/liquid_waves_background.dart';
|
||||
import '../widgets/tap_bounce.dart';
|
||||
import 'quiz_progress_prefs.dart';
|
||||
|
||||
typedef QuizNextBuilder =
|
||||
Route<void> Function(BuildContext context, QuizScore nextScore);
|
||||
@@ -85,6 +86,7 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
this.fallbackColor,
|
||||
this.answerImageAspectRatio,
|
||||
this.correctBadgeLabel = QuizUiStrings.defaultCorrectBadgeLabel,
|
||||
this.scopeId,
|
||||
});
|
||||
|
||||
final String title;
|
||||
@@ -93,6 +95,11 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
final QuizNextBuilder nextRoute;
|
||||
final QuizScore currentScore;
|
||||
|
||||
/// Identifica a criança selecionada — usado só para guardar o progresso
|
||||
/// do quiz localmente (ver [QuizProgressPrefs]), para poder oferecer
|
||||
/// "Continuar de onde parou" caso o utilizador saia a meio.
|
||||
final String? scopeId;
|
||||
|
||||
/// Quando true, uma resposta "de risco" (weight 2) soma a
|
||||
/// [QuizScore.signs] em vez de [QuizScore.factors] — usado só nas 4
|
||||
/// perguntas que representam sinais de má oclusão já instalados
|
||||
@@ -612,6 +619,21 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
final scopeId =
|
||||
widget.scopeId;
|
||||
if (scopeId != null &&
|
||||
scopeId
|
||||
.trim()
|
||||
.isNotEmpty) {
|
||||
QuizProgressPrefs.saveProgress(
|
||||
scopeId: scopeId,
|
||||
questionIndex:
|
||||
_questionIndex +
|
||||
1,
|
||||
score: nextScore,
|
||||
);
|
||||
}
|
||||
|
||||
await Navigator.of(
|
||||
context,
|
||||
).push(
|
||||
|
||||
@@ -11,6 +11,7 @@ import '../widgets/entrance.dart';
|
||||
import '../widgets/liquid_waves_background.dart';
|
||||
import '../widgets/tap_bounce.dart';
|
||||
import 'quiz_prefs.dart';
|
||||
import 'quiz_progress_prefs.dart';
|
||||
import 'quiz_question_screen.dart' show QuizScore;
|
||||
import 'quiz_video_guide.dart';
|
||||
|
||||
@@ -52,6 +53,8 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
||||
|
||||
Future<void> _saveResult() async {
|
||||
QuizPrefs.markQuizSeen();
|
||||
// O quiz terminou — não há progresso por retomar.
|
||||
QuizProgressPrefs.clearProgress();
|
||||
final result = QuizResultData(
|
||||
signs: widget.finalScore.signs,
|
||||
signsMax: kSignsMax,
|
||||
|
||||
Reference in New Issue
Block a user