more cleanup and small fixes, new sound effects
This commit is contained in:
Binary file not shown.
BIN
assets/sfx/correct.wav
Normal file
BIN
assets/sfx/correct.wav
Normal file
Binary file not shown.
BIN
assets/sfx/incorrect.wav
Normal file
BIN
assets/sfx/incorrect.wav
Normal file
Binary file not shown.
@@ -868,8 +868,9 @@ class _BrowseScreenState extends State<BrowseScreen>
|
|||||||
|
|
||||||
class _VocabDetailsDialog extends StatefulWidget {
|
class _VocabDetailsDialog extends StatefulWidget {
|
||||||
final VocabularyItem vocab;
|
final VocabularyItem vocab;
|
||||||
|
final ThemeData theme;
|
||||||
|
|
||||||
const _VocabDetailsDialog({required this.vocab});
|
const _VocabDetailsDialog({required this.vocab, required this.theme});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_VocabDetailsDialog> createState() => _VocabDetailsDialogState();
|
State<_VocabDetailsDialog> createState() => _VocabDetailsDialogState();
|
||||||
@@ -881,10 +882,10 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchExampleSentences(context);
|
_fetchExampleSentences();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchExampleSentences(BuildContext context) async {
|
Future<void> _fetchExampleSentences() async {
|
||||||
try {
|
try {
|
||||||
final uri = Uri.parse(
|
final uri = Uri.parse(
|
||||||
'https://jisho.org/api/v1/search/words?keyword=${Uri.encodeComponent(widget.vocab.characters)}',
|
'https://jisho.org/api/v1/search/words?keyword=${Uri.encodeComponent(widget.vocab.characters)}',
|
||||||
@@ -911,11 +912,11 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
japaneseWord,
|
japaneseWord,
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
style: TextStyle(color: widget.theme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
englishDefinition,
|
englishDefinition,
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
style: TextStyle(color: widget.theme.colorScheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
@@ -929,7 +930,7 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
|||||||
sentences.add(
|
sentences.add(
|
||||||
Text(
|
Text(
|
||||||
'No example sentences found.',
|
'No example sentences found.',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
style: TextStyle(color: widget.theme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -944,7 +945,7 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
|||||||
_exampleSentences = [
|
_exampleSentences = [
|
||||||
Text(
|
Text(
|
||||||
'Failed to load example sentences.',
|
'Failed to load example sentences.',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
style: TextStyle(color: widget.theme.colorScheme.error),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
@@ -956,7 +957,7 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
|||||||
_exampleSentences = [
|
_exampleSentences = [
|
||||||
Text(
|
Text(
|
||||||
'Error loading example sentences.',
|
'Error loading example sentences.',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
style: TextStyle(color: widget.theme.colorScheme.error),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
@@ -992,39 +993,39 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Level: ${widget.vocab.level}',
|
'Level: ${widget.vocab.level}',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
style: TextStyle(color: widget.theme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (widget.vocab.meanings.isNotEmpty)
|
if (widget.vocab.meanings.isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
'Meanings: ${widget.vocab.meanings.join(', ')}',
|
'Meanings: ${widget.vocab.meanings.join(', ')}',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
style: TextStyle(color: widget.theme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (widget.vocab.readings.isNotEmpty)
|
if (widget.vocab.readings.isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
'Readings: ${widget.vocab.readings.join(', ')}',
|
'Readings: ${widget.vocab.readings.join(', ')}',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
style: TextStyle(color: widget.theme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Divider(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
Divider(color: widget.theme.colorScheme.onSurfaceVariant),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'SRS Scores:',
|
'SRS Scores:',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.bold),
|
style: TextStyle(color: widget.theme.colorScheme.onSurface, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
...srsScores.entries.map(
|
...srsScores.entries.map(
|
||||||
(entry) => Text(
|
(entry) => Text(
|
||||||
' ${entry.key}: ${entry.value}',
|
' ${entry.key}: ${entry.value}',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
style: TextStyle(color: widget.theme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Divider(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
Divider(color: widget.theme.colorScheme.onSurfaceVariant),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Example Sentences:',
|
'Example Sentences:',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.bold),
|
style: TextStyle(color: widget.theme.colorScheme.onSurface, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
..._exampleSentences,
|
..._exampleSentences,
|
||||||
],
|
],
|
||||||
@@ -1034,22 +1035,23 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showVocabDetailsDialog(BuildContext context, VocabularyItem vocab) {
|
void _showVocabDetailsDialog(BuildContext context, VocabularyItem vocab) {
|
||||||
|
final currentTheme = Theme.of(context);
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) {
|
builder: (dialogContext) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
backgroundColor: currentTheme.colorScheme.surfaceContainer,
|
||||||
title: Text(
|
title: Text(
|
||||||
'Details for ${vocab.characters}',
|
'Details for ${vocab.characters}',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
style: TextStyle(color: currentTheme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
content: _VocabDetailsDialog(vocab: vocab),
|
content: _VocabDetailsDialog(vocab: vocab, theme: currentTheme),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Close',
|
'Close',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
style: TextStyle(color: currentTheme.colorScheme.primary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import '../widgets/options_grid.dart';
|
|||||||
import '../widgets/kanji_card.dart';
|
import '../widgets/kanji_card.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../services/tts_service.dart';
|
import '../services/tts_service.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:audioplayers/audioplayers.dart';
|
||||||
|
|
||||||
enum CustomQuizMode {
|
enum CustomQuizMode {
|
||||||
japaneseToEnglish,
|
japaneseToEnglish,
|
||||||
@@ -42,6 +44,11 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
|||||||
late AnimationController _shakeController;
|
late AnimationController _shakeController;
|
||||||
late Animation<double> _shakeAnimation;
|
late Animation<double> _shakeAnimation;
|
||||||
final List<String> _incorrectlyAnsweredItems = [];
|
final List<String> _incorrectlyAnsweredItems = [];
|
||||||
|
final _audioPlayer = AudioPlayer();
|
||||||
|
|
||||||
|
bool _playIncorrectSound = true;
|
||||||
|
bool _playCorrectSound = true;
|
||||||
|
bool _playNarrator = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -58,6 +65,16 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
|||||||
_shakeAnimation = Tween<double>(begin: 0, end: 1).animate(
|
_shakeAnimation = Tween<double>(begin: 0, end: 1).animate(
|
||||||
CurvedAnimation(parent: _shakeController, curve: Curves.elasticIn),
|
CurvedAnimation(parent: _shakeController, curve: Curves.elasticIn),
|
||||||
);
|
);
|
||||||
|
_loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadSettings() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
setState(() {
|
||||||
|
_playIncorrectSound = prefs.getBool('playIncorrectSound') ?? true;
|
||||||
|
_playCorrectSound = prefs.getBool('playCorrectSound') ?? true;
|
||||||
|
_playNarrator = prefs.getBool('playNarrator') ?? true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -88,7 +105,7 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
|||||||
|
|
||||||
void playAudio() async {
|
void playAudio() async {
|
||||||
if (widget.quizMode == CustomQuizMode.listeningComprehension &&
|
if (widget.quizMode == CustomQuizMode.listeningComprehension &&
|
||||||
_currentIndex < _shuffledDeck.length) {
|
_currentIndex < _shuffledDeck.length && _playNarrator) {
|
||||||
final ttsService = Provider.of<TtsService>(context, listen: false);
|
final ttsService = Provider.of<TtsService>(context, listen: false);
|
||||||
await ttsService.speak(_shuffledDeck[_currentIndex].characters);
|
await ttsService.speak(_shuffledDeck[_currentIndex].characters);
|
||||||
}
|
}
|
||||||
@@ -143,8 +160,8 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
|||||||
: currentItem.meaning;
|
: currentItem.meaning;
|
||||||
final isCorrect = answer == correctAnswer;
|
final isCorrect = answer == correctAnswer;
|
||||||
|
|
||||||
|
int currentSrsLevel = 0; // Initialize with a default value
|
||||||
if (currentItem.useInterval) {
|
if (currentItem.useInterval) {
|
||||||
int currentSrsLevel;
|
|
||||||
switch (widget.quizMode) {
|
switch (widget.quizMode) {
|
||||||
case CustomQuizMode.japaneseToEnglish:
|
case CustomQuizMode.japaneseToEnglish:
|
||||||
currentSrsLevel = currentItem.srsData.japaneseToEnglish;
|
currentSrsLevel = currentItem.srsData.japaneseToEnglish;
|
||||||
@@ -234,12 +251,55 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isCorrect) {
|
if (isCorrect) {
|
||||||
if (widget.quizMode == CustomQuizMode.japaneseToEnglish ||
|
if (_incorrectlyAnsweredItems.contains(currentItem.characters)) {
|
||||||
widget.quizMode == CustomQuizMode.englishToJapanese) {
|
_incorrectlyAnsweredItems.remove(currentItem.characters);
|
||||||
await _speak(currentItem.characters);
|
} else {
|
||||||
|
currentSrsLevel++;
|
||||||
|
}
|
||||||
|
final interval = pow(2, currentSrsLevel).toInt();
|
||||||
|
final newNextReview = DateTime.now().add(Duration(hours: interval));
|
||||||
|
switch (widget.quizMode) {
|
||||||
|
case CustomQuizMode.japaneseToEnglish:
|
||||||
|
currentItem.srsData.japaneseToEnglishNextReview = newNextReview;
|
||||||
|
break;
|
||||||
|
case CustomQuizMode.englishToJapanese:
|
||||||
|
currentItem.srsData.englishToJapaneseNextReview = newNextReview;
|
||||||
|
break;
|
||||||
|
case CustomQuizMode.listeningComprehension:
|
||||||
|
currentItem.srsData.listeningComprehensionNextReview =
|
||||||
|
newNextReview;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (_playCorrectSound && !_playNarrator) {
|
||||||
|
await _audioPlayer.play(AssetSource('sfx/correct.wav'));
|
||||||
|
} else if (_playNarrator) {
|
||||||
|
if (widget.quizMode == CustomQuizMode.japaneseToEnglish ||
|
||||||
|
widget.quizMode == CustomQuizMode.englishToJapanese) {
|
||||||
|
await _speak(currentItem.characters);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
} else {
|
} else {
|
||||||
|
if (!_incorrectlyAnsweredItems.contains(currentItem.characters)) {
|
||||||
|
_incorrectlyAnsweredItems.add(currentItem.characters);
|
||||||
|
}
|
||||||
|
currentSrsLevel = max(0, currentSrsLevel - 1);
|
||||||
|
final newNextReview = DateTime.now().add(const Duration(hours: 1));
|
||||||
|
switch (widget.quizMode) {
|
||||||
|
case CustomQuizMode.japaneseToEnglish:
|
||||||
|
currentItem.srsData.japaneseToEnglishNextReview = newNextReview;
|
||||||
|
break;
|
||||||
|
case CustomQuizMode.englishToJapanese:
|
||||||
|
currentItem.srsData.englishToJapaneseNextReview = newNextReview;
|
||||||
|
break;
|
||||||
|
case CustomQuizMode.listeningComprehension:
|
||||||
|
currentItem.srsData.listeningComprehensionNextReview =
|
||||||
|
newNextReview;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (_playIncorrectSound) {
|
||||||
|
await _audioPlayer.play(AssetSource('sfx/incorrect.wav'));
|
||||||
|
}
|
||||||
_shakeController.forward(from: 0);
|
_shakeController.forward(from: 0);
|
||||||
await Future.delayed(const Duration(milliseconds: 900));
|
await Future.delayed(const Duration(milliseconds: 900));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
final _quizStates = [_QuizState(), _QuizState(), _QuizState()];
|
final _quizStates = [_QuizState(), _QuizState(), _QuizState()];
|
||||||
_QuizState get _currentQuizState => _quizStates[_tabController.index];
|
_QuizState get _currentQuizState => _quizStates[_tabController.index];
|
||||||
|
|
||||||
|
bool _playIncorrectSound = true;
|
||||||
bool _playCorrectSound = true;
|
bool _playCorrectSound = true;
|
||||||
bool _apiKeyMissing = false;
|
bool _apiKeyMissing = false;
|
||||||
|
|
||||||
@@ -78,6 +79,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
Future<void> _loadSettings() async {
|
Future<void> _loadSettings() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_playIncorrectSound = prefs.getBool('playIncorrectSound') ?? true;
|
||||||
_playCorrectSound = prefs.getBool('playCorrectSound') ?? true;
|
_playCorrectSound = prefs.getBool('playCorrectSound') ?? true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -311,10 +313,13 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
quizState.score += 1;
|
quizState.score += 1;
|
||||||
srsItemForUpdate.srsStage += 1;
|
srsItemForUpdate.srsStage += 1;
|
||||||
if (_playCorrectSound) {
|
if (_playCorrectSound) {
|
||||||
_audioPlayer.play(AssetSource('sfx/confirm.mp3'));
|
_audioPlayer.play(AssetSource('sfx/correct.wav'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
srsItemForUpdate.srsStage = max(0, srsItemForUpdate.srsStage - 1);
|
srsItemForUpdate.srsStage = max(0, srsItemForUpdate.srsStage - 1);
|
||||||
|
if (_playIncorrectSound) {
|
||||||
|
_audioPlayer.play(AssetSource('sfx/incorrect.wav'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
srsItemForUpdate.lastAsked = DateTime.now();
|
srsItemForUpdate.lastAsked = DateTime.now();
|
||||||
current.srsItems[srsKey] = srsItemForUpdate;
|
current.srsItems[srsKey] = srsItemForUpdate;
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ class SettingsScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _SettingsScreenState extends State<SettingsScreen> {
|
class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
final TextEditingController _apiKeyController = TextEditingController();
|
final TextEditingController _apiKeyController = TextEditingController();
|
||||||
bool _playAudio = true;
|
bool _playIncorrectSound = true;
|
||||||
bool _playCorrectSound = true;
|
bool _playCorrectSound = true;
|
||||||
|
bool _playNarrator = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -39,8 +40,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
Future<void> _loadSettings() async {
|
Future<void> _loadSettings() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
setState(() {
|
setState(() {
|
||||||
_playAudio = prefs.getBool('playAudio') ?? true;
|
_playIncorrectSound = prefs.getBool('playIncorrectSound') ?? true;
|
||||||
_playCorrectSound = prefs.getBool('playCorrectSound') ?? true;
|
_playCorrectSound = prefs.getBool('playCorrectSound') ?? true;
|
||||||
|
_playNarrator = prefs.getBool('playNarrator') ?? true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,17 +110,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
'Play audio for vocabulary',
|
'Play incorrect sound',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
value: _playAudio,
|
value: _playIncorrectSound,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
prefs.setBool('playAudio', value);
|
prefs.setBool('playIncorrectSound', value);
|
||||||
setState(() {
|
setState(() {
|
||||||
_playAudio = value;
|
_playIncorrectSound = value;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
activeThumbColor: Theme.of(context).colorScheme.primary,
|
activeThumbColor: Theme.of(context).colorScheme.primary,
|
||||||
@@ -133,7 +135,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
'Play sound on correct answer',
|
'Play correct sound',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
@@ -156,6 +158,31 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(
|
||||||
|
'Play narrator (TTS)',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
value: _playNarrator,
|
||||||
|
onChanged: (value) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
prefs.setBool('playNarrator', value);
|
||||||
|
setState(() {
|
||||||
|
_playNarrator = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
activeThumbColor: Theme.of(context).colorScheme.primary,
|
||||||
|
inactiveThumbColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant,
|
||||||
|
tileColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
'Theme',
|
'Theme',
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
final _quizStates = [_QuizState(), _QuizState(), _QuizState()];
|
final _quizStates = [_QuizState(), _QuizState(), _QuizState()];
|
||||||
_QuizState get _currentQuizState => _quizStates[_tabController.index];
|
_QuizState get _currentQuizState => _quizStates[_tabController.index];
|
||||||
|
|
||||||
bool _playAudio = true;
|
bool _playIncorrectSound = true;
|
||||||
bool _playCorrectSound = true;
|
bool _playCorrectSound = true;
|
||||||
|
bool _playNarrator = true;
|
||||||
bool _apiKeyMissing = false;
|
bool _apiKeyMissing = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -72,8 +73,9 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
Future<void> _loadSettings() async {
|
Future<void> _loadSettings() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
setState(() {
|
setState(() {
|
||||||
_playAudio = prefs.getBool('playAudio') ?? true;
|
_playIncorrectSound = prefs.getBool('playIncorrectSound') ?? true;
|
||||||
_playCorrectSound = prefs.getBool('playCorrectSound') ?? true;
|
_playCorrectSound = prefs.getBool('playCorrectSound') ?? true;
|
||||||
|
_playNarrator = prefs.getBool('playNarrator') ?? true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +224,7 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
final current = _currentQuizState.current;
|
final current = _currentQuizState.current;
|
||||||
if (current == null || current.pronunciationAudios.isEmpty) return;
|
if (current == null || current.pronunciationAudios.isEmpty) return;
|
||||||
|
|
||||||
if (playOnLoad && !_playAudio) return;
|
if (playOnLoad && !_playNarrator) return;
|
||||||
|
|
||||||
final maleAudios = current.pronunciationAudios.where(
|
final maleAudios = current.pronunciationAudios.where(
|
||||||
(a) => a.gender == 'male',
|
(a) => a.gender == 'male',
|
||||||
@@ -263,6 +265,9 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
srsItem.srsStage += 1;
|
srsItem.srsStage += 1;
|
||||||
} else {
|
} else {
|
||||||
srsItem.srsStage = max(0, srsItem.srsStage - 1);
|
srsItem.srsStage = max(0, srsItem.srsStage - 1);
|
||||||
|
if (_playIncorrectSound) {
|
||||||
|
await _audioPlayer.play(AssetSource('sfx/incorrect.wav'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
srsItem.lastAsked = DateTime.now();
|
srsItem.lastAsked = DateTime.now();
|
||||||
current.srsItems[srsKey] = srsItem;
|
current.srsItems[srsKey] = srsItem;
|
||||||
@@ -294,10 +299,9 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(snack);
|
ScaffoldMessenger.of(context).showSnackBar(snack);
|
||||||
|
|
||||||
if (isCorrect) {
|
if (isCorrect) {
|
||||||
if (_playCorrectSound) {
|
if (_playCorrectSound && !_playNarrator) {
|
||||||
await _audioPlayer.play(AssetSource('sfx/confirm.mp3'));
|
await _audioPlayer.play(AssetSource('sfx/correct.wav'));
|
||||||
}
|
} else if (_playNarrator) {
|
||||||
if (_playAudio) {
|
|
||||||
final maleAudios = current.pronunciationAudios.where(
|
final maleAudios = current.pronunciationAudios.where(
|
||||||
(a) => a.gender == 'male',
|
(a) => a.gender == 'male',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,4 +34,5 @@ flutter_icons:
|
|||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
- assets/sfx/confirm.mp3
|
- assets/sfx/correct.wav
|
||||||
|
- assets/sfx/incorrect.wav
|
||||||
Reference in New Issue
Block a user