Nova tela de login | Adaptaçao nova da AppBar | Animções novas
This commit is contained in:
@@ -13,7 +13,7 @@ class Quiz1Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 1/26',
|
||||
title: 'Quiz 1/25',
|
||||
question: 'O rosto do seu filho/a se parece com o desta imagem?',
|
||||
questionImagePaths: const ['assets/mockup_images/2.jpeg'],
|
||||
suggestedYoutubeId: '', // TODO: colar o ID do YouTube do Episódio 1
|
||||
@@ -52,7 +52,7 @@ class Quiz2Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 2/26',
|
||||
title: 'Quiz 2/25',
|
||||
question:
|
||||
'A boca do seu filho/a fica habitualmente na posição desta imagem (entreaberta)?',
|
||||
questionImagePaths: const ['assets/mockup_images/4.jpeg'],
|
||||
@@ -92,7 +92,7 @@ class Quiz3Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 3/26',
|
||||
title: 'Quiz 3/25',
|
||||
question: 'O seu filho/a tem olheiras semelhantes às desta imagem?',
|
||||
questionImagePaths: const ['assets/mockup_images/8.jpeg'],
|
||||
suggestedYoutubeId: '', // TODO: colar o ID do YouTube do Episódio 3
|
||||
@@ -131,7 +131,7 @@ class Quiz4Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 4/26',
|
||||
title: 'Quiz 4/25',
|
||||
question:
|
||||
'Com a boca fechada, o queixo do seu filho/a se parece com o desta imagem?',
|
||||
questionImagePaths: const ['assets/mockup_images/6.jpeg'],
|
||||
@@ -171,7 +171,7 @@ class Quiz5Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 5/26',
|
||||
title: 'Quiz 5/25',
|
||||
question: 'Quantos dentes tem o seu filho/a em cima na boca?',
|
||||
answers: const [],
|
||||
currentScore: currentScore,
|
||||
@@ -194,7 +194,7 @@ class Quiz6Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 6/26',
|
||||
title: 'Quiz 6/25',
|
||||
question: 'Quantos dentes tem o seu filho/a em baixo na boca?',
|
||||
answers: const [],
|
||||
currentScore: currentScore,
|
||||
@@ -217,7 +217,7 @@ class Quiz7Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 7/26',
|
||||
title: 'Quiz 7/25',
|
||||
question: 'A boca do seu filho/a se parece com a desta imagem?',
|
||||
questionImagePaths: const ['assets/mockup_images/14.jpeg'],
|
||||
suggestedYoutubeId: '', // TODO: colar o ID do YouTube do Episódio 5
|
||||
@@ -256,7 +256,7 @@ class Quiz8Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 8/26',
|
||||
title: 'Quiz 8/25',
|
||||
question:
|
||||
'O frénulo (freio) da língua do seu filho/a se parece com o desta imagem?',
|
||||
questionImagePaths: const ['assets/mockup_images/17.png'],
|
||||
@@ -296,7 +296,7 @@ class Quiz9Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 9/26',
|
||||
title: 'Quiz 9/25',
|
||||
question: 'O seu filho/a tem problemas respiratórios diagnosticados?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -332,7 +332,7 @@ class Quiz10Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 10/26',
|
||||
title: 'Quiz 10/25',
|
||||
question: 'O seu filho/a respira habitualmente pela boca?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -368,7 +368,7 @@ class Quiz11Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 11/26',
|
||||
title: 'Quiz 11/25',
|
||||
question: 'O seu filho/a ressona habitualmente durante a noite?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -404,7 +404,7 @@ class Quiz12Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 12/26',
|
||||
title: 'Quiz 12/25',
|
||||
question: 'O seu filho/a sente habitualmente o nariz "tapado"?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -440,7 +440,7 @@ class Quiz13Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 13/26',
|
||||
title: 'Quiz 13/25',
|
||||
question:
|
||||
'Durante o sono, o seu filho/a tem habitualmente interrupções da respiração?',
|
||||
answers: const [
|
||||
@@ -478,7 +478,7 @@ class Quiz14Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 14/26',
|
||||
title: 'Quiz 14/25',
|
||||
question: 'O seu filho/a range os dentes com frequência?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -514,7 +514,7 @@ class Quiz15Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 15/26',
|
||||
title: 'Quiz 15/25',
|
||||
question: 'O seu filho/a habitualmente tem alergias sazonais?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -550,7 +550,7 @@ class Quiz16Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 16/26',
|
||||
title: 'Quiz 16/25',
|
||||
question: 'O seu filho/a acorda com saliva seca na cara ou na almofada?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -586,7 +586,7 @@ class Quiz17Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 17/26',
|
||||
title: 'Quiz 17/25',
|
||||
question: 'O seu filho/a teve ou costuma ter com frequência otites?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -622,7 +622,7 @@ class Quiz18Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 18/26',
|
||||
title: 'Quiz 18/25',
|
||||
question: 'O seu filho/a teve ou costuma ter com frequência amigdalites?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -658,7 +658,7 @@ class Quiz19Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 19/26',
|
||||
title: 'Quiz 19/25',
|
||||
question:
|
||||
'O seu filho/a teve ou costuma ter com frequência bronquiolites?',
|
||||
answers: const [
|
||||
@@ -695,7 +695,7 @@ class Quiz20Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 20/26',
|
||||
title: 'Quiz 20/25',
|
||||
question: 'O seu filho/a apresenta dificuldades a mastigar?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -731,7 +731,7 @@ class Quiz21Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 21/26',
|
||||
title: 'Quiz 21/25',
|
||||
question: 'O seu filho/a habitualmente é lento a comer?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -767,7 +767,7 @@ class Quiz22Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 22/26',
|
||||
title: 'Quiz 22/25',
|
||||
question: 'O seu filho/a habitualmente prefere comer alimentos moles?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -803,7 +803,7 @@ class Quiz23Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 23/26',
|
||||
title: 'Quiz 23/25',
|
||||
question: 'Em bebé apenas foi alimentado por biberão?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -839,7 +839,7 @@ class Quiz24Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 24/26',
|
||||
title: 'Quiz 24/25',
|
||||
question: 'O seu filho/a usa ou usou chupeta com frequência?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -875,7 +875,7 @@ class Quiz25Screen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 25/26',
|
||||
title: 'Quiz 25/25',
|
||||
question: 'O seu filho/a chucha ou já chuchou o dedo com frequência?',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
@@ -892,35 +892,6 @@ class Quiz25Screen extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => MaterialPageRoute<void>(
|
||||
builder: (_) => Quiz26Screen(currentScore: nextScore, scopeId: scopeId),
|
||||
),
|
||||
answerType: QuizAnswerType.yesNo,
|
||||
showBackButton: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Quiz 26: Final
|
||||
class Quiz26Screen extends StatelessWidget {
|
||||
const Quiz26Screen({super.key, required this.currentScore, this.scopeId});
|
||||
|
||||
final int currentScore;
|
||||
final String? scopeId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QuizQuestionScreen(
|
||||
title: 'Quiz 26/26',
|
||||
question: 'Obrigado por completar o questionário!',
|
||||
answers: const [
|
||||
QuizAnswer(
|
||||
title: 'Concluir',
|
||||
description: 'Clique para ver os resultados',
|
||||
weight: 0,
|
||||
),
|
||||
],
|
||||
currentScore: currentScore,
|
||||
nextRoute: (context, nextScore) => MaterialPageRoute<void>(
|
||||
builder: (_) => QuizResultScreen(
|
||||
finalScore: nextScore,
|
||||
@@ -928,6 +899,7 @@ class Quiz26Screen extends StatelessWidget {
|
||||
scopeId: scopeId,
|
||||
),
|
||||
),
|
||||
answerType: QuizAnswerType.yesNo,
|
||||
isFinal: true,
|
||||
showBackButton: true,
|
||||
);
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
|
||||
import '../screens/video_screen.dart';
|
||||
import '../widgets/entrance.dart';
|
||||
import '../widgets/tap_bounce.dart';
|
||||
|
||||
typedef QuizNextBuilder =
|
||||
Route<void> Function(BuildContext context, int nextScore);
|
||||
@@ -90,6 +92,10 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
canProceed = _numberValue != null && _numberValue! >= 0 && !_navigating;
|
||||
}
|
||||
|
||||
final bool hasSuggestedVideo =
|
||||
(widget.suggestedVideoPath?.isNotEmpty ?? false) ||
|
||||
(widget.suggestedYoutubeId?.isNotEmpty ?? false);
|
||||
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
@@ -127,267 +133,391 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 520),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 18, 20, 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
widget.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black.withValues(alpha: 0.55),
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
if (widget.questionImagePaths.isNotEmpty) ...[
|
||||
const SizedBox(height: 6),
|
||||
_QuestionReferenceImages(
|
||||
paths: widget.questionImagePaths,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
if (widget.suggestedVideoPath != null ||
|
||||
widget.suggestedYoutubeId != null) ...[
|
||||
TextButton.icon(
|
||||
onPressed: () => showVideoPlayerDialog(
|
||||
context,
|
||||
VideoData(
|
||||
id: 0,
|
||||
title:
|
||||
widget.suggestedVideoTitle ?? 'Vídeo',
|
||||
description: '',
|
||||
videoPath: widget.suggestedVideoPath,
|
||||
youtubeId: widget.suggestedYoutubeId,
|
||||
),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.play_circle_outline_rounded,
|
||||
color: Color(0xFF2F9E94),
|
||||
),
|
||||
label: Text(
|
||||
widget.suggestedVideoTitle ??
|
||||
'Ver vídeo (opcional)',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF2F9E94),
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
Text(
|
||||
widget.question,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color(0xFFFF55A7),
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
widget.answerType == QuizAnswerType.number
|
||||
? 'Insira o número'
|
||||
: widget.answerType == QuizAnswerType.yesNo
|
||||
? 'Escolha uma opção'
|
||||
: 'Escolha apenas uma opção',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black.withValues(alpha: 0.55),
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 44,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Text(
|
||||
widget.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black.withValues(alpha: 0.55),
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: widget.answerType == QuizAnswerType.number
|
||||
? _buildNumberInput()
|
||||
: ListView.separated(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
itemCount: widget.answers.length,
|
||||
separatorBuilder: (context, index) =>
|
||||
const SizedBox(height: 12),
|
||||
itemBuilder: (context, i) {
|
||||
return _QuizAnswerTile(
|
||||
answer: widget.answers[i],
|
||||
selected: _selected == i,
|
||||
onTap: () => setState(() => _selected = i),
|
||||
);
|
||||
},
|
||||
if (widget.showBackButton)
|
||||
Positioned(
|
||||
left: 4,
|
||||
child: TapBounce(
|
||||
scale: 0.9,
|
||||
child: Material(
|
||||
color: Colors.white.withValues(alpha: 0.85),
|
||||
shape: const CircleBorder(),
|
||||
elevation: 4,
|
||||
shadowColor: Colors.black.withValues(
|
||||
alpha: 0.15,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 18),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: size.width * 0.62,
|
||||
height: 46,
|
||||
child: FilledButton(
|
||||
style:
|
||||
FilledButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF2F9E94),
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
).copyWith(
|
||||
animationDuration: const Duration(
|
||||
milliseconds: 180,
|
||||
),
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
overlayColor:
|
||||
WidgetStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
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;
|
||||
},
|
||||
),
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () => Navigator.of(context).maybePop(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Icon(
|
||||
Icons.arrow_back_rounded,
|
||||
color: Color(0xFF2F9E94),
|
||||
size: 22,
|
||||
),
|
||||
onPressed: !canProceed
|
||||
? null
|
||||
: () {
|
||||
setState(() => _navigating = true);
|
||||
int nextScore = widget.currentScore;
|
||||
if (widget.answerType ==
|
||||
QuizAnswerType.number) {
|
||||
nextScore =
|
||||
widget.currentScore +
|
||||
(_numberValue ?? 0);
|
||||
} else {
|
||||
final picked =
|
||||
widget.answers[_selected!];
|
||||
nextScore =
|
||||
widget.currentScore + picked.weight;
|
||||
}
|
||||
|
||||
if (widget.isFinal) {
|
||||
final finishedRoute = widget.nextRoute(
|
||||
context,
|
||||
nextScore,
|
||||
);
|
||||
Navigator.of(
|
||||
context,
|
||||
).pushReplacement(finishedRoute);
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(context).push(
|
||||
widget.nextRoute(context, nextScore),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
widget.isFinal ? 'Concluir' : 'Avançar',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.showBackButton) ...[
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
width: size.width * 0.62,
|
||||
height: 42,
|
||||
child: FilledButton(
|
||||
style:
|
||||
FilledButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF2F9E94),
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
).copyWith(
|
||||
animationDuration: const Duration(
|
||||
milliseconds: 180,
|
||||
),
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
overlayColor:
|
||||
WidgetStateProperty.resolveWith<
|
||||
Color?
|
||||
>((states) {
|
||||
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: () =>
|
||||
Navigator.of(context).maybePop(),
|
||||
child: const Text('Voltar'),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
width: size.width * 0.62,
|
||||
height: 42,
|
||||
child: OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: const Color(0xFF2F9E94),
|
||||
side: const BorderSide(
|
||||
color: Color(0xFF2F9E94),
|
||||
width: 1.3,
|
||||
),
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
onPressed: () => Navigator.of(
|
||||
context,
|
||||
).popUntil((route) => route.isFirst),
|
||||
child: const Text('Voltar para homepage'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 520,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FadeSlideIn(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
20,
|
||||
4,
|
||||
20,
|
||||
10,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (widget
|
||||
.questionImagePaths
|
||||
.isNotEmpty) ...[
|
||||
const SizedBox(height: 6),
|
||||
_QuestionReferenceImages(
|
||||
paths:
|
||||
widget.questionImagePaths,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
if (hasSuggestedVideo) ...[
|
||||
TextButton.icon(
|
||||
onPressed: () =>
|
||||
showVideoPlayerDialog(
|
||||
context,
|
||||
VideoData(
|
||||
id: 0,
|
||||
title:
|
||||
widget
|
||||
.suggestedVideoTitle ??
|
||||
'Vídeo',
|
||||
description: '',
|
||||
videoPath: widget
|
||||
.suggestedVideoPath,
|
||||
youtubeId: widget
|
||||
.suggestedYoutubeId,
|
||||
),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons
|
||||
.play_circle_outline_rounded,
|
||||
color: Color(0xFF2F9E94),
|
||||
),
|
||||
label: Text(
|
||||
widget.suggestedVideoTitle ??
|
||||
'Ver vídeo (opcional)',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF2F9E94),
|
||||
fontWeight:
|
||||
FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
Text(
|
||||
widget.question,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color(0xFFFF55A7),
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
widget.answerType ==
|
||||
QuizAnswerType.number
|
||||
? 'Insira o número'
|
||||
: widget.answerType ==
|
||||
QuizAnswerType.yesNo
|
||||
? 'Escolha uma opção'
|
||||
: 'Escolha apenas uma opção',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black
|
||||
.withValues(alpha: 0.55),
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
),
|
||||
child:
|
||||
widget.answerType ==
|
||||
QuizAnswerType.number
|
||||
? _buildNumberInput()
|
||||
: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (
|
||||
int i = 0;
|
||||
i < widget.answers.length;
|
||||
i++
|
||||
) ...[
|
||||
if (i > 0)
|
||||
const SizedBox(height: 12),
|
||||
FadeSlideIn(
|
||||
delay: Duration(
|
||||
milliseconds: 60 * i,
|
||||
),
|
||||
child: _QuizAnswerTile(
|
||||
answer:
|
||||
widget.answers[i],
|
||||
selected:
|
||||
_selected == i,
|
||||
onTap: () => setState(
|
||||
() => _selected = i,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
20,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
TapBounce(
|
||||
child: SizedBox(
|
||||
width: size.width * 0.62,
|
||||
height: 46,
|
||||
child: FilledButton(
|
||||
style:
|
||||
FilledButton.styleFrom(
|
||||
backgroundColor:
|
||||
const Color(
|
||||
0xFF2F9E94,
|
||||
),
|
||||
foregroundColor:
|
||||
Colors.white,
|
||||
shape:
|
||||
const StadiumBorder(),
|
||||
textStyle:
|
||||
const TextStyle(
|
||||
fontWeight:
|
||||
FontWeight
|
||||
.w900,
|
||||
),
|
||||
).copyWith(
|
||||
animationDuration:
|
||||
const Duration(
|
||||
milliseconds: 180,
|
||||
),
|
||||
splashFactory: InkSparkle
|
||||
.splashFactory,
|
||||
overlayColor:
|
||||
WidgetStateProperty.resolveWith<
|
||||
Color?
|
||||
>((states) {
|
||||
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
|
||||
? null
|
||||
: () async {
|
||||
setState(
|
||||
() => _navigating =
|
||||
true,
|
||||
);
|
||||
int nextScore =
|
||||
widget
|
||||
.currentScore;
|
||||
if (widget.answerType ==
|
||||
QuizAnswerType
|
||||
.number) {
|
||||
nextScore =
|
||||
widget
|
||||
.currentScore +
|
||||
(_numberValue ??
|
||||
0);
|
||||
} else {
|
||||
final picked = widget
|
||||
.answers[_selected!];
|
||||
nextScore =
|
||||
widget
|
||||
.currentScore +
|
||||
picked.weight;
|
||||
}
|
||||
|
||||
if (widget.isFinal) {
|
||||
final finishedRoute =
|
||||
widget.nextRoute(
|
||||
context,
|
||||
nextScore,
|
||||
);
|
||||
Navigator.of(
|
||||
context,
|
||||
).pushReplacement(
|
||||
finishedRoute,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await Navigator.of(
|
||||
context,
|
||||
).push(
|
||||
widget.nextRoute(
|
||||
context,
|
||||
nextScore,
|
||||
),
|
||||
);
|
||||
if (mounted) {
|
||||
setState(
|
||||
() => _navigating =
|
||||
false,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
widget.isFinal
|
||||
? 'Concluir'
|
||||
: 'Avançar',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
TapBounce(
|
||||
child: SizedBox(
|
||||
width: size.width * 0.62,
|
||||
height: 42,
|
||||
child: OutlinedButton(
|
||||
style:
|
||||
OutlinedButton.styleFrom(
|
||||
foregroundColor:
|
||||
const Color(
|
||||
0xFF2F9E94,
|
||||
),
|
||||
side: const BorderSide(
|
||||
color: Color(
|
||||
0xFF2F9E94,
|
||||
),
|
||||
width: 1.3,
|
||||
),
|
||||
shape:
|
||||
const StadiumBorder(),
|
||||
textStyle:
|
||||
const TextStyle(
|
||||
fontWeight:
|
||||
FontWeight
|
||||
.w900,
|
||||
),
|
||||
),
|
||||
onPressed: () =>
|
||||
Navigator.of(
|
||||
context,
|
||||
).popUntil(
|
||||
(route) =>
|
||||
route.isFirst,
|
||||
),
|
||||
child: const Text(
|
||||
'Voltar para homepage',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -469,67 +599,109 @@ class _QuizAnswerTile extends StatelessWidget {
|
||||
? Colors.white.withValues(alpha: 0.88)
|
||||
: Colors.white.withValues(alpha: 0.70);
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 220),
|
||||
curve: Curves.easeOutCubic,
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: borderColor, width: selected ? 1.4 : 1.0),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.06),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: onTap,
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (answer.imagePath != null) ...[
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 4 / 3,
|
||||
child: Image.asset(
|
||||
answer.imagePath!,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) => Container(
|
||||
color: Colors.black.withValues(alpha: 0.06),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.image_not_supported_outlined,
|
||||
color: Colors.black38,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
Text(
|
||||
answer.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 15,
|
||||
color: Color(0xFF2F9E94),
|
||||
),
|
||||
return TapBounce(
|
||||
scale: 0.97,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.passthrough,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 220),
|
||||
curve: Curves.easeOutCubic,
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: borderColor,
|
||||
width: selected ? 1.4 : 1.0,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.06),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: onTap,
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (answer.imagePath != null) ...[
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 4 / 3,
|
||||
child: Image.asset(
|
||||
answer.imagePath!,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
Container(
|
||||
color: Colors.black.withValues(
|
||||
alpha: 0.06,
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.image_not_supported_outlined,
|
||||
color: Colors.black38,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
Text(
|
||||
answer.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 15,
|
||||
color: Color(0xFF2F9E94),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 8,
|
||||
right: 8,
|
||||
child: IgnorePointer(
|
||||
child: AnimatedScale(
|
||||
scale: selected ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 220),
|
||||
curve: Curves.easeOutBack,
|
||||
child: Container(
|
||||
width: 22,
|
||||
height: 22,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF2F9E94),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.check_rounded,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../main.dart' show supabase;
|
||||
import '../widgets/entrance.dart';
|
||||
import '../widgets/tap_bounce.dart';
|
||||
import 'quiz_prefs.dart';
|
||||
|
||||
class QuizResultScreen extends StatefulWidget {
|
||||
@@ -115,82 +117,98 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'A percentagem de risco\navaliada é de:',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color(0xFFFF55A7),
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Center(
|
||||
child: SizedBox(
|
||||
width: 220,
|
||||
height: 220,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 200,
|
||||
height: 200,
|
||||
child: CircularProgressIndicator(
|
||||
value: progress,
|
||||
strokeWidth: 12,
|
||||
backgroundColor: Colors.black
|
||||
.withValues(alpha: 0.10),
|
||||
valueColor:
|
||||
const AlwaysStoppedAnimation(
|
||||
Color(0xFF2F9E94),
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'$percent%',
|
||||
style: const TextStyle(
|
||||
fontSize: 34,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${clamped.toInt()}/${widget.maxScore}',
|
||||
style: TextStyle(
|
||||
color: Colors.black.withValues(
|
||||
alpha: 0.60,
|
||||
),
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
FadeSlideIn(
|
||||
child: const Text(
|
||||
'A percentagem de risco\navaliada é de:',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color(0xFFFF55A7),
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
'Conclusões:',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black.withValues(alpha: 0.75),
|
||||
fontWeight: FontWeight.w900,
|
||||
Center(
|
||||
child: TweenAnimationBuilder<double>(
|
||||
duration: const Duration(milliseconds: 1100),
|
||||
curve: Curves.easeOutCubic,
|
||||
tween: Tween<double>(begin: 0, end: progress),
|
||||
builder: (context, animatedProgress, _) {
|
||||
final animatedPercent =
|
||||
(animatedProgress * 100).round();
|
||||
return SizedBox(
|
||||
width: 220,
|
||||
height: 220,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 200,
|
||||
height: 200,
|
||||
child: CircularProgressIndicator(
|
||||
value: animatedProgress,
|
||||
strokeWidth: 12,
|
||||
backgroundColor: Colors.black
|
||||
.withValues(alpha: 0.10),
|
||||
valueColor:
|
||||
const AlwaysStoppedAnimation(
|
||||
Color(0xFF2F9E94),
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'$animatedPercent%',
|
||||
style: const TextStyle(
|
||||
fontSize: 34,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${clamped.toInt()}/${widget.maxScore}',
|
||||
style: TextStyle(
|
||||
color: Colors.black
|
||||
.withValues(alpha: 0.60),
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
FadeSlideIn(
|
||||
delay: const Duration(milliseconds: 120),
|
||||
child: Text(
|
||||
'Conclusões:',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black.withValues(alpha: 0.75),
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Esta avaliação é apenas educativa.\nSe tiver dúvidas ou sinais de cárie/dor, procure um Dentista.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black.withValues(alpha: 0.70),
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.25,
|
||||
FadeSlideIn(
|
||||
delay: const Duration(milliseconds: 160),
|
||||
child: Text(
|
||||
'Esta avaliação é apenas educativa.\nSe tiver dúvidas ou sinais de cárie/dor, procure um Dentista.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black.withValues(alpha: 0.70),
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.25,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -210,7 +228,8 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: SizedBox(
|
||||
child: TapBounce(
|
||||
child: SizedBox(
|
||||
width: 260,
|
||||
height: 46,
|
||||
child: FilledButton(
|
||||
@@ -229,6 +248,7 @@ class _QuizResultScreenState extends State<QuizResultScreen> {
|
||||
},
|
||||
child: const Text('Avançar'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user