gradually_md_text.dart 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import 'dart:async';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
  5. import 'package:markdown/markdown.dart' as md;
  6. import '../module/browser/view.dart';
  7. class GraduallyMdText extends StatefulWidget {
  8. const GraduallyMdText({
  9. super.key,
  10. this.initTxt,
  11. this.graduallyController,
  12. this.textStyle,
  13. });
  14. final TextStyle? textStyle;
  15. final GraduallyController? graduallyController;
  16. final String? initTxt;
  17. @override
  18. State<GraduallyMdText> createState() => _GraduallyMdTextState();
  19. }
  20. typedef GraduallyTxtListener = void Function(String txt);
  21. class GraduallyController {
  22. String graduallyTxt = '';
  23. int progressIndex = 0;
  24. Timer? appendTimer;
  25. bool? isAppendDone;
  26. bool? isShowDone;
  27. GraduallyTxtListener? listener;
  28. setGraduallyTxtListener(GraduallyTxtListener listener) {
  29. this.listener = listener;
  30. }
  31. append(String txt) {
  32. graduallyTxt += txt;
  33. _startAppend();
  34. }
  35. void _initAppend() {
  36. progressIndex = 0;
  37. isAppendDone = null;
  38. isShowDone = null;
  39. appendTimer = null;
  40. }
  41. appendDone() {
  42. isAppendDone = true;
  43. // debugPrint('GraduallyMdText: appendDone');
  44. }
  45. _startAppend() {
  46. if (appendTimer != null) {
  47. return;
  48. }
  49. _initAppend();
  50. appendTimer = Timer.periodic(const Duration(milliseconds: 50), (timer) {
  51. // debugPrint('GraduallyMdText-progressIndex: $progressIndex');
  52. if (progressIndex < graduallyTxt.length) {
  53. progressIndex += 1;
  54. listener?.call(graduallyTxt.substring(0, progressIndex));
  55. } else if (isAppendDone == true) {
  56. dispose();
  57. }
  58. });
  59. }
  60. dispose() {
  61. appendTimer?.cancel();
  62. isAppendDone = null;
  63. isShowDone = true;
  64. appendTimer = null;
  65. }
  66. }
  67. class _GraduallyMdTextState extends State<GraduallyMdText> {
  68. String mdTxt = '';
  69. @override
  70. void initState() {
  71. super.initState();
  72. mdTxt = widget.initTxt ?? '';
  73. widget.graduallyController?.setGraduallyTxtListener((txt) {
  74. setState(() {
  75. mdTxt = txt;
  76. });
  77. });
  78. }
  79. @override
  80. void dispose() {
  81. super.dispose();
  82. // debugPrint('GraduallyMdText-dispose');
  83. widget.graduallyController?.dispose();
  84. }
  85. @override
  86. Widget build(BuildContext context) {
  87. return SelectionArea(
  88. child: HtmlWidget(
  89. onTapUrl: (url) {
  90. BrowserPage.start(url);
  91. return true;
  92. },
  93. md.markdownToHtml(mdTxt, inlineSyntaxes: [
  94. md.InlineHtmlSyntax(),
  95. md.StrikethroughSyntax(),
  96. md.EmojiSyntax(),
  97. md.ColorSwatchSyntax(),
  98. md.AutolinkExtensionSyntax(),
  99. md.ImageSyntax()
  100. ], blockSyntaxes: [
  101. const md.FencedCodeBlockSyntax(),
  102. const md.HeaderWithIdSyntax(),
  103. const md.SetextHeaderWithIdSyntax(),
  104. const md.UnorderedListWithCheckboxSyntax(),
  105. const md.OrderedListWithCheckboxSyntax(),
  106. const md.FootnoteDefSyntax(),
  107. const md.AlertBlockSyntax(),
  108. ]),
  109. textStyle: widget.textStyle,
  110. ),
  111. );
  112. }
  113. }