import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:hirameki_srs/src/themes.dart'; import 'package:provider/provider.dart'; import 'package:http/http.dart' as http; import '../models/kanji_item.dart'; import '../models/vocabulary_item.dart'; import '../models/srs_item.dart'; import '../services/deck_repository.dart'; import 'package:hirameki_srs/src/services/vocab_deck_repository.dart'; import '../services/custom_deck_repository.dart'; import '../models/custom_kanji_item.dart'; import 'settings_screen.dart'; import 'custom_card_details_screen.dart'; import 'add_card_screen.dart'; class BrowseScreen extends StatefulWidget { const BrowseScreen({super.key}); @override State createState() => _BrowseScreenState(); } class _BrowseScreenState extends State with SingleTickerProviderStateMixin { late TabController _tabController; late PageController _kanjiPageController; late PageController _vocabPageController; List _kanjiDeck = []; List _vocabDeck = []; List _customDeck = []; Map> _kanjiByLevel = {}; Map> _vocabByLevel = {}; List _kanjiSortedLevels = []; List _vocabSortedLevels = []; final _customDeckRepository = CustomDeckRepository(); bool _isSelectionMode = false; List _selectedItems = []; bool _loading = true; String _status = 'Loading...'; int _currentKanjiPage = 0; int _currentVocabPage = 0; bool _apiKeyMissing = false; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); _kanjiPageController = PageController(); _vocabPageController = PageController(); _tabController.addListener(() { setState(() {}); }); _kanjiPageController.addListener(() { if (_kanjiPageController.page?.round() != _currentKanjiPage) { setState(() { _currentKanjiPage = _kanjiPageController.page!.round(); }); } }); _vocabPageController.addListener(() { if (_vocabPageController.page?.round() != _currentVocabPage) { setState(() { _currentVocabPage = _vocabPageController.page!.round(); }); } }); _loadDecks(); _loadCustomDeck(); } @override void dispose() { _tabController.dispose(); _kanjiPageController.dispose(); _vocabPageController.dispose(); super.dispose(); } Future _loadCustomDeck() async { final customDeck = await _customDeckRepository.getCustomDeck(); setState(() { _customDeck = customDeck; }); } Widget _buildWaniKaniTab(Widget child) { if (_apiKeyMissing) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'WaniKani API key is not set.', style: TextStyle(color: Theme.of(context).colorScheme.onSurface), ), const SizedBox(height: 16), ElevatedButton( onPressed: () async { await Navigator.of(context).push( MaterialPageRoute(builder: (_) => const SettingsScreen()), ); if (!mounted) return; _loadDecks(); }, child: const Text('Go to Settings'), ), ], ), ); } if (_loading) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 16), Text( _status, style: TextStyle(color: Theme.of(context).colorScheme.onSurface), ), ], ), ); } return child; } Widget _buildCustomSrsTab() { if (_customDeck.isEmpty) { return Center( child: Text( 'No custom cards yet.', style: TextStyle(color: Theme.of(context).colorScheme.onSurface), ), ); } return _buildCustomGridView(_customDeck); } Widget _buildPaginatedView( Map> groupedItems, List sortedLevels, PageController pageController, Widget Function(List) buildPageContent, dynamic repository, ) { if (sortedLevels.isEmpty) { return Center( child: Text( 'No items to display.', style: TextStyle(color: Theme.of(context).colorScheme.onSurface), ), ); } return PageView.builder( controller: pageController, itemCount: sortedLevels.length, 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: 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)), ], ); }, ); } Widget _buildLevelSelector() { final isKanji = _tabController.index == 0; final levels = isKanji ? _kanjiSortedLevels : _vocabSortedLevels; final controller = isKanji ? _kanjiPageController : _vocabPageController; final currentPage = isKanji ? _currentKanjiPage : _currentVocabPage; if (levels.isEmpty) return const SizedBox.shrink(); return Container( padding: const EdgeInsets.symmetric(vertical: 8.0), color: Theme.of(context).colorScheme.surfaceContainer, 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: 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()), ), ), ); }), ), ); } Widget _buildGridView(List items) { return GridView.builder( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 150, childAspectRatio: 1, crossAxisSpacing: 8, mainAxisSpacing: 8, ), itemCount: items.length, itemBuilder: (context, index) { final item = items[index]; return GestureDetector( onTap: () => _showReadingsDialog(item), child: _buildSrsItemCard(item), ); }, padding: const EdgeInsets.all(8), ); } Widget _buildListView(List items) { return ListView.builder( itemCount: items.length, itemBuilder: (context, index) { final item = items[index]; return _buildVocabListTile(item); }, ); } Widget _buildVocabListTile(VocabularyItem item) { final requiredModes = [ QuizMode.vocabToEnglish.toString(), QuizMode.englishToVocab.toString(), QuizMode.audioToEnglish.toString(), ]; int minSrsStage = 9; for (final mode in requiredModes) { final srsItem = item.srsItems[mode]; if (srsItem == null) { minSrsStage = 0; break; } if (srsItem.srsStage < minSrsStage) { minSrsStage = srsItem.srsStage; } } return GestureDetector( onTap: () => _showVocabDetailsDialog(context, item), child: Card( color: Theme.of(context).colorScheme.surfaceContainer, margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Padding( padding: const EdgeInsets.all(12.0), child: Row( children: [ Expanded( child: Text( item.characters, style: TextStyle( fontSize: 24, color: Theme.of(context).colorScheme.onSurface, ), ), ), const SizedBox(width: 16), Expanded( flex: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.meanings.join(', '), style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, ), overflow: TextOverflow.ellipsis, ), const SizedBox(height: 8), _buildSrsIndicator(minSrsStage), ], ), ), ], ), ), ), ); } Widget _buildSrsItemCard(KanjiItem item) { final requiredModes = [ QuizMode.kanjiToEnglish.toString(), QuizMode.englishToKanji.toString(), ]; if (item.onyomi.isNotEmpty) { requiredModes.add('${QuizMode.reading}onyomi'); } if (item.kunyomi.isNotEmpty) { requiredModes.add('${QuizMode.reading}kunyomi'); } int minSrsStage = 9; for (final mode in requiredModes) { final srsItem = item.srsItems[mode]; if (srsItem == null) { minSrsStage = 0; break; } if (srsItem.srsStage < minSrsStage) { minSrsStage = srsItem.srsStage; } } return Card( color: Theme.of(context).colorScheme.surfaceContainer, child: Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( item.characters, style: TextStyle( fontSize: 32, color: Theme.of(context).colorScheme.onSurface, ), textAlign: TextAlign.center, ), const SizedBox(height: 8), _buildSrsIndicator(minSrsStage), ], ), ), ); } Widget _buildSrsIndicator(int level) { return Tooltip( message: 'SRS Level: $level', child: ClipRRect( borderRadius: BorderRadius.circular(4), child: SizedBox( height: 10, child: LinearProgressIndicator( value: level / 9.0, backgroundColor: Theme.of( context, ).colorScheme.surfaceContainerHighest, valueColor: AlwaysStoppedAnimation( _getColorForSrsLevel(level), ), ), ), ), ); } Color _getColorForSrsLevel(int level) { final srsColors = Theme.of(context).srsColors; if (level >= 9) return srsColors.level9; if (level >= 8) return srsColors.level8; if (level >= 7) return srsColors.level7; if (level >= 6) return srsColors.level6; if (level >= 5) return srsColors.level5; if (level >= 4) return srsColors.level4; if (level >= 3) return srsColors.level3; if (level >= 2) return srsColors.level2; if (level >= 1) return srsColors.level1; return Colors.grey; } void _showReadingsDialog(KanjiItem kanji) { final srsScores = { 'JP -> EN': 0, 'EN -> JP': 0, 'Reading (onyomi)': 0, 'Reading (kunyomi)': 0, }; for (final entry in kanji.srsItems.entries) { final srsItem = entry.value; switch (srsItem.quizMode) { case QuizMode.kanjiToEnglish: srsScores['JP -> EN'] = srsItem.srsStage; break; case QuizMode.englishToKanji: srsScores['EN -> JP'] = srsItem.srsStage; break; case QuizMode.reading: if (srsItem.readingType == 'onyomi') { srsScores['Reading (onyomi)'] = srsItem.srsStage; } else if (srsItem.readingType == 'kunyomi') { srsScores['Reading (kunyomi)'] = srsItem.srsStage; } break; default: break; } } showDialog( context: context, builder: (dialogContext) { return AlertDialog( backgroundColor: Theme.of(context).colorScheme.surfaceContainer, title: Text( 'Details for ${kanji.characters}', style: TextStyle(color: Theme.of(context).colorScheme.onSurface), ), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Level: ${kanji.level}', 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, ), ), const SizedBox(height: 16), if (kanji.onyomi.isNotEmpty) Text( 'On\'yomi: ${kanji.onyomi.join(', ')}', 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, ), ), if (kanji.onyomi.isEmpty && kanji.kunyomi.isEmpty) Text( 'No readings available.', style: TextStyle( color: Theme.of(context).colorScheme.onSurface, ), ), const SizedBox(height: 16), Divider(color: Theme.of(context).colorScheme.onSurfaceVariant), const SizedBox(height: 16), Text( 'SRS Scores:', style: TextStyle( color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.bold, ), ), ...srsScores.entries.map( (entry) => Text( ' ${entry.key}: ${entry.value}', style: TextStyle( color: Theme.of(context).colorScheme.onSurface, ), ), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: Text( 'Close', style: TextStyle(color: Theme.of(context).colorScheme.primary), ), ), ], ); }, ); } Future _loadDecks() async { setState(() => _loading = true); try { final kanjiRepo = Provider.of(context, listen: false); final vocabRepo = Provider.of( context, listen: false, ); await kanjiRepo.loadApiKey(); final apiKey = kanjiRepo.apiKey; if (apiKey == null || apiKey.isEmpty) { setState(() { _apiKeyMissing = true; _loading = false; }); return; } var kanji = await kanjiRepo.loadKanji(); if (kanji.isEmpty || kanji.every((k) => k.level == 0)) { setState(() => _status = 'Fetching kanji from WaniKani...'); kanji = await kanjiRepo.fetchAndCacheFromWk(apiKey); } var vocab = await vocabRepo.loadVocabulary(); if (vocab.isEmpty || vocab.every((v) => v.level == 0)) { setState(() => _status = 'Fetching vocabulary from WaniKani...'); vocab = await vocabRepo.fetchAndCacheVocabularyFromWk(apiKey); } _kanjiDeck = kanji; _vocabDeck = vocab; _groupItemsByLevel(); setState(() { _loading = false; _status = 'Loaded ${_kanjiDeck.length} kanji and ${_vocabDeck.length} vocabulary.'; _apiKeyMissing = false; }); } catch (e) { setState(() { _status = 'Error: $e'; _loading = false; }); } } void _groupItemsByLevel() { _kanjiByLevel = {}; for (final item in _kanjiDeck) { if (item.level > 0) { (_kanjiByLevel[item.level] ??= []).add(item); } } _kanjiSortedLevels = _kanjiByLevel.keys.toList()..sort(); _vocabByLevel = {}; for (final item in _vocabDeck) { if (item.level > 0) { (_vocabByLevel[item.level] ??= []).add(item); } } _vocabSortedLevels = _vocabByLevel.keys.toList()..sort(); } Future _toggleLevelExclusion(int level, dynamic repository) async { final List 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( appBar: _isSelectionMode ? _buildSelectionAppBar() : _buildDefaultAppBar(), body: Column( children: [ Expanded( child: TabBarView( controller: _tabController, children: [ _buildWaniKaniTab( _buildPaginatedView( _kanjiByLevel, _kanjiSortedLevels, _kanjiPageController, (items) => _buildGridView(items.cast()), Provider.of(context, listen: false), ), ), _buildWaniKaniTab( _buildPaginatedView( _vocabByLevel, _vocabSortedLevels, _vocabPageController, (items) => _buildListView(items.cast()), Provider.of(context, listen: false), ), ), _buildCustomSrsTab(), ], ), ), if (!_isSelectionMode) SafeArea( top: false, child: _tabController.index < 2 ? _buildLevelSelector() : const SizedBox.shrink(), ), ], ), floatingActionButton: _tabController.index == 2 ? FloatingActionButton( onPressed: () async { await Navigator.of( context, ).push(MaterialPageRoute(builder: (_) => AddCardScreen())); if (!mounted) return; _loadCustomDeck(); }, child: const Icon(Icons.add), ) : null, ); } AppBar _buildDefaultAppBar() { return AppBar( title: const Text('Browse'), bottom: TabBar( controller: _tabController, tabs: const [ Tab(text: 'Kanji'), Tab(text: 'Vocabulary'), Tab(text: 'Custom SRS'), ], ), ); } AppBar _buildSelectionAppBar() { return AppBar( leading: IconButton( icon: const Icon(Icons.close), onPressed: () { setState(() { _isSelectionMode = false; _selectedItems.clear(); }); }, ), title: Text('${_selectedItems.length} selected'), actions: [ IconButton(icon: const Icon(Icons.select_all), onPressed: _selectAll), IconButton(icon: const Icon(Icons.delete), onPressed: _deleteSelected), IconButton( icon: Icon(_toggleIntervalIcon), onPressed: _toggleIntervalForSelected, ), ], ); } IconData get _toggleIntervalIcon { if (_selectedItems.isEmpty) { return Icons.timer_off; } final bool willEnable = _selectedItems.any((item) => !item.useInterval); return willEnable ? Icons.timer : Icons.timer_off; } void _selectAll() { setState(() { if (_selectedItems.length == _customDeck.length) { _selectedItems.clear(); } else { _selectedItems = List.from(_customDeck); } }); } void _deleteSelected() { showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('Delete Selected'), content: Text( 'Are you sure you want to delete ${_selectedItems.length} cards?', ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text('Cancel'), ), TextButton( onPressed: () { Navigator.of(dialogContext).pop(); () async { for (final item in _selectedItems) { await _customDeckRepository.deleteCard(item); } if (!mounted) return; setState(() { _isSelectionMode = false; _selectedItems.clear(); }); _loadCustomDeck(); }(); }, child: const Text('Delete'), ), ], ), ); } Future _toggleIntervalForSelected() async { if (_selectedItems.isEmpty) { return; } final bool targetState = _selectedItems.any((item) => !item.useInterval); final selectedCharacters = _selectedItems .map((item) => item.characters) .toSet(); final List updatedItems = []; for (final item in _selectedItems) { final updatedItem = CustomKanjiItem( characters: item.characters, meaning: item.meaning, kanji: item.kanji, useInterval: targetState, srsData: item.srsData, ); updatedItems.add(updatedItem); } await _customDeckRepository.updateCards(updatedItems); await _loadCustomDeck(); final newSelectedItems = _customDeck .where((item) => selectedCharacters.contains(item.characters)) .toList(); setState(() { _selectedItems = newSelectedItems; if (_selectedItems.isEmpty) { _isSelectionMode = false; } }); } Widget _buildCustomGridView(List items) { return GridView.builder( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, childAspectRatio: 1.2, crossAxisSpacing: 8, mainAxisSpacing: 8, ), itemCount: items.length, itemBuilder: (context, index) { final item = items[index]; final isSelected = _selectedItems.contains(item); return GestureDetector( onLongPress: () { setState(() { _isSelectionMode = true; _selectedItems.add(item); }); }, onTap: () { if (_isSelectionMode) { setState(() { if (isSelected) { _selectedItems.remove(item); if (_selectedItems.isEmpty) { _isSelectionMode = false; } } else { _selectedItems.add(item); } }); } else { Navigator.of(context) .push( MaterialPageRoute( builder: (_) => CustomCardDetailsScreen( item: item, repository: _customDeckRepository, ), ), ) .then((_) => _loadCustomDeck()); } }, child: Card( shape: RoundedRectangleBorder( side: isSelected ? 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.surfaceContainer, child: Stack( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ FittedBox( fit: BoxFit.scaleDown, child: Text( item.kanji?.isNotEmpty == true ? item.kanji! : item.characters, style: TextStyle( fontSize: 32, color: Theme.of(context).colorScheme.onSurface, ), textAlign: TextAlign.center, ), ), const SizedBox(height: 8), Text( item.meaning, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 16, ), textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 8), Builder( builder: (context) { final avgSrs = (item.srsData.japaneseToEnglish + item.srsData.englishToJapanese + item.srsData.listeningComprehension) / 3; return _buildSrsIndicator(avgSrs.round()); }, ), ], ), ), if (item.useInterval) Positioned( top: 4, right: 4, child: Icon( Icons.timer, color: Theme.of(context).colorScheme.tertiary, size: 16, ), ), ], ), ), ); }, padding: const EdgeInsets.all(8), ); } } class _VocabDetailsDialog extends StatefulWidget { final VocabularyItem vocab; final ThemeData theme; const _VocabDetailsDialog({required this.vocab, required this.theme}); @override State<_VocabDetailsDialog> createState() => _VocabDetailsDialogState(); } class _VocabDetailsDialogState extends State<_VocabDetailsDialog> { List _exampleSentences = [const CircularProgressIndicator()]; @override void initState() { super.initState(); _fetchExampleSentences(); } Future _fetchExampleSentences() async { try { final uri = Uri.parse( 'https://jisho.org/api/v1/search/words?keyword=${Uri.encodeComponent(widget.vocab.characters)}', ); final response = await http.get(uri); if (response.statusCode == 200) { final data = jsonDecode(utf8.decode(response.bodyBytes)); final sentences = []; if (data['data'] != null && (data['data'] as List).isNotEmpty) { for (final result in data['data']) { if (result['japanese'] != null && (result['japanese'] as List).isNotEmpty && result['senses'] != null && (result['senses'] as List).isNotEmpty) { final japaneseWord = result['japanese'][0]['word'] ?? result['japanese'][0]['reading']; final englishDefinition = result['senses'][0]['english_definitions'].join(', '); if (japaneseWord != null && englishDefinition != null) { sentences.add( Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( japaneseWord, style: TextStyle( color: widget.theme.colorScheme.onSurface, ), ), Text( englishDefinition, style: TextStyle( color: widget.theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 8), ], ), ); } } } } if (sentences.isEmpty) { sentences.add( Text( 'No example sentences found.', style: TextStyle(color: widget.theme.colorScheme.onSurface), ), ); } if (mounted) { setState(() { _exampleSentences = sentences; }); } } else { if (mounted) { setState(() { _exampleSentences = [ Text( 'Failed to load example sentences.', style: TextStyle(color: widget.theme.colorScheme.error), ), ]; }); } } } catch (e) { if (mounted) { setState(() { _exampleSentences = [ Text( 'Error loading example sentences.', style: TextStyle(color: widget.theme.colorScheme.error), ), ]; }); } } } @override Widget build(BuildContext context) { final srsScores = {'JP -> EN': 0, 'EN -> JP': 0, 'Audio': 0}; for (final entry in widget.vocab.srsItems.entries) { final srsItem = entry.value; switch (srsItem.quizMode) { case QuizMode.vocabToEnglish: srsScores['JP -> EN'] = srsItem.srsStage; break; case QuizMode.englishToVocab: srsScores['EN -> JP'] = srsItem.srsStage; break; case QuizMode.audioToEnglish: srsScores['Audio'] = srsItem.srsStage; break; default: break; } } return SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Level: ${widget.vocab.level}', style: TextStyle(color: widget.theme.colorScheme.onSurface), ), const SizedBox(height: 16), if (widget.vocab.meanings.isNotEmpty) Text( 'Meanings: ${widget.vocab.meanings.join(', ')}', style: TextStyle(color: widget.theme.colorScheme.onSurface), ), const SizedBox(height: 16), if (widget.vocab.readings.isNotEmpty) Text( 'Readings: ${widget.vocab.readings.join(', ')}', style: TextStyle(color: widget.theme.colorScheme.onSurface), ), const SizedBox(height: 16), Divider(color: widget.theme.colorScheme.onSurfaceVariant), const SizedBox(height: 16), Text( 'SRS Scores:', style: TextStyle( color: widget.theme.colorScheme.onSurface, fontWeight: FontWeight.bold, ), ), ...srsScores.entries.map( (entry) => Text( ' ${entry.key}: ${entry.value}', style: TextStyle(color: widget.theme.colorScheme.onSurface), ), ), const SizedBox(height: 16), Divider(color: widget.theme.colorScheme.onSurfaceVariant), const SizedBox(height: 16), Text( 'Example Sentences:', style: TextStyle( color: widget.theme.colorScheme.onSurface, fontWeight: FontWeight.bold, ), ), ..._exampleSentences, ], ), ); } } void _showVocabDetailsDialog(BuildContext context, VocabularyItem vocab) { final currentTheme = Theme.of(context); showDialog( context: context, builder: (dialogContext) { return AlertDialog( backgroundColor: currentTheme.colorScheme.surfaceContainer, title: Text( 'Details for ${vocab.characters}', style: TextStyle(color: currentTheme.colorScheme.onSurface), ), content: _VocabDetailsDialog(vocab: vocab, theme: currentTheme), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: Text( 'Close', style: TextStyle(color: currentTheme.colorScheme.primary), ), ), ], ); }, ); }