This commit is contained in:
DESKTOP-I3JPKHK\wy 2025-11-28 11:01:49 +08:00
parent ecf000e466
commit 7bc9b5d709
1 changed files with 187 additions and 169 deletions

View File

@ -1115,191 +1115,201 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
///
///
///
/// - `****` -
/// - `__斜体__` -
/// - `` `` `` -
/// - `[]` -
/// - `****`
/// - `__斜体__`
/// - `` `` ``
/// - `[]`
///
///
/// - `__在[]__` -
/// - `[****]` -
/// - `` `****` `` -
///
///
/// 1.
/// 2.
/// 3.
/// 4.
/// 使
List<TextSpan> _parseRichText(String content, ColorScheme colorScheme) {
final result = _parseRichTextInternal(content, colorScheme, 0, null);
return result.spans;
}
_RichTextParseResult _parseRichTextInternal(
String text,
ColorScheme colorScheme,
int startIndex,
String? endToken,
) {
final spans = <TextSpan>[];
final buffer = StringBuffer();
var index = startIndex;
// 使
// [] > ` > ** > __
final patterns = [
(regex: RegExp(r'\[([^\]]+)\]'), type: 'highlight'), // [] - ]
(regex: RegExp(r'`([^`]+)`'), type: 'code'), // `` - `
(regex: RegExp(r'\*\*([^*]+(?:\*(?!\*)[^*]*)*)\*\*'), type: 'bold'), // **** - **
(regex: RegExp(r'__([^_]+(?:_(?!_)[^_]*)*)__'), type: 'italic'), // __斜体__ - __
];
void flushBuffer() {
if (buffer.isEmpty) return;
spans.add(TextSpan(
text: buffer.toString(),
style: _defaultRichTextStyle(colorScheme),
));
buffer.clear();
}
//
final allMatches = <({Match match, String type, int priority})>[];
for (int i = 0; i < patterns.length; i++) {
final pattern = patterns[i];
for (final match in pattern.regex.allMatches(content)) {
//
if (match.group(1) != null && match.group(1)!.isNotEmpty) {
allMatches.add((match: match, type: pattern.type, priority: i));
while (index < text.length) {
//
if (endToken != null && text.startsWith(endToken, index)) {
flushBuffer();
return _RichTextParseResult(spans, index + endToken.length, true);
}
final currentChar = text[index];
var handled = false;
// []
if (currentChar == '[') {
final innerResult = _parseRichTextInternal(text, colorScheme, index + 1, ']');
if (innerResult.closed) {
flushBuffer();
final innerText = text.substring(index + 1, innerResult.nextIndex - 1);
spans.addAll(_applyStyleToSpans(
innerResult.spans,
_highlightTextStyle(colorScheme),
innerText,
));
index = innerResult.nextIndex;
handled = true;
} else {
// ]
buffer.write(currentChar);
index++;
handled = true;
}
}
// ``
else if (currentChar == '`') {
final closingIndex = text.indexOf('`', index + 1);
if (closingIndex != -1) {
flushBuffer();
final codeText = text.substring(index + 1, closingIndex);
spans.add(TextSpan(
text: codeText,
style: _codeTextStyle(colorScheme),
));
index = closingIndex + 1;
handled = true;
} else {
// `
buffer.write(currentChar);
index++;
handled = true;
}
}
// ****
else if (text.startsWith('**', index)) {
final innerResult = _parseRichTextInternal(text, colorScheme, index + 2, '**');
if (innerResult.closed) {
flushBuffer();
final innerText = text.substring(index + 2, innerResult.nextIndex - 2);
spans.addAll(_applyStyleToSpans(
innerResult.spans,
_boldTextStyle(colorScheme),
innerText,
));
index = innerResult.nextIndex;
handled = true;
} else {
// **
buffer.write('**');
index += 2;
handled = true;
}
}
// __斜体__
else if (text.startsWith('__', index)) {
final innerResult = _parseRichTextInternal(text, colorScheme, index + 2, '__');
if (innerResult.closed) {
flushBuffer();
final innerText = text.substring(index + 2, innerResult.nextIndex - 2);
spans.addAll(_applyStyleToSpans(
innerResult.spans,
_italicTextStyle(colorScheme),
innerText,
));
index = innerResult.nextIndex;
handled = true;
} else {
buffer.write('__');
index += 2;
handled = true;
}
}
if (!handled) {
buffer.write(currentChar);
index++;
}
}
//
if (allMatches.isEmpty) {
flushBuffer();
return _RichTextParseResult(spans, index, false);
}
TextStyle _defaultRichTextStyle(ColorScheme colorScheme) {
return TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.8),
height: 1.4,
);
}
TextStyle _boldTextStyle(ColorScheme colorScheme) {
return TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.9),
height: 1.4,
fontWeight: FontWeight.bold,
);
}
TextStyle _italicTextStyle(ColorScheme colorScheme) {
return TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.9),
height: 1.4,
fontStyle: FontStyle.italic,
);
}
TextStyle _codeTextStyle(ColorScheme colorScheme) {
return TextStyle(
fontSize: 12,
color: colorScheme.primary,
height: 1.4,
fontFamily: 'monospace',
backgroundColor: colorScheme.primaryContainer.withOpacity(0.2),
);
}
TextStyle _highlightTextStyle(ColorScheme colorScheme) {
return TextStyle(
fontSize: 13,
color: colorScheme.primary,
height: 1.4,
fontWeight: FontWeight.w600,
);
}
List<TextSpan> _applyStyleToSpans(List<TextSpan> spans, TextStyle style, String fallbackText) {
if (spans.isEmpty) {
return [
TextSpan(
text: content,
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.8),
height: 1.4,
),
text: fallbackText,
style: style,
),
];
}
//
allMatches.sort((a, b) {
//
if (a.match.start != b.match.start) {
return a.match.start.compareTo(b.match.start);
}
// [] `
if (a.priority != b.priority) {
return a.priority.compareTo(b.priority);
}
//
return a.match.end.compareTo(b.match.end);
});
return spans.map((span) => _mergeTextSpanStyle(span, style)).toList();
}
//
Match? selectedMatch;
String? selectedType;
for (final item in allMatches) {
bool isNested = false;
//
for (final other in allMatches) {
if (other.match != item.match &&
other.match.start <= item.match.start &&
other.match.end >= item.match.end &&
(other.match.start < item.match.start || other.match.end > item.match.end)) {
isNested = true;
break;
}
}
//
if (!isNested) {
selectedMatch = item.match;
selectedType = item.type;
break;
}
}
// 使
selectedMatch ??= allMatches.first.match;
selectedType ??= allMatches.first.type;
int lastIndex = 0;
//
if (selectedMatch.start > lastIndex) {
spans.add(TextSpan(
text: content.substring(lastIndex, selectedMatch.start),
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.8),
height: 1.4,
),
));
}
//
final matchedContent = selectedMatch.group(1)!;
final nestedSpans = _parseRichText(matchedContent, colorScheme);
//
TextStyle baseStyle;
switch (selectedType) {
case 'bold':
baseStyle = TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.9),
height: 1.4,
fontWeight: FontWeight.bold,
);
break;
case 'italic':
baseStyle = TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.9),
height: 1.4,
fontStyle: FontStyle.italic,
);
break;
case 'code':
baseStyle = TextStyle(
fontSize: 12,
color: colorScheme.primary,
height: 1.4,
fontFamily: 'monospace',
backgroundColor: colorScheme.primaryContainer.withOpacity(0.2),
);
break;
case 'highlight':
baseStyle = TextStyle(
fontSize: 13,
color: colorScheme.primary,
height: 1.4,
fontWeight: FontWeight.w600,
);
break;
default:
baseStyle = TextStyle(
fontSize: 13,
color: colorScheme.onSurface.withOpacity(0.8),
height: 1.4,
);
}
// 使
if (nestedSpans.isNotEmpty &&
(nestedSpans.length > 1 || (nestedSpans.isNotEmpty && nestedSpans[0].text != matchedContent))) {
// spans
final styledSpans = nestedSpans.map((span) {
return TextSpan(
text: span.text,
style: span.style?.merge(baseStyle) ?? baseStyle,
children: span.children,
);
}).toList();
spans.addAll(styledSpans);
} else {
//
spans.add(TextSpan(
text: matchedContent,
style: baseStyle,
));
}
//
lastIndex = selectedMatch.end;
if (lastIndex < content.length) {
final remainingSpans = _parseRichText(content.substring(lastIndex), colorScheme);
spans.addAll(remainingSpans);
}
return spans;
TextSpan _mergeTextSpanStyle(TextSpan span, TextStyle style) {
final mergedChildren =
span.children?.map((child) => child is TextSpan ? _mergeTextSpanStyle(child, style) : child).toList();
return TextSpan(
text: span.text,
style: span.style?.merge(style) ?? style,
children: mergedChildren,
);
}
Widget _buildEnhancedDownloadProgress(BuildContext context, ColorScheme colorScheme) {
@ -2207,3 +2217,11 @@ class _ToastWidget extends StatelessWidget {
}
typedef BoolCallback = void Function(bool success);
class _RichTextParseResult {
final List<TextSpan> spans;
final int nextIndex;
final bool closed;
const _RichTextParseResult(this.spans, this.nextIndex, this.closed);
}