import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/external_symbol_model.dart'; import '../services/external_symbols_repository.dart'; class ExternalSymbolsBrowser extends StatefulWidget { final String searchQuery; const ExternalSymbolsBrowser({ super.key, this.searchQuery = '', }); @override State createState() => _ExternalSymbolsBrowserState(); } class _ExternalSymbolsBrowserState extends State { final _repo = ExternalSymbolsRepository(); bool _onlyWithImages = false; String? _selectedAuthor; @override void initState() { super.initState(); _repo.init().then((_) { if (mounted) setState(() {}); }); } void _showDetailModal(ExternalSymbol symbol) { HapticFeedback.selectionClick(); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: const Color(0xFF121424), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (ctx) => DraggableScrollableSheet( initialChildSize: 0.85, minChildSize: 0.5, maxChildSize: 0.95, expand: false, builder: (context, scrollController) => Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 20), child: ListView( controller: scrollController, children: [ Center( child: Container( width: 40, height: 4, margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.white24, borderRadius: BorderRadius.circular(2), ), ), ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( symbol.term.toUpperCase(), style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w900, color: Color(0xFFFFD54F), letterSpacing: 0.5, ), ), const SizedBox(height: 4), Text( 'Внешний пантеон • ${symbol.sources.length} источн.', style: const TextStyle( fontSize: 13, color: Color(0xFF8E90A4), ), ), ], ), ), IconButton( tooltip: 'Поделиться символом', icon: const Icon(Icons.share_rounded, color: Color(0xFFFFD54F)), onPressed: () { final buf = StringBuffer(); buf.writeln('📚 ОРАКУЛ • АКАДЕМИЧЕСКИЙ СИМВОЛ: «${symbol.term.toUpperCase()}»'); buf.writeln(''); for (final s in symbol.sources) { buf.writeln('🏛 [${s.author} · ${s.work}]:'); buf.writeln(s.fullText.isNotEmpty ? s.fullText : s.excerpt); buf.writeln(''); } buf.writeln('— Академический корпус системы «Оракул»'); Clipboard.setData(ClipboardData(text: buf.toString())); HapticFeedback.lightImpact(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.share_rounded, color: Color(0xFFFFD54F), size: 18), const SizedBox(width: 8), Expanded(child: Text('Символ «${symbol.term}» скопирован для отправки')), ], ), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); }, ), IconButton( icon: const Icon(Icons.close_rounded, color: Colors.white70), onPressed: () => Navigator.pop(ctx), ), ], ), const Divider(color: Color(0xFF24273E), height: 24), // Иллюстрации (если есть) if (symbol.hasIllustrations) ...[ const Text( 'ГРАВЮРЫ И ИЛЛЮСТРАЦИИ', style: TextStyle( fontSize: 11, letterSpacing: 1.2, fontWeight: FontWeight.w800, color: Color(0xFFFFD54F), ), ), const SizedBox(height: 10), for (final ill in symbol.illustrations) ...[ ClipRRect( borderRadius: BorderRadius.circular(14), child: Container( color: const Color(0xFF161828), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Image.asset( ill['file'] as String? ?? '', fit: BoxFit.contain, errorBuilder: (_, __, ___) => Container( height: 140, color: const Color(0xFF161828), child: Center( child: Text('Илл.: ${ill['file']}', style: const TextStyle(fontSize: 12, color: Colors.white54)), ), ), ), Padding( padding: const EdgeInsets.all(10), child: Text( ill['caption'] as String? ?? '', style: const TextStyle(fontSize: 12, color: Color(0xFFFFD54F), fontWeight: FontWeight.w600), ), ), ], ), ), ), const SizedBox(height: 12), ], const SizedBox(height: 12), ], // Академические источники const Text( 'АКАДЕМИЧЕСКИЕ СТАТЬИ И ПЕРВОИСТОЧНИКИ', style: TextStyle( fontSize: 11, letterSpacing: 1.2, fontWeight: FontWeight.w800, color: Color(0xFFFFD54F), ), ), const SizedBox(height: 10), for (final src in symbol.sources) ...[ Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFF161828), borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.15)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.menu_book_rounded, size: 16, color: Color(0xFFFFD54F)), const SizedBox(width: 8), Expanded( child: Text( '${src.author} • ${src.work}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: Colors.white), ), ), IconButton( icon: const Icon(Icons.copy_rounded, size: 16, color: Color(0xFFFFD54F)), tooltip: 'Скопировать цитату', onPressed: () { Clipboard.setData(ClipboardData(text: '[${src.author} · ${src.work}]: «${src.fullText}»')); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Цитата скопирована в буфер')), ); }, ), ], ), const SizedBox(height: 8), Text( src.fullText.isNotEmpty ? src.fullText : src.excerpt, style: const TextStyle(fontSize: 13, height: 1.5, color: Color(0xFFE0E0E0)), ), ], ), ), ], ], ), ), ), ); } @override Widget build(BuildContext context) { if (!_repo.isLoaded) { return const Center(child: CircularProgressIndicator(color: Color(0xFFFFD54F))); } final query = widget.searchQuery; var list = _repo.search(query, onlyWithImages: _onlyWithImages); if (_selectedAuthor != null) { list = list.where((s) => s.sources.any((src) => src.author == _selectedAuthor)).toList(); } // Собираем уникальных авторов для быстрого фильтра final authors = {}; for (final s in _repo.allSymbols) { for (final src in s.sources) { if (src.author.isNotEmpty) authors.add(src.author); } } return Column( children: [ // Плашка фильтров внешнего пантеона Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), color: const Color(0xFF101222), child: Row( children: [ FilterChip( label: Text('🖼️ С иллюстрациями (${_repo.search("", onlyWithImages: true).length})'), selected: _onlyWithImages, selectedColor: const Color(0xFFFFD54F), labelStyle: TextStyle( color: _onlyWithImages ? Colors.black : const Color(0xFFD1D5DB), fontSize: 12, fontWeight: FontWeight.bold, ), backgroundColor: const Color(0xFF161828), side: BorderSide(color: _onlyWithImages ? const Color(0xFFFFD54F) : const Color(0xFF2A2D4A)), onSelected: (val) => setState(() => _onlyWithImages = val), ), const SizedBox(width: 8), Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ for (final author in authors) ...[ Padding( padding: const EdgeInsets.only(right: 6), child: ChoiceChip( label: Text(author, style: const TextStyle(fontSize: 11)), selected: _selectedAuthor == author, selectedColor: const Color(0xFFFFD54F).withValues(alpha: 0.3), labelStyle: TextStyle( color: _selectedAuthor == author ? const Color(0xFFFFD54F) : Colors.white70, fontWeight: _selectedAuthor == author ? FontWeight.bold : FontWeight.normal, ), backgroundColor: const Color(0xFF161828), onSelected: (val) { setState(() { _selectedAuthor = val ? author : null; }); }, ), ), ], ], ), ), ), ], ), ), // Счетчик Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 4), child: Row( children: [ Text( 'Внешний реестр: ${list.length} понятий (из ${_repo.totalCount})', style: const TextStyle(fontSize: 12, color: Color(0xFF8E90A4), fontWeight: FontWeight.bold), ), ], ), ), // Список символов Expanded( child: list.isEmpty ? const Center( child: Text( 'Понятия не найдены', style: TextStyle(color: Color(0xFF8E90A4)), ), ) : ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), itemCount: list.length, separatorBuilder: (_, __) => const SizedBox(height: 8), itemBuilder: (context, idx) { final item = list[idx]; return InkWell( onTap: () => _showDetailModal(item), borderRadius: BorderRadius.circular(14), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFF161828), borderRadius: BorderRadius.circular(14), border: Border.all( color: item.hasIllustrations ? const Color(0xFFFFD54F).withValues(alpha: 0.3) : const Color(0xFF2A2D4A), ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (item.hasIllustrations) ...[ ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.asset( item.illustrations.first['file'] as String? ?? '', width: 48, height: 48, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const Icon(Icons.image, size: 36, color: Color(0xFFFFD54F)), ), ), const SizedBox(width: 12), ], Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( item.term.toUpperCase(), style: const TextStyle( fontWeight: FontWeight.w900, fontSize: 15, color: Color(0xFFFFD54F), ), ), const Spacer(), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: const Color(0xFF24273E), borderRadius: BorderRadius.circular(6), ), child: Text( item.primaryAuthor, style: const TextStyle(fontSize: 10, color: Colors.white70), ), ), ], ), const SizedBox(height: 6), Text( item.primaryExcerpt, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12, color: Color(0xFFC0C4D6), height: 1.4), ), ], ), ), ], ), ), ); }, ), ), ], ); } }