import 'package:flutter/material.dart'; import 'package:html/parser.dart' as html_parser; import 'package:html/dom.dart' as dom; import 'dart:convert'; import 'package:be_happy/di/service_locator.dart'; import '../../core/app_colors.dart'; import '../components/custom_app_bar.dart'; import '../../domain/usecase/get_news_by_id_usecase.dart'; class NewsDetailScreen extends StatefulWidget { final int newsId; final String title; const NewsDetailScreen({ super.key, required this.newsId, required this.title, }); @override State createState() => _NewsDetailScreenState(); } class _NewsDetailScreenState extends State { bool _isLoading = true; String? _errorMessage; dynamic _news; @override void initState() { super.initState(); _fetchNews(); } Future _fetchNews() async { try { final usecase = getIt(); final news = await usecase(widget.newsId); if (mounted) { setState(() { _news = news; _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _errorMessage = e.toString(); _isLoading = false; }); } } } @override Widget build(BuildContext context) { return Scaffold( body: Container( decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg), child: SafeArea( child: Column( children: [ const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: CustomAppBar(title: widget.title), ), // 🔹 Контент Expanded( child: _isLoading ? const Center( child: CircularProgressIndicator(color: Colors.white), ) : _errorMessage != null ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Ошибка загрузки новости', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 16), Text( _errorMessage!, style: TextStyle( color: Colors.white.withOpacity(0.6), fontSize: 14, ), textAlign: TextAlign.center, ), const SizedBox(height: 24), ElevatedButton( onPressed: () { setState(() { _isLoading = true; _errorMessage = null; _fetchNews(); }); }, child: const Text('Повторить'), ), ], ), ) : _news != null ? _buildNewsContent(_news) : const SizedBox.shrink(), ), ], ), ), ), ); } Widget _buildNewsContent(dynamic news) { return SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Текст новости: сначала проверяем textJson, потом text if (news.textJson != null) ..._parseJsonText(news.textJson) else if (news.text != null) ..._parseHtmlText(news.text), ], ), ); } List _parseHtmlText(String htmlText) { final parsedHtml = html_parser.parse(htmlText); final List elements = parsedHtml.body?.nodes ?? []; return _parseHtmlElements(elements); } List _parseHtmlElements(List nodes) { List widgets = []; for (final node in nodes) { if (node is dom.Element) { switch (node.localName) { case 'h1': case 'h2': case 'h3': widgets.add( Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( node.text, style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ); break; case 'p': widgets.add( Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Text( node.text, style: const TextStyle( color: Colors.white70, fontSize: 14, height: 1.5, ), ), ), ); break; case 'ul': widgets.add( Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: node.children .where((element) => element is dom.Element && element.localName == 'li') .map((dom.Element li) => Padding( padding: const EdgeInsets.only(left: 16, top: 4, bottom: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('• ', style: TextStyle( color: Colors.white70, fontSize: 14, )), Expanded( child: Text( li.text, style: const TextStyle( color: Colors.white70, fontSize: 14, height: 1.5, ), ), ), ], ), )) .toList(), ), ), ); break; default: widgets.add( Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Text( node.text, style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), ), ); } } else if (node is dom.Text) { widgets.add( Text( node.text, style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), ); } } return widgets; } // 🔹 Парсинг JSON-текста (новый метод) List _parseJsonText(String jsonString) { try { final dynamic data = jsonDecode(jsonString); return _parseJsonNode(data); } catch (e) { return [ const Text( 'Ошибка отображения текста новости', style: TextStyle(color: Colors.red, fontSize: 14), ), ]; } } List _parseJsonNode(dynamic node) { List widgets = []; if (node is List) { // Если корень — массив, парсим каждый элемент for (final item in node) { widgets.addAll(_parseJsonNode(item)); } } else if (node is Map) { final type = node['type'] as String?; final content = node['content']; switch (type) { case 'div': if (content is List) { widgets.addAll(_parseJsonNode(content)); } break; case 'h2': widgets.add( Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( (content is List) ? content.join(' ') : content?.toString() ?? '', style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ); break; case 'p': widgets.add( Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Text( (content is List) ? content.join(' ') : content?.toString() ?? '', style: const TextStyle( color: Colors.white70, fontSize: 14, height: 1.5, ), ), ), ); break; case 'ul': if (content is List) { widgets.add( Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: content .where((item) => item is Map) .map((item) => item as Map) .where((item) => item['type'] == 'li') .map((li) => Padding( padding: const EdgeInsets.only(left: 16, top: 4, bottom: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('• ', style: TextStyle( color: Colors.white70, fontSize: 14, )), Expanded( child: Text( (li['content'] is List) ? li['content'].join(' ') : li['content']?.toString() ?? '', style: const TextStyle( color: Colors.white70, fontSize: 14, height: 1.5, ), ), ), ], ), )) .toList(), ), ), ); } break; default: // Если тип неизвестен, просто выводим текст widgets.add( Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Text( (content is List) ? content.join(' ') : content?.toString() ?? type ?? 'unknown', style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), ), ); } } return widgets; } String _formatDate(DateTime? date) { if (date == null) return ''; final localDate = date.toLocal(); return '${localDate.day.toString().padLeft(2, '0')}.${localDate.month.toString().padLeft(2, '0')}.${localDate.year}'; } }