possible to exclude levels and change how questions are served

This commit is contained in:
Rene Kievits
2025-11-02 17:07:23 +01:00
parent e9f115a32a
commit 16da0f04ac
10 changed files with 477 additions and 175 deletions

View File

@@ -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) {
},
);
}