Atulização do quiz | Porcentagem na base de dados | Telas de Contrato com o usuario

This commit is contained in:
Carlos Correia
2026-07-13 22:28:01 +01:00
parent 824fdb8089
commit 4fcf3a475a
23 changed files with 2061 additions and 369 deletions

View File

@@ -0,0 +1,168 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
import '../widgets/entrance.dart';
import '../widgets/tap_bounce.dart';
const Color _pink = Color(0xFFFF55A7);
const Color _teal = Color(0xFF2F9E94);
/// Ecrã intersticial informativo, mostrado a meio do quiz para preparar o
/// utilizador antes de continuar (ex.: logo a seguir a um vídeo-guia) —
/// um ícone, um título e uma lista de pontos com visto.
class QuizChecklistScreen extends StatelessWidget {
const QuizChecklistScreen({
super.key,
required this.heading,
required this.items,
required this.onAdvance,
this.icon = Icons.health_and_safety_rounded,
});
final String heading;
final List<String> items;
final void Function(BuildContext context) onAdvance;
final IconData icon;
@override
Widget build(BuildContext context) {
final size = MediaQuery.sizeOf(context);
return Scaffold(
body: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(child: Container(color: const Color(0xFFFAFAF7))),
Positioned(
right: -size.width * 0.40,
bottom: -size.width * 0.45,
child: IgnorePointer(
child: SizedBox(
width: size.width * 1.05,
height: size.width * 1.05,
child: Transform.rotate(
angle: -35 * math.pi / 180,
child: Opacity(
opacity: 0.95,
child: Lottie.asset(
'lottie/Liquid waves.json',
fit: BoxFit.cover,
repeat: true,
),
),
),
),
),
),
SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FadeSlideIn(
child: Container(
width: 76,
height: 76,
decoration: BoxDecoration(
color: _teal.withValues(alpha: 0.12),
shape: BoxShape.circle,
),
child: Icon(icon, color: _teal, size: 36),
),
),
const SizedBox(height: 22),
FadeSlideIn(
delay: const Duration(milliseconds: 60),
child: Text(
heading,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w900,
color: Colors.black87,
height: 1.25,
),
),
),
const SizedBox(height: 22),
for (var i = 0; i < items.length; i++) ...[
if (i > 0) const SizedBox(height: 14),
FadeSlideIn(
delay: Duration(milliseconds: 100 + 40 * i),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 22,
height: 22,
margin: const EdgeInsets.only(top: 1),
decoration: const BoxDecoration(
color: _teal,
shape: BoxShape.circle,
),
child: const Icon(
Icons.check_rounded,
size: 15,
color: Colors.white,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
items[i],
style: TextStyle(
fontSize: 14.5,
fontWeight: FontWeight.w700,
color: Colors.black.withValues(
alpha: 0.75,
),
height: 1.35,
),
),
),
],
),
),
],
],
),
),
),
FadeSlideIn(
delay: const Duration(milliseconds: 260),
child: TapBounce(
child: SizedBox(
width: double.infinity,
height: 52,
child: FilledButton(
style: FilledButton.styleFrom(
backgroundColor: _pink,
foregroundColor: Colors.white,
shape: const StadiumBorder(),
textStyle: const TextStyle(
fontWeight: FontWeight.w800,
fontSize: 16,
),
),
onPressed: () => onAdvance(context),
child: const Text('Avançar'),
),
),
),
),
],
),
),
),
],
),
);
}
}