fix news,
This commit is contained in:
@@ -86,12 +86,12 @@ class BlogPostModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Extract excerpt from blogIntro or metaDescription
|
||||
final excerpt = blogIntro ?? metaDescription ?? '';
|
||||
|
||||
// Use content_html preferentially, fall back to content
|
||||
final htmlContent = contentHtml ?? content;
|
||||
|
||||
// Excerpt is ONLY from blog_intro (plain text)
|
||||
final excerpt = blogIntro ?? '';
|
||||
|
||||
// Use meta image with full URL path
|
||||
String imageUrl;
|
||||
if (metaImage != null && metaImage!.isNotEmpty) {
|
||||
@@ -117,7 +117,9 @@ class BlogPostModel {
|
||||
return NewsArticle(
|
||||
id: name,
|
||||
title: title,
|
||||
excerpt: excerpt.length > 200 ? '${excerpt.substring(0, 200)}...' : excerpt,
|
||||
excerpt: excerpt.isNotEmpty
|
||||
? (excerpt.length > 300 ? '${excerpt.substring(0, 300)}...' : excerpt)
|
||||
: 'Không có mô tả',
|
||||
content: htmlContent,
|
||||
imageUrl: imageUrl,
|
||||
category: category,
|
||||
|
||||
@@ -7,6 +7,7 @@ library;
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
@@ -181,9 +182,79 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Article Body
|
||||
if (article.content != null)
|
||||
_buildArticleBody(article.content!),
|
||||
// Article Body - Render HTML content
|
||||
if (article.content != null && article.content!.isNotEmpty)
|
||||
Container(
|
||||
// Wrap Html in Container to prevent rendering issues
|
||||
child: Html(
|
||||
data: article.content,
|
||||
style: {
|
||||
"body": Style(
|
||||
margin: Margins.zero,
|
||||
padding: HtmlPaddings.zero,
|
||||
fontSize: FontSize(16),
|
||||
lineHeight: const LineHeight(1.7),
|
||||
color: const Color(0xFF1E293B),
|
||||
),
|
||||
"h2": Style(
|
||||
fontSize: FontSize(20),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF1E293B),
|
||||
margin: Margins.only(top: 32, bottom: 16),
|
||||
),
|
||||
"h3": Style(
|
||||
fontSize: FontSize(18),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF1E293B),
|
||||
margin: Margins.only(top: 24, bottom: 12),
|
||||
),
|
||||
"p": Style(
|
||||
fontSize: FontSize(16),
|
||||
color: const Color(0xFF1E293B),
|
||||
lineHeight: const LineHeight(1.7),
|
||||
margin: Margins.only(bottom: 16),
|
||||
),
|
||||
"strong": Style(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF1E293B),
|
||||
),
|
||||
"img": Style(
|
||||
margin: Margins.symmetric(vertical: 16),
|
||||
),
|
||||
"ul": Style(
|
||||
margin: Margins.only(left: 16, bottom: 16),
|
||||
),
|
||||
"ol": Style(
|
||||
margin: Margins.only(left: 16, bottom: 16),
|
||||
),
|
||||
"li": Style(
|
||||
fontSize: FontSize(16),
|
||||
color: const Color(0xFF1E293B),
|
||||
lineHeight: const LineHeight(1.5),
|
||||
margin: Margins.only(bottom: 8),
|
||||
),
|
||||
"blockquote": Style(
|
||||
backgroundColor: const Color(0xFFF0F9FF),
|
||||
border: const Border(
|
||||
left: BorderSide(color: AppColors.primaryBlue, width: 4),
|
||||
),
|
||||
padding: HtmlPaddings.all(16),
|
||||
margin: Margins.symmetric(vertical: 24),
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
"div": Style(
|
||||
margin: Margins.zero,
|
||||
padding: HtmlPaddings.zero,
|
||||
),
|
||||
},
|
||||
onLinkTap: (url, attributes, element) {
|
||||
// Handle link taps if needed
|
||||
if (url != null) {
|
||||
debugPrint('Link tapped: $url');
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
@@ -261,192 +332,6 @@ class _NewsDetailPageState extends ConsumerState<NewsDetailPage> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Build article body with simple HTML parsing
|
||||
Widget _buildArticleBody(String content) {
|
||||
final elements = _parseHTMLContent(content);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: elements,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse HTML-like content into widgets
|
||||
List<Widget> _parseHTMLContent(String content) {
|
||||
final List<Widget> widgets = [];
|
||||
final lines = content.split('\n').where((line) => line.trim().isNotEmpty);
|
||||
|
||||
for (final line in lines) {
|
||||
final trimmed = line.trim();
|
||||
|
||||
// H2 heading
|
||||
if (trimmed.startsWith('<h2>') && trimmed.endsWith('</h2>')) {
|
||||
final text = trimmed.substring(4, trimmed.length - 5);
|
||||
widgets.add(_buildH2(text));
|
||||
}
|
||||
// H3 heading
|
||||
else if (trimmed.startsWith('<h3>') && trimmed.endsWith('</h3>')) {
|
||||
final text = trimmed.substring(4, trimmed.length - 5);
|
||||
widgets.add(_buildH3(text));
|
||||
}
|
||||
// Paragraph
|
||||
else if (trimmed.startsWith('<p>') && trimmed.endsWith('</p>')) {
|
||||
final text = trimmed.substring(3, trimmed.length - 4);
|
||||
widgets.add(_buildParagraph(text));
|
||||
}
|
||||
// Unordered list start
|
||||
else if (trimmed == '<ul>') {
|
||||
// Collect list items
|
||||
final listItems = <String>[];
|
||||
continue;
|
||||
}
|
||||
// List item
|
||||
else if (trimmed.startsWith('<li>') && trimmed.endsWith('</li>')) {
|
||||
final text = trimmed.substring(4, trimmed.length - 5);
|
||||
widgets.add(_buildListItem(text, false));
|
||||
}
|
||||
// Ordered list item (number prefix)
|
||||
else if (RegExp(r'^\d+\.').hasMatch(trimmed)) {
|
||||
widgets.add(_buildListItem(trimmed, true));
|
||||
}
|
||||
// Blockquote
|
||||
else if (trimmed.startsWith('<blockquote>') &&
|
||||
trimmed.endsWith('</blockquote>')) {
|
||||
final text = trimmed.substring(12, trimmed.length - 13);
|
||||
widgets.add(_buildBlockquote(text));
|
||||
}
|
||||
// Highlight box (custom tag)
|
||||
else if (trimmed.startsWith('<highlight type="')) {
|
||||
final typeMatch = RegExp(r'type="(\w+)"').firstMatch(trimmed);
|
||||
final contentMatch = RegExp(r'>(.*)</highlight>').firstMatch(trimmed);
|
||||
|
||||
if (typeMatch != null && contentMatch != null) {
|
||||
final type = typeMatch.group(1);
|
||||
final content = contentMatch.group(1);
|
||||
|
||||
widgets.add(
|
||||
HighlightBox(
|
||||
type: type == 'tip' ? HighlightType.tip : HighlightType.warning,
|
||||
title: type == 'tip' ? 'Mẹo từ chuyên gia' : 'Lưu ý khi sử dụng',
|
||||
content: content ?? '',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
/// Build H2 heading
|
||||
Widget _buildH2(String text) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 32, bottom: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF1E293B),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(height: 2, width: 60, color: AppColors.primaryBlue),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build H3 heading
|
||||
Widget _buildH3(String text) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 24, bottom: 12),
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF1E293B),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build paragraph
|
||||
Widget _buildParagraph(String text) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: Color(0xFF1E293B),
|
||||
height: 1.7,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build list item
|
||||
Widget _buildListItem(String text, bool isOrdered) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16, bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
isOrdered ? '' : '• ',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: AppColors.primaryBlue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: Color(0xFF1E293B),
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build blockquote
|
||||
Widget _buildBlockquote(String text) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 24),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF0F9FF),
|
||||
border: const Border(
|
||||
left: BorderSide(color: AppColors.primaryBlue, width: 4),
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(8),
|
||||
bottomRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: Color(0xFF1E293B),
|
||||
fontStyle: FontStyle.italic,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build tags section
|
||||
Widget _buildTagsSection(List<String> tags) {
|
||||
return Container(
|
||||
|
||||
@@ -116,10 +116,10 @@ class NewsCard extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
// Date
|
||||
Icon(
|
||||
const Icon(
|
||||
Icons.calendar_today,
|
||||
size: 12,
|
||||
color: const Color(0xFF64748B),
|
||||
color: Color(0xFF64748B),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
|
||||
Reference in New Issue
Block a user