refactor: optimize renderer

This commit is contained in:
cogwheel0
2025-10-02 14:41:17 +05:30
parent 7a880b507c
commit 0081d56703
5 changed files with 579 additions and 76 deletions

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:gpt_markdown/gpt_markdown.dart';
@@ -5,7 +7,7 @@ import 'markdown_config.dart';
typedef MarkdownLinkTapCallback = void Function(String url, String title);
class StreamingMarkdownWidget extends StatelessWidget {
class StreamingMarkdownWidget extends StatefulWidget {
const StreamingMarkdownWidget({
super.key,
required this.content,
@@ -17,6 +19,78 @@ class StreamingMarkdownWidget extends StatelessWidget {
final bool isStreaming;
final MarkdownLinkTapCallback? onTapLink;
@override
State<StreamingMarkdownWidget> createState() =>
_StreamingMarkdownWidgetState();
}
class _StreamingMarkdownWidgetState extends State<StreamingMarkdownWidget> {
late final ValueNotifier<String> _contentNotifier;
late String _currentContent;
Timer? _debounce;
String? _pendingContent;
@override
void initState() {
super.initState();
_currentContent = widget.content;
_contentNotifier = ValueNotifier(widget.content);
}
@override
void didUpdateWidget(covariant StreamingMarkdownWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.content == _currentContent) {
return;
}
// Coalesce rapid streaming updates so we only rebuild markdown a few times.
_pendingContent = widget.content;
_debounce ??= Timer(const Duration(milliseconds: 45), () {
if (!mounted) {
return;
}
final next = _pendingContent ?? widget.content;
_currentContent = next;
_contentNotifier.value = next;
_pendingContent = null;
_debounce = null;
});
}
@override
void dispose() {
_debounce?.cancel();
_contentNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<String>(
valueListenable: _contentNotifier,
builder: (context, value, _) {
return _StreamingMarkdownContent(
content: value,
isStreaming: widget.isStreaming,
onTapLink: widget.onTapLink,
);
},
);
}
}
class _StreamingMarkdownContent extends StatelessWidget {
const _StreamingMarkdownContent({
required this.content,
required this.isStreaming,
required this.onTapLink,
});
final String content;
final bool isStreaming;
final MarkdownLinkTapCallback? onTapLink;
@override
Widget build(BuildContext context) {
final markdownTheme = ConduitMarkdownConfig.resolve(context);
@@ -38,6 +112,8 @@ class StreamingMarkdownWidget extends StatelessWidget {
onLinkTap: onTapLink,
codeBuilder: markdownTheme.codeBuilder,
imageBuilder: markdownTheme.imageBuilder,
components: markdownTheme.blockComponents,
inlineComponents: markdownTheme.inlineComponents,
),
);
}