diff --git a/assets/sfx/confirm.mp3 b/assets/sfx/confirm.mp3 new file mode 100644 index 0000000..7cfc0a8 Binary files /dev/null and b/assets/sfx/confirm.mp3 differ diff --git a/lib/src/screens/home_screen.dart b/lib/src/screens/home_screen.dart index 0a90d4e..0fac4c4 100644 --- a/lib/src/screens/home_screen.dart +++ b/lib/src/screens/home_screen.dart @@ -6,6 +6,8 @@ import '../services/deck_repository.dart'; import '../services/distractor_generator.dart'; import '../widgets/kanji_card.dart'; import '../widgets/options_grid.dart'; +import 'package:audioplayers/audioplayers.dart'; + import 'settings_screen.dart'; class _ReadingInfo { @@ -28,6 +30,7 @@ class _HomeScreenState extends State { String _status = 'Loading deck...'; final DistractorGenerator _dg = DistractorGenerator(); final Random _random = Random(); + final _audioPlayer = AudioPlayer(); QuizMode _mode = QuizMode.kanjiToEnglish; KanjiItem? _current; @@ -109,14 +112,20 @@ class _HomeScreenState extends State { void _nextQuestion() { _deck.sort((a, b) { - final aSrsItem = a.srsItems[_mode.toString()] ?? SrsItem(kanjiId: a.id, quizMode: _mode); - final bSrsItem = b.srsItems[_mode.toString()] ?? SrsItem(kanjiId: b.id, quizMode: _mode); + final aSrsItem = a.srsItems[_mode.toString()]; + final bSrsItem = b.srsItems[_mode.toString()]; - final stageComparison = aSrsItem.srsStage.compareTo(bSrsItem.srsStage); - if (stageComparison != 0) { - return stageComparison; + final aStage = aSrsItem?.srsStage ?? 0; + final bStage = bSrsItem?.srsStage ?? 0; + + if (aStage != bStage) { + return aStage.compareTo(bStage); } - return aSrsItem.lastAsked.compareTo(bSrsItem.lastAsked); + + final aLastAsked = aSrsItem?.lastAsked ?? DateTime.fromMillisecondsSinceEpoch(0); + final bLastAsked = bSrsItem?.lastAsked ?? DateTime.fromMillisecondsSinceEpoch(0); + + return aLastAsked.compareTo(bLastAsked); }); _current = _deck.first; @@ -128,16 +137,19 @@ class _HomeScreenState extends State { switch (_mode) { case QuizMode.kanjiToEnglish: _correctAnswers = [_current!.meanings.first]; - _options = [_correctAnswers.first, ..._dg.generateMeanings(_current!, _deck, 3)] - .map(_toTitleCase) - .toList() + _options = [ + _correctAnswers.first, + ..._dg.generateMeanings(_current!, _deck, 3) + ].map(_toTitleCase).toList() ..shuffle(); break; case QuizMode.englishToKanji: _correctAnswers = [_current!.characters]; - _options = [_correctAnswers.first, ..._dg.generateKanji(_current!, _deck, 3)] - ..shuffle(); + _options = [ + _correctAnswers.first, + ..._dg.generateKanji(_current!, _deck, 3) + ]..shuffle(); break; case QuizMode.reading: @@ -149,10 +161,15 @@ class _HomeScreenState extends State { ? _deck.expand((k) => k.onyomi) : _deck.expand((k) => k.kunyomi); - final distractors = - readingsSource.where((r) => !_correctAnswers.contains(r)).toSet().toList() + final distractors = readingsSource + .where((r) => !_correctAnswers.contains(r)) + .toSet() + .toList() ..shuffle(); - _options = ([_correctAnswers[_random.nextInt(_correctAnswers.length)], ...distractors.take(3)]) + _options = ([ + _correctAnswers[_random.nextInt(_correctAnswers.length)], + ...distractors.take(3) + ]) ..shuffle(); break; } @@ -176,29 +193,31 @@ class _HomeScreenState extends State { var srsItem = current.srsItems[srsKey]; final isNew = srsItem == null; - srsItem ??= SrsItem(kanjiId: current.id, quizMode: _mode, readingType: readingType); - + final srsItemForUpdate = srsItem ??= + SrsItem(kanjiId: current.id, quizMode: _mode, readingType: readingType); setState(() { _asked += 1; if (isCorrect) { _score += 1; - srsItem!.srsStage += 1; + _audioPlayer.play(AssetSource('sfx/confirm.mp3')); } else { - srsItem!.srsStage = max(0, srsItem.srsStage - 1); + srsItemForUpdate.srsStage = max(0, srsItemForUpdate.srsStage - 1); } - srsItem.lastAsked = DateTime.now(); - current.srsItems[srsKey] = srsItem; + srsItemForUpdate.lastAsked = DateTime.now(); + current.srsItems[srsKey] = srsItemForUpdate; }); if (isNew) { - await repo.insertSrsItem(srsItem); + await repo.insertSrsItem(srsItemForUpdate); } else { - await repo.updateSrsItem(srsItem); + await repo.updateSrsItem(srsItemForUpdate); } final correctDisplay = (_mode == QuizMode.kanjiToEnglish) ? _toTitleCase(_correctAnswers.first) - : (_mode == QuizMode.reading ? _correctAnswers.join(', ') : _correctAnswers.first); + : (_mode == QuizMode.reading + ? _correctAnswers.join(', ') + : _correctAnswers.first); final snack = SnackBar( content: Text( @@ -271,7 +290,6 @@ class _HomeScreenState extends State { ], ), const SizedBox(height: 12), - Wrap( spacing: 6, runSpacing: 4, @@ -282,9 +300,7 @@ class _HomeScreenState extends State { _buildChoiceChip('Reading', QuizMode.reading), ], ), - const SizedBox(height: 18), - Expanded( flex: 3, child: Center( @@ -303,9 +319,7 @@ class _HomeScreenState extends State { ), ), ), - const SizedBox(height: 12), - SafeArea( top: false, child: Column( @@ -346,4 +360,4 @@ class _HomeScreenState extends State { backgroundColor: const Color(0xFF1E1E1E), ); } -} \ No newline at end of file +} diff --git a/lib/src/screens/vocab_screen.dart b/lib/src/screens/vocab_screen.dart index 1f53898..33cbb1e 100644 --- a/lib/src/screens/vocab_screen.dart +++ b/lib/src/screens/vocab_screen.dart @@ -6,11 +6,13 @@ import '../services/deck_repository.dart'; import '../services/distractor_generator.dart'; import '../widgets/kanji_card.dart'; import '../widgets/options_grid.dart'; +import 'package:audioplayers/audioplayers.dart'; import 'settings_screen.dart'; class VocabScreen extends StatefulWidget { const VocabScreen({super.key}); + @override State createState() => _VocabScreenState(); } @@ -19,7 +21,7 @@ class _VocabScreenState extends State { bool _loading = false; String _status = 'Loading deck...'; final DistractorGenerator _dg = DistractorGenerator(); - final Random _random = Random(); + final _audioPlayer = AudioPlayer(); VocabQuizMode _mode = VocabQuizMode.vocabToEnglish; VocabularyItem? _current; @@ -138,17 +140,19 @@ class _VocabScreenState extends State { final srsKey = _mode.toString(); - var srsItem = current.srsItems[srsKey]; - final isNew = srsItem == null; - srsItem ??= VocabSrsItem(vocabId: current.id, quizMode: _mode); + var srsItemNullable = current.srsItems[srsKey]; + final isNew = srsItemNullable == null; + final srsItem = + srsItemNullable ?? VocabSrsItem(vocabId: current.id, quizMode: _mode); setState(() { _asked += 1; if (isCorrect) { _score += 1; - srsItem!.srsStage += 1; + _audioPlayer.play(AssetSource('sfx/confirm.mp3')); + srsItem.srsStage += 1; } else { - srsItem!.srsStage = max(0, srsItem.srsStage - 1); + srsItem.srsStage = max(0, srsItem.srsStage - 1); } srsItem.lastAsked = DateTime.now(); current.srsItems[srsKey] = srsItem; diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..1830e5c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..e9abb91 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d0e7d18..2a8f994 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,11 +5,13 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import path_provider_foundation import shared_preferences_foundation import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) diff --git a/pubspec.lock b/pubspec.lock index 37097cf..df4bfef 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: "5441fa0ceb8807a5ad701199806510e56afde2b4913d9d17c2f19f2902cf0ae4" + url: "https://pub.dev" + source: hosted + version: "6.5.1" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" + url: "https://pub.dev" + source: hosted + version: "5.2.1" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 + url: "https://pub.dev" + source: hosted + version: "4.2.1" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" + url: "https://pub.dev" + source: hosted + version: "7.1.1" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7" + url: "https://pub.dev" + source: hosted + version: "4.2.1" boolean_selector: dependency: transitive description: @@ -693,6 +749,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" sqflite: dependency: "direct main" description: @@ -821,6 +885,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 14cf507..6efbdf4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: path: ^1.9.1 provider: ^6.1.5+1 http: ^1.5.0 + audioplayers: ^6.0.0 dev_dependencies: flutter_test: @@ -28,3 +29,5 @@ flutter_icons: flutter: uses-material-design: true + assets: + - assets/sfx/confirm.mp3 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..09e8e2c 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..375535c 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST