kz_educa/lib/screens/quiz_screen.dart

622 lines
27 KiB
Dart
Executable File

// lib/screens/quiz_screen.dart
// (Manter as importações)
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:kzeduca_app/services/i18n_service.dart';
import 'package:kzeduca_app/services/firebase_service.dart';
import 'package:kzeduca_app/models/app_models.dart';
import 'dart:math';
import 'daily_quiz_screen.dart'; // Importar a nova tela do quiz diário
class QuizScreen extends StatefulWidget {
const QuizScreen({super.key});
@override
State<QuizScreen> createState() => _QuizScreenState();
}
class _QuizScreenState extends State<QuizScreen> {
List<QuizQuestion> _allQuestions = [];
List<QuizQuestion> _currentQuizQuestions = [];
List<UserQuizResult> _quizResults = [];
bool _isLoading = true;
String _error = '';
bool _quizStarted = false;
int _currentQuestionIndex = 0;
int _score = 0;
String? _selectedOption;
bool _showAnswerFeedback = false;
bool _quizFinished = false;
FinancialChallenge? _dailyChallengeNotification;
@override
void initState() {
super.initState();
_loadAllQuizData();
_checkDailyChallengeStatus();
}
// (Manter _showAlertDialog e _checkDailyChallengeStatus)
void _showAlertDialog(String message, {bool isError = false}) {
final i18n = Provider.of<I18nService>(context, listen: false);
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(isError ? i18n.t('error_prefix') : 'Info'),
content: Text(message),
actions: <Widget>[
TextButton(
child: const Text('Ok'),
onPressed: () {
Navigator.of(ctx).pop();
},
)
],
),
);
}
Future<void> _checkDailyChallengeStatus() async {
final prefs = await SharedPreferences.getInstance();
final lastCompletionDate = prefs.getString('daily_quiz_completion_date');
final today = DateFormat('yyyy-MM-dd').format(DateTime.now());
// A notificação deve aparecer se nunca foi completada hoje
if (lastCompletionDate != today) {
final firebaseService = Provider.of<FirebaseService>(context, listen: false);
try {
final allChallenges = await firebaseService.getAllChallenges();
if (allChallenges.isNotEmpty) {
final random = Random();
setState(() {
_dailyChallengeNotification = allChallenges[random.nextInt(allChallenges.length)];
});
}
} catch (e) {
print("Erro ao carregar desafios para notificação: $e");
}
} else {
setState(() {
_dailyChallengeNotification = null;
});
}
}
Future<void> _loadAllQuizData() async {
final firebaseService = Provider.of<FirebaseService>(context, listen: false);
final userId = FirebaseAuth.instance.currentUser?.uid;
setState(() {
_isLoading = true;
_error = '';
});
try {
final querySnapshot = await firebaseService.firestore
.collection('artifacts')
.doc('default-kzeduca-app')
.collection('public')
.doc('data')
.collection('quizQuestions')
.get();
_allQuestions = querySnapshot.docs.map((doc) => QuizQuestion.fromFirestore(doc)).toList();
// Carrega resultados do utilizador
firebaseService.streamUserQuizResults(userId!).listen((snapshot) {
if (mounted) {
setState(() {
_quizResults = snapshot.docs.map((doc) => UserQuizResult.fromFirestore(doc)).toList();
_quizResults.sort((a, b) => DateTime.parse(b.timestamp).compareTo(DateTime.parse(a.timestamp)));
});
}
});
} catch (e) {
print("Erro geral ao carregar dados do quiz: $e");
if (mounted) {
_showAlertDialog("Erro ao carregar dados do quiz.", isError: true);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
void _startRandomQuiz() {
if (_allQuestions.length < 5) {
_showAlertDialog('Não há perguntas suficientes para começar o quiz.');
return;
}
_allQuestions.shuffle();
_currentQuizQuestions = _allQuestions.take(5).toList();
setState(() {
_quizStarted = true;
_quizFinished = false;
_currentQuestionIndex = 0;
_score = 0;
_selectedOption = null;
_showAnswerFeedback = false;
});
}
// Resto da lógica do quiz (handleAnswer, handleNextQuestion, saveQuizResult)
Future<void> _handleAnswer(String option) async {
if (_showAnswerFeedback) return;
setState(() {
_selectedOption = option;
_showAnswerFeedback = true;
if (option == _currentQuizQuestions[_currentQuestionIndex].answer) {
_score++;
}
});
// Lógica para ir para a próxima pergunta
await Future.delayed(const Duration(seconds: 2));
if (mounted) {
_handleNextQuestion();
}
}
Future<void> _handleNextQuestion() async {
setState(() {
_selectedOption = null;
_showAnswerFeedback = false;
});
if (_currentQuestionIndex < _currentQuizQuestions.length - 1) {
setState(() {
_currentQuestionIndex++;
});
} else {
await _saveQuizResult();
setState(() {
_quizStarted = false;
_quizFinished = true;
});
}
}
Future<void> _saveQuizResult() async {
final i18n = Provider.of<I18nService>(context, listen: false);
final firebaseService = Provider.of<FirebaseService>(context, listen: false);
final userId = FirebaseAuth.instance.currentUser?.uid;
if (userId == null || _currentQuizQuestions.isEmpty) {
_showAlertDialog(i18n.t('quiz_save_error'), isError: true);
return;
}
try {
await firebaseService.saveQuizResult(userId, {
'score': _score,
'totalQuestions': _currentQuizQuestions.length,
'timestamp': DateTime.now().toIso8601String(),
});
_showAlertDialog(i18n.t('quiz_saved'));
} catch (e) {
print("Erro ao salvar resultado do quiz: $e");
_showAlertDialog(i18n.t('quiz_save_error'), isError: true);
}
}
void _showDailyChallengeNotificationDialog(FinancialChallenge challenge) {
final i18n = Provider.of<I18nService>(context, listen: false);
showDialog(
context: context,
builder: (ctx) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: Colors.purple[900]?.withOpacity(0.8),
title: Text(
i18n.t('bonus_daily_challenge'),
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(Icons.star, color: Colors.amber, size: 40),
const SizedBox(height: 10),
Text(
'${i18n.t('congratulations_quiz_bonus_intro')}!',
style: const TextStyle(color: Colors.white, fontSize: 16),
),
const SizedBox(height: 20),
Text(
challenge.nome,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
const SizedBox(height: 5),
Text(
challenge.meta,
style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 14),
),
],
),
actions: <Widget>[
TextButton(
child: Text(
i18n.t('accept_challenge'),
style: const TextStyle(color: Colors.amber, fontWeight: FontWeight.bold),
),
onPressed: () {
Navigator.of(ctx).pop();
},
),
TextButton(
child: Text(
i18n.t('later'),
style: TextStyle(color: Colors.white.withOpacity(0.7)),
),
onPressed: () {
Navigator.of(ctx).pop();
},
),
],
),
);
}
@override
Widget build(BuildContext context) {
final i18n = Provider.of<I18nService>(context);
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
title: Text(i18n.t('financial_quiz')),
backgroundColor: Colors.black.withOpacity(0.5),
foregroundColor: Colors.white,
elevation: 0,
),
body: Stack(
children: [
Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/quiz_background.png'),
fit: BoxFit.cover,
),
),
),
Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Container(
padding: const EdgeInsets.all(32.0),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: BorderRadius.circular(24.0),
border: Border.all(color: Colors.white.withOpacity(0.2)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
blurRadius: 32,
offset: const Offset(0, 8),
),
],
),
constraints: const BoxConstraints(maxWidth: 600),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (_dailyChallengeNotification != null)
GestureDetector(
onTap: () {
// Navegar para a tela do quiz diário
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DailyQuizScreen(
dailyChallenge: _dailyChallengeNotification!,
),
),
).then((_) => _checkDailyChallengeStatus()); // Atualiza o estado ao voltar
},
child: Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(bottom: 24),
decoration: BoxDecoration(
color: Colors.purple[800]?.withOpacity(0.8),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.amber, width: 2),
),
child: Row(
children: [
const Icon(Icons.notifications_active, color: Colors.amber, size: 30),
const SizedBox(width: 16),
Expanded(
child: Text(
i18n.t('daily_challenge_notification'),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
const Icon(Icons.arrow_forward_ios, color: Colors.white, size: 16),
],
),
),
),
Text(
i18n.t('test_knowledge'),
style: TextStyle(fontSize: 16, color: Colors.white.withOpacity(0.7)),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
if (_isLoading)
const Center(child: CircularProgressIndicator())
else if (_allQuestions.isEmpty)
Text(
'Nenhuma pergunta de quiz disponível. Por favor, adicione perguntas no Firestore.',
style: TextStyle(color: Colors.white.withOpacity(0.8)),
textAlign: TextAlign.center,
)
else if (!_quizStarted && !_quizFinished)
Column(
children: [
Icon(Icons.lightbulb_outline, size: 80, color: Colors.teal[400]),
const SizedBox(height: 16),
Text(
i18n.t('ready_for_challenge'),
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white),
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Text(
'Teste os seus conhecimentos sobre finanças de forma divertida e interativa!',
style: TextStyle(fontSize: 16, color: Colors.white.withOpacity(0.7)),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: _startRandomQuiz, // Inicia o quiz aleatório de 5 perguntas
icon: const Icon(Icons.play_arrow),
label: Text(i18n.t('start_quiz')),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal[500],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 30),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 8,
shadowColor: Colors.teal[300]?.withOpacity(0.5),
),
),
const SizedBox(height: 30),
Text(
i18n.t('quiz_results_history'),
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
_quizResults.isEmpty
? Text(i18n.t('no_quiz_results'), style: TextStyle(color: Colors.white.withOpacity(0.6)), textAlign: TextAlign.center)
: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _quizResults.length,
itemBuilder: (context, index) {
final result = _quizResults[index];
return Card(
elevation: 2,
margin: const EdgeInsets.symmetric(vertical: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
color: Colors.black.withOpacity(0.4),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
i18n.t('your_score', options: {
'score': result.score.toString(),
'total': result.totalQuestions.toString(),
}),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
),
Text(
'Em: ${DateFormat('dd/MM/yyyy HH:mm').format(DateTime.parse(result.timestamp).toLocal())}',
style: TextStyle(fontSize: 14, color: Colors.white.withOpacity(0.7)),
),
if (result.score / result.totalQuestions >= 0.7)
Align(
alignment: Alignment.bottomRight,
child: Text('Parabéns! Ótimo resultado!', style: TextStyle(color: Colors.green[300], fontStyle: FontStyle.italic)),
)
],
),
),
);
},
),
],
)
else if (_quizStarted && !_quizFinished)
Column(
children: [
LinearProgressIndicator(
value: (_currentQuestionIndex + 1) / _currentQuizQuestions.length,
backgroundColor: Colors.white.withOpacity(0.3),
color: Colors.teal[400],
minHeight: 10,
borderRadius: BorderRadius.circular(5),
),
const SizedBox(height: 16),
Text(
'Questão ${_currentQuestionIndex + 1} de ${_currentQuizQuestions.length}',
style: TextStyle(fontSize: 14, color: Colors.white.withOpacity(0.6)),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
_currentQuizQuestions[_currentQuestionIndex].question,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
..._currentQuizQuestions[_currentQuestionIndex].options.map((option) {
bool isCorrect = option == _currentQuizQuestions[_currentQuestionIndex].answer;
bool isSelected = option == _selectedOption;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ElevatedButton(
onPressed: _showAnswerFeedback ? null : () => _handleAnswer(option),
style: ElevatedButton.styleFrom(
backgroundColor: _showAnswerFeedback
? (isCorrect ? Colors.green[500] : Colors.red[500])
: Colors.black.withOpacity(0.4),
foregroundColor: Colors.white,
side: BorderSide(
color: _showAnswerFeedback
? (isCorrect ? Colors.green[400]! : Colors.red[400]!)
: Colors.white.withOpacity(0.3),
width: 1,
),
padding: const EdgeInsets.all(16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: isSelected ? 4 : 2,
),
child: Align(
alignment: Alignment.centerLeft,
child: Text(option, style: const TextStyle(fontSize: 16)),
),
),
);
}),
if (_showAnswerFeedback) ...[
const SizedBox(height: 16),
Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
color: _selectedOption == _currentQuizQuestions[_currentQuestionIndex].answer
? Colors.green[900]?.withOpacity(0.6)
: Colors.red[900]?.withOpacity(0.6),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_selectedOption == _currentQuizQuestions[_currentQuestionIndex].answer
? i18n.t('correct')
: i18n.t('incorrect'),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
_currentQuizQuestions[_currentQuestionIndex].explanation,
style: TextStyle(fontSize: 16, color: Colors.white.withOpacity(0.9)),
),
],
),
),
),
],
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: _showAnswerFeedback ? _handleNextQuestion : null,
icon: Icon(_currentQuestionIndex < _currentQuizQuestions.length - 1 ? Icons.arrow_forward : Icons.bar_chart),
label: Text(
_currentQuestionIndex < _currentQuizQuestions.length - 1
? i18n.t('next_question')
: i18n.t('view_results'),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue[500],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 8,
shadowColor: Colors.blue[300]?.withOpacity(0.5),
),
),
],
)
else if (_quizFinished)
Column(
children: [
Icon(Icons.emoji_events, size: 80, color: Colors.amber[600]),
const SizedBox(height: 16),
Text(
i18n.t('quiz_finished'),
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white),
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Text(
i18n.t('your_final_score', options: {
'score': _score.toString(),
'total': _currentQuizQuestions.length.toString(),
}),
style: TextStyle(fontSize: 20, color: Colors.teal[300], fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: () {
_startRandomQuiz();
},
icon: const Icon(Icons.refresh),
label: Text(i18n.t('retake_quiz')),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal[500],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 30),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 8,
shadowColor: Colors.teal[300]?.withOpacity(0.5),
),
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () {
_quizFinished = false;
_quizStarted = false;
setState(() {});
},
icon: const Icon(Icons.history),
label: Text(i18n.t('view_all_results')),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueGrey[500],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 30),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 8,
),
),
],
),
],
),
),
),
),
],
),
);
}
}