add custom srs
This commit is contained in:
229
lib/src/screens/custom_quiz_screen.dart
Normal file
229
lib/src/screens/custom_quiz_screen.dart
Normal file
@@ -0,0 +1,229 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math';
|
||||
import 'package:flutter_tts/flutter_tts.dart';
|
||||
import '../models/custom_kanji_item.dart';
|
||||
import '../widgets/options_grid.dart';
|
||||
|
||||
enum CustomQuizMode { japaneseToEnglish, englishToJapanese, listeningComprehension }
|
||||
|
||||
class CustomQuizScreen extends StatefulWidget {
|
||||
final List<CustomKanjiItem> deck;
|
||||
final CustomQuizMode quizMode;
|
||||
|
||||
const CustomQuizScreen(
|
||||
{super.key, required this.deck, required this.quizMode});
|
||||
|
||||
@override
|
||||
State<CustomQuizScreen> createState() => CustomQuizScreenState();
|
||||
}
|
||||
|
||||
class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
||||
int _currentIndex = 0;
|
||||
List<CustomKanjiItem> _shuffledDeck = [];
|
||||
List<String> _options = [];
|
||||
bool _answered = false;
|
||||
bool? _correct;
|
||||
late FlutterTts _flutterTts;
|
||||
late AnimationController _shakeController;
|
||||
late Animation<double> _shakeAnimation;
|
||||
bool _useKanji = false;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_shuffledDeck = widget.deck.toList()..shuffle();
|
||||
_initTts();
|
||||
_generateOptions();
|
||||
|
||||
_shakeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
vsync: this,
|
||||
);
|
||||
_shakeAnimation = Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: _shakeController,
|
||||
curve: Curves.elasticIn,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void playAudio() {
|
||||
if (widget.quizMode == CustomQuizMode.listeningComprehension) {
|
||||
_speak(_shuffledDeck[_currentIndex].characters);
|
||||
}
|
||||
}
|
||||
|
||||
void _initTts() async {
|
||||
_flutterTts = FlutterTts();
|
||||
await _flutterTts.setLanguage("ja-JP");
|
||||
if (widget.quizMode == CustomQuizMode.listeningComprehension) {
|
||||
_speak(_shuffledDeck[_currentIndex].characters);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_flutterTts.stop();
|
||||
_shakeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _generateOptions() {
|
||||
final currentItem = _shuffledDeck[_currentIndex];
|
||||
if (widget.quizMode == CustomQuizMode.listeningComprehension || widget.quizMode == CustomQuizMode.japaneseToEnglish) {
|
||||
_options = [currentItem.meaning];
|
||||
} else {
|
||||
_options = [_useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters];
|
||||
}
|
||||
final otherItems = widget.deck
|
||||
.where((item) => item.characters != currentItem.characters)
|
||||
.toList();
|
||||
otherItems.shuffle();
|
||||
for (var i = 0; i < min(3, otherItems.length); i++) {
|
||||
if (widget.quizMode == CustomQuizMode.listeningComprehension || widget.quizMode == CustomQuizMode.japaneseToEnglish) {
|
||||
_options.add(otherItems[i].meaning);
|
||||
} else {
|
||||
_options.add(_useKanji && otherItems[i].kanji != null ? otherItems[i].kanji! : otherItems[i].characters);
|
||||
}
|
||||
}
|
||||
_options.shuffle();
|
||||
}
|
||||
|
||||
void _checkAnswer(String answer) {
|
||||
final currentItem = _shuffledDeck[_currentIndex];
|
||||
final correctAnswer = (widget.quizMode == CustomQuizMode.englishToJapanese)
|
||||
? (_useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters)
|
||||
: currentItem.meaning;
|
||||
final isCorrect = answer == correctAnswer;
|
||||
|
||||
setState(() {
|
||||
_answered = true;
|
||||
_correct = isCorrect;
|
||||
});
|
||||
|
||||
if (isCorrect) {
|
||||
if (widget.quizMode == CustomQuizMode.japaneseToEnglish ||
|
||||
widget.quizMode == CustomQuizMode.listeningComprehension) {
|
||||
_speak(currentItem.characters);
|
||||
}
|
||||
} else {
|
||||
_shakeController.forward(from: 0);
|
||||
}
|
||||
}
|
||||
|
||||
void _nextQuestion() {
|
||||
setState(() {
|
||||
_currentIndex = (_currentIndex + 1) % _shuffledDeck.length;
|
||||
_answered = false;
|
||||
_correct = null;
|
||||
_generateOptions();
|
||||
});
|
||||
if (widget.quizMode == CustomQuizMode.listeningComprehension) {
|
||||
_speak(_shuffledDeck[_currentIndex].characters);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _speak(String text) async {
|
||||
await _flutterTts.speak(text);
|
||||
}
|
||||
|
||||
void _onOptionSelected(String option) {
|
||||
if (!(_answered && _correct!)) {
|
||||
_checkAnswer(option);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
if (_shuffledDeck.isEmpty) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: const Center(
|
||||
child: Text('No cards in the deck!'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final currentItem = _shuffledDeck[_currentIndex];
|
||||
final question = (widget.quizMode == CustomQuizMode.englishToJapanese)
|
||||
? currentItem.meaning
|
||||
: (_useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Quiz'),
|
||||
actions: [
|
||||
if (widget.quizMode != CustomQuizMode.listeningComprehension)
|
||||
Row(
|
||||
children: [
|
||||
const Text('Kanji'),
|
||||
Switch(
|
||||
value: _useKanji,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_useKanji = value;
|
||||
_generateOptions();
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.quizMode == CustomQuizMode.listeningComprehension)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.volume_up, size: 64),
|
||||
onPressed: () => _speak(currentItem.characters),
|
||||
)
|
||||
else
|
||||
GestureDetector(
|
||||
onTap: () => _speak(question),
|
||||
child: Text(
|
||||
question,
|
||||
style: const TextStyle(fontSize: 48),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
if (_answered)
|
||||
Text(
|
||||
_correct! ? 'Correct!' : 'Incorrect, try again!',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: _correct! ? Colors.green : Colors.red,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
AnimatedBuilder(
|
||||
animation: _shakeAnimation,
|
||||
builder: (context, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(_shakeAnimation.value * 10, 0),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: OptionsGrid(
|
||||
options: _options,
|
||||
onSelected: _onOptionSelected,
|
||||
),
|
||||
),
|
||||
if (_answered && _correct!)
|
||||
ElevatedButton(
|
||||
onPressed: _nextQuestion,
|
||||
child: const Text('Next'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user