fix: 评论区识别逻辑重构,修复含有关键词的评论重复出现的问题
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.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:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
@ -585,153 +586,88 @@ InlineSpan buildContent(
|
|||||||
if (content.message.contains('&')) {
|
if (content.message.contains('&')) {
|
||||||
content.message = content.message.replaceAll('&', '&');
|
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(
|
content.message.splitMapJoin(
|
||||||
RegExp(r"\[.*?\]"),
|
pattern,
|
||||||
onMatch: (Match match) {
|
onMatch: (Match match) {
|
||||||
final String matchStr = match[0]!;
|
String matchStr = match[0]!;
|
||||||
if (content.emote.isNotEmpty &&
|
if (content.emote.containsKey(matchStr)) {
|
||||||
matchStr.indexOf('[') == matchStr.lastIndexOf('[') &&
|
// 处理表情
|
||||||
matchStr.indexOf(']') == matchStr.lastIndexOf(']')) {
|
|
||||||
final int size = content.emote[matchStr]['meta']['size'];
|
final int size = content.emote[matchStr]['meta']['size'];
|
||||||
if (content.emote.keys.contains(matchStr)) {
|
spanChilds.add(WidgetSpan(
|
||||||
spanChilds.add(
|
|
||||||
WidgetSpan(
|
|
||||||
child: NetworkImgLayer(
|
child: NetworkImgLayer(
|
||||||
src: content.emote[matchStr]['url'],
|
src: content.emote[matchStr]['url'],
|
||||||
type: 'emote',
|
type: 'emote',
|
||||||
width: size * 20,
|
width: size * 20,
|
||||||
height: size * 20,
|
height: size * 20,
|
||||||
),
|
),
|
||||||
),
|
));
|
||||||
);
|
} else if (matchStr.startsWith("@") &&
|
||||||
} else {
|
content.atNameToMid.containsKey(matchStr.substring(1))) {
|
||||||
spanChilds.add(TextSpan(
|
// 处理@用户
|
||||||
text: matchStr,
|
final String userName = matchStr.substring(1);
|
||||||
recognizer: TapGestureRecognizer()
|
final int userId = content.atNameToMid[userName];
|
||||||
..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(
|
spanChilds.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '回复 ',
|
text: matchStr,
|
||||||
style: TextStyle(
|
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,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () {
|
..onTap = () {
|
||||||
final String heroTag = Utils.makeHeroTag(value);
|
final String heroTag = Utils.makeHeroTag(userId);
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/member?mid=$value',
|
'/member?mid=$userId',
|
||||||
arguments: {'face': '', 'heroTag': heroTag},
|
arguments: {'face': '', 'heroTag': heroTag},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
} else if (matchStr.contains(':')) {
|
||||||
}
|
spanChilds.add(
|
||||||
return '';
|
TextSpan(
|
||||||
},
|
text: ' $matchStr ',
|
||||||
onNonMatch: (String str) {
|
style: TextStyle(
|
||||||
if (!str.contains('@')) {
|
color: Theme.of(context).colorScheme.primary,
|
||||||
spanChilds.add(TextSpan(text: str));
|
),
|
||||||
}
|
recognizer: TapGestureRecognizer()
|
||||||
print(str);
|
..onTap = () {
|
||||||
return str;
|
// 跳转到指定位置
|
||||||
|
try {
|
||||||
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
||||||
|
.plPlayerController
|
||||||
|
.seekTo(
|
||||||
|
Duration(seconds: Utils.duration(matchStr)),
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
matchMember = str;
|
print("matchStr=$matchStr");
|
||||||
}
|
|
||||||
|
|
||||||
// 匹配 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 = '';
|
String appUrlSchema = '';
|
||||||
if (content.jumpUrl[matchStr] != null) {
|
|
||||||
appUrlSchema = content.jumpUrl[matchStr]['app_url_schema'];
|
|
||||||
}
|
|
||||||
// 默认不显示关键词
|
|
||||||
final bool enableWordRe = setting.get(SettingBoxKey.enableWordRe,
|
final bool enableWordRe = setting.get(SettingBoxKey.enableWordRe,
|
||||||
defaultValue: false) as bool;
|
defaultValue: false) as bool;
|
||||||
if (content.jumpUrl[matchStr] != null) {
|
if (enableWordRe && content.jumpUrl[matchStr] != null && !matchedStrs.contains(matchStr)) {
|
||||||
|
appUrlSchema = content.jumpUrl[matchStr]['app_url_schema'];
|
||||||
spanChilds.add(
|
spanChilds.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: content.jumpUrl[matchStr]['title'],
|
text: content.jumpUrl[matchStr]['title'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: enableWordRe
|
color: Theme.of(context).colorScheme.primary,
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () {
|
..onTap = () {
|
||||||
@ -757,8 +693,7 @@ InlineSpan buildContent(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (appUrlSchema.startsWith('bilibili://search') &&
|
if (appUrlSchema.startsWith('bilibili://search')) {
|
||||||
enableWordRe) {
|
|
||||||
Get.toNamed('/searchResult', parameters: {
|
Get.toNamed('/searchResult', parameters: {
|
||||||
'keyword': content.jumpUrl[matchStr]['title']
|
'keyword': content.jumpUrl[matchStr]['title']
|
||||||
});
|
});
|
||||||
@ -767,8 +702,6 @@ InlineSpan buildContent(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (appUrlSchema.startsWith('bilibili://search') && enableWordRe) {
|
if (appUrlSchema.startsWith('bilibili://search') && enableWordRe) {
|
||||||
spanChilds.add(
|
spanChilds.add(
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
@ -781,59 +714,25 @@ InlineSpan buildContent(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return '';
|
// 只显示一次
|
||||||
},
|
matchedStrs.add(matchStr);
|
||||||
onNonMatch: (String str) {
|
} else {
|
||||||
spanChilds.add(TextSpan(
|
spanChilds.add(TextSpan(
|
||||||
text: str,
|
text: matchStr,
|
||||||
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()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () =>
|
..onTap = () =>
|
||||||
replyReply(replyItem.root == 0 ? replyItem : fReplyItem)));
|
replyReply(replyItem.root == 0 ? replyItem : fReplyItem)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return str;
|
return '';
|
||||||
|
},
|
||||||
|
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