gradually_md_text.dart 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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. VoidCallback? finishedListener;
  29. setGraduallyTxtListener(GraduallyTxtListener listener) {
  30. this.listener = listener;
  31. }
  32. setGraduallyFinishedListener(VoidCallback finishedListener) {
  33. this.finishedListener = finishedListener;
  34. }
  35. append(String txt) {
  36. graduallyTxt += txt;
  37. _startAppend();
  38. }
  39. void _initAppend() {
  40. progressIndex = 0;
  41. isAppendDone = null;
  42. isShowDone = null;
  43. appendTimer = null;
  44. }
  45. appendDone() {
  46. isAppendDone = true;
  47. // debugPrint('GraduallyMdText: appendDone');
  48. }
  49. _startAppend() {
  50. if (appendTimer != null) {
  51. return;
  52. }
  53. _initAppend();
  54. appendTimer = Timer.periodic(const Duration(milliseconds: 50), (timer) {
  55. // debugPrint('GraduallyMdText-progressIndex: $progressIndex');
  56. if (progressIndex < graduallyTxt.length) {
  57. progressIndex += 1;
  58. listener?.call(graduallyTxt.substring(0, progressIndex));
  59. } else if (isAppendDone == true) {
  60. // debugPrint('GraduallyMdText: appendTimer done');
  61. dispose();
  62. finishedListener?.call();
  63. }
  64. });
  65. }
  66. dispose() {
  67. appendTimer?.cancel();
  68. isAppendDone = null;
  69. isShowDone = true;
  70. appendTimer = null;
  71. }
  72. }
  73. class _GraduallyMdTextState extends State<GraduallyMdText> {
  74. String mdTxt = '';
  75. @override
  76. void initState() {
  77. super.initState();
  78. mdTxt = widget.initTxt ?? '';
  79. widget.graduallyController?.setGraduallyTxtListener((txt) {
  80. if (mounted) {
  81. setState(() {
  82. mdTxt = txt;
  83. });
  84. }
  85. });
  86. }
  87. @override
  88. void dispose() {
  89. super.dispose();
  90. // debugPrint('GraduallyMdText-dispose');
  91. // widget.graduallyController?.dispose();
  92. }
  93. @override
  94. Widget build(BuildContext context) {
  95. return SelectionArea(
  96. child: HtmlWidget(
  97. onTapUrl: (url) {
  98. BrowserPage.start(url);
  99. return true;
  100. },
  101. md.markdownToHtml(mdTxt, inlineSyntaxes: [
  102. md.InlineHtmlSyntax(),
  103. md.StrikethroughSyntax(),
  104. md.EmojiSyntax(),
  105. md.ColorSwatchSyntax(),
  106. md.AutolinkExtensionSyntax(),
  107. md.ImageSyntax()
  108. ], blockSyntaxes: [
  109. const md.FencedCodeBlockSyntax(),
  110. const md.HeaderWithIdSyntax(),
  111. const md.SetextHeaderWithIdSyntax(),
  112. const md.UnorderedListWithCheckboxSyntax(),
  113. const md.OrderedListWithCheckboxSyntax(),
  114. const md.FootnoteDefSyntax(),
  115. const md.AlertBlockSyntax(),
  116. ]),
  117. textStyle: widget.textStyle,
  118. ),
  119. );
  120. }
  121. }