add a shit ton of feature for the custom srs
This commit is contained in:
@@ -9,16 +9,23 @@ enum CustomQuizMode { japaneseToEnglish, englishToJapanese, listeningComprehensi
|
||||
class CustomQuizScreen extends StatefulWidget {
|
||||
final List<CustomKanjiItem> deck;
|
||||
final CustomQuizMode quizMode;
|
||||
final Function(CustomKanjiItem) onCardReviewed;
|
||||
final bool useKanji;
|
||||
|
||||
const CustomQuizScreen(
|
||||
{super.key, required this.deck, required this.quizMode});
|
||||
const CustomQuizScreen({
|
||||
super.key,
|
||||
required this.deck,
|
||||
required this.quizMode,
|
||||
required this.onCardReviewed,
|
||||
required this.useKanji,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CustomQuizScreen> createState() => CustomQuizScreenState();
|
||||
}
|
||||
|
||||
class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
||||
with TickerProviderStateMixin {
|
||||
int _currentIndex = 0;
|
||||
List<CustomKanjiItem> _shuffledDeck = [];
|
||||
List<String> _options = [];
|
||||
@@ -27,17 +34,15 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
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();
|
||||
if (_shuffledDeck.isNotEmpty) {
|
||||
_generateOptions();
|
||||
}
|
||||
|
||||
_shakeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
@@ -51,6 +56,16 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(CustomQuizScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.useKanji != oldWidget.useKanji) {
|
||||
setState(() {
|
||||
_generateOptions();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void playAudio() {
|
||||
if (widget.quizMode == CustomQuizMode.listeningComprehension) {
|
||||
_speak(_shuffledDeck[_currentIndex].characters);
|
||||
@@ -60,7 +75,7 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
void _initTts() async {
|
||||
_flutterTts = FlutterTts();
|
||||
await _flutterTts.setLanguage("ja-JP");
|
||||
if (widget.quizMode == CustomQuizMode.listeningComprehension) {
|
||||
if (_shuffledDeck.isNotEmpty && widget.quizMode == CustomQuizMode.listeningComprehension) {
|
||||
_speak(_shuffledDeck[_currentIndex].characters);
|
||||
}
|
||||
}
|
||||
@@ -77,7 +92,7 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
if (widget.quizMode == CustomQuizMode.listeningComprehension || widget.quizMode == CustomQuizMode.japaneseToEnglish) {
|
||||
_options = [currentItem.meaning];
|
||||
} else {
|
||||
_options = [_useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters];
|
||||
_options = [widget.useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters];
|
||||
}
|
||||
final otherItems = widget.deck
|
||||
.where((item) => item.characters != currentItem.characters)
|
||||
@@ -87,7 +102,7 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
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.add(widget.useKanji && otherItems[i].kanji != null ? otherItems[i].kanji! : otherItems[i].characters);
|
||||
}
|
||||
}
|
||||
_options.shuffle();
|
||||
@@ -96,10 +111,22 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
void _checkAnswer(String answer) {
|
||||
final currentItem = _shuffledDeck[_currentIndex];
|
||||
final correctAnswer = (widget.quizMode == CustomQuizMode.englishToJapanese)
|
||||
? (_useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters)
|
||||
? (widget.useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters)
|
||||
: currentItem.meaning;
|
||||
final isCorrect = answer == correctAnswer;
|
||||
|
||||
if (currentItem.useInterval) {
|
||||
if (isCorrect) {
|
||||
currentItem.srsLevel++;
|
||||
final interval = pow(2, currentItem.srsLevel).toInt();
|
||||
currentItem.nextReview = DateTime.now().add(Duration(hours: interval));
|
||||
} else {
|
||||
currentItem.srsLevel = max(0, currentItem.srsLevel - 1);
|
||||
currentItem.nextReview = DateTime.now().add(const Duration(hours: 1));
|
||||
}
|
||||
widget.onCardReviewed(currentItem);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_answered = true;
|
||||
_correct = isCorrect;
|
||||
@@ -139,91 +166,65 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
|
||||
@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!'),
|
||||
),
|
||||
return const Center(
|
||||
child: Text('Review session complete!'),
|
||||
);
|
||||
}
|
||||
|
||||
final currentItem = _shuffledDeck[_currentIndex];
|
||||
final question = (widget.quizMode == CustomQuizMode.englishToJapanese)
|
||||
? currentItem.meaning
|
||||
: (_useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters);
|
||||
: (widget.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();
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
return 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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
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