fix: 评论区识别逻辑重构,修复含有关键词的评论重复出现的问题
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
@ -585,255 +586,153 @@ InlineSpan buildContent(
|
||||
if (content.message.contains('&')) {
|
||||
content.message = content.message.replaceAll('&', '&');
|
||||
}
|
||||
// 匹配表情
|
||||
print("content.jumpUrl.keys:" + content.jumpUrl.keys.toString());
|
||||
// 构建正则表达式
|
||||
final List<String> specialTokens = [
|
||||
...content.emote.keys,
|
||||
...content.atNameToMid.keys.map((e) => '@$e'),
|
||||
...content.jumpUrl.keys.map((e) =>
|
||||
e.replaceAll('?', '\\?').replaceAll('+', '\\+').replaceAll('*', '\\*')),
|
||||
r'\b\d{2}:\d{2}\b'
|
||||
];
|
||||
|
||||
final String patternStr = specialTokens.map(RegExp.escape).join('|');
|
||||
final RegExp pattern = RegExp(patternStr);
|
||||
List<String> matchedStrs = [];
|
||||
// 分割文本并处理每个部分
|
||||
content.message.splitMapJoin(
|
||||
RegExp(r"\[.*?\]"),
|
||||
pattern,
|
||||
onMatch: (Match match) {
|
||||
final String matchStr = match[0]!;
|
||||
if (content.emote.isNotEmpty &&
|
||||
matchStr.indexOf('[') == matchStr.lastIndexOf('[') &&
|
||||
matchStr.indexOf(']') == matchStr.lastIndexOf(']')) {
|
||||
String matchStr = match[0]!;
|
||||
if (content.emote.containsKey(matchStr)) {
|
||||
// 处理表情
|
||||
final int size = content.emote[matchStr]['meta']['size'];
|
||||
if (content.emote.keys.contains(matchStr)) {
|
||||
spanChilds.add(WidgetSpan(
|
||||
child: NetworkImgLayer(
|
||||
src: content.emote[matchStr]['url'],
|
||||
type: 'emote',
|
||||
width: size * 20,
|
||||
height: size * 20,
|
||||
),
|
||||
));
|
||||
} else if (matchStr.startsWith("@") &&
|
||||
content.atNameToMid.containsKey(matchStr.substring(1))) {
|
||||
// 处理@用户
|
||||
final String userName = matchStr.substring(1);
|
||||
final int userId = content.atNameToMid[userName];
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: matchStr,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
final String heroTag = Utils.makeHeroTag(userId);
|
||||
Get.toNamed(
|
||||
'/member?mid=$userId',
|
||||
arguments: {'face': '', 'heroTag': heroTag},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else if (matchStr.contains(':')) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: ' $matchStr ',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
// 跳转到指定位置
|
||||
try {
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
||||
.plPlayerController
|
||||
.seekTo(
|
||||
Duration(seconds: Utils.duration(matchStr)),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
print("matchStr=$matchStr");
|
||||
String appUrlSchema = '';
|
||||
final bool enableWordRe = setting.get(SettingBoxKey.enableWordRe,
|
||||
defaultValue: false) as bool;
|
||||
if (enableWordRe && content.jumpUrl[matchStr] != null && !matchedStrs.contains(matchStr)) {
|
||||
appUrlSchema = content.jumpUrl[matchStr]['app_url_schema'];
|
||||
spanChilds.add(
|
||||
WidgetSpan(
|
||||
child: NetworkImgLayer(
|
||||
src: content.emote[matchStr]['url'],
|
||||
type: 'emote',
|
||||
width: size * 20,
|
||||
height: size * 20,
|
||||
TextSpan(
|
||||
text: content.jumpUrl[matchStr]['title'],
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
if (appUrlSchema == '') {
|
||||
final String str = Uri.parse(matchStr).pathSegments[0];
|
||||
final Map matchRes = IdUtils.matchAvorBv(input: str);
|
||||
final List matchKeys = matchRes.keys.toList();
|
||||
if (matchKeys.isNotEmpty) {
|
||||
if (matchKeys.first == 'BV') {
|
||||
Get.toNamed(
|
||||
'/searchResult',
|
||||
parameters: {'keyword': matchRes['BV']},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': matchStr,
|
||||
'type': 'url',
|
||||
'pageTitle': ''
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (appUrlSchema.startsWith('bilibili://search')) {
|
||||
Get.toNamed('/searchResult', parameters: {
|
||||
'keyword': content.jumpUrl[matchStr]['title']
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
if (appUrlSchema.startsWith('bilibili://search') && enableWordRe) {
|
||||
spanChilds.add(
|
||||
WidgetSpan(
|
||||
child: Icon(
|
||||
FontAwesomeIcons.magnifyingGlass,
|
||||
size: 9,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
alignment: PlaceholderAlignment.top,
|
||||
),
|
||||
);
|
||||
}
|
||||
// 只显示一次
|
||||
matchedStrs.add(matchStr);
|
||||
} else {
|
||||
spanChilds.add(TextSpan(
|
||||
text: matchStr,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () =>
|
||||
replyReply(replyItem.root == 0 ? replyItem : fReplyItem)));
|
||||
return matchStr;
|
||||
}
|
||||
} else {
|
||||
spanChilds.add(TextSpan(
|
||||
text: matchStr,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () =>
|
||||
replyReply(replyItem.root == 0 ? replyItem : fReplyItem)));
|
||||
return matchStr;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
onNonMatch: (String str) {
|
||||
// 匹配@用户
|
||||
String matchMember = str;
|
||||
if (content.atNameToMid.isNotEmpty) {
|
||||
final List atNameToMidKeys = content.atNameToMid.keys.toList();
|
||||
RegExp reg = RegExp(atNameToMidKeys.map((key) => key).join('|'));
|
||||
// if (!content.message.contains(':')) {
|
||||
// reg = RegExp(r"@.*( |:)");
|
||||
// }
|
||||
|
||||
// 只@用户没有内容
|
||||
if (!content.message.contains(':') ||
|
||||
(content.atNameToMid.length == 1 &&
|
||||
content.message == '@${content.members.first.uname}')) {
|
||||
reg = RegExp(r"@.*( |:|$)");
|
||||
}
|
||||
matchMember = str.splitMapJoin(
|
||||
reg,
|
||||
onMatch: (Match match) {
|
||||
if (match[0] != null) {
|
||||
hasMatchMember = true;
|
||||
content.atNameToMid.forEach((key, value) {
|
||||
if (str.contains('回复')) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: '回复 ',
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: '@$key',
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
final String heroTag = Utils.makeHeroTag(value);
|
||||
Get.toNamed(
|
||||
'/member?mid=$value',
|
||||
arguments: {'face': '', 'heroTag': heroTag},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
return '';
|
||||
},
|
||||
onNonMatch: (String str) {
|
||||
if (!str.contains('@')) {
|
||||
spanChilds.add(TextSpan(text: str));
|
||||
}
|
||||
print(str);
|
||||
return str;
|
||||
},
|
||||
);
|
||||
} else {
|
||||
matchMember = str;
|
||||
}
|
||||
|
||||
// 匹配 jumpUrl
|
||||
String matchUrl = matchMember;
|
||||
if (content.jumpUrl.isNotEmpty) {
|
||||
final List urlKeys = content.jumpUrl.keys.toList().reversed.toList();
|
||||
for (int index = 0; index < urlKeys.length; index++) {
|
||||
var i = urlKeys[index];
|
||||
if (i.contains('?')) {
|
||||
urlKeys[index] = i.replaceAll('?', '\\?');
|
||||
}
|
||||
if (i.contains('+')) {
|
||||
urlKeys[index] = i.replaceAll('+', '\\+');
|
||||
}
|
||||
if (i.contains('*')) {
|
||||
urlKeys[index] = i.replaceAll('*', '\\*');
|
||||
}
|
||||
}
|
||||
if (hasMatchMember) {
|
||||
matchMember = matchMember.split('回复 @ :').length > 1
|
||||
? matchMember.split('回复 @ :')[1]
|
||||
: matchMember;
|
||||
}
|
||||
matchUrl = matchMember.splitMapJoin(
|
||||
/// RegExp.escape() 转义特殊字符
|
||||
RegExp(urlKeys.map((key) => key).join("|")),
|
||||
// RegExp('What does the fox say\\?'),
|
||||
onMatch: (Match match) {
|
||||
final String matchStr = match[0]!;
|
||||
String appUrlSchema = '';
|
||||
if (content.jumpUrl[matchStr] != null) {
|
||||
appUrlSchema = content.jumpUrl[matchStr]['app_url_schema'];
|
||||
}
|
||||
// 默认不显示关键词
|
||||
final bool enableWordRe = setting.get(SettingBoxKey.enableWordRe,
|
||||
defaultValue: false) as bool;
|
||||
if (content.jumpUrl[matchStr] != null) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: content.jumpUrl[matchStr]['title'],
|
||||
style: TextStyle(
|
||||
color: enableWordRe
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
if (appUrlSchema == '') {
|
||||
final String str = Uri.parse(matchStr).pathSegments[0];
|
||||
final Map matchRes = IdUtils.matchAvorBv(input: str);
|
||||
final List matchKeys = matchRes.keys.toList();
|
||||
if (matchKeys.isNotEmpty) {
|
||||
if (matchKeys.first == 'BV') {
|
||||
Get.toNamed(
|
||||
'/searchResult',
|
||||
parameters: {'keyword': matchRes['BV']},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': matchStr,
|
||||
'type': 'url',
|
||||
'pageTitle': ''
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (appUrlSchema.startsWith('bilibili://search') &&
|
||||
enableWordRe) {
|
||||
Get.toNamed('/searchResult', parameters: {
|
||||
'keyword': content.jumpUrl[matchStr]['title']
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (appUrlSchema.startsWith('bilibili://search') && enableWordRe) {
|
||||
spanChilds.add(
|
||||
WidgetSpan(
|
||||
child: Icon(
|
||||
FontAwesomeIcons.magnifyingGlass,
|
||||
size: 9,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
alignment: PlaceholderAlignment.top,
|
||||
),
|
||||
);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
onNonMatch: (String str) {
|
||||
spanChilds.add(TextSpan(
|
||||
text: str,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => replyReply(
|
||||
replyItem.root == 0 ? replyItem : fReplyItem)));
|
||||
return str;
|
||||
},
|
||||
);
|
||||
}
|
||||
str = matchUrl.splitMapJoin(
|
||||
RegExp(r'\b\d{2}:\d{2}\b'),
|
||||
onMatch: (Match match) {
|
||||
String matchStr = match[0]!;
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: ' $matchStr ',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
// 跳转到指定位置
|
||||
try {
|
||||
Get.find<VideoDetailController>(
|
||||
tag: Get.arguments['heroTag'])
|
||||
.plPlayerController
|
||||
.seekTo(
|
||||
Duration(seconds: Utils.duration(matchStr)),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
);
|
||||
return '';
|
||||
},
|
||||
onNonMatch: (str) {
|
||||
return str;
|
||||
},
|
||||
);
|
||||
|
||||
if (content.atNameToMid.isEmpty && content.jumpUrl.isEmpty) {
|
||||
if (str != '') {
|
||||
spanChilds.add(TextSpan(
|
||||
text: str,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () =>
|
||||
replyReply(replyItem.root == 0 ? replyItem : fReplyItem)));
|
||||
}
|
||||
}
|
||||
return str;
|
||||
onNonMatch: (String nonMatchStr) {
|
||||
spanChilds.add(TextSpan(
|
||||
text: nonMatchStr,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () =>
|
||||
replyReply(replyItem.root == 0 ? replyItem : fReplyItem)));
|
||||
return nonMatchStr;
|
||||
},
|
||||
);
|
||||
|
||||
|
Reference in New Issue
Block a user