Sonnat Project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

251 lines
9.2 KiB

import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:sonnat/core/extensions/number_extension.dart';
import 'package:sonnat/core/html/custom_render.dart';
import 'package:sonnat/core/html/flutter_html.dart';
import 'package:sonnat/core/html/src/style/fontsize.dart';
import 'package:sonnat/core/html/src/style/length.dart';
import 'package:sonnat/core/html/src/style/lineheight.dart';
import 'package:sonnat/core/html/string_proccess.dart';
import 'package:sonnat/core/html/style.dart';
import 'package:sonnat/core/player_widgets/audio_player.dart';
import 'package:sonnat/core/player_widgets/video_player.dart';
import 'package:sonnat/core/theme/app_colors.dart';
import 'package:sonnat/core/theme/app_theme.dart';
import 'package:sonnat/core/theme/reader_theme.dart';
import 'package:sonnat/core/utils/app_utils.dart';
import 'package:sonnat/core/widgets/show_image_widget.dart';
import 'package:url_launcher/url_launcher.dart';
class HTMLViewer extends StatelessWidget {
final String htmlContent;
final double fontSizeFactor;
final bool needToReplaceTags;
final ReaderTheme? theme;
final String? searchHighLight;
final double baseFontSize = 16.0;
final Color? textColor;
const HTMLViewer({
super.key,
required this.htmlContent,
this.fontSizeFactor = 1,
this.needToReplaceTags = false,
this.theme = ReaderTheme.light,
this.searchHighLight,
this.textColor,
});
@override
Widget build(BuildContext context) {
var style = AppTheme.instance.fontCreator(
17,
FontWeights.regular,
AppColors.settingSemiBlack,
FontFamilyName.segoeui,
-0.0,
1.5,
);
Widget html = Builder(
builder: (context) {
double lineHeight = Theme.of(context).textTheme.displayLarge?.height ?? 1.1;
return Html(
data: needToReplaceTags
? htmlContent.replaceTHeader().replaceQHeader().replaceQText().replaceQAnswer().replaceTextStyle()
: htmlContent.replaceTextStyle(),
onLinkTap: (url, context, attributes, element) {
if (url == null) {
return;
}
launchUrl(Uri.parse(url)).then((value) {
return null;
});
},
customRenders: {
_stringMatcher('video'): CustomRender.widget(widget: (context, buildChildren) {
return _RoundFrame(
child: VideoPlayer(
url: context.tree.element!.attributes['src'] ?? '',
),
);
}),
_stringMatcher('img'): CustomRender.widget(widget: (renderContext, buildChildren) {
return GestureDetector(
onTap: () {
if (renderContext.tree.element!.attributes['src'] == null) {
return;
}
_openImage(
imageUrl: renderContext.tree.element!.attributes['src'] ?? '',
context: context,
);
},
child: _RoundFrame(
child: CachedNetworkImage(
imageUrl: renderContext.tree.element!.attributes['src'] ?? '',
),
),
);
}),
_stringMatcher('audio'): CustomRender.widget(widget: (context, buildChildren) {
return AudioPlayer(
url: context.tree.element!.nodes[1].attributes['src'] ?? '',
);
}),
_stringMatcher('q_header'): CustomRender.widget(widget: (context, buildChildren) {
if (context.tree.element?.hasChildNodes() ?? false) {
if (context.tree.element?.firstChild?.text != null) {
String txt = context.tree.element?.firstChild?.text ?? '';
return QHeaderTextShower(
title: txt,
searchHighLight: searchHighLight,
fontSizeFactor: fontSizeFactor,
);
}
}
return const _RoundFrame(child: SizedBox());
}),
_stringMatcher('q_text'): CustomRender.widget(widget: (context, buildChildren) {
if (context.tree.element?.hasChildNodes() ?? false) {
if (context.tree.element?.firstChild?.text != null) {
String txt = context.tree.element?.firstChild?.text ?? '';
return QTextShower(
title: txt,
searchHighLight: searchHighLight,
fontSizeFactor: fontSizeFactor,
theme: theme,
);
}
}
return const _RoundFrame(child: SizedBox());
}),
_stringMatcher('q_answer'): CustomRender.widget(widget: (context, buildChildren) {
if (context.tree.element?.hasChildNodes() ?? false) {
if (context.tree.element?.firstChild?.text != null) {
String txt = context.tree.element?.firstChild?.text ?? '';
return QAnswerShower(
title: txt,
searchHighLight: searchHighLight,
fontSizeFactor: fontSizeFactor,
theme: theme,
);
}
}
return const _RoundFrame(child: SizedBox());
}),
_stringMatcher('t_header'): CustomRender.widget(widget: (context, buildChildren) {
if (context.tree.element?.hasChildNodes() ?? false) {
if (context.tree.element?.firstChild?.text != null) {
String txt = context.tree.element?.firstChild?.text ?? '';
return THeaderTextShower(
title: txt,
searchHighLight: searchHighLight,
fontSizeFactor: fontSizeFactor,
theme: theme,
);
}
}
return const _RoundFrame(child: SizedBox());
}),
},
style: {
'p': Style(
color: textColor,
fontWeight: FontWeight.normal,
fontSize: FontSize(fontSizeFactor * 1.3, Unit.rem),
textAlign: TextAlign.justify,
),
'h1': Style(
color: textColor,
fontWeight: FontWeight.normal,
fontSize: FontSize(fontSizeFactor * 2.3, Unit.rem),
),
'h2': Style(
color: textColor,
fontWeight: FontWeight.normal,
fontSize: FontSize(fontSizeFactor * 2.1, Unit.rem),
),
'h3': Style(
color: textColor,
fontWeight: FontWeight.normal,
fontSize: FontSize(fontSizeFactor * 1.9, Unit.rem),
),
'h4': Style(
color: textColor,
fontWeight: FontWeight.normal,
fontSize: FontSize(fontSizeFactor * 1.7, Unit.rem),
),
'h5': Style(
color: textColor,
fontWeight: FontWeight.normal,
fontSize: FontSize(fontSizeFactor * 1.6, Unit.rem),
),
'h6': Style(
color: textColor,
fontWeight: FontWeight.normal,
fontSize: FontSize(fontSizeFactor * 1.4, Unit.rem),
),
'li': Style(
fontWeight: FontWeight.normal,
fontSize: FontSize(fontSizeFactor * 1.3, Unit.rem),
),
'a': Style(
color: textColor,
fontWeight: FontWeight.normal,
fontSize: FontSize(fontSizeFactor * 1.3, Unit.rem),
),
'ol': Style(
fontWeight: FontWeight.normal,
fontSize: FontSize(fontSizeFactor * 1.3, Unit.rem),
),
'html': Style(
fontSize: FontSize(baseFontSize * fontSizeFactor),
),
'*': Style.fromTextStyle(style).copyWith(
color: textColor,
lineHeight: LineHeight.rem(lineHeight),
fontSize: FontSize(fontSizeFactor * baseFontSize),
padding: const EdgeInsets.symmetric(vertical: 8),
),
},
tagsList: Html.tags..addAll(['flutter', 'q_header', 'q_text', 'q_answer', 't_header']),
);
},
);
return Padding(
padding: Utils.instance.singleMargin(left: 15, right: 15, bottom: 60.h),
child: html,
);
}
void _openImage({required String imageUrl, required BuildContext context}) {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return ShowImageWidget(imageUrl);
},
));
}
}
CustomRenderMatcher _stringMatcher(String tag) => (context) => context.tree.element?.localName == tag;
class _RoundFrame extends StatelessWidget {
final Widget child;
final bool hasFullWidth;
const _RoundFrame({super.key, required this.child, this.hasFullWidth = true});
@override
Widget build(BuildContext context) {
return Container(
width: hasFullWidth ? 1.sw : null,
margin: Utils.instance.singleMargin(top: 7, bottom: 7),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: child,
),
);
}
}