Files
999/lib/screens/catalog_screen.dart
T

314 lines
10 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import '../models/word_model.dart';
import '../services/words_repository.dart';
import '../widgets/gradient_star_badge.dart';
import '../widgets/word_card_widget.dart';
class CatalogScreen extends StatefulWidget {
const CatalogScreen({super.key});
@override
State<CatalogScreen> createState() => _CatalogScreenState();
}
class _CatalogScreenState extends State<CatalogScreen> {
final _repo = WordsRepository();
final _searchController = TextEditingController();
String _selectedCategory = 'Все';
String _sortBy = 'id'; // 'id', 'alpha'
List<WordItem> _filteredWords = [];
@override
void initState() {
super.initState();
_applyFilter();
_searchController.addListener(_applyFilter);
}
@override
void dispose() {
_searchController.removeListener(_applyFilter);
_searchController.dispose();
super.dispose();
}
void _applyFilter() {
final query = _searchController.text.trim();
List<WordItem> list;
if (query.isEmpty) {
list = _repo.allWords;
} else {
list = _repo.search(query);
}
if (_selectedCategory != 'Все') {
list = list
.where((w) => w.partOfSpeech == _selectedCategory || w.category == _selectedCategory)
.toList();
}
if (_sortBy == 'alpha') {
list = List.of(list)..sort((a, b) => a.word.compareTo(b.word));
} else {
// By session number
list = List.of(list)..sort((a, b) => a.sessionNumber.compareTo(b.sessionNumber));
}
setState(() {
_filteredWords = list;
});
}
void _showWordDetails(WordItem word) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => StatefulBuilder(
builder: (context, setSheetState) {
return Container(
padding: const EdgeInsets.only(top: 12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(28)),
),
child: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outlineVariant,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: 12),
WordCardWidget(
word: word,
onNext: () {
final next = _repo.getNextWord(word.sessionNumber);
Navigator.pop(context);
_showWordDetails(next);
},
onPrevious: () {
final prev = _repo.getPreviousWord(word.sessionNumber);
Navigator.pop(context);
_showWordDetails(prev);
},
onRandom: () {
final rnd = _repo.getRandomWord();
Navigator.pop(context);
_showWordDetails(rnd);
},
),
],
),
),
),
);
},
),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final categories = _repo.getPartsOfSpeech();
return Scaffold(
appBar: AppBar(
title: const Row(
children: [
GradientStar(size: 22),
SizedBox(width: 8),
Text(
'Каталог 999 символов',
style: TextStyle(fontWeight: FontWeight.w900),
),
],
),
actions: [
PopupMenuButton<String>(
tooltip: 'Сортировка',
icon: const Icon(Icons.sort_rounded),
initialValue: _sortBy,
onSelected: (val) {
setState(() {
_sortBy = val;
_applyFilter();
});
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'id',
child: Row(
children: [
Icon(Icons.format_list_numbered, size: 20),
SizedBox(width: 8),
Text('По номеру сессии (#000..#999)'),
],
),
),
const PopupMenuItem(
value: 'alpha',
child: Row(
children: [
Icon(Icons.sort_by_alpha, size: 20),
SizedBox(width: 8),
Text('По алфавиту (А..Я)'),
],
),
),
],
),
],
),
body: Column(
children: [
// Search input
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Поиск по слову, номеру или смыслу...',
prefixIcon: const Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: GradientStar(size: 16),
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () => _searchController.clear(),
)
: null,
filled: true,
fillColor: isDark
? const Color(0xFF1B1D30)
: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.4),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
color: const Color(0xFFFFD54F).withValues(alpha: 0.3),
),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
),
),
// Categories Filter Chips
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Row(
children: categories.map((cat) {
final isSelected = _selectedCategory == cat;
return Padding(
padding: const EdgeInsets.only(right: 6),
child: FilterChip(
label: Text(cat),
selected: isSelected,
onSelected: (selected) {
setState(() {
_selectedCategory = cat;
_applyFilter();
});
},
selectedColor: const Color(0xFF2A2D4A),
checkmarkColor: const Color(0xFFFFD54F),
labelStyle: TextStyle(
color: isSelected ? const Color(0xFFFFF9C4) : null,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
);
}).toList(),
),
),
// Stats Bar
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Найдено: ${_filteredWords.length}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: theme.colorScheme.outline,
),
),
Text(
'Всего знаков: ${_repo.totalWords}',
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
),
],
),
),
const Divider(height: 1),
// Words List
Expanded(
child: _filteredWords.isEmpty
? const Center(
child: Text('Ничего не найдено'),
)
: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
itemCount: _filteredWords.length,
separatorBuilder: (_, _) => const Divider(height: 1, indent: 64),
itemBuilder: (context, index) {
final item = _filteredWords[index];
return ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
leading: GradientStarBadge(
text: '#${item.formattedSessionNumber}',
size: 14,
),
title: Text(
item.word.toUpperCase(),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
letterSpacing: 0.5,
),
),
subtitle: Text(
item.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.onSurfaceVariant,
),
),
trailing: const Icon(Icons.chevron_right_rounded, size: 20),
onTap: () => _showWordDetails(item),
);
},
),
),
],
),
);
}
}