diff --git a/lib/app_upgrade_simple.dart b/lib/app_upgrade_simple.dart index ddffb90..2ed8ef0 100644 --- a/lib/app_upgrade_simple.dart +++ b/lib/app_upgrade_simple.dart @@ -1089,80 +1089,10 @@ mixin _UpgradeDialogLogic on State { } Widget _buildRichText(String content, ColorScheme colorScheme) { - final spans = []; - final regex = RegExp(r'\*\*(.*?)\*\*|__(.*?)__|`(.*?)`|\[(.*?)\]'); - int lastIndex = 0; + // 递归解析文本,支持嵌套格式 + final spans = _parseRichText(content, colorScheme); - for (final match in regex.allMatches(content)) { - if (match.start > lastIndex) { - spans.add(TextSpan( - text: content.substring(lastIndex, match.start), - style: TextStyle( - fontSize: 13, - color: colorScheme.onSurface.withOpacity(0.8), - height: 1.4, - ), - )); - } - - if (match.group(1) != null) { - spans.add(TextSpan( - text: match.group(1), - style: TextStyle( - fontSize: 13, - color: colorScheme.onSurface.withOpacity(0.9), - height: 1.4, - fontWeight: FontWeight.bold, - ), - )); - } else if (match.group(2) != null) { - spans.add(TextSpan( - text: match.group(2), - style: TextStyle( - fontSize: 13, - color: colorScheme.onSurface.withOpacity(0.9), - height: 1.4, - fontStyle: FontStyle.italic, - ), - )); - } else if (match.group(3) != null) { - spans.add(TextSpan( - text: match.group(3), - style: TextStyle( - fontSize: 12, - color: colorScheme.primary, - height: 1.4, - fontFamily: 'monospace', - backgroundColor: colorScheme.primaryContainer.withOpacity(0.2), - ), - )); - } else if (match.group(4) != null) { - spans.add(TextSpan( - text: match.group(4), - style: TextStyle( - fontSize: 13, - color: colorScheme.primary, - height: 1.4, - fontWeight: FontWeight.w600, - ), - )); - } - - lastIndex = match.end; - } - - if (lastIndex < content.length) { - spans.add(TextSpan( - text: content.substring(lastIndex), - style: TextStyle( - fontSize: 13, - color: colorScheme.onSurface.withOpacity(0.8), - height: 1.4, - ), - )); - } - - if (spans.isEmpty) { + if (spans.isEmpty || spans.length == 1 && spans[0].text == content) { return Text( content, style: TextStyle( @@ -1182,6 +1112,196 @@ mixin _UpgradeDialogLogic on State { ); } + /// 递归解析富文本,支持嵌套格式 + /// + /// 支持的格式: + /// - `**粗体**` - 加粗文本 + /// - `__斜体__` - 斜体文本 + /// - `` `代码` `` - 等宽字体代码 + /// - `[高亮]` - 主题色高亮 + /// + /// 支持嵌套,例如: + /// - `__在[学生管理]中添加详情页__` - 斜体中嵌套高亮 + /// - `[高亮**粗体**文本]` - 高亮中嵌套粗体 + /// - `` `代码**粗体**` `` - 代码中嵌套粗体 + /// + /// 处理逻辑: + /// 1. 找到所有格式标记 + /// 2. 识别嵌套关系(被其他格式完全包围的为内层) + /// 3. 优先处理最外层格式,递归处理内层内容 + /// 4. 继续处理剩余文本 + List _parseRichText(String content, ColorScheme colorScheme) { + final spans = []; + + // 定义格式标记:使用严格的正则表达式,避免匹配不完整的标记 + // 优先级:[] > ` > ** > __ (内层格式优先) + final patterns = [ + (regex: RegExp(r'\[([^\]]+)\]'), type: 'highlight'), // [高亮] - 不包含 ] 的内容 + (regex: RegExp(r'`([^`]+)`'), type: 'code'), // `代码` - 不包含 ` 的内容 + (regex: RegExp(r'\*\*([^*]+(?:\*(?!\*)[^*]*)*)\*\*'), type: 'bold'), // **粗体** - 不包含 ** 的内容 + (regex: RegExp(r'__([^_]+(?:_(?!_)[^_]*)*)__'), type: 'italic'), // __斜体__ - 不包含 __ 的内容 + ]; + + // 找到所有匹配的格式标记 + 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)); + } + } + } + + // 如果没有匹配,返回普通文本 + if (allMatches.isEmpty) { + return [ + TextSpan( + text: content, + style: TextStyle( + fontSize: 13, + color: colorScheme.onSurface.withOpacity(0.8), + height: 1.4, + ), + ), + ]; + } + + // 按位置排序,找到最左边的匹配 + 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); + }); + + // 找到第一个不被其他格式完全包围的匹配(最外层的) + 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; + } + Widget _buildEnhancedDownloadProgress(BuildContext context, ColorScheme colorScheme) { final bool showRetryButton = _downloadedFilePath != null && !_isDownloading &&