Files
iiEsaywebUIapp/lib/shared/widgets/markdown/streaming_markdown_widget.dart
cogwheel0 81a83b146d refactor: enhance markdown rendering capabilities
- Introduced new builders for ordered and unordered lists, improving the rendering of list elements in markdown.
- Added a table builder to support structured data presentation within markdown content.
- Enhanced checkbox and radio button themes for better visual consistency and user interaction.
- Updated the streaming markdown widget to utilize the new list and table builders, ensuring a cohesive markdown experience.
- Improved overall maintainability and adaptability of the markdown configuration by centralizing theme-related logic.
2025-10-03 13:37:57 +05:30

132 lines
4.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:gpt_markdown/gpt_markdown.dart';
import '../../theme/theme_extensions.dart';
import 'markdown_config.dart';
import 'markdown_preprocessor.dart';
typedef MarkdownLinkTapCallback = void Function(String url, String title);
class StreamingMarkdownWidget extends StatelessWidget {
const StreamingMarkdownWidget({
super.key,
required this.content,
required this.isStreaming,
this.onTapLink,
});
final String content;
final bool isStreaming;
final MarkdownLinkTapCallback? onTapLink;
@override
Widget build(BuildContext context) {
if (content.trim().isEmpty) {
return const SizedBox.shrink();
}
final normalized = ConduitMarkdownPreprocessor.normalize(content);
const featureFlags = MarkdownFeatureFlags(
enableSyntaxHighlighting: true,
enableMermaid: true,
);
final markdownTheme = ConduitMarkdownConfig.resolve(
context,
flags: featureFlags,
);
final textScaler = MediaQuery.maybeOf(context)?.textScaler;
final themedControls = Theme.of(context).copyWith(
checkboxTheme: markdownTheme.checkboxTheme,
radioTheme: markdownTheme.radioTheme,
);
return GptMarkdownTheme(
gptThemeData: markdownTheme.themeData,
child: Theme(
data: themedControls,
child: SelectionArea(
child: GptMarkdown(
normalized,
style: markdownTheme.textStyle,
followLinkColor: markdownTheme.followLinkColor,
textDirection: Directionality.of(context),
textScaler: textScaler,
onLinkTap: onTapLink,
codeBuilder: markdownTheme.codeBuilder,
imageBuilder: markdownTheme.imageBuilder,
orderedListBuilder: markdownTheme.orderedListBuilder,
unOrderedListBuilder: markdownTheme.unOrderedListBuilder,
tableBuilder: markdownTheme.tableBuilder,
useDollarSignsForLatex: true,
highlightBuilder: (highlightContext, inline, baseStyle) {
final softened = ConduitMarkdownPreprocessor.softenInlineCode(
inline,
);
final theme = highlightContext.conduitTheme;
final base = baseStyle;
final fontSize = (base.fontSize ?? 13).clamp(11, 15).toDouble();
return Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.xs,
vertical: Spacing.xxs,
),
decoration: BoxDecoration(
color: theme.surfaceBackground.withValues(alpha: 0.55),
borderRadius: BorderRadius.circular(AppBorderRadius.xs),
border: Border.all(
color: theme.cardBorder.withValues(alpha: 0.2),
width: BorderWidth.micro,
),
),
child: Text(
softened,
style: base.copyWith(
fontFamily: AppTypography.monospaceFontFamily,
fontSize: fontSize,
height: 1.35,
color: theme.code?.color ?? theme.textSecondary,
),
),
);
},
),
),
),
);
}
}
extension StreamingMarkdownExtension on String {
Widget toMarkdown({
required BuildContext context,
bool isStreaming = false,
MarkdownLinkTapCallback? onTapLink,
}) {
return StreamingMarkdownWidget(
content: this,
isStreaming: isStreaming,
onTapLink: onTapLink,
);
}
}
class MarkdownWithLoading extends StatelessWidget {
const MarkdownWithLoading({super.key, this.content, required this.isLoading});
final String? content;
final bool isLoading;
@override
Widget build(BuildContext context) {
final value = content ?? '';
if (isLoading && value.trim().isEmpty) {
return const Center(child: CircularProgressIndicator());
}
return StreamingMarkdownWidget(content: value, isStreaming: isLoading);
}
}