From fb5a0d52bcfb6b9df93b3dce2b2dd1fd078d4b3c Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 24 Oct 2025 18:55:01 +0300 Subject: [PATCH] feat(feed): mdToHtml --- .../presentation/pages/blog_post_screen.dart | 32 ++++- .../widgets/blog_post_widget.dart | 45 +++++-- .../presentation/widgets/diets_widget.dart | 86 +++++++------ .../presentation/widgets/plan_widget.dart | 116 +++++++----------- .../presentation/widgets/recipes_widget.dart | 112 +++++++---------- .../presentation/widgets/my_diet_widget.dart | 61 +++++---- .../presentation/widgets/my_plan_widget.dart | 44 +++++-- .../widgets/profile_blog_post_widget.dart | 107 +++++++--------- .../widgets/my_recipe_widget.dart | 80 ++++++------ pubspec.yaml | 1 + 10 files changed, 355 insertions(+), 329 deletions(-) diff --git a/lib/features/blog_post/presentation/pages/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart index 4b08b333..f10bb742 100644 --- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart +++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart @@ -18,6 +18,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:markdown/markdown.dart' as md; class BlogPostScreen extends StatefulWidget { final int id; @@ -231,10 +232,35 @@ class _BlogPostScreenState extends State { ), if (blogPostState.blogPostModel?.description.isNotEmpty ?? false) Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Html( - data: blogPostState.blogPostModel?.description.replaceAll('\n', '
'), - style: {"*": Style(color: Colors.black87)}, + data: md.markdownToHtml(blogPostState.blogPostModel!.description.replaceAll('\n', '
')), + style: { + "body": Style(margin: Margins.zero, padding: HtmlPaddings.zero, fontSize: FontSize(16), lineHeight: const LineHeight(1.5)), + "p": Style(margin: Margins.only(bottom: 12), color: Colors.black87), + "h1, h2, h3, h4, h5, h6": Style(margin: Margins.only(top: 16, bottom: 8), color: Colors.black, fontWeight: FontWeight.bold), + "h1": Style(fontSize: FontSize(24)), + "h2": Style(fontSize: FontSize(20)), + "h3": Style(fontSize: FontSize(18)), + "ul, ol": Style(margin: Margins.only(bottom: 12), padding: HtmlPaddings.only(left: 16)), + "li": Style(margin: Margins.only(bottom: 4), color: Colors.black87), + "strong, b": Style(fontWeight: FontWeight.bold, color: Colors.black), + "em, i": Style(fontStyle: FontStyle.italic), + "code": Style( + backgroundColor: Colors.grey.shade100, + padding: HtmlPaddings.symmetric(horizontal: 4, vertical: 2), + fontFamily: 'monospace', + ), + "pre": Style(backgroundColor: Colors.grey.shade100, padding: HtmlPaddings.all(12), margin: Margins.only(bottom: 12)), + "blockquote": Style( + padding: HtmlPaddings.only(left: 16), + margin: Margins.only(left: 8, bottom: 12), + fontStyle: FontStyle.italic, + color: Colors.grey.shade700, + ), + "a": Style(color: Colors.blue, textDecoration: TextDecoration.underline), + "img": Style(width: Width(MediaQuery.of(context).size.width - 32)), + }, ), ), const SizedBox(height: 8), diff --git a/lib/features/feed/presentation/widgets/blog_post_widget.dart b/lib/features/feed/presentation/widgets/blog_post_widget.dart index 6718f6e0..baebe3a5 100644 --- a/lib/features/feed/presentation/widgets/blog_post_widget.dart +++ b/lib/features/feed/presentation/widgets/blog_post_widget.dart @@ -17,6 +17,7 @@ import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:markdown/markdown.dart' as md; class BlogPostWidget extends StatefulWidget { final int index; @@ -57,10 +58,7 @@ class _BlogPostWidgetState extends State { children: [ GestureDetector( onTap: () { - GoRouter.of(context).pushNamed( - 'profileScreen', - extra: state.feedBlogPostList[widget.index].author.authorSlug, - ); + GoRouter.of(context).pushNamed('profileScreen', extra: state.feedBlogPostList[widget.index].author.authorSlug); }, child: CircleAvatar( radius: 20, @@ -126,14 +124,39 @@ class _BlogPostWidgetState extends State { const SizedBox(height: 8), if (state.feedBlogPostList[widget.index].description.isNotEmpty) Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.symmetric(horizontal: 12), child: Html( - data: - (state.feedBlogPostList[widget.index].description.length > 200 - ? state.feedBlogPostList[widget.index].description.substring(0, 200) - : state.feedBlogPostList[widget.index].description) - .replaceAll('\n', '
'), - style: {"*": Style(fontSize: FontSize(16), color: Colors.black87)}, + data: md.markdownToHtml( + (state.feedBlogPostList[widget.index].description.length > 200 + ? state.feedBlogPostList[widget.index].description.substring(0, 200).replaceAll('\n', '
') + : state.feedBlogPostList[widget.index].description.replaceAll('\n', '
')), + ), + style: { + "body": Style(margin: Margins.zero, padding: HtmlPaddings.zero, fontSize: FontSize(16), lineHeight: const LineHeight(1.5)), + "p": Style(margin: Margins.only(bottom: 12), color: Colors.black87), + "h1, h2, h3, h4, h5, h6": Style(margin: Margins.only(top: 16, bottom: 8), color: Colors.black, fontWeight: FontWeight.bold), + "h1": Style(fontSize: FontSize(24)), + "h2": Style(fontSize: FontSize(20)), + "h3": Style(fontSize: FontSize(18)), + "ul, ol": Style(margin: Margins.only(bottom: 12), padding: HtmlPaddings.only(left: 16)), + "li": Style(margin: Margins.only(bottom: 4), color: Colors.black87), + "strong, b": Style(fontWeight: FontWeight.bold, color: Colors.black), + "em, i": Style(fontStyle: FontStyle.italic), + "code": Style( + backgroundColor: Colors.grey.shade100, + padding: HtmlPaddings.symmetric(horizontal: 4, vertical: 2), + fontFamily: 'monospace', + ), + "pre": Style(backgroundColor: Colors.grey.shade100, padding: HtmlPaddings.all(12), margin: Margins.only(bottom: 12)), + "blockquote": Style( + padding: HtmlPaddings.only(left: 16), + margin: Margins.only(left: 8, bottom: 12), + fontStyle: FontStyle.italic, + color: Colors.grey.shade700, + ), + "a": Style(color: Colors.blue, textDecoration: TextDecoration.underline), + "img": Style(width: Width(MediaQuery.of(context).size.width - 32)), + }, ), ), if (state.feedBlogPostList[widget.index].description.length > 200) diff --git a/lib/features/feed/presentation/widgets/diets_widget.dart b/lib/features/feed/presentation/widgets/diets_widget.dart index 1f39ff72..257811bf 100644 --- a/lib/features/feed/presentation/widgets/diets_widget.dart +++ b/lib/features/feed/presentation/widgets/diets_widget.dart @@ -17,15 +17,12 @@ import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:markdown/markdown.dart' as md; -class DietsWidget extends StatefulWidget { +class DietsWidget extends StatefulWidget { final Diet diet; final int index; - const DietsWidget({ - super.key, - required this.diet, - required this.index, - }); + const DietsWidget({super.key, required this.diet, required this.index}); @override State createState() => _DietsWidgetState(); @@ -33,18 +30,11 @@ class DietsWidget extends StatefulWidget { class _DietsWidgetState extends State { bool isMoreOptionsVisible = false; - + @override Widget build(BuildContext context) { return InkWell( - onTap: () => GoRouter.of(context).pushNamed( - 'aboutDietScreen', - extra: { - 'slug': widget.diet.slug, - 'index': widget.index, - 'navigateToComments': false, - }, - ), + onTap: () => GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'slug': widget.diet.slug, 'index': widget.index, 'navigateToComments': false}), child: Material( borderRadius: BorderRadius.circular(12), color: Colors.white, @@ -104,12 +94,7 @@ class _DietsWidgetState extends State { bottom: 16, child: SizedBox( width: MediaQuery.sizeOf(context).width, - child: InfoRow( - diet: widget.diet, - itemsBackgroundColor: appTheme.whiteA700, - disableDescriptionOfTypes: true, - type: ProductType.diet, - ), + child: InfoRow(diet: widget.diet, itemsBackgroundColor: appTheme.whiteA700, disableDescriptionOfTypes: true, type: ProductType.diet), ), ), ], @@ -120,26 +105,48 @@ class _DietsWidgetState extends State { children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - widget.diet.name, - style: TextStyles.textMSemibold.copyWith(color: appTheme.black), - ), + child: Text(widget.diet.name, style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), ), if (widget.diet.details.isNotEmpty) ...[ - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Html( - data: widget.diet.details.length > 200 ? '${widget.diet.details.substring(0, 200).replaceAll('\n', '
')}...' : widget.diet.details.replaceAll('\n', '
'), + data: md.markdownToHtml( + widget.diet.details.length > 200 + ? '${widget.diet.details.substring(0, 200).replaceAll('\n', '
')}...' + : widget.diet.details.replaceAll('\n', '
'), + ), style: { - "*": Style( + "body": Style( margin: Margins.zero, - color: appTheme.black, + padding: HtmlPaddings.zero, fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14), - fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400, + lineHeight: const LineHeight(1.5), ), + "p": Style(margin: Margins.only(bottom: 12), color: appTheme.black), + "h1, h2, h3, h4, h5, h6": Style(margin: Margins.only(top: 16, bottom: 8), color: appTheme.black, fontWeight: FontWeight.bold), + "h1": Style(fontSize: FontSize(24)), + "h2": Style(fontSize: FontSize(20)), + "h3": Style(fontSize: FontSize(18)), + "ul, ol": Style(margin: Margins.only(bottom: 12), padding: HtmlPaddings.only(left: 16)), + "li": Style(margin: Margins.only(bottom: 4), color: appTheme.black), + "strong, b": Style(fontWeight: FontWeight.bold, color: appTheme.black), + "em, i": Style(fontStyle: FontStyle.italic), + "code": Style( + backgroundColor: Colors.grey.shade100, + padding: HtmlPaddings.symmetric(horizontal: 4, vertical: 2), + fontFamily: 'monospace', + ), + "pre": Style(backgroundColor: Colors.grey.shade100, padding: HtmlPaddings.all(12), margin: Margins.only(bottom: 12)), + "blockquote": Style( + padding: HtmlPaddings.only(left: 16), + margin: Margins.only(left: 8, bottom: 12), + fontStyle: FontStyle.italic, + color: Colors.grey.shade700, + ), + "a": Style(color: Colors.blue, textDecoration: TextDecoration.underline), + "img": Style(width: Width(MediaQuery.of(context).size.width - 32)), }, ), ), @@ -149,12 +156,7 @@ class _DietsWidgetState extends State { padding: EdgeInsets.only(left: 16, top: 8), child: Text( 'Показать ещё', - style: TextStyle( - height: 1, - color: Color(0xFF8B8B8B), - fontSize: 14, - fontWeight: FontWeight.w600, - ), + style: TextStyle(height: 1, color: Color(0xFF8B8B8B), fontSize: 14, fontWeight: FontWeight.w600), ), ), ], @@ -172,18 +174,14 @@ class _DietsWidgetState extends State { await context.read().likeDiet(widget.diet.id, widget.index); }, ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), FeedIconButton( icon: Icons.chat_bubble_outline, text: widget.diet.totalComments.toString(), onTap: () => GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'slug': widget.diet.slug, 'index': widget.index, 'navigateToComments': true}), ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), FeedIconButton( svgIcon: SvgPicture.asset('assets/icons/repost.svg'), onTap: () async { diff --git a/lib/features/feed/presentation/widgets/plan_widget.dart b/lib/features/feed/presentation/widgets/plan_widget.dart index 271594df..dae66fbf 100644 --- a/lib/features/feed/presentation/widgets/plan_widget.dart +++ b/lib/features/feed/presentation/widgets/plan_widget.dart @@ -19,16 +19,13 @@ import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:markdown/markdown.dart' as md; class PlanWidget extends StatefulWidget { final PlanModel plan; final int index; - const PlanWidget({ - super.key, - required this.plan, - required this.index, - }); + const PlanWidget({super.key, required this.plan, required this.index}); @override State createState() => _PlanWidgetState(); @@ -41,14 +38,7 @@ class _PlanWidgetState extends State { Widget build(BuildContext context) { final s = S.of(context); return InkWell( - onTap: () => GoRouter.of(context).pushNamed( - 'aboutPlanScreen', - extra: { - 'slug': widget.plan.slug, - 'index': widget.index, - 'navigateToComments': false, - }, - ), + onTap: () => GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'slug': widget.plan.slug, 'index': widget.index, 'navigateToComments': false}), child: Material( borderRadius: BorderRadius.circular(12), color: Colors.white, @@ -109,12 +99,7 @@ class _PlanWidgetState extends State { bottom: 16, child: SizedBox( width: MediaQuery.sizeOf(context).width, - child: InfoRow( - plan: widget.plan, - itemsBackgroundColor: appTheme.whiteA700, - disableDescriptionOfTypes: true, - type: ProductType.plan, - ), + child: InfoRow(plan: widget.plan, itemsBackgroundColor: appTheme.whiteA700, disableDescriptionOfTypes: true, type: ProductType.plan), ), ), ], @@ -125,26 +110,43 @@ class _PlanWidgetState extends State { children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - widget.plan.name, - style: TextStyles.textMSemibold.copyWith(color: appTheme.black, fontSize: 18), - ), + child: Text(widget.plan.name, style: TextStyles.textMSemibold.copyWith(color: appTheme.black, fontSize: 18)), ), if (widget.plan.details.isNotEmpty) ...[ - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Html( - data: widget.plan.details.length > 200 ? '${widget.plan.details.substring(0, 200).replaceAll('\n', '
')}...' : widget.plan.details.replaceAll('\n', '
'), + data: md.markdownToHtml( + widget.plan.details.length > 200 + ? '${widget.plan.details.substring(0, 200).replaceAll('\n', '
')}...' + : widget.plan.details.replaceAll('\n', '
'), + ), style: { - "*": Style( - margin: Margins.zero, - color: appTheme.black, - fontSize: FontSize(16), - fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400, + "body": Style(margin: Margins.zero, padding: HtmlPaddings.zero, fontSize: FontSize(16), lineHeight: const LineHeight(1.5)), + "p": Style(margin: Margins.only(bottom: 12), color: appTheme.black), + "h1, h2, h3, h4, h5, h6": Style(margin: Margins.only(top: 16, bottom: 8), color: appTheme.black, fontWeight: FontWeight.bold), + "h1": Style(fontSize: FontSize(24)), + "h2": Style(fontSize: FontSize(20)), + "h3": Style(fontSize: FontSize(18)), + "ul, ol": Style(margin: Margins.only(bottom: 12), padding: HtmlPaddings.only(left: 16)), + "li": Style(margin: Margins.only(bottom: 4), color: appTheme.black), + "strong, b": Style(fontWeight: FontWeight.bold, color: appTheme.black), + "em, i": Style(fontStyle: FontStyle.italic), + "code": Style( + backgroundColor: Colors.grey.shade100, + padding: HtmlPaddings.symmetric(horizontal: 4, vertical: 2), + fontFamily: 'monospace', ), + "pre": Style(backgroundColor: Colors.grey.shade100, padding: HtmlPaddings.all(12), margin: Margins.only(bottom: 12)), + "blockquote": Style( + padding: HtmlPaddings.only(left: 16), + margin: Margins.only(left: 8, bottom: 12), + fontStyle: FontStyle.italic, + color: Colors.grey.shade700, + ), + "a": Style(color: Colors.blue, textDecoration: TextDecoration.underline), + "img": Style(width: Width(MediaQuery.of(context).size.width - 32)), }, ), ), @@ -153,12 +155,7 @@ class _PlanWidgetState extends State { padding: EdgeInsets.only(left: 16, top: 8), child: Text( 'Показать ещё', - style: TextStyle( - height: 1, - color: Color(0xFF8B8B8B), - fontSize: 14, - fontWeight: FontWeight.w600, - ), + style: TextStyle(height: 1, color: Color(0xFF8B8B8B), fontSize: 14, fontWeight: FontWeight.w600), ), ), ], @@ -167,10 +164,7 @@ class _PlanWidgetState extends State { const SizedBox(height: 16), GestureDetector( onTap: () { - GoRouter.of(context).pushNamed( - 'profileScreen', - extra: widget.plan.author.authorSlug, - ); + GoRouter.of(context).pushNamed('profileScreen', extra: widget.plan.author.authorSlug); }, child: Padding( padding: const EdgeInsets.only(left: 16.0), @@ -181,10 +175,7 @@ class _PlanWidgetState extends State { height: 46, decoration: BoxDecoration( shape: BoxShape.circle, - image: DecorationImage( - image: NetworkImage(sl().baseUrl + widget.plan.author.authorProfileIcon), - fit: BoxFit.cover, - ), + image: DecorationImage(image: NetworkImage(sl().baseUrl + widget.plan.author.authorProfileIcon), fit: BoxFit.cover), ), ), subtitle: Row( @@ -194,12 +185,7 @@ class _PlanWidgetState extends State { backgroundColor: appTheme.gray50, padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 10), children: [ - Text( - '${widget.plan.author.authorPlaceInRating}-й в рейтинге', - style: TextStyles.textXSSemibold.copyWith( - color: appTheme.blueA400, - ), - ), + Text('${widget.plan.author.authorPlaceInRating}-й в рейтинге', style: TextStyles.textXSSemibold.copyWith(color: appTheme.blueA400)), ], ), const Spacer(), @@ -212,21 +198,14 @@ class _PlanWidgetState extends State { ), title: Padding( padding: const EdgeInsets.only(bottom: 4.0), - child: Text( - widget.plan.author.authorName, - style: TextStyles.textSSemibold.copyWith( - color: appTheme.black, - ), - ), + child: Text(widget.plan.author.authorName, style: TextStyles.textSSemibold.copyWith(color: appTheme.black)), ), ), ), ), const SizedBox(height: 16), Padding( - padding: const EdgeInsets.only( - left: 16, - ), + padding: const EdgeInsets.only(left: 16), child: Row( children: [ FeedIconButton( @@ -238,20 +217,17 @@ class _PlanWidgetState extends State { await context.read().likePlan(widget.plan.slug, widget.index); }, ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), FeedIconButton( icon: Icons.chat_bubble_outline, text: widget.plan.totalComments.toString(), - onTap: () { - return GoRouter.of(context) - .pushNamed('aboutPlanScreen', extra: {'slug': widget.plan.slug, 'index': widget.index, 'navigateToComments': true}); + onTap: () { + return GoRouter.of( + context, + ).pushNamed('aboutPlanScreen', extra: {'slug': widget.plan.slug, 'index': widget.index, 'navigateToComments': true}); }, ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), FeedIconButton( svgIcon: SvgPicture.asset('assets/icons/repost.svg'), onTap: () async { diff --git a/lib/features/feed/presentation/widgets/recipes_widget.dart b/lib/features/feed/presentation/widgets/recipes_widget.dart index 5fb415b1..349fdb7d 100644 --- a/lib/features/feed/presentation/widgets/recipes_widget.dart +++ b/lib/features/feed/presentation/widgets/recipes_widget.dart @@ -20,16 +20,13 @@ import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:markdown/markdown.dart' as md; class RecipesWidget extends StatefulWidget { final int index; final Recipe recipe; - const RecipesWidget({ - super.key, - required this.recipe, - required this.index, - }); + const RecipesWidget({super.key, required this.recipe, required this.index}); @override State createState() => _RecipesWidgetState(); @@ -56,14 +53,8 @@ class _RecipesWidgetState extends State { } return InkWell( borderRadius: BorderRadius.circular(12), - onTap: () => GoRouter.of(context).pushNamed( - 'aboutRecipesScreen', - extra: { - 'recipe': widget.recipe.slug, - 'index': widget.index, - 'navigateToComments': false, - }, - ), + onTap: () => + GoRouter.of(context).pushNamed('aboutRecipesScreen', extra: {'recipe': widget.recipe.slug, 'index': widget.index, 'navigateToComments': false}), child: Material( borderRadius: BorderRadius.circular(12), color: Colors.white, @@ -139,10 +130,7 @@ class _RecipesWidgetState extends State { const SizedBox(height: 12), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - widget.recipe.title ?? '', - style: TextStyles.textMSemibold.copyWith(color: appTheme.black), - ), + child: Text(widget.recipe.title ?? '', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), ), if (widget.recipe.videoUrl != null && widget.recipe.videoUrl!.isNotEmpty) ...[ const SizedBox(height: 12), @@ -152,10 +140,7 @@ class _RecipesWidgetState extends State { children: [ SvgPicture.asset('assets/icons/play_icon.svg', width: 16, height: 16), const SizedBox(width: 8), - Text( - s.there_is_a_video, - style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), - ), + Text(s.there_is_a_video, style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), ], ), ), @@ -165,17 +150,41 @@ class _RecipesWidgetState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Html( - data: widget.recipe.description.length > 200 - ? widget.recipe.description.substring(0, 200).replaceAll('\n', '
') - : widget.recipe.description.replaceAll('\n', '
'), + data: md.markdownToHtml( + widget.recipe.description.length > 200 + ? widget.recipe.description.substring(0, 200).replaceAll('\n', '
') + : widget.recipe.description.replaceAll('\n', '
'), + ), style: { - "*": Style( - color: appTheme.black, - margin: Margins.all(0), - fontFamily: TextStyles.textSMedium.fontFamily, + "body": Style( + margin: Margins.zero, + padding: HtmlPaddings.zero, fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14), - fontWeight: TextStyles.textSMedium.fontWeight, + lineHeight: const LineHeight(1.5), ), + "p": Style(margin: Margins.only(bottom: 12), color: appTheme.black), + "h1, h2, h3, h4, h5, h6": Style(margin: Margins.only(top: 16, bottom: 8), color: appTheme.black, fontWeight: FontWeight.bold), + "h1": Style(fontSize: FontSize(24)), + "h2": Style(fontSize: FontSize(20)), + "h3": Style(fontSize: FontSize(18)), + "ul, ol": Style(margin: Margins.only(bottom: 12), padding: HtmlPaddings.only(left: 16)), + "li": Style(margin: Margins.only(bottom: 4), color: appTheme.black), + "strong, b": Style(fontWeight: FontWeight.bold, color: appTheme.black), + "em, i": Style(fontStyle: FontStyle.italic), + "code": Style( + backgroundColor: Colors.grey.shade100, + padding: HtmlPaddings.symmetric(horizontal: 4, vertical: 2), + fontFamily: 'monospace', + ), + "pre": Style(backgroundColor: Colors.grey.shade100, padding: HtmlPaddings.all(12), margin: Margins.only(bottom: 12)), + "blockquote": Style( + padding: HtmlPaddings.only(left: 16), + margin: Margins.only(left: 8, bottom: 12), + fontStyle: FontStyle.italic, + color: Colors.grey.shade700, + ), + "a": Style(color: Colors.blue, textDecoration: TextDecoration.underline), + "img": Style(width: Width(MediaQuery.of(context).size.width - 32)), }, ), ), @@ -184,12 +193,7 @@ class _RecipesWidgetState extends State { padding: EdgeInsets.only(left: 16, top: 8), child: Text( 'Показать ещё', - style: TextStyle( - height: 1, - color: Color(0xFF8B8B8B), - fontSize: 14, - fontWeight: FontWeight.w600, - ), + style: TextStyle(height: 1, color: Color(0xFF8B8B8B), fontSize: 14, fontWeight: FontWeight.w600), ), ), const SizedBox(height: 12), @@ -211,14 +215,9 @@ class _RecipesWidgetState extends State { FeedIconButton( icon: Icons.chat_bubble_outline, text: widget.recipe.commentsCount.toString(), - onTap: () => GoRouter.of(context).pushNamed( - 'aboutRecipesScreen', - extra: { - 'recipe': widget.recipe.slug, - 'index': widget.index, - 'navigateToComments': true, - }, - ), + onTap: () => GoRouter.of( + context, + ).pushNamed('aboutRecipesScreen', extra: {'recipe': widget.recipe.slug, 'index': widget.index, 'navigateToComments': true}), ), const SizedBox(width: 8), FeedIconButton( @@ -239,19 +238,11 @@ class _RecipesWidgetState extends State { }, ), const Spacer(), - Icon( - Icons.visibility_rounded, - size: 16, - color: appTheme.gray500, - ), + Icon(Icons.visibility_rounded, size: 16, color: appTheme.gray500), const SizedBox(width: 4), Text( widget.recipe.viewsCount.toString(), - style: TextStyle( - fontSize: 12, - color: appTheme.gray500, - fontWeight: FontWeight.w400, - ), + style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400), ), ], ), @@ -268,26 +259,17 @@ class _RecipesWidgetState extends State { Container _iconButton(String iconPath, [String? text]) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - decoration: BoxDecoration( - color: appTheme.whiteA700, - borderRadius: BorderRadius.circular(16), - ), + decoration: BoxDecoration(color: appTheme.whiteA700, borderRadius: BorderRadius.circular(16)), height: 32, child: Row( mainAxisSize: MainAxisSize.min, children: [ - SvgPicture.asset( - iconPath, - ), + SvgPicture.asset(iconPath), if (text != null) const SizedBox(width: 8), if (text != null) Text( text, - style: const TextStyle( - fontSize: 12, - color: Colors.black, - fontWeight: FontWeight.w500, - ), + style: const TextStyle(fontSize: 12, color: Colors.black, fontWeight: FontWeight.w500), ), ], ), diff --git a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart index d6f41d7f..c3c9c5a3 100644 --- a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart +++ b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart @@ -24,14 +24,12 @@ import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:markdown/markdown.dart' as md; class MyDietWidget extends StatefulWidget { final Diet diet; - const MyDietWidget({ - super.key, - required this.diet, - }); + const MyDietWidget({super.key, required this.diet}); @override State createState() => _MyDietWidgetState(); @@ -42,10 +40,7 @@ class _MyDietWidgetState extends State { Widget build(BuildContext context) { final s = S.of(context); return DecoratedBox( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.white, - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.white), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -78,26 +73,48 @@ class _MyDietWidgetState extends State { children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - widget.diet.name, - style: TextStyles.textMSemibold.copyWith(color: appTheme.black), - ), + child: Text(widget.diet.name, style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), ), if (widget.diet.details.isNotEmpty) ...[ - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Html( - data: widget.diet.details.length > 200 ? '${widget.diet.details.substring(0, 200).replaceAll('\n', '
')}...' : widget.diet.details.replaceAll('\n', '
'), + data: md.markdownToHtml( + widget.diet.details.length > 200 + ? '${widget.diet.details.substring(0, 200).replaceAll('\n', '
')}...' + : widget.diet.details.replaceAll('\n', '
'), + ), style: { - "*": Style( + "body": Style( margin: Margins.zero, - color: appTheme.black, + padding: HtmlPaddings.zero, fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14), - fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400, + lineHeight: const LineHeight(1.5), ), + "p": Style(margin: Margins.only(bottom: 12), color: appTheme.black), + "h1, h2, h3, h4, h5, h6": Style(margin: Margins.only(top: 16, bottom: 8), color: appTheme.black, fontWeight: FontWeight.bold), + "h1": Style(fontSize: FontSize(24)), + "h2": Style(fontSize: FontSize(20)), + "h3": Style(fontSize: FontSize(18)), + "ul, ol": Style(margin: Margins.only(bottom: 12), padding: HtmlPaddings.only(left: 16)), + "li": Style(margin: Margins.only(bottom: 4), color: appTheme.black), + "strong, b": Style(fontWeight: FontWeight.bold, color: appTheme.black), + "em, i": Style(fontStyle: FontStyle.italic), + "code": Style( + backgroundColor: Colors.grey.shade100, + padding: HtmlPaddings.symmetric(horizontal: 4, vertical: 2), + fontFamily: 'monospace', + ), + "pre": Style(backgroundColor: Colors.grey.shade100, padding: HtmlPaddings.all(12), margin: Margins.only(bottom: 12)), + "blockquote": Style( + padding: HtmlPaddings.only(left: 16), + margin: Margins.only(left: 8, bottom: 12), + fontStyle: FontStyle.italic, + color: Colors.grey.shade700, + ), + "a": Style(color: Colors.blue, textDecoration: TextDecoration.underline), + "img": Style(width: Width(MediaQuery.of(context).size.width - 32)), }, ), ), @@ -230,11 +247,7 @@ class _MyDietWidgetState extends State { }, height: 52, width: 52, - leftIcon: SvgPicture.asset( - 'assets/icons/trash_can.svg', - width: 20, - height: 20, - ), + leftIcon: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 20), buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red), buttonStyle: ButtonStyle( shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))), diff --git a/lib/features/my_plans/presentation/widgets/my_plan_widget.dart b/lib/features/my_plans/presentation/widgets/my_plan_widget.dart index 5d057be5..14a2567f 100644 --- a/lib/features/my_plans/presentation/widgets/my_plan_widget.dart +++ b/lib/features/my_plans/presentation/widgets/my_plan_widget.dart @@ -18,6 +18,7 @@ import 'package:flutter_html/flutter_html.dart'; // Removed SVG icon usage after switching to text buttons import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:markdown/markdown.dart' as md; class MyPlanWidget extends StatefulWidget { final PlanModel plan; @@ -43,7 +44,11 @@ class _MyPlanWidgetState extends State { onTap: () => GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'slug': widget.plan.slug, 'navigateToComments': false}), child: ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), - child: CachedNetworkImage(imageUrl: '${sl().baseUrl}/storage/${widget.plan.image}', fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width), + child: CachedNetworkImage( + imageUrl: '${sl().baseUrl}/storage/${widget.plan.image}', + fit: BoxFit.fitWidth, + width: MediaQuery.of(context).size.width, + ), ), ), Positioned( @@ -68,16 +73,41 @@ class _MyPlanWidgetState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Html( - data: widget.plan.details.length > 200 - ? '${widget.plan.details.substring(0, 200).replaceAll('\n', '
')}...' - : widget.plan.details.replaceAll('\n', '
'), + data: md.markdownToHtml( + widget.plan.details.length > 200 + ? '${widget.plan.details.substring(0, 200).replaceAll('\n', '
')}...' + : widget.plan.details.replaceAll('\n', '
'), + ), style: { - "*": Style( + "body": Style( margin: Margins.zero, - color: appTheme.black, + padding: HtmlPaddings.zero, fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14), - fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400, + lineHeight: const LineHeight(1.5), ), + "p": Style(margin: Margins.only(bottom: 12), color: appTheme.black), + "h1, h2, h3, h4, h5, h6": Style(margin: Margins.only(top: 16, bottom: 8), color: appTheme.black, fontWeight: FontWeight.bold), + "h1": Style(fontSize: FontSize(24)), + "h2": Style(fontSize: FontSize(20)), + "h3": Style(fontSize: FontSize(18)), + "ul, ol": Style(margin: Margins.only(bottom: 12), padding: HtmlPaddings.only(left: 16)), + "li": Style(margin: Margins.only(bottom: 4), color: appTheme.black), + "strong, b": Style(fontWeight: FontWeight.bold, color: appTheme.black), + "em, i": Style(fontStyle: FontStyle.italic), + "code": Style( + backgroundColor: Colors.grey.shade100, + padding: HtmlPaddings.symmetric(horizontal: 4, vertical: 2), + fontFamily: 'monospace', + ), + "pre": Style(backgroundColor: Colors.grey.shade100, padding: HtmlPaddings.all(12), margin: Margins.only(bottom: 12)), + "blockquote": Style( + padding: HtmlPaddings.only(left: 16), + margin: Margins.only(left: 8, bottom: 12), + fontStyle: FontStyle.italic, + color: Colors.grey.shade700, + ), + "a": Style(color: Colors.blue, textDecoration: TextDecoration.underline), + "img": Style(width: Width(MediaQuery.of(context).size.width - 32)), }, ), ), diff --git a/lib/features/profile/presentation/widgets/profile_blog_post_widget.dart b/lib/features/profile/presentation/widgets/profile_blog_post_widget.dart index 7c55c420..7163179f 100644 --- a/lib/features/profile/presentation/widgets/profile_blog_post_widget.dart +++ b/lib/features/profile/presentation/widgets/profile_blog_post_widget.dart @@ -16,17 +16,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; +import 'package:markdown/markdown.dart' as md; import 'package:share_plus/share_plus.dart'; class ProfileBlogPostWidget extends StatefulWidget { final int index; final ProfileCubit profileCubit; - const ProfileBlogPostWidget({ - super.key, - required this.index, - required this.profileCubit, - }); + const ProfileBlogPostWidget({super.key, required this.index, required this.profileCubit}); @override State createState() => _ProfileBlogPostWidgetState(); @@ -46,15 +43,9 @@ class _ProfileBlogPostWidgetState extends State { totalLikes = state.blogPosts[widget.index].totalLikes; return InkWell( borderRadius: BorderRadius.circular(12), - onTap: () => GoRouter.of(context).pushNamed( - 'blogPostScreen', - extra: state.blogPosts[widget.index].id, - ), + onTap: () => GoRouter.of(context).pushNamed('blogPostScreen', extra: state.blogPosts[widget.index].id), child: Ink( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.white, - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.white), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -80,19 +71,12 @@ class _ProfileBlogPostWidgetState extends State { children: [ Text( state.blogPosts[widget.index].author.authorName, - style: const TextStyle( - height: 1, - color: Colors.black87, - fontWeight: FontWeight.w500, - ), + style: const TextStyle(height: 1, color: Colors.black87, fontWeight: FontWeight.w500), ), const SizedBox(height: 4), Text( '${calculateDifference(state.blogPosts[widget.index].createdAt)} в ${state.blogPosts[widget.index].createdAt.hour.toString().padLeft(2, '0')}:${state.blogPosts[widget.index].createdAt.minute.toString().padLeft(2, '0')}', - style: const TextStyle( - height: 1, - color: Colors.black45, - ), + style: const TextStyle(height: 1, color: Colors.black45), ), ], ), @@ -114,11 +98,7 @@ class _ProfileBlogPostWidgetState extends State { padding: const EdgeInsets.symmetric(horizontal: 12), child: Text( state.blogPosts[widget.index].title, - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 18, - color: Colors.black87, - ), + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18, color: Colors.black87), ), ), ], @@ -128,14 +108,36 @@ class _ProfileBlogPostWidgetState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Html( - data: state.blogPosts[widget.index].description.length > 200 - ? state.blogPosts[widget.index].description.substring(0, 200).replaceAll('\n', '
') - : state.blogPosts[widget.index].description.replaceAll('\n', '
'), + data: md.markdownToHtml( + state.blogPosts[widget.index].description.length > 200 + ? state.blogPosts[widget.index].description.substring(0, 200).replaceAll('\n', '
') + : state.blogPosts[widget.index].description.replaceAll('\n', '
'), + ), style: { - "*": Style( - fontSize: FontSize(16), - color: Colors.black87, + "body": Style(margin: Margins.zero, padding: HtmlPaddings.zero, fontSize: FontSize(16), lineHeight: const LineHeight(1.5)), + "p": Style(margin: Margins.only(bottom: 12), color: Colors.black87), + "h1, h2, h3, h4, h5, h6": Style(margin: Margins.only(top: 16, bottom: 8), color: Colors.black, fontWeight: FontWeight.bold), + "h1": Style(fontSize: FontSize(24)), + "h2": Style(fontSize: FontSize(20)), + "h3": Style(fontSize: FontSize(18)), + "ul, ol": Style(margin: Margins.only(bottom: 12), padding: HtmlPaddings.only(left: 16)), + "li": Style(margin: Margins.only(bottom: 4), color: Colors.black87), + "strong, b": Style(fontWeight: FontWeight.bold, color: Colors.black), + "em, i": Style(fontStyle: FontStyle.italic), + "code": Style( + backgroundColor: Colors.grey.shade100, + padding: HtmlPaddings.symmetric(horizontal: 4, vertical: 2), + fontFamily: 'monospace', ), + "pre": Style(backgroundColor: Colors.grey.shade100, padding: HtmlPaddings.all(12), margin: Margins.only(bottom: 12)), + "blockquote": Style( + padding: HtmlPaddings.only(left: 16), + margin: Margins.only(left: 8, bottom: 12), + fontStyle: FontStyle.italic, + color: Colors.grey.shade700, + ), + "a": Style(color: Colors.blue, textDecoration: TextDecoration.underline), + "img": Style(width: Width(MediaQuery.of(context).size.width - 32)), }, ), ), @@ -144,12 +146,7 @@ class _ProfileBlogPostWidgetState extends State { padding: EdgeInsets.symmetric(horizontal: 12), child: Text( 'Показать ещё', - style: TextStyle( - height: 1, - color: Color(0xFF8B8B8B), - fontSize: 14, - fontWeight: FontWeight.w600, - ), + style: TextStyle(height: 1, color: Color(0xFF8B8B8B), fontSize: 14, fontWeight: FontWeight.w600), ), ), if (state.blogPosts[widget.index].title.isNotEmpty || state.blogPosts[widget.index].description.isNotEmpty) const SizedBox(height: 8), @@ -157,10 +154,7 @@ class _ProfileBlogPostWidgetState extends State { onTap: () { context.pushTransparentRoute( rootNavigator: true, - PhotoViewScreen( - imageUrls: ['${sl().baseUrl}/storage/${state.blogPosts[widget.index].image}'], - index: 0, - ), + PhotoViewScreen(imageUrls: ['${sl().baseUrl}/storage/${state.blogPosts[widget.index].image}'], index: 0), ); }, child: Hero( @@ -192,10 +186,7 @@ class _ProfileBlogPostWidgetState extends State { isLiked ? await context.read().likePost(state.blogPosts[widget.index].id) : await context.read().unlikedPost(state.blogPosts[widget.index].id); - widget.profileCubit.updateFeedBlogPostList( - state.blogPosts[widget.index], - isLiked ? false : true, - ); + widget.profileCubit.updateFeedBlogPostList(state.blogPosts[widget.index], isLiked ? false : true); }, ), const SizedBox(width: 8), @@ -204,16 +195,10 @@ class _ProfileBlogPostWidgetState extends State { 'assets/icons/comment.svg', height: 20, width: 20, - colorFilter: ColorFilter.mode( - appTheme.gray600, - BlendMode.srcIn, - ), + colorFilter: ColorFilter.mode(appTheme.gray600, BlendMode.srcIn), ), text: state.blogPosts[widget.index].totalComments.toString(), - onTap: () => GoRouter.of(context).pushNamed( - 'blogPostScreen', - extra: state.blogPosts[widget.index].id, - ), + onTap: () => GoRouter.of(context).pushNamed('blogPostScreen', extra: state.blogPosts[widget.index].id), ), const SizedBox(width: 8), FeedIconButton( @@ -234,19 +219,11 @@ class _ProfileBlogPostWidgetState extends State { }, ), const Spacer(), - Icon( - Icons.visibility_rounded, - size: 16, - color: appTheme.gray500, - ), + Icon(Icons.visibility_rounded, size: 16, color: appTheme.gray500), const SizedBox(width: 4), Text( state.blogPosts[widget.index].totalViews.toString(), - style: TextStyle( - fontSize: 12, - color: appTheme.gray500, - fontWeight: FontWeight.w400, - ), + style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400), ), ], ), diff --git a/lib/features/recipe/presentation/widgets/my_recipe_widget.dart b/lib/features/recipe/presentation/widgets/my_recipe_widget.dart index d791135a..3e73b7cc 100644 --- a/lib/features/recipe/presentation/widgets/my_recipe_widget.dart +++ b/lib/features/recipe/presentation/widgets/my_recipe_widget.dart @@ -15,15 +15,13 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; +import 'package:markdown/markdown.dart' as md; import 'package:share_plus/share_plus.dart'; class MyRecipeWidget extends StatefulWidget { final MyRecipeModel recipe; - const MyRecipeWidget({ - super.key, - required this.recipe, - }); + const MyRecipeWidget({super.key, required this.recipe}); @override State createState() => _MyRecipeWidgetState(); @@ -50,10 +48,7 @@ class _MyRecipeWidgetState extends State { Widget build(BuildContext context) { final s = S.of(context); return DecoratedBox( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.white, - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.white), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -61,13 +56,7 @@ class _MyRecipeWidgetState extends State { Stack( children: [ InkWell( - onTap: () async => GoRouter.of(context).pushNamed( - 'aboutRecipesScreen', - extra: { - 'recipe': widget.recipe.slug, - 'navigateToComments': false, - }, - ), + onTap: () async => GoRouter.of(context).pushNamed('aboutRecipesScreen', extra: {'recipe': widget.recipe.slug, 'navigateToComments': false}), child: ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), child: CachedNetworkImage( @@ -99,28 +88,48 @@ class _MyRecipeWidgetState extends State { children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - widget.recipe.title!, - style: TextStyles.textMSemibold.copyWith(color: appTheme.black), - ), + child: Text(widget.recipe.title!, style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), ), if (widget.recipe.description.isNotEmpty) ...[ - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Html( - data: widget.recipe.description.length > 200 - ? '${widget.recipe.description.substring(0, 200).replaceAll('\n', '
')}...' - : widget.recipe.description.replaceAll('\n', '
'), + data: md.markdownToHtml( + widget.recipe.description.length > 200 + ? '${widget.recipe.description.substring(0, 200).replaceAll('\n', '
')}...' + : widget.recipe.description.replaceAll('\n', '
'), + ), style: { - "*": Style( + "body": Style( margin: Margins.zero, - color: appTheme.black, + padding: HtmlPaddings.zero, fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14), - fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400, + lineHeight: const LineHeight(1.5), ), + "p": Style(margin: Margins.only(bottom: 12), color: appTheme.black), + "h1, h2, h3, h4, h5, h6": Style(margin: Margins.only(top: 16, bottom: 8), color: appTheme.black, fontWeight: FontWeight.bold), + "h1": Style(fontSize: FontSize(24)), + "h2": Style(fontSize: FontSize(20)), + "h3": Style(fontSize: FontSize(18)), + "ul, ol": Style(margin: Margins.only(bottom: 12), padding: HtmlPaddings.only(left: 16)), + "li": Style(margin: Margins.only(bottom: 4), color: appTheme.black), + "strong, b": Style(fontWeight: FontWeight.bold, color: appTheme.black), + "em, i": Style(fontStyle: FontStyle.italic), + "code": Style( + backgroundColor: Colors.grey.shade100, + padding: HtmlPaddings.symmetric(horizontal: 4, vertical: 2), + fontFamily: 'monospace', + ), + "pre": Style(backgroundColor: Colors.grey.shade100, padding: HtmlPaddings.all(12), margin: Margins.only(bottom: 12)), + "blockquote": Style( + padding: HtmlPaddings.only(left: 16), + margin: Margins.only(left: 8, bottom: 12), + fontStyle: FontStyle.italic, + color: Colors.grey.shade700, + ), + "a": Style(color: Colors.blue, textDecoration: TextDecoration.underline), + "img": Style(width: Width(MediaQuery.of(context).size.width - 32)), }, ), ), @@ -192,26 +201,17 @@ class _MyRecipeWidgetState extends State { Container _iconButton(String iconPath, [String? text]) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - decoration: BoxDecoration( - color: appTheme.whiteA700, - borderRadius: BorderRadius.circular(16), - ), + decoration: BoxDecoration(color: appTheme.whiteA700, borderRadius: BorderRadius.circular(16)), height: 32, child: Row( mainAxisSize: MainAxisSize.min, children: [ - SvgPicture.asset( - iconPath, - ), + SvgPicture.asset(iconPath), if (text != null) const SizedBox(width: 8), if (text != null) Text( text, - style: const TextStyle( - fontSize: 12, - color: Colors.black, - fontWeight: FontWeight.w500, - ), + style: const TextStyle(fontSize: 12, color: Colors.black, fontWeight: FontWeight.w500), ), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index addc3f51..75b80445 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,7 @@ dependencies: appmetrica_plugin: ^3.3.0 vk_id: ^1.0.3 flutter_login_yandex: ^0.0.5 + markdown: ^7.3.0 dev_dependencies: build_runner: ^2.7.0