add custom srs

This commit is contained in:
Rene Kievits
2025-10-30 02:00:29 +01:00
parent fe5ac30294
commit b58a4020e1
12 changed files with 802 additions and 250 deletions

View 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'),
),
],
),
),
);
}
}