possible to exclude levels and change how questions are served
This commit is contained in:
@@ -6,6 +6,7 @@ class SrsItem {
|
||||
final String? readingType;
|
||||
int srsStage;
|
||||
DateTime lastAsked;
|
||||
bool disabled;
|
||||
|
||||
SrsItem({
|
||||
required this.subjectId,
|
||||
@@ -13,5 +14,6 @@ class SrsItem {
|
||||
this.readingType,
|
||||
this.srsStage = 0,
|
||||
DateTime? lastAsked,
|
||||
this.disabled = false,
|
||||
}) : lastAsked = lastAsked ?? DateTime.now();
|
||||
}
|
||||
|
||||
@@ -123,9 +123,14 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(color: Theme.of(context).colorScheme.primary),
|
||||
CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(_status, style: TextStyle(color: Theme.of(context).colorScheme.onSurface)),
|
||||
Text(
|
||||
_status,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -151,6 +156,7 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
List<int> sortedLevels,
|
||||
PageController pageController,
|
||||
Widget Function(List<dynamic>) buildPageContent,
|
||||
dynamic repository,
|
||||
) {
|
||||
if (sortedLevels.isEmpty) {
|
||||
return Center(
|
||||
@@ -167,18 +173,34 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
itemBuilder: (context, index) {
|
||||
final level = sortedLevels[index];
|
||||
final levelItems = groupedItems[level]!;
|
||||
final bool isDisabled = levelItems.every(
|
||||
(item) => (item as dynamic).srsItems.values.every(
|
||||
(srs) => (srs as SrsItem).disabled,
|
||||
),
|
||||
);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
'Level $level',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Level $level',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: !isDisabled,
|
||||
onChanged: (value) {
|
||||
_toggleLevelExclusion(level, repository);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(child: buildPageContent(levelItems)),
|
||||
@@ -199,31 +221,55 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
height: 60,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(levels.length, (index) {
|
||||
final level = levels[index];
|
||||
final isSelected = index == currentPage;
|
||||
final items = isKanji ? _kanjiByLevel[level] : _vocabByLevel[level];
|
||||
final bool isDisabled =
|
||||
items?.every(
|
||||
(item) => (item as dynamic).srsItems.values.every(
|
||||
(srs) => (srs as SrsItem).disabled,
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
controller.animateToPage(
|
||||
index,
|
||||
|
||||
duration: const Duration(milliseconds: 300),
|
||||
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
},
|
||||
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: isSelected
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: isDisabled
|
||||
? Theme.of(context).colorScheme.surfaceVariant
|
||||
: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
|
||||
foregroundColor: isSelected
|
||||
? Theme.of(context).colorScheme.onPrimary
|
||||
: isDisabled
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
|
||||
padding: const EdgeInsets.all(12),
|
||||
),
|
||||
|
||||
child: Text(level.toString()),
|
||||
),
|
||||
),
|
||||
@@ -295,7 +341,10 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.characters,
|
||||
style: TextStyle(fontSize: 24, color: Theme.of(context).colorScheme.onSurface),
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
@@ -306,7 +355,9 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
children: [
|
||||
Text(
|
||||
item.meanings.join(', '),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -355,7 +406,10 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
children: [
|
||||
Text(
|
||||
item.characters,
|
||||
style: TextStyle(fontSize: 32, color: Theme.of(context).colorScheme.onSurface),
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -375,7 +429,9 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
height: 10,
|
||||
child: LinearProgressIndicator(
|
||||
value: level / 9.0,
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerHighest,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
_getColorForSrsLevel(level),
|
||||
),
|
||||
@@ -444,29 +500,39 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
children: [
|
||||
Text(
|
||||
'Level: ${kanji.level}',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (kanji.meanings.isNotEmpty)
|
||||
Text(
|
||||
'Meanings: ${kanji.meanings.join(', ')}',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (kanji.onyomi.isNotEmpty)
|
||||
Text(
|
||||
'On\'yomi: ${kanji.onyomi.join(', ')}',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
if (kanji.kunyomi.isNotEmpty)
|
||||
Text(
|
||||
'Kun\'yomi: ${kanji.kunyomi.join(', ')}',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
if (kanji.onyomi.isEmpty && kanji.kunyomi.isEmpty)
|
||||
Text(
|
||||
'No readings available.',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Divider(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
@@ -481,7 +547,9 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
...srsScores.entries.map(
|
||||
(entry) => Text(
|
||||
' ${entry.key}: ${entry.value}',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -568,6 +636,42 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
_vocabSortedLevels = _vocabByLevel.keys.toList()..sort();
|
||||
}
|
||||
|
||||
Future<void> _toggleLevelExclusion(int level, dynamic repository) async {
|
||||
final List<SrsItem> itemsToUpdate = [];
|
||||
final bool currentlyDisabled;
|
||||
|
||||
if (repository is DeckRepository) {
|
||||
final items = _kanjiByLevel[level] ?? [];
|
||||
currentlyDisabled = items.every(
|
||||
(item) => item.srsItems.values.every((srs) => srs.disabled),
|
||||
);
|
||||
for (final item in items) {
|
||||
for (final srsItem in item.srsItems.values) {
|
||||
itemsToUpdate.add(srsItem);
|
||||
}
|
||||
}
|
||||
} else if (repository is VocabDeckRepository) {
|
||||
final items = _vocabByLevel[level] ?? [];
|
||||
currentlyDisabled = items.every(
|
||||
(item) => item.srsItems.values.every((srs) => srs.disabled),
|
||||
);
|
||||
for (final item in items) {
|
||||
for (final srsItem in item.srsItems.values) {
|
||||
itemsToUpdate.add(srsItem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final item in itemsToUpdate) {
|
||||
item.disabled = !currentlyDisabled;
|
||||
}
|
||||
|
||||
await repository.updateSrsItems(itemsToUpdate);
|
||||
_loadDecks();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -586,6 +690,7 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
_kanjiSortedLevels,
|
||||
_kanjiPageController,
|
||||
(items) => _buildGridView(items.cast<KanjiItem>()),
|
||||
Provider.of<DeckRepository>(context, listen: false),
|
||||
),
|
||||
),
|
||||
_buildWaniKaniTab(
|
||||
@@ -594,6 +699,7 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
_vocabSortedLevels,
|
||||
_vocabPageController,
|
||||
(items) => _buildListView(items.cast<VocabularyItem>()),
|
||||
Provider.of<VocabDeckRepository>(context, listen: false),
|
||||
),
|
||||
),
|
||||
_buildCustomSrsTab(),
|
||||
@@ -673,8 +779,7 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
setState(() {
|
||||
if (_selectedItems.length == _customDeck.length) {
|
||||
_selectedItems.clear();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
_selectedItems = List.from(_customDeck);
|
||||
}
|
||||
});
|
||||
@@ -799,12 +904,17 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: isSelected
|
||||
? BorderSide(color: Theme.of(context).colorScheme.primary, width: 2.0)
|
||||
? BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2.0,
|
||||
)
|
||||
: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.primary.withAlpha((255 * 0.5).round())
|
||||
? Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withAlpha((255 * 0.5).round())
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: Stack(
|
||||
children: [
|
||||
@@ -854,7 +964,11 @@ class _BrowseScreenState extends State<BrowseScreen>
|
||||
Positioned(
|
||||
top: 4,
|
||||
right: 4,
|
||||
child: Icon(Icons.timer, color: Theme.of(context).colorScheme.tertiary, size: 16),
|
||||
child: Icon(
|
||||
Icons.timer,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -912,11 +1026,15 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
||||
children: [
|
||||
Text(
|
||||
japaneseWord,
|
||||
style: TextStyle(color: widget.theme.colorScheme.onSurface),
|
||||
style: TextStyle(
|
||||
color: widget.theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
englishDefinition,
|
||||
style: TextStyle(color: widget.theme.colorScheme.onSurfaceVariant),
|
||||
style: TextStyle(
|
||||
color: widget.theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
@@ -1012,7 +1130,10 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'SRS Scores:',
|
||||
style: TextStyle(color: widget.theme.colorScheme.onSurface, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
color: widget.theme.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
...srsScores.entries.map(
|
||||
(entry) => Text(
|
||||
@@ -1025,7 +1146,10 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Example Sentences:',
|
||||
style: TextStyle(color: widget.theme.colorScheme.onSurface, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
color: widget.theme.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
..._exampleSentences,
|
||||
],
|
||||
@@ -1059,4 +1183,3 @@ void _showVocabDetailsDialog(BuildContext context, VocabularyItem vocab) {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
bool _playCorrectSound = true;
|
||||
bool _playNarrator = true;
|
||||
|
||||
String? _selectedOption;
|
||||
String? _correctAnswer;
|
||||
bool _showResult = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -148,6 +152,9 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
);
|
||||
}
|
||||
}
|
||||
while (_options.length < 4) {
|
||||
_options.add('---');
|
||||
}
|
||||
_options.shuffle();
|
||||
}
|
||||
|
||||
@@ -160,6 +167,12 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
: currentItem.meaning;
|
||||
final isCorrect = answer == correctAnswer;
|
||||
|
||||
setState(() {
|
||||
_selectedOption = answer;
|
||||
_correctAnswer = correctAnswer;
|
||||
_showResult = true;
|
||||
});
|
||||
|
||||
int currentSrsLevel = 0; // Initialize with a default value
|
||||
if (currentItem.useInterval) {
|
||||
switch (widget.quizMode) {
|
||||
@@ -239,7 +252,9 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
content: Text(
|
||||
isCorrect ? 'Correct!' : 'Wrong — correct: $correctDisplay',
|
||||
style: TextStyle(
|
||||
color: isCorrect ? Theme.of(context).colorScheme.tertiary : Theme.of(context).colorScheme.error,
|
||||
color: isCorrect
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: Theme.of(context).colorScheme.error,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -312,6 +327,9 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
|
||||
_currentIndex++;
|
||||
_answered = false;
|
||||
_correct = null;
|
||||
_selectedOption = null;
|
||||
_correctAnswer = null;
|
||||
_showResult = false;
|
||||
if (_currentIndex < _shuffledDeck.length) {
|
||||
_generateOptions();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class _QuizState {
|
||||
Key key = UniqueKey();
|
||||
String? selectedOption;
|
||||
bool showResult = false;
|
||||
Set<int> wrongItems = {};
|
||||
}
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
@@ -52,6 +53,8 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
final _audioPlayer = AudioPlayer();
|
||||
|
||||
final _quizStates = [_QuizState(), _QuizState(), _QuizState()];
|
||||
final _sessionDecks = <int, List<KanjiItem>>{};
|
||||
final _sessionDeckSizes = <int, int>{};
|
||||
_QuizState get _currentQuizState => _quizStates[_tabController.index];
|
||||
|
||||
bool _playIncorrectSound = true;
|
||||
@@ -118,6 +121,44 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
_apiKeyMissing = false;
|
||||
});
|
||||
|
||||
final disabledLevels = <int>{};
|
||||
final itemsByLevel = <int, List<KanjiItem>>{};
|
||||
for (final item in _deck) {
|
||||
(itemsByLevel[item.level] ??= []).add(item);
|
||||
}
|
||||
|
||||
itemsByLevel.forEach((level, items) {
|
||||
final allSrsItems = items.expand((item) => item.srsItems.values).toList();
|
||||
if (allSrsItems.isNotEmpty && allSrsItems.every((srs) => srs.disabled)) {
|
||||
disabledLevels.add(level);
|
||||
}
|
||||
});
|
||||
|
||||
for (var i = 0; i < _tabController.length; i++) {
|
||||
final mode = _modeForIndex(i);
|
||||
final filteredDeck = _deck.where((item) {
|
||||
if (disabledLevels.contains(item.level)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode == QuizMode.reading) {
|
||||
final onyomiSrs = item.srsItems['${QuizMode.reading}onyomi'];
|
||||
final kunyomiSrs = item.srsItems['${QuizMode.reading}kunyomi'];
|
||||
final hasOnyomi = item.onyomi.isNotEmpty &&
|
||||
(onyomiSrs == null || !onyomiSrs.disabled);
|
||||
final hasKunyomi = item.kunyomi.isNotEmpty &&
|
||||
(kunyomiSrs == null || !kunyomiSrs.disabled);
|
||||
return hasOnyomi || hasKunyomi;
|
||||
}
|
||||
final srsItem = item.srsItems[mode.toString()];
|
||||
return srsItem == null || !srsItem.disabled;
|
||||
}).toList();
|
||||
|
||||
filteredDeck.shuffle(_random);
|
||||
_sessionDecks[i] = filteredDeck;
|
||||
_sessionDeckSizes[i] = filteredDeck.length;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _tabController.length; i++) {
|
||||
_nextQuestion(i);
|
||||
}
|
||||
@@ -164,61 +205,20 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
}
|
||||
|
||||
void _nextQuestion([int? index]) {
|
||||
if (_deck.isEmpty) return;
|
||||
final tabIndex = index ?? _tabController.index;
|
||||
final quizState = _quizStates[tabIndex];
|
||||
final sessionDeck = _sessionDecks[tabIndex];
|
||||
final mode = _modeForIndex(tabIndex);
|
||||
|
||||
final quizState = _quizStates[index ?? _tabController.index];
|
||||
final mode = _modeForIndex(index ?? _tabController.index);
|
||||
if (sessionDeck == null || sessionDeck.isEmpty) {
|
||||
setState(() {
|
||||
quizState.current = null;
|
||||
_status = 'Quiz complete!';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_deck.sort((a, b) {
|
||||
int getSrsStage(KanjiItem item) {
|
||||
if (mode == QuizMode.reading) {
|
||||
final onyomiStage =
|
||||
item.srsItems['${QuizMode.reading}onyomi']?.srsStage;
|
||||
final kunyomiStage =
|
||||
item.srsItems['${QuizMode.reading}kunyomi']?.srsStage;
|
||||
|
||||
if (onyomiStage != null && kunyomiStage != null) {
|
||||
return min(onyomiStage, kunyomiStage);
|
||||
}
|
||||
return onyomiStage ?? kunyomiStage ?? 0;
|
||||
}
|
||||
return item.srsItems[mode.toString()]?.srsStage ?? 0;
|
||||
}
|
||||
|
||||
DateTime getLastAsked(KanjiItem item) {
|
||||
if (mode == QuizMode.reading) {
|
||||
final onyomiLastAsked =
|
||||
item.srsItems['${QuizMode.reading}onyomi']?.lastAsked;
|
||||
final kunyomiLastAsked =
|
||||
item.srsItems['${QuizMode.reading}kunyomi']?.lastAsked;
|
||||
|
||||
if (onyomiLastAsked != null && kunyomiLastAsked != null) {
|
||||
return onyomiLastAsked.isBefore(kunyomiLastAsked)
|
||||
? onyomiLastAsked
|
||||
: kunyomiLastAsked;
|
||||
}
|
||||
return onyomiLastAsked ??
|
||||
kunyomiLastAsked ??
|
||||
DateTime.fromMillisecondsSinceEpoch(0);
|
||||
}
|
||||
return item.srsItems[mode.toString()]?.lastAsked ??
|
||||
DateTime.fromMillisecondsSinceEpoch(0);
|
||||
}
|
||||
|
||||
final aStage = getSrsStage(a);
|
||||
final bStage = getSrsStage(b);
|
||||
|
||||
if (aStage != bStage) {
|
||||
return aStage.compareTo(bStage);
|
||||
}
|
||||
|
||||
final aLastAsked = getLastAsked(a);
|
||||
final bLastAsked = getLastAsked(b);
|
||||
|
||||
return aLastAsked.compareTo(bLastAsked);
|
||||
});
|
||||
|
||||
quizState.current = _deck.first;
|
||||
quizState.current = sessionDeck.removeAt(0);
|
||||
quizState.key = UniqueKey();
|
||||
|
||||
quizState.correctAnswers = [];
|
||||
@@ -284,12 +284,13 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
|
||||
final repo = Provider.of<DeckRepository>(context, listen: false);
|
||||
final current = quizState.current!;
|
||||
final tabIndex = _tabController.index;
|
||||
final sessionDeck = _sessionDecks[tabIndex]!;
|
||||
|
||||
String readingType = '';
|
||||
if (mode == QuizMode.reading) {
|
||||
readingType = quizState.readingHint.contains("on'yomi")
|
||||
? 'onyomi'
|
||||
: 'kunyomi';
|
||||
readingType =
|
||||
quizState.readingHint.contains("on'yomi") ? 'onyomi' : 'kunyomi';
|
||||
}
|
||||
final srsKey = mode.toString() + readingType;
|
||||
|
||||
@@ -301,8 +302,6 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
readingType: readingType,
|
||||
);
|
||||
|
||||
quizState.asked += 1;
|
||||
|
||||
quizState.selectedOption = option;
|
||||
|
||||
quizState.showResult = true;
|
||||
@@ -310,13 +309,19 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
setState(() {});
|
||||
|
||||
if (isCorrect) {
|
||||
quizState.score += 1;
|
||||
quizState.asked += 1;
|
||||
if (!quizState.wrongItems.contains(current.id)) {
|
||||
quizState.score += 1;
|
||||
}
|
||||
srsItemForUpdate.srsStage += 1;
|
||||
if (_playCorrectSound) {
|
||||
_audioPlayer.play(AssetSource('sfx/correct.wav'));
|
||||
}
|
||||
} else {
|
||||
srsItemForUpdate.srsStage = max(0, srsItemForUpdate.srsStage - 1);
|
||||
sessionDeck.add(current);
|
||||
sessionDeck.shuffle(_random);
|
||||
quizState.wrongItems.add(current.id);
|
||||
if (_playIncorrectSound) {
|
||||
_audioPlayer.play(AssetSource('sfx/incorrect.wav'));
|
||||
}
|
||||
@@ -336,8 +341,8 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
final correctDisplay = (mode == QuizMode.kanjiToEnglish)
|
||||
? _toTitleCase(quizState.correctAnswers.first)
|
||||
: (mode == QuizMode.reading
|
||||
? quizState.correctAnswers.join(', ')
|
||||
: quizState.correctAnswers.first);
|
||||
? quizState.correctAnswers.join(', ')
|
||||
: quizState.correctAnswers.first);
|
||||
|
||||
final snack = SnackBar(
|
||||
content: Text(
|
||||
@@ -398,6 +403,23 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
);
|
||||
}
|
||||
|
||||
if (_loading) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Kanji Quiz'),
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: const [
|
||||
Tab(text: 'Kanji→English'),
|
||||
Tab(text: 'English→Kanji'),
|
||||
Tab(text: 'Reading'),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Kanji Quiz'),
|
||||
@@ -422,6 +444,16 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
final quizState = _quizStates[index];
|
||||
final mode = _modeForIndex(index);
|
||||
|
||||
if (quizState.current == null) {
|
||||
return Center(
|
||||
child: Text(
|
||||
_status,
|
||||
style: TextStyle(
|
||||
fontSize: 24, color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String prompt = '';
|
||||
String subtitle = '';
|
||||
|
||||
@@ -447,20 +479,27 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
_status,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
Text(
|
||||
'${quizState.asked} / ${_sessionDeckSizes[index] ?? 0}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (_loading)
|
||||
CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LinearProgressIndicator(
|
||||
value: (_sessionDeckSizes[index] ?? 0) > 0
|
||||
? quizState.asked / (_sessionDeckSizes[index] ?? 1)
|
||||
: 0,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
@@ -490,10 +529,9 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
OptionsGrid(
|
||||
options: quizState.options,
|
||||
onSelected: _isAnswering ? (option) {} : _answer,
|
||||
isDisabled: false,
|
||||
selectedOption: null,
|
||||
correctAnswers: [],
|
||||
showResult: false,
|
||||
showResult: quizState.showResult,
|
||||
selectedOption: quizState.selectedOption,
|
||||
correctAnswers: quizState.correctAnswers,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
|
||||
@@ -21,8 +21,7 @@ class _QuizState {
|
||||
Key key = UniqueKey();
|
||||
String? selectedOption;
|
||||
bool showResult = false;
|
||||
List<VocabularyItem> shuffledDeck = [];
|
||||
int currentIndex = 0;
|
||||
Set<int> wrongItems = {};
|
||||
}
|
||||
|
||||
class VocabScreen extends StatefulWidget {
|
||||
@@ -40,10 +39,13 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
bool _isAnswering = false;
|
||||
String _status = 'Loading deck...';
|
||||
final DistractorGenerator _dg = DistractorGenerator();
|
||||
final Random _random = Random();
|
||||
final _audioPlayer = AudioPlayer();
|
||||
|
||||
final _quizStates = [_QuizState(), _QuizState(), _QuizState()];
|
||||
_QuizState get _currentQuizState => _quizStates[_tabController.index];
|
||||
final _sessionDecks = <int, List<VocabularyItem>>{};
|
||||
final _sessionDeckSizes = <int, int>{};
|
||||
|
||||
bool _playIncorrectSound = true;
|
||||
bool _playCorrectSound = true;
|
||||
@@ -114,6 +116,40 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
_apiKeyMissing = false;
|
||||
});
|
||||
|
||||
final disabledLevels = <int>{};
|
||||
final itemsByLevel = <int, List<VocabularyItem>>{};
|
||||
for (final item in _deck) {
|
||||
(itemsByLevel[item.level] ??= []).add(item);
|
||||
}
|
||||
|
||||
itemsByLevel.forEach((level, items) {
|
||||
final allSrsItems = items.expand((item) => item.srsItems.values).toList();
|
||||
if (allSrsItems.isNotEmpty && allSrsItems.every((srs) => srs.disabled)) {
|
||||
disabledLevels.add(level);
|
||||
}
|
||||
});
|
||||
|
||||
for (var i = 0; i < _tabController.length; i++) {
|
||||
final mode = _modeForIndex(i);
|
||||
var filteredDeck = _deck.where((item) {
|
||||
if (disabledLevels.contains(item.level)) {
|
||||
return false;
|
||||
}
|
||||
final srsItem = item.srsItems[mode.toString()];
|
||||
return srsItem == null || !srsItem.disabled;
|
||||
}).toList();
|
||||
|
||||
if (mode == QuizMode.audioToEnglish) {
|
||||
filteredDeck = filteredDeck
|
||||
.where((item) => item.pronunciationAudios.isNotEmpty)
|
||||
.toList();
|
||||
}
|
||||
|
||||
filteredDeck.shuffle(_random);
|
||||
_sessionDecks[i] = filteredDeck;
|
||||
_sessionDeckSizes[i] = filteredDeck.length;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _tabController.length; i++) {
|
||||
_nextQuestion(i);
|
||||
}
|
||||
@@ -147,47 +183,20 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
}
|
||||
|
||||
void _nextQuestion([int? index]) {
|
||||
if (_deck.isEmpty) return;
|
||||
final tabIndex = index ?? _tabController.index;
|
||||
final quizState = _quizStates[tabIndex];
|
||||
final sessionDeck = _sessionDecks[tabIndex];
|
||||
final mode = _modeForIndex(tabIndex);
|
||||
|
||||
final quizState = _quizStates[index ?? _tabController.index];
|
||||
final mode = _modeForIndex(index ?? _tabController.index);
|
||||
|
||||
List<VocabularyItem> currentDeckForMode = _deck;
|
||||
if (mode == QuizMode.audioToEnglish) {
|
||||
currentDeckForMode = _deck
|
||||
.where((item) => item.pronunciationAudios.isNotEmpty)
|
||||
.toList();
|
||||
if (currentDeckForMode.isEmpty) {
|
||||
setState(() {
|
||||
_status = 'No vocabulary with audio found.';
|
||||
quizState.current = null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (quizState.shuffledDeck.isEmpty ||
|
||||
quizState.currentIndex >= quizState.shuffledDeck.length) {
|
||||
quizState.shuffledDeck = currentDeckForMode.toList();
|
||||
quizState.shuffledDeck.sort((a, b) {
|
||||
final aSrsItem =
|
||||
a.srsItems[mode.toString()] ??
|
||||
SrsItem(subjectId: a.id, quizMode: mode);
|
||||
final bSrsItem =
|
||||
b.srsItems[mode.toString()] ??
|
||||
SrsItem(subjectId: b.id, quizMode: mode);
|
||||
final stageComparison = aSrsItem.srsStage.compareTo(bSrsItem.srsStage);
|
||||
if (stageComparison != 0) {
|
||||
return stageComparison;
|
||||
}
|
||||
return aSrsItem.lastAsked.compareTo(bSrsItem.lastAsked);
|
||||
if (sessionDeck == null || sessionDeck.isEmpty) {
|
||||
setState(() {
|
||||
quizState.current = null;
|
||||
_status = 'Quiz complete!';
|
||||
});
|
||||
quizState.currentIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
quizState.current = quizState.shuffledDeck[quizState.currentIndex];
|
||||
quizState.currentIndex++;
|
||||
|
||||
quizState.current = sessionDeck.removeAt(0);
|
||||
quizState.key = UniqueKey();
|
||||
quizState.correctAnswers = [];
|
||||
quizState.options = [];
|
||||
@@ -218,6 +227,10 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
setState(() {
|
||||
_isAnswering = false;
|
||||
});
|
||||
|
||||
if (mode == QuizMode.audioToEnglish) {
|
||||
_playCurrentAudio(playOnLoad: true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _playCurrentAudio({bool playOnLoad = false}) async {
|
||||
@@ -247,6 +260,8 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
|
||||
final repo = Provider.of<VocabDeckRepository>(context, listen: false);
|
||||
final current = quizState.current!;
|
||||
final tabIndex = _tabController.index;
|
||||
final sessionDeck = _sessionDecks[tabIndex]!;
|
||||
|
||||
final srsKey = mode.toString();
|
||||
|
||||
@@ -255,16 +270,21 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
final srsItem =
|
||||
srsItemNullable ?? SrsItem(subjectId: current.id, quizMode: mode);
|
||||
|
||||
quizState.asked += 1;
|
||||
quizState.selectedOption = option;
|
||||
quizState.showResult = true;
|
||||
setState(() {});
|
||||
|
||||
if (isCorrect) {
|
||||
quizState.score += 1;
|
||||
quizState.asked += 1;
|
||||
if (!quizState.wrongItems.contains(current.id)) {
|
||||
quizState.score += 1;
|
||||
}
|
||||
srsItem.srsStage += 1;
|
||||
} else {
|
||||
srsItem.srsStage = max(0, srsItem.srsStage - 1);
|
||||
sessionDeck.add(current);
|
||||
sessionDeck.shuffle(_random);
|
||||
quizState.wrongItems.add(current.id);
|
||||
if (_playIncorrectSound) {
|
||||
await _audioPlayer.play(AssetSource('sfx/incorrect.wav'));
|
||||
}
|
||||
@@ -325,7 +345,11 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
_isAnswering = true;
|
||||
});
|
||||
|
||||
_nextQuestion();
|
||||
Future.delayed(const Duration(milliseconds: 900), () {
|
||||
if (mounted) {
|
||||
_nextQuestion();
|
||||
}
|
||||
});;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -360,6 +384,23 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
);
|
||||
}
|
||||
|
||||
if (_loading) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Vocabulary Quiz'),
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: const [
|
||||
Tab(text: 'Vocab→English'),
|
||||
Tab(text: 'English→Vocab'),
|
||||
Tab(text: 'Listening'),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Vocabulary Quiz'),
|
||||
@@ -383,6 +424,16 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
final quizState = _quizStates[index];
|
||||
final mode = _modeForIndex(index);
|
||||
|
||||
if (quizState.current == null) {
|
||||
return Center(
|
||||
child: Text(
|
||||
_status,
|
||||
style: TextStyle(
|
||||
fontSize: 24, color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget promptWidget;
|
||||
|
||||
if (quizState.current == null) {
|
||||
@@ -424,20 +475,27 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
_status,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
Text(
|
||||
'${quizState.asked} / ${_sessionDeckSizes[index] ?? 0}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (_loading)
|
||||
CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LinearProgressIndicator(
|
||||
value: (_sessionDeckSizes[index] ?? 0) > 0
|
||||
? quizState.asked / (_sessionDeckSizes[index] ?? 1)
|
||||
: 0,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
@@ -462,10 +520,9 @@ class _VocabScreenState extends State<VocabScreen>
|
||||
OptionsGrid(
|
||||
options: quizState.options,
|
||||
onSelected: _isAnswering ? (option) {} : _answer,
|
||||
isDisabled: false,
|
||||
selectedOption: null,
|
||||
correctAnswers: [],
|
||||
showResult: false,
|
||||
showResult: quizState.showResult,
|
||||
selectedOption: quizState.selectedOption,
|
||||
correctAnswers: quizState.correctAnswers,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
|
||||
@@ -24,4 +24,5 @@ class DbConstants {
|
||||
static const String readingTypeColumn = 'readingType';
|
||||
static const String srsStageColumn = 'srsStage';
|
||||
static const String lastAskedColumn = 'lastAsked';
|
||||
static const String disabledColumn = 'disabled';
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class DatabaseHelper {
|
||||
|
||||
return openDatabase(
|
||||
path,
|
||||
version: 7,
|
||||
version: 8,
|
||||
onCreate: (db, version) async {
|
||||
await db.execute(
|
||||
'''CREATE TABLE ${DbConstants.kanjiTable} (${DbConstants.idColumn} INTEGER PRIMARY KEY, ${DbConstants.levelColumn} INTEGER, ${DbConstants.charactersColumn} TEXT, ${DbConstants.meaningsColumn} TEXT, ${DbConstants.onyomiColumn} TEXT, ${DbConstants.kunyomiColumn} TEXT)''',
|
||||
@@ -41,15 +41,21 @@ class DatabaseHelper {
|
||||
'''CREATE TABLE ${DbConstants.settingsTable} (${DbConstants.keyColumn} TEXT PRIMARY KEY, ${DbConstants.valueColumn} TEXT)''',
|
||||
);
|
||||
await db.execute(
|
||||
'''CREATE TABLE ${DbConstants.srsItemsTable} (${DbConstants.kanjiIdColumn} INTEGER, ${DbConstants.quizModeColumn} TEXT, ${DbConstants.readingTypeColumn} TEXT, ${DbConstants.srsStageColumn} INTEGER, ${DbConstants.lastAskedColumn} TEXT, PRIMARY KEY (${DbConstants.kanjiIdColumn}, ${DbConstants.quizModeColumn}, ${DbConstants.readingTypeColumn}))''',
|
||||
'''CREATE TABLE ${DbConstants.srsItemsTable} (${DbConstants.kanjiIdColumn} INTEGER, ${DbConstants.quizModeColumn} TEXT, ${DbConstants.readingTypeColumn} TEXT, ${DbConstants.srsStageColumn} INTEGER, ${DbConstants.lastAskedColumn} TEXT, ${DbConstants.disabledColumn} INTEGER DEFAULT 0, PRIMARY KEY (${DbConstants.kanjiIdColumn}, ${DbConstants.quizModeColumn}, ${DbConstants.readingTypeColumn}))''',
|
||||
);
|
||||
await db.execute(
|
||||
'''CREATE TABLE ${DbConstants.vocabularyTable} (${DbConstants.idColumn} INTEGER PRIMARY KEY, ${DbConstants.levelColumn} INTEGER, ${DbConstants.charactersColumn} TEXT, ${DbConstants.meaningsColumn} TEXT, ${DbConstants.readingsColumn} TEXT, ${DbConstants.pronunciationAudiosColumn} TEXT)''',
|
||||
);
|
||||
await db.execute(
|
||||
'''CREATE TABLE ${DbConstants.srsVocabItemsTable} (${DbConstants.vocabIdColumn} INTEGER, ${DbConstants.quizModeColumn} TEXT, ${DbConstants.srsStageColumn} INTEGER, ${DbConstants.lastAskedColumn} TEXT, PRIMARY KEY (${DbConstants.vocabIdColumn}, ${DbConstants.quizModeColumn}))''',
|
||||
'''CREATE TABLE ${DbConstants.srsVocabItemsTable} (${DbConstants.vocabIdColumn} INTEGER, ${DbConstants.quizModeColumn} TEXT, ${DbConstants.srsStageColumn} INTEGER, ${DbConstants.lastAskedColumn} TEXT, ${DbConstants.disabledColumn} INTEGER DEFAULT 0, PRIMARY KEY (${DbConstants.vocabIdColumn}, ${DbConstants.quizModeColumn}))''',
|
||||
);
|
||||
},
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
if (oldVersion < 8) {
|
||||
await db.execute('ALTER TABLE ${DbConstants.srsItemsTable} ADD COLUMN ${DbConstants.disabledColumn} INTEGER DEFAULT 0');
|
||||
await db.execute('ALTER TABLE ${DbConstants.srsVocabItemsTable} ADD COLUMN ${DbConstants.disabledColumn} INTEGER DEFAULT 0');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ class DeckRepository {
|
||||
readingType: r[DbConstants.readingTypeColumn] as String?,
|
||||
srsStage: r[DbConstants.srsStageColumn] as int,
|
||||
lastAsked: DateTime.parse(r[DbConstants.lastAskedColumn] as String),
|
||||
disabled: (r[DbConstants.disabledColumn] as int? ?? 0) == 1,
|
||||
);
|
||||
srsItemsByKanjiId.putIfAbsent(srsItem.subjectId, () => []).add(srsItem);
|
||||
}
|
||||
@@ -118,17 +119,53 @@ class DeckRepository {
|
||||
return kanjiItems;
|
||||
}
|
||||
|
||||
Future<void> updateSrsItems(List<SrsItem> items) async {
|
||||
final db = await DatabaseHelper().db;
|
||||
final batch = db.batch();
|
||||
for (final item in items) {
|
||||
var where = '${DbConstants.kanjiIdColumn} = ? AND ${DbConstants.quizModeColumn} = ?';
|
||||
final whereArgs = [item.subjectId, item.quizMode.toString()];
|
||||
if (item.readingType != null) {
|
||||
where += ' AND ${DbConstants.readingTypeColumn} = ?';
|
||||
whereArgs.add(item.readingType!);
|
||||
} else {
|
||||
where += ' AND ${DbConstants.readingTypeColumn} IS NULL';
|
||||
}
|
||||
|
||||
batch.update(
|
||||
DbConstants.srsItemsTable,
|
||||
{
|
||||
DbConstants.srsStageColumn: item.srsStage,
|
||||
DbConstants.lastAskedColumn: item.lastAsked.toIso8601String(),
|
||||
DbConstants.disabledColumn: item.disabled ? 1 : 0,
|
||||
},
|
||||
where: where,
|
||||
whereArgs: whereArgs,
|
||||
);
|
||||
}
|
||||
await batch.commit(noResult: true);
|
||||
}
|
||||
|
||||
Future<void> updateSrsItem(SrsItem item) async {
|
||||
final db = await DatabaseHelper().db;
|
||||
var where = '${DbConstants.kanjiIdColumn} = ? AND ${DbConstants.quizModeColumn} = ?';
|
||||
final whereArgs = [item.subjectId, item.quizMode.toString()];
|
||||
if (item.readingType != null) {
|
||||
where += ' AND ${DbConstants.readingTypeColumn} = ?';
|
||||
whereArgs.add(item.readingType!);
|
||||
} else {
|
||||
where += ' AND ${DbConstants.readingTypeColumn} IS NULL';
|
||||
}
|
||||
|
||||
await db.update(
|
||||
DbConstants.srsItemsTable,
|
||||
{
|
||||
DbConstants.srsStageColumn: item.srsStage,
|
||||
DbConstants.lastAskedColumn: item.lastAsked.toIso8601String(),
|
||||
DbConstants.disabledColumn: item.disabled ? 1 : 0,
|
||||
},
|
||||
where:
|
||||
'${DbConstants.kanjiIdColumn} = ? AND ${DbConstants.quizModeColumn} = ? AND ${DbConstants.readingTypeColumn} = ?',
|
||||
whereArgs: [item.subjectId, item.quizMode.toString(), item.readingType],
|
||||
where: where,
|
||||
whereArgs: whereArgs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,6 +177,7 @@ class DeckRepository {
|
||||
DbConstants.readingTypeColumn: item.readingType,
|
||||
DbConstants.srsStageColumn: item.srsStage,
|
||||
DbConstants.lastAskedColumn: item.lastAsked.toIso8601String(),
|
||||
DbConstants.disabledColumn: item.disabled ? 1 : 0,
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,10 +68,29 @@ class VocabDeckRepository {
|
||||
),
|
||||
srsStage: r['srsStage'] as int,
|
||||
lastAsked: DateTime.parse(r['lastAsked'] as String),
|
||||
disabled: (r['disabled'] as int? ?? 0) == 1,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future<void> updateSrsItems(List<SrsItem> items) async {
|
||||
final db = await DatabaseHelper().db;
|
||||
final batch = db.batch();
|
||||
for (final item in items) {
|
||||
batch.update(
|
||||
'srs_vocab_items',
|
||||
{
|
||||
'srsStage': item.srsStage,
|
||||
'lastAsked': item.lastAsked.toIso8601String(),
|
||||
'disabled': item.disabled ? 1 : 0,
|
||||
},
|
||||
where: 'vocabId = ? AND quizMode = ?',
|
||||
whereArgs: [item.subjectId, item.quizMode.toString()],
|
||||
);
|
||||
}
|
||||
await batch.commit(noResult: true);
|
||||
}
|
||||
|
||||
Future<void> updateVocabSrsItem(SrsItem item) async {
|
||||
final db = await DatabaseHelper().db;
|
||||
await db.update(
|
||||
@@ -79,6 +98,7 @@ class VocabDeckRepository {
|
||||
{
|
||||
'srsStage': item.srsStage,
|
||||
'lastAsked': item.lastAsked.toIso8601String(),
|
||||
'disabled': item.disabled ? 1 : 0,
|
||||
},
|
||||
where: 'vocabId = ? AND quizMode = ?',
|
||||
whereArgs: [item.subjectId, item.quizMode.toString()],
|
||||
@@ -92,6 +112,7 @@ class VocabDeckRepository {
|
||||
'quizMode': item.quizMode.toString(),
|
||||
'srsStage': item.srsStage,
|
||||
'lastAsked': item.lastAsked.toIso8601String(),
|
||||
'disabled': item.disabled ? 1 : 0,
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,15 +41,13 @@ class OptionsGrid extends StatelessWidget {
|
||||
if (showResult) {
|
||||
if (correctAnswers != null && correctAnswers!.contains(o)) {
|
||||
currentButtonColor = theme.colorScheme.tertiary;
|
||||
} else if (o == selectedOption) {
|
||||
currentButtonColor = theme.colorScheme.error;
|
||||
}
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: 160,
|
||||
child: ElevatedButton(
|
||||
onPressed: isDisabled ? null : () => onSelected(o),
|
||||
onPressed: isDisabled || o == '---' ? null : () => onSelected(o),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: currentButtonColor,
|
||||
foregroundColor: currentTextColor,
|
||||
|
||||
Reference in New Issue
Block a user