import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/interpreter_persona.dart'; import '../services/settings_repository.dart'; import '../services/words_repository.dart'; import '../services/true_random_service.dart'; import '../widgets/gradient_star_badge.dart'; import 'manual_dive_screen.dart'; class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final settings = SettingsRepository(); return Scaffold( appBar: AppBar( title: const Row( children: [ GradientStar(size: 24), SizedBox(width: 10), Text( 'Настройки', style: TextStyle(fontWeight: FontWeight.w900), ), ], ), ), body: ListenableBuilder( listenable: settings, builder: (context, _) { final selected = settings.personaId; return ListView( padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), children: [ Text( 'Голос толкователя', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w900, color: const Color(0xFFFFD54F), letterSpacing: 0.3, ), ), const SizedBox(height: 6), Text( 'От этого зависит, как ИИ свяжет выпавшие знаки с вопросом. Базовый голос — прямой, без тумана.', style: theme.textTheme.bodyMedium?.copyWith( height: 1.35, color: theme.colorScheme.onSurface.withValues(alpha: 0.78), ), ), const SizedBox(height: 14), for (final persona in InterpreterPersona.all) ...[ _PersonaCard( persona: persona, selected: persona.id == selected, onTap: () { HapticFeedback.selectionClick(); settings.setPersonaId(persona.id); }, ), const SizedBox(height: 10), ], const SizedBox(height: 22), const _ManualDiveEntry(), const SizedBox(height: 22), const _SymbolDescriptionsBackupSection(), const SizedBox(height: 24), const _RandomOrgKeySection(), ], ); }, ), ); } } class _PersonaCard extends StatelessWidget { final InterpreterPersona persona; final bool selected; final VoidCallback onTap; const _PersonaCard({ required this.persona, required this.selected, required this.onTap, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(18), child: AnimatedContainer( duration: const Duration(milliseconds: 180), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: isDark ? const Color(0xFF17192C) : theme.colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(18), border: Border.all( color: selected ? const Color(0xFFFFD54F) : const Color(0xFFFFD54F).withValues(alpha: 0.22), width: selected ? 1.6 : 1.0, ), boxShadow: selected ? [ BoxShadow( color: const Color(0xFFFFD54F).withValues(alpha: 0.14), blurRadius: 12, ), ] : null, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 42, height: 42, alignment: Alignment.center, decoration: BoxDecoration( color: const Color(0xFF2A2D4A), borderRadius: BorderRadius.circular(12), ), child: Icon(persona.icon, color: const Color(0xFFFFD54F), size: 22), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( persona.title, style: const TextStyle( fontWeight: FontWeight.w900, fontSize: 15.5, color: Color(0xFFFFF9C4), ), ), ), Icon( selected ? Icons.radio_button_checked_rounded : Icons.radio_button_off_rounded, size: 20, color: selected ? const Color(0xFFFFD54F) : theme.colorScheme.outline, ), ], ), const SizedBox(height: 4), Text( persona.subtitle, style: TextStyle( fontSize: 13, height: 1.35, color: theme.colorScheme.onSurface.withValues(alpha: 0.78), ), ), ], ), ), ], ), ), ), ); } } class _RandomOrgKeySection extends StatefulWidget { const _RandomOrgKeySection(); @override State<_RandomOrgKeySection> createState() => _RandomOrgKeySectionState(); } class _RandomOrgKeySectionState extends State<_RandomOrgKeySection> { late final TextEditingController _controller; bool _obscure = true; bool _saving = false; @override void initState() { super.initState(); _controller = TextEditingController( text: SettingsRepository().randomOrgApiKey, ); } @override void dispose() { _controller.dispose(); super.dispose(); } Future _save() async { setState(() => _saving = true); await SettingsRepository().setRandomOrgApiKey(_controller.text); TrueRandomService.instance.invalidatePool(); TrueRandomService.instance.prefetch(); if (!mounted) return; setState(() => _saving = false); HapticFeedback.mediumImpact(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( SettingsRepository().hasRandomOrgApiKey ? 'Ключ RANDOM.ORG сохранён. Броски идут через JSON-RPC.' : 'Ключ снят. Броски пойдут через бесплатный HTTP или локальный жребий.', ), behavior: SnackBarBehavior.floating, ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final settings = SettingsRepository(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'RANDOM.ORG', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w900, color: const Color(0xFFFFD54F), letterSpacing: 0.3, ), ), const SizedBox(height: 6), Text( 'В запрос уходит UUID apiKey, не hashed key с дашборда. ' 'Developer-ключ бесплатный, 1000 запросов/день.\n' 'Матрица 1000 слов пересобирается раз в сутки (полночь UTC) тем же API: ' 'перестановка 0–999 без повторов.', style: theme.textTheme.bodyMedium?.copyWith( height: 1.35, color: theme.colorScheme.onSurface.withValues(alpha: 0.78), ), ), const SizedBox(height: 12), TextField( controller: _controller, obscureText: _obscure, autocorrect: false, enableSuggestions: false, keyboardType: TextInputType.visiblePassword, decoration: InputDecoration( labelText: 'API key (UUID)', hintText: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', helperText: settings.hasRandomOrgApiKey ? 'Сейчас: ${settings.randomOrgApiKeyMasked}' : 'Пусто — запасной HTTP без ключа', filled: true, fillColor: const Color(0xFF17192C), suffixIcon: IconButton( tooltip: _obscure ? 'Показать' : 'Скрыть', icon: Icon( _obscure ? Icons.visibility_outlined : Icons.visibility_off_outlined, color: const Color(0xFFFFD54F), ), onPressed: () => setState(() => _obscure = !_obscure), ), border: OutlineInputBorder(borderRadius: BorderRadius.circular(16)), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide( color: const Color(0xFFFFD54F).withValues(alpha: 0.25), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFFFFD54F), width: 1.5), ), ), ), const SizedBox(height: 10), SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: _saving ? null : _save, icon: _saving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.save_outlined), label: Text(_saving ? 'Сохранение…' : 'Сохранить ключ'), style: FilledButton.styleFrom( backgroundColor: const Color(0xFF2A2D4A), foregroundColor: const Color(0xFFFFF9C4), padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: const BorderSide(color: Color(0xFFFFD54F), width: 1.2), ), ), ), ), ], ); } } class _ManualDiveEntry extends StatelessWidget { const _ManualDiveEntry(); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(18), onTap: () { HapticFeedback.selectionClick(); Navigator.of(context).push( MaterialPageRoute(builder: (_) => const ManualDiveScreen()), ); }, child: Ink( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFF17192C), borderRadius: BorderRadius.circular(18), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.45)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Ручной обогатитель (Паспорт 999)', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w900, color: const Color(0xFFFFD54F), ), ), const SizedBox(height: 6), Text( 'Система окошек по 6 разделам и 78 предпромптов: копируй строки, вставляй ответы или разбирай Markdown целиком.', style: theme.textTheme.bodyMedium?.copyWith(height: 1.35), ), ], ), ), ), ); } } class _SymbolDescriptionsBackupSection extends StatefulWidget { const _SymbolDescriptionsBackupSection(); @override State<_SymbolDescriptionsBackupSection> createState() => _SymbolDescriptionsBackupSectionState(); } class _SymbolDescriptionsBackupSectionState extends State<_SymbolDescriptionsBackupSection> { final _repo = WordsRepository(); Future _exportCustomEdits() async { final editsCount = _repo.customEditsCount; final jsonStr = _repo.exportCustomEditsJson(); final defaultFileName = 'oracle_999_custom_symbols_${WordsRepository.utcDateStamp()}.json'; _showExportDialog( title: 'Экспорт новых описаний', subtitle: editsCount > 0 ? 'Выгрузка $editsCount отредактированных символов со всеми статьями' : 'Пользовательских правок пока нет, файл содержит структуру выгрузки', fileName: defaultFileName, content: jsonStr, itemCount: editsCount, ); } Future _exportFullLexicon() async { final jsonStr = _repo.exportFullLexiconJson(); final defaultFileName = 'oracle_999_full_canon_1000_${WordsRepository.utcDateStamp()}.json'; _showExportDialog( title: 'Экспорт полной базы (1000 символов)', subtitle: 'Полный корпус всех 1000 знаков Оракула с каноном, статьями и формулами', fileName: defaultFileName, content: jsonStr, itemCount: 1000, ); } Future _showExportDialog({ required String title, required String subtitle, required String fileName, required String content, required int itemCount, }) async { HapticFeedback.lightImpact(); final bytes = utf8.encode(content).length; final sizeKb = (bytes / 1024).toStringAsFixed(1); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: const Color(0xFF151829), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (context) { return Padding( padding: EdgeInsets.fromLTRB( 20, 16, 20, MediaQuery.of(context).viewInsets.bottom + 28, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Container( width: 44, height: 4, decoration: BoxDecoration( color: const Color(0xFFFFD54F).withValues(alpha: 0.4), borderRadius: BorderRadius.circular(2), ), ), ), const SizedBox(height: 16), Row( children: [ const Icon(Icons.file_download_outlined, color: Color(0xFFFFD54F), size: 26), const SizedBox(width: 10), Expanded( child: Text( title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w900, color: Color(0xFFFFF9C4), ), ), ), ], ), const SizedBox(height: 6), Text( subtitle, style: const TextStyle(fontSize: 13, color: Color(0xFF8C93B8), height: 1.35), ), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFF0F111E), borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFF2A2D4A)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Имя файла:', style: TextStyle(color: Color(0xFF8C93B8), fontSize: 12)), Text(fileName, style: const TextStyle(color: Color(0xFFFFD54F), fontWeight: FontWeight.bold, fontSize: 12)), ], ), const SizedBox(height: 6), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Объём данных:', style: TextStyle(color: Color(0xFF8C93B8), fontSize: 12)), Text('$itemCount знаков • $sizeKb КБ', style: const TextStyle(color: Colors.white, fontSize: 12)), ], ), ], ), ), const SizedBox(height: 18), FilledButton.icon( icon: const Icon(Icons.copy_rounded, color: Colors.black), label: const Text( 'Скопировать весь JSON в буфер', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black), ), style: FilledButton.styleFrom( backgroundColor: const Color(0xFFFFD54F), padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), onPressed: () { Clipboard.setData(ClipboardData(text: content)); HapticFeedback.mediumImpact(); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.check_circle_rounded, color: Color(0xFFFFD54F), size: 20), const SizedBox(width: 10), Expanded( child: Text( 'Файл ($sizeKb КБ, $itemCount знаков) скопирован в буфер обмена! Перенесите его в новую версию через "Импорт".', style: const TextStyle(fontWeight: FontWeight.w600), ), ), ], ), duration: const Duration(seconds: 4), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); }, ), const SizedBox(height: 10), OutlinedButton.icon( icon: const Icon(Icons.save_alt_rounded, color: Color(0xFFFFD54F)), label: const Text( 'Сохранить в папку Загрузки (Download)', style: TextStyle(fontWeight: FontWeight.bold, color: Color(0xFFFFD54F)), ), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), side: const BorderSide(color: Color(0xFFFFD54F), width: 1.2), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), onPressed: () async { final savedPath = await _saveContentToDownloads(fileName, content); if (!context.mounted) return; Navigator.pop(context); HapticFeedback.mediumImpact(); if (savedPath != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Файл успешно сохранён: $savedPath'), backgroundColor: const Color(0xFF1E2238), duration: const Duration(seconds: 4), behavior: SnackBarBehavior.floating, ), ); } else { Clipboard.setData(ClipboardData(text: content)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Папка Download недоступна напрямую. Файл скопирован в буфер обмена!'), duration: Duration(seconds: 4), behavior: SnackBarBehavior.floating, ), ); } }, ), ], ), ); }, ); } Future _saveContentToDownloads(String fileName, String content) async { try { final candidates = []; if (Platform.isAndroid) { candidates.add('/storage/emulated/0/Download'); candidates.add('/sdcard/Download'); candidates.add('/storage/emulated/0/Documents'); } else if (Platform.isLinux || Platform.isMacOS) { final home = Platform.environment['HOME']; if (home != null) { candidates.add('$home/Downloads'); candidates.add('$home/Documents'); } } else if (Platform.isWindows) { final userProfile = Platform.environment['USERPROFILE']; if (userProfile != null) { candidates.add('$userProfile/Downloads'); candidates.add('$userProfile/Documents'); } } for (final dirPath in candidates) { final dir = Directory(dirPath); if (dir.existsSync()) { final file = File('${dir.path}/$fileName'); await file.writeAsString(content, flush: true); return file.path; } } for (final dirPath in candidates) { try { final dir = Directory(dirPath); await dir.create(recursive: true); final file = File('${dir.path}/$fileName'); await file.writeAsString(content, flush: true); return file.path; } catch (_) {} } return null; } catch (_) { return null; } } Future _openImportDialog() async { final textController = TextEditingController(); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: const Color(0xFF151829), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (context) { return StatefulBuilder( builder: (context, setModalState) { return Padding( padding: EdgeInsets.fromLTRB( 20, 16, 20, MediaQuery.of(context).viewInsets.bottom + 24, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Container( width: 44, height: 4, decoration: BoxDecoration( color: const Color(0xFFFFD54F).withValues(alpha: 0.4), borderRadius: BorderRadius.circular(2), ), ), ), const SizedBox(height: 16), const Row( children: [ Icon(Icons.file_upload_outlined, color: Color(0xFFFFD54F), size: 26), SizedBox(width: 10), Text( 'Импорт описаний в эту версию', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w900, color: Color(0xFFFFF9C4), ), ), ], ), const SizedBox(height: 6), const Text( 'Вставьте JSON-файл описаний, выгруженный из предыдущей версии приложения.', style: TextStyle(fontSize: 13, color: Color(0xFF8C93B8), height: 1.35), ), const SizedBox(height: 14), TextField( controller: textController, maxLines: 6, style: const TextStyle(fontSize: 12.5, fontFamily: 'monospace'), decoration: InputDecoration( hintText: 'Вставьте JSON сюда...', hintStyle: const TextStyle(color: Color(0xFF555B7A)), filled: true, fillColor: const Color(0xFF0F111E), border: OutlineInputBorder(borderRadius: BorderRadius.circular(14)), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFF2A2D4A)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFFFFD54F)), ), ), ), const SizedBox(height: 12), Row( children: [ Expanded( child: OutlinedButton.icon( icon: const Icon(Icons.paste_rounded, size: 18), label: const Text('Вставить из буфера'), style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFFFFD54F), side: const BorderSide(color: Color(0xFFFFD54F), width: 1.1), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), onPressed: () async { final data = await Clipboard.getData('text/plain'); if (data?.text != null) { textController.text = data!.text!; HapticFeedback.lightImpact(); } }, ), ), const SizedBox(width: 10), Expanded( child: FilledButton.icon( icon: const Icon(Icons.check_rounded, color: Colors.black, size: 18), label: const Text('Применить', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black)), style: FilledButton.styleFrom( backgroundColor: const Color(0xFFFFD54F), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), onPressed: () async { final raw = textController.text.trim(); if (raw.isEmpty) return; final count = await _repo.importCustomEditsJson(raw); if (!context.mounted) return; Navigator.pop(context); setState(() {}); HapticFeedback.heavyImpact(); if (count > 0) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.check_circle_rounded, color: Color(0xFFFFD54F), size: 20), const SizedBox(width: 10), Expanded( child: Text( 'Успешно импортировано $count описаний символов! Они активированы в базе.', style: const TextStyle(fontWeight: FontWeight.w600), ), ), ], ), backgroundColor: const Color(0xFF1E2238), duration: const Duration(seconds: 4), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Не удалось распознать формат JSON или нет данных о символах.'), backgroundColor: Color(0xFF3E1E22), duration: Duration(seconds: 3), behavior: SnackBarBehavior.floating, ), ); } }, ), ), ], ), ], ), ); }, ); }, ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final editsCount = _repo.customEditsCount; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF151829), borderRadius: BorderRadius.circular(18), border: Border.all( color: const Color(0xFFFFD54F).withValues(alpha: 0.3), width: 1.2, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 38, height: 38, alignment: Alignment.center, decoration: BoxDecoration( color: const Color(0xFF2A2D4A), borderRadius: BorderRadius.circular(10), ), child: const Icon(Icons.auto_stories_outlined, color: Color(0xFFFFD54F), size: 20), ), const SizedBox(width: 12), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'База и статьи символов', style: TextStyle( fontWeight: FontWeight.w900, fontSize: 16, color: Color(0xFFFFF9C4), ), ), Text( 'Перенос описаний между версиями', style: TextStyle(fontSize: 12, color: Color(0xFF8C93B8)), ), ], ), ), ], ), const SizedBox(height: 12), Text( 'Выгрузите все отредактированные и обогащённые описания одним файлом JSON. В новой версии приложения нажмите «Импортировать» и вставьте файл — все ваши статьи мгновенно встанут на свои места.', style: TextStyle( fontSize: 13, height: 1.4, color: theme.colorScheme.onSurface.withValues(alpha: 0.8), ), ), const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: const Color(0xFF0F111E), borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFF2A2D4A)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Expanded( child: Text('Пользовательских статей/правок:', style: TextStyle(color: Color(0xFF8C93B8), fontSize: 12)), ), const SizedBox(width: 8), Text( '$editsCount знаков', style: const TextStyle(color: Color(0xFFFFD54F), fontWeight: FontWeight.bold, fontSize: 13), ), ], ), ), const SizedBox(height: 14), Row( children: [ Expanded( child: FilledButton.icon( onPressed: _exportCustomEdits, icon: const Icon(Icons.file_download_outlined, color: Colors.black, size: 18), label: const Text( 'Выгрузить новые', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black, fontSize: 12.5), ), style: FilledButton.styleFrom( backgroundColor: const Color(0xFFFFD54F), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ), ), const SizedBox(width: 8), Expanded( child: OutlinedButton.icon( onPressed: _openImportDialog, icon: const Icon(Icons.file_upload_outlined, color: Color(0xFFFFD54F), size: 18), label: const Text( 'Импортировать', style: TextStyle(fontWeight: FontWeight.bold, color: Color(0xFFFFD54F), fontSize: 12.5), ), style: OutlinedButton.styleFrom( side: const BorderSide(color: Color(0xFFFFD54F), width: 1.1), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ), ), ], ), const SizedBox(height: 8), SizedBox( width: double.infinity, child: TextButton.icon( onPressed: _exportFullLexicon, icon: const Icon(Icons.inventory_2_outlined, size: 16, color: Color(0xFF80DEEA)), label: const Text( 'Выгрузить полный канон (все 1000 символов одним файлом)', style: TextStyle(fontSize: 12, color: Color(0xFF80DEEA)), ), ), ), ], ), ); } }