622 lines
27 KiB
Dart
Executable File
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |