import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:markdown/markdown.dart' as md; import '../module/browser/view.dart'; class GraduallyMdText extends StatefulWidget { const GraduallyMdText({ super.key, this.initTxt, this.graduallyController, this.textStyle, }); final TextStyle? textStyle; final GraduallyController? graduallyController; final String? initTxt; @override State createState() => _GraduallyMdTextState(); } typedef GraduallyTxtListener = void Function(String txt); class GraduallyController { String graduallyTxt = ''; int progressIndex = 0; Timer? appendTimer; bool? isAppendDone; bool? isShowDone; GraduallyTxtListener? listener; VoidCallback? finishedListener; setGraduallyTxtListener(GraduallyTxtListener listener) { this.listener = listener; } setGraduallyFinishedListener(VoidCallback finishedListener) { this.finishedListener = finishedListener; } append(String txt) { graduallyTxt += txt; _startAppend(); } void _initAppend() { progressIndex = 0; isAppendDone = null; isShowDone = null; appendTimer = null; } appendDone() { isAppendDone = true; // debugPrint('GraduallyMdText: appendDone'); } _startAppend() { if (appendTimer != null) { return; } _initAppend(); appendTimer = Timer.periodic(const Duration(milliseconds: 50), (timer) { // debugPrint('GraduallyMdText-progressIndex: $progressIndex'); if (progressIndex < graduallyTxt.length) { progressIndex += 1; listener?.call(graduallyTxt.substring(0, progressIndex)); } else if (isAppendDone == true) { // debugPrint('GraduallyMdText: appendTimer done'); dispose(); finishedListener?.call(); } }); } dispose() { appendTimer?.cancel(); isAppendDone = null; isShowDone = true; appendTimer = null; } } class _GraduallyMdTextState extends State { String mdTxt = ''; @override void initState() { super.initState(); mdTxt = widget.initTxt ?? ''; widget.graduallyController?.setGraduallyTxtListener((txt) { if (mounted) { setState(() { mdTxt = txt; }); } }); } @override void dispose() { super.dispose(); // debugPrint('GraduallyMdText-dispose'); // widget.graduallyController?.dispose(); } @override Widget build(BuildContext context) { return SelectionArea( child: HtmlWidget( onTapUrl: (url) { BrowserPage.start(url); return true; }, md.markdownToHtml(mdTxt, inlineSyntaxes: [ md.InlineHtmlSyntax(), md.StrikethroughSyntax(), md.EmojiSyntax(), md.ColorSwatchSyntax(), md.AutolinkExtensionSyntax(), md.ImageSyntax() ], blockSyntaxes: [ const md.FencedCodeBlockSyntax(), const md.HeaderWithIdSyntax(), const md.SetextHeaderWithIdSyntax(), const md.UnorderedListWithCheckboxSyntax(), const md.OrderedListWithCheckboxSyntax(), const md.FootnoteDefSyntax(), const md.AlertBlockSyntax(), ]), textStyle: widget.textStyle, ), ); } }