Atulização do quiz | Porcentagem na base de dados | Telas de Contrato com o usuario
This commit is contained in:
@@ -19,6 +19,8 @@ class QuizAnswer {
|
||||
required this.description,
|
||||
required this.weight,
|
||||
this.imagePath,
|
||||
this.imageBuilder,
|
||||
this.hideTitle = false,
|
||||
this.value,
|
||||
this.helpVideoId,
|
||||
});
|
||||
@@ -27,6 +29,18 @@ class QuizAnswer {
|
||||
final String description;
|
||||
final int weight;
|
||||
final String? imagePath;
|
||||
|
||||
/// Quando definido, substitui [imagePath] na renderização — usado para
|
||||
/// mostrar um recorte/zoom de uma imagem partilhada (ex.: metade
|
||||
/// esquerda/direita de uma foto de comparação) sem precisar de gerar
|
||||
/// novos ficheiros de imagem.
|
||||
final WidgetBuilder? imageBuilder;
|
||||
|
||||
/// Quando true, esconde o texto [title] por baixo da imagem — usado nas
|
||||
/// perguntas "escolha a imagem" do quiz, onde a própria imagem já é a
|
||||
/// resposta e uma legenda seria redundante.
|
||||
final bool hideTitle;
|
||||
|
||||
final String? value;
|
||||
|
||||
/// Quando definido (normalmente só na resposta "Não sei"), identifica um
|
||||
@@ -55,6 +69,7 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
this.categoryIcon,
|
||||
this.fallbackIcon,
|
||||
this.fallbackColor,
|
||||
this.answerImageAspectRatio,
|
||||
});
|
||||
|
||||
final String title;
|
||||
@@ -71,6 +86,12 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
final String? suggestedYoutubeId;
|
||||
final String? suggestedVideoTitle;
|
||||
|
||||
/// Substitui o cálculo automático (baseado no número de opções) do
|
||||
/// aspect ratio dos blocos de imagem em [QuizAnswerType.image] — usado
|
||||
/// quando as fotos já vêm pré-recortadas num formato específico (ex.:
|
||||
/// retrato, para a postura).
|
||||
final double? answerImageAspectRatio;
|
||||
|
||||
/// Rótulo pequeno do tema da pergunta (ex.: "Avaliação facial"), mostrado
|
||||
/// num badge acima da imagem/pergunta.
|
||||
final String? category;
|
||||
@@ -147,9 +168,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
body: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(color: const Color(0xFFFAFAF7)),
|
||||
),
|
||||
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
|
||||
Positioned(
|
||||
left: -size.width * 0.40,
|
||||
bottom: -size.width * 0.45,
|
||||
@@ -282,9 +301,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 520,
|
||||
),
|
||||
constraints: const BoxConstraints(maxWidth: 520),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
@@ -316,7 +333,8 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
: _FallbackIconBlock(
|
||||
icon:
|
||||
widget.fallbackIcon ??
|
||||
Icons.info_outline_rounded,
|
||||
Icons
|
||||
.info_outline_rounded,
|
||||
color:
|
||||
widget.fallbackColor ??
|
||||
const Color(0xFF2F9E94),
|
||||
@@ -350,8 +368,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
'Ver vídeo (opcional)',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF2F9E94),
|
||||
fontWeight:
|
||||
FontWeight.w800,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -378,8 +395,9 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
: 'Escolha apenas uma opção',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black
|
||||
.withValues(alpha: 0.55),
|
||||
color: Colors.black.withValues(
|
||||
alpha: 0.55,
|
||||
),
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
@@ -387,7 +405,13 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
SizedBox(
|
||||
height:
|
||||
widget.answerType ==
|
||||
QuizAnswerType.image
|
||||
? 6
|
||||
: 18,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
@@ -407,7 +431,14 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
i++
|
||||
) ...[
|
||||
if (i > 0)
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
height:
|
||||
widget.answerType ==
|
||||
QuizAnswerType
|
||||
.image
|
||||
? 5
|
||||
: 12,
|
||||
),
|
||||
FadeSlideIn(
|
||||
delay: Duration(
|
||||
milliseconds: 60 * i,
|
||||
@@ -437,6 +468,22 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
.answers[i],
|
||||
selected:
|
||||
_selected == i,
|
||||
// Perguntas com mais opções de
|
||||
// imagem precisam de blocos mais
|
||||
// compactos para caber sem rolar
|
||||
// — com 2 opções sobra espaço
|
||||
// para um enquadramento mais
|
||||
// vertical (ex.: postura), a não
|
||||
// ser que a pergunta imponha um
|
||||
// aspect ratio específico.
|
||||
imageAspectRatio:
|
||||
widget.answerImageAspectRatio ??
|
||||
(widget
|
||||
.answers
|
||||
.length >=
|
||||
3
|
||||
? 2.3
|
||||
: 1.5),
|
||||
onTap: () =>
|
||||
setState(
|
||||
() =>
|
||||
@@ -449,7 +496,13 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
height:
|
||||
widget.answerType ==
|
||||
QuizAnswerType.image
|
||||
? 8
|
||||
: 24,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
20,
|
||||
@@ -462,7 +515,11 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
TapBounce(
|
||||
child: SizedBox(
|
||||
width: size.width * 0.62,
|
||||
height: 46,
|
||||
height:
|
||||
widget.answerType ==
|
||||
QuizAnswerType.image
|
||||
? 38
|
||||
: 46,
|
||||
child: FilledButton(
|
||||
style:
|
||||
FilledButton.styleFrom(
|
||||
@@ -477,8 +534,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
textStyle:
|
||||
const TextStyle(
|
||||
fontWeight:
|
||||
FontWeight
|
||||
.w900,
|
||||
FontWeight.w900,
|
||||
),
|
||||
).copyWith(
|
||||
animationDuration:
|
||||
@@ -491,16 +547,14 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
WidgetStateProperty.resolveWith<
|
||||
Color?
|
||||
>((states) {
|
||||
if (states
|
||||
.contains(
|
||||
WidgetState
|
||||
.pressed,
|
||||
)) {
|
||||
if (states.contains(
|
||||
WidgetState
|
||||
.pressed,
|
||||
)) {
|
||||
return Colors
|
||||
.white
|
||||
.withValues(
|
||||
alpha:
|
||||
0.14,
|
||||
alpha: 0.14,
|
||||
);
|
||||
}
|
||||
if (states.contains(
|
||||
@@ -514,8 +568,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
return Colors
|
||||
.white
|
||||
.withValues(
|
||||
alpha:
|
||||
0.08,
|
||||
alpha: 0.08,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -529,8 +582,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
true,
|
||||
);
|
||||
int nextScore =
|
||||
widget
|
||||
.currentScore;
|
||||
widget.currentScore;
|
||||
if (widget.answerType ==
|
||||
QuizAnswerType
|
||||
.number) {
|
||||
@@ -587,7 +639,13 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(
|
||||
height:
|
||||
widget.answerType ==
|
||||
QuizAnswerType.image
|
||||
? 0
|
||||
: 6,
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: const Color(
|
||||
@@ -596,10 +654,19 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
padding:
|
||||
widget.answerType ==
|
||||
QuizAnswerType.image
|
||||
? const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 12,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
onPressed: () => Navigator.of(
|
||||
context,
|
||||
).popUntil((route) => route.isFirst),
|
||||
onPressed: () =>
|
||||
Navigator.of(context).popUntil(
|
||||
(route) => route.isFirst,
|
||||
),
|
||||
child: const Text(
|
||||
'Voltar para homepage',
|
||||
),
|
||||
@@ -764,11 +831,13 @@ class _QuizAnswerTile extends StatelessWidget {
|
||||
required this.answer,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
this.imageAspectRatio = 4 / 3,
|
||||
});
|
||||
|
||||
final QuizAnswer answer;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
final double imageAspectRatio;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -810,47 +879,56 @@ class _QuizAnswerTile extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
vertical: answer.hideTitle ? 8 : 14,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (answer.imagePath != null) ...[
|
||||
if (answer.imagePath != null ||
|
||||
answer.imageBuilder != 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,
|
||||
),
|
||||
),
|
||||
aspectRatio: imageAspectRatio,
|
||||
child: answer.imageBuilder != null
|
||||
? answer.imageBuilder!(context)
|
||||
: 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),
|
||||
if (!answer.hideTitle) const SizedBox(height: 10),
|
||||
],
|
||||
Text(
|
||||
answer.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 15,
|
||||
color: Color(0xFF2F9E94),
|
||||
if (!answer.hideTitle)
|
||||
Text(
|
||||
answer.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 15,
|
||||
color: Color(0xFF2F9E94),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -983,13 +1061,13 @@ class _FallbackIconBlock extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
width: 52,
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Icon(icon, size: 32, color: Colors.white),
|
||||
child: Icon(icon, size: 26, color: Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user