mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-01 16:48:16 +08:00
opt reply item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -37,8 +37,8 @@ class Constants {
|
|||||||
static const String statisticsApp =
|
static const String statisticsApp =
|
||||||
'{"appId":1,"platform":3,"version":"8.43.0","abtest":""}';
|
'{"appId":1,"platform":3,"version":"8.43.0","abtest":""}';
|
||||||
|
|
||||||
static const urlPattern =
|
static final urlRegex =
|
||||||
r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]';
|
RegExp(r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]');
|
||||||
|
|
||||||
static const goodsUrlPrefix = "https://gaoneng.bilibili.com/tetris";
|
static const goodsUrlPrefix = "https://gaoneng.bilibili.com/tetris";
|
||||||
|
|
||||||
|
|||||||
@@ -643,7 +643,7 @@ class _VideoInfoState extends State<VideoInfo> {
|
|||||||
case 1:
|
case 1:
|
||||||
final List<InlineSpan> spanChildren = <InlineSpan>[];
|
final List<InlineSpan> spanChildren = <InlineSpan>[];
|
||||||
final RegExp urlRegExp = RegExp(
|
final RegExp urlRegExp = RegExp(
|
||||||
'${Constants.urlPattern}|av\\d+|bv[a-z\\d]{10}',
|
'${Constants.urlRegex.pattern}|av\\d+|bv[a-z\\d]{10}',
|
||||||
caseSensitive: false,
|
caseSensitive: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
||||||
import 'package:PiliPlus/common/widgets/text/text.dart' as custom_text;
|
import 'package:PiliPlus/common/widgets/text/text.dart' as custom_text;
|
||||||
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
||||||
show ReplyInfo, ReplyControl, Content;
|
show ReplyInfo, ReplyControl, Content, Url;
|
||||||
import 'package:PiliPlus/http/init.dart';
|
import 'package:PiliPlus/http/init.dart';
|
||||||
import 'package:PiliPlus/http/video.dart';
|
import 'package:PiliPlus/http/video.dart';
|
||||||
import 'package:PiliPlus/models/common/badge_type.dart';
|
import 'package:PiliPlus/models/common/badge_type.dart';
|
||||||
@@ -170,7 +170,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget lfAvtar() => PendantAvatar(
|
Widget _buildAvatar() => PendantAvatar(
|
||||||
avatar: replyItem.member.face,
|
avatar: replyItem.member.face,
|
||||||
size: 34,
|
size: 34,
|
||||||
badgeSize: 14,
|
badgeSize: 14,
|
||||||
@@ -201,7 +201,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
spacing: 12,
|
spacing: 12,
|
||||||
children: [
|
children: [
|
||||||
lfAvtar(),
|
_buildAvatar(),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -245,7 +245,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
color: theme.colorScheme.outline,
|
color: theme.colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (replyItem.replyControl.location.isNotEmpty)
|
if (replyItem.replyControl.hasLocation())
|
||||||
Text(
|
Text(
|
||||||
' • ${replyItem.replyControl.location}',
|
' • ${replyItem.replyControl.location}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -297,13 +297,11 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
builder: (context, constraints) => imageView(
|
builder: (context, constraints) => imageView(
|
||||||
constraints.maxWidth,
|
constraints.maxWidth,
|
||||||
replyItem.content.pictures
|
replyItem.content.pictures
|
||||||
.map(
|
.map((item) => ImageModel(
|
||||||
(item) => ImageModel(
|
|
||||||
width: item.imgWidth,
|
width: item.imgWidth,
|
||||||
height: item.imgHeight,
|
height: item.imgHeight,
|
||||||
url: item.imgSrc,
|
url: item.imgSrc,
|
||||||
),
|
))
|
||||||
)
|
|
||||||
.toList(),
|
.toList(),
|
||||||
onViewImage: onViewImage,
|
onViewImage: onViewImage,
|
||||||
onDismissed: onDismissed,
|
onDismissed: onDismissed,
|
||||||
@@ -312,12 +310,10 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
// 操作区域
|
|
||||||
if (replyLevel != 0) ...[
|
if (replyLevel != 0) ...[
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
buttonAction(context, theme, replyItem.replyControl),
|
buttonAction(context, theme, replyItem.replyControl),
|
||||||
],
|
],
|
||||||
// 一楼的评论
|
|
||||||
if (replyLevel == 1 && replyItem.count > Int64.ZERO) ...[
|
if (replyLevel == 1 && replyItem.count > Int64.ZERO) ...[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 5, bottom: 12),
|
padding: const EdgeInsets.only(top: 5, bottom: 12),
|
||||||
@@ -328,22 +324,20 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonStyle get _style => TextButton.styleFrom(
|
Widget buttonAction(
|
||||||
|
BuildContext context, ThemeData theme, ReplyControl replyControl) {
|
||||||
|
final ButtonStyle style = TextButton.styleFrom(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 感谢、回复、复制
|
|
||||||
Widget buttonAction(
|
|
||||||
BuildContext context, ThemeData theme, ReplyControl replyControl) {
|
|
||||||
return Row(
|
return Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const SizedBox(width: 36),
|
const SizedBox(width: 36),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 32,
|
height: 32,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
style: _style,
|
style: style,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
feedBack();
|
feedBack();
|
||||||
onReply?.call(replyItem);
|
onReply?.call(replyItem);
|
||||||
@@ -371,7 +365,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
height: 32,
|
height: 32,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: null,
|
onPressed: null,
|
||||||
style: _style,
|
style: style,
|
||||||
child: Text(
|
child: Text(
|
||||||
'UP主觉得很赞',
|
'UP主觉得很赞',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -399,7 +393,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
height: 32,
|
height: 32,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: showDialogue,
|
onPressed: showDialogue,
|
||||||
style: _style,
|
style: style,
|
||||||
child: Text(
|
child: Text(
|
||||||
'查看对话',
|
'查看对话',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -524,7 +518,6 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
}),
|
}),
|
||||||
if (extraRow)
|
if (extraRow)
|
||||||
InkWell(
|
InkWell(
|
||||||
// 一楼点击【共xx条回复】展开评论详情
|
|
||||||
onTap: () => replyReply?.call(replyItem, null),
|
onTap: () => replyReply?.call(replyItem, null),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
@@ -570,7 +563,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
final Content content = replyItem.content;
|
final Content content = replyItem.content;
|
||||||
final List<InlineSpan> spanChildren = <InlineSpan>[];
|
final List<InlineSpan> spanChildren = <InlineSpan>[];
|
||||||
|
|
||||||
if (content.hasRichText()) {
|
if (content.richText.hasNote()) {
|
||||||
spanChildren.add(
|
spanChildren.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '[笔记] ',
|
text: '[笔记] ',
|
||||||
@@ -584,161 +577,56 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final urlKeys = content.urls.keys;
|
||||||
// 构建正则表达式
|
// 构建正则表达式
|
||||||
final List<String> specialTokens = [
|
final List<String> specialTokens = [
|
||||||
...content.emotes.keys,
|
...content.emotes.keys,
|
||||||
...content.topics.keys.map((e) => '#$e#'),
|
...content.topics.keys.map((e) => '#$e#'),
|
||||||
...content.atNameToMid.keys.map((e) => '@$e'),
|
...content.atNameToMid.keys.map((e) => '@$e'),
|
||||||
...content.urls.keys,
|
...urlKeys,
|
||||||
];
|
];
|
||||||
String patternStr = [
|
String patternStr = [
|
||||||
...specialTokens.map(RegExp.escape),
|
...specialTokens.map(RegExp.escape),
|
||||||
r'(\b(?:\d+[::])?\d+[::]\d+\b)',
|
r'(\b(?:\d+[::])?\d+[::]\d+\b)',
|
||||||
r'(\{vote:\d+?\})',
|
r'(\{vote:\d+?\})',
|
||||||
Constants.urlPattern,
|
Constants.urlRegex.pattern,
|
||||||
].join('|');
|
].join('|');
|
||||||
final RegExp pattern = RegExp(patternStr);
|
final RegExp pattern = RegExp(patternStr);
|
||||||
|
|
||||||
late List<String> matchedStrs = [];
|
late List<String> matchedUrls = [];
|
||||||
|
|
||||||
void addPlainTextSpan(str) {
|
void addPlainTextSpan(str) {
|
||||||
spanChildren.add(TextSpan(text: str));
|
spanChildren.add(TextSpan(text: str));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分割文本并处理每个部分
|
void addUrl(String matchStr, Url url, {bool addPlainText = false}) {
|
||||||
content.message.splitMapJoin(
|
if (url.extra.isWordSearch && !enableWordRe) {
|
||||||
pattern,
|
if (addPlainText) {
|
||||||
onMatch: (Match match) {
|
|
||||||
String matchStr = match[0]!;
|
|
||||||
if (content.emotes.containsKey(matchStr)) {
|
|
||||||
// 处理表情
|
|
||||||
final size = content.emotes[matchStr]!.size.toInt() * 20.0;
|
|
||||||
spanChildren.add(
|
|
||||||
WidgetSpan(
|
|
||||||
child: NetworkImgLayer(
|
|
||||||
src: content.emotes[matchStr]?.hasGifUrl() == true
|
|
||||||
? content.emotes[matchStr]?.gifUrl
|
|
||||||
: content.emotes[matchStr]?.url,
|
|
||||||
type: ImageType.emote,
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (matchStr.startsWith("@") &&
|
|
||||||
content.atNameToMid.containsKey(matchStr.substring(1))) {
|
|
||||||
// 处理@用户
|
|
||||||
final String userName = matchStr.substring(1);
|
|
||||||
final userId = content.atNameToMid[userName]!.toString();
|
|
||||||
spanChildren.add(
|
|
||||||
TextSpan(
|
|
||||||
text: matchStr,
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () => Get.toNamed('/member?mid=$userId'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (_voteRegExp.hasMatch(matchStr)) {
|
|
||||||
spanChildren.add(
|
|
||||||
TextSpan(
|
|
||||||
text: '投票: ${content.vote.title}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap =
|
|
||||||
() => showVoteDialog(context, content.vote.id.toInt()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (_timeRegExp.hasMatch(matchStr)) {
|
|
||||||
matchStr = matchStr.replaceAll(':', ':');
|
|
||||||
bool isValid = false;
|
|
||||||
try {
|
|
||||||
List<int> split = matchStr
|
|
||||||
.split(':')
|
|
||||||
.map((item) => int.parse(item))
|
|
||||||
.toList()
|
|
||||||
.reversed
|
|
||||||
.toList();
|
|
||||||
int seek = 0;
|
|
||||||
for (int i = 0; i < split.length; i++) {
|
|
||||||
seek += split[i] * pow(60, i).toInt();
|
|
||||||
}
|
|
||||||
int duration = Get.find<VideoDetailController>(
|
|
||||||
tag: getTag?.call() ?? Get.arguments['heroTag'],
|
|
||||||
).data.timeLength ??
|
|
||||||
0;
|
|
||||||
isValid = seek * 1000 <= duration;
|
|
||||||
} catch (e) {
|
|
||||||
if (kDebugMode) debugPrint('failed to validate: $e');
|
|
||||||
}
|
|
||||||
bool isVideoPage = Get.currentRoute.startsWith('/video');
|
|
||||||
spanChildren.add(
|
|
||||||
TextSpan(
|
|
||||||
text: isValid ? ' $matchStr ' : matchStr,
|
|
||||||
style: isValid && isVideoPage
|
|
||||||
? TextStyle(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
recognizer: isValid
|
|
||||||
? (TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
// 跳转到指定位置
|
|
||||||
if (isVideoPage) {
|
|
||||||
try {
|
|
||||||
SmartDialog.showToast('跳转至:$matchStr');
|
|
||||||
Get.find<VideoDetailController>(
|
|
||||||
tag: Get.arguments['heroTag'])
|
|
||||||
.plPlayerController
|
|
||||||
.seekTo(
|
|
||||||
Duration(
|
|
||||||
seconds:
|
|
||||||
DurationUtil.parseDuration(matchStr)),
|
|
||||||
type: 'slider');
|
|
||||||
} catch (e) {
|
|
||||||
SmartDialog.showToast('跳转失败: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
String appUrlSchema = '';
|
|
||||||
if (content.urls[matchStr] != null &&
|
|
||||||
!matchedStrs.contains(matchStr)) {
|
|
||||||
appUrlSchema = content.urls[matchStr]!.appUrlSchema;
|
|
||||||
if (appUrlSchema.startsWith('bilibili://search') && !enableWordRe) {
|
|
||||||
addPlainTextSpan(matchStr);
|
addPlainTextSpan(matchStr);
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
spanChildren.addAll(
|
return;
|
||||||
[
|
}
|
||||||
if (content.urls[matchStr]?.hasPrefixIcon() == true) ...[
|
final isCv = url.clickReport.startsWith('{"cvid');
|
||||||
|
final children = [
|
||||||
|
if (!isCv && url.hasPrefixIcon())
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: ImageUtil.thumbnailUrl(
|
imageUrl: ImageUtil.thumbnailUrl(url.prefixIcon),
|
||||||
content.urls[matchStr]!.prefixIcon),
|
|
||||||
height: 19,
|
height: 19,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
placeholder: (context, url) {
|
placeholder: (context, url) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: content.urls[matchStr]!.title,
|
text: isCv ? '[笔记] ' : url.title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () {
|
..onTap = () {
|
||||||
late final String title = content.urls[matchStr]!.title;
|
if (url.appUrlSchema.isEmpty) {
|
||||||
if (appUrlSchema == '') {
|
|
||||||
if (RegExp(r'^(av|bv)', caseSensitive: false)
|
if (RegExp(r'^(av|bv)', caseSensitive: false)
|
||||||
.hasMatch(matchStr)) {
|
.hasMatch(matchStr)) {
|
||||||
UrlUtils.matchUrlPush(matchStr, '');
|
UrlUtils.matchUrlPush(matchStr, '');
|
||||||
@@ -763,34 +651,129 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
PageUtils.handleWebview(matchStr);
|
PageUtils.handleWebview(matchStr);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (appUrlSchema.startsWith('bilibili://search')) {
|
if (url.extra.isWordSearch) {
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/searchResult',
|
'/searchResult',
|
||||||
parameters: {'keyword': title},
|
parameters: {'keyword': url.title},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
PageUtils.handleWebview(matchStr);
|
PageUtils.handleWebview(matchStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
];
|
||||||
|
if (isCv) {
|
||||||
|
spanChildren.insertAll(0, children);
|
||||||
|
} else {
|
||||||
|
spanChildren.addAll(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分割文本并处理每个部分
|
||||||
|
content.message.splitMapJoin(
|
||||||
|
pattern,
|
||||||
|
onMatch: (Match match) {
|
||||||
|
String matchStr = match[0]!;
|
||||||
|
late final name = matchStr.substring(1);
|
||||||
|
late final topic = matchStr.substring(1, matchStr.length - 1);
|
||||||
|
if (content.emotes.containsKey(matchStr)) {
|
||||||
|
// 处理表情
|
||||||
|
final emote = content.emotes[matchStr]!;
|
||||||
|
final size = emote.size.toInt() * 20.0;
|
||||||
|
spanChildren.add(
|
||||||
|
WidgetSpan(
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: emote.hasGifUrl() == true ? emote.gifUrl : emote.url,
|
||||||
|
type: ImageType.emote,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
// 只显示一次
|
} else if (matchStr.startsWith("@") &&
|
||||||
matchedStrs.add(matchStr);
|
content.atNameToMid.containsKey(name)) {
|
||||||
} else if (matchStr.length > 1 &&
|
// 处理@用户
|
||||||
content.topics[matchStr.substring(1, matchStr.length - 1)] !=
|
|
||||||
null) {
|
|
||||||
spanChildren.add(
|
spanChildren.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: matchStr,
|
text: matchStr,
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.primary),
|
||||||
color: theme.colorScheme.primary,
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () =>
|
||||||
|
Get.toNamed('/member?mid=${content.atNameToMid[name]}'),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
} else if (_voteRegExp.hasMatch(matchStr)) {
|
||||||
|
spanChildren.add(
|
||||||
|
TextSpan(
|
||||||
|
text: '投票: ${content.vote.title}',
|
||||||
|
style: TextStyle(color: theme.colorScheme.primary),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap =
|
||||||
|
() => showVoteDialog(context, content.vote.id.toInt()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (_timeRegExp.hasMatch(matchStr)) {
|
||||||
|
matchStr = matchStr.replaceAll(':', ':');
|
||||||
|
bool isValid = false;
|
||||||
|
if (Get.currentRoute.startsWith('/video')) {
|
||||||
|
try {
|
||||||
|
final ctr = Get.find<VideoDetailController>(
|
||||||
|
tag: getTag?.call() ?? Get.arguments['heroTag']);
|
||||||
|
int duration = ctr.data.timeLength!;
|
||||||
|
List<int> split = matchStr
|
||||||
|
.split(':')
|
||||||
|
.reversed
|
||||||
|
.map((item) => int.parse(item))
|
||||||
|
.toList();
|
||||||
|
int seek = 0;
|
||||||
|
for (int i = 0; i < split.length; i++) {
|
||||||
|
seek += split[i] * pow(60, i).toInt();
|
||||||
|
}
|
||||||
|
isValid = seek * 1000 <= duration;
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) debugPrint('failed to validate: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spanChildren.add(
|
||||||
|
TextSpan(
|
||||||
|
text: isValid ? ' $matchStr ' : matchStr,
|
||||||
|
style:
|
||||||
|
isValid ? TextStyle(color: theme.colorScheme.primary) : null,
|
||||||
|
recognizer: isValid
|
||||||
|
? (TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
// 跳转到指定位置
|
||||||
|
try {
|
||||||
|
SmartDialog.showToast('跳转至:$matchStr');
|
||||||
|
Get.find<VideoDetailController>(
|
||||||
|
tag: Get.arguments['heroTag'])
|
||||||
|
.plPlayerController
|
||||||
|
.seekTo(
|
||||||
|
Duration(
|
||||||
|
seconds:
|
||||||
|
DurationUtil.parseDuration(matchStr)),
|
||||||
|
type: 'slider');
|
||||||
|
} catch (e) {
|
||||||
|
SmartDialog.showToast('跳转失败: $e');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final url = content.urls[matchStr];
|
||||||
|
if (url != null && !matchedUrls.contains(matchStr)) {
|
||||||
|
addUrl(matchStr, url, addPlainText: true);
|
||||||
|
// 只显示一次
|
||||||
|
matchedUrls.add(matchStr);
|
||||||
|
} else if (matchStr.length > 1 && content.topics[topic] != null) {
|
||||||
|
spanChildren.add(
|
||||||
|
TextSpan(
|
||||||
|
text: matchStr,
|
||||||
|
style: TextStyle(color: theme.colorScheme.primary),
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () {
|
..onTap = () {
|
||||||
final String topic =
|
|
||||||
matchStr.substring(1, matchStr.length - 1);
|
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/searchResult',
|
'/searchResult',
|
||||||
parameters: {'keyword': topic},
|
parameters: {'keyword': topic},
|
||||||
@@ -798,13 +781,11 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (RegExp(Constants.urlPattern).hasMatch(matchStr)) {
|
} else if (Constants.urlRegex.hasMatch(matchStr)) {
|
||||||
spanChildren.add(
|
spanChildren.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: matchStr,
|
text: matchStr,
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.primary),
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () => PageUtils.handleWebview(matchStr),
|
..onTap = () => PageUtils.handleWebview(matchStr),
|
||||||
),
|
),
|
||||||
@@ -821,59 +802,12 @@ class ReplyItemGrpc extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (content.urls.keys.isNotEmpty) {
|
if (urlKeys.isNotEmpty) {
|
||||||
List<String> unmatchedItems = content.urls.keys
|
List<String> unmatchedItems =
|
||||||
.toList()
|
urlKeys.where((url) => !matchedUrls.contains(url)).toList();
|
||||||
.where((item) => !content.message.contains(item))
|
|
||||||
.toList();
|
|
||||||
if (unmatchedItems.isNotEmpty) {
|
if (unmatchedItems.isNotEmpty) {
|
||||||
for (int i = 0; i < unmatchedItems.length; i++) {
|
for (var patternStr in unmatchedItems) {
|
||||||
String patternStr = unmatchedItems[i];
|
addUrl(patternStr, content.urls[patternStr]!);
|
||||||
if (content.urls[patternStr]?.extra.isWordSearch == true &&
|
|
||||||
!enableWordRe) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
spanChildren.addAll(
|
|
||||||
[
|
|
||||||
if (content.urls[patternStr]?.hasPrefixIcon() == true) ...[
|
|
||||||
WidgetSpan(
|
|
||||||
child: CachedNetworkImage(
|
|
||||||
imageUrl: ImageUtil.thumbnailUrl(
|
|
||||||
content.urls[patternStr]!.prefixIcon),
|
|
||||||
height: 19,
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
placeholder: (context, url) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
TextSpan(
|
|
||||||
text: content.urls[patternStr]!.title,
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
String? cvid = RegExp(r'note-app/view\?cvid=(\d+)')
|
|
||||||
.firstMatch(patternStr)
|
|
||||||
?.group(1);
|
|
||||||
if (cvid != null) {
|
|
||||||
Get.toNamed(
|
|
||||||
'/articlePage',
|
|
||||||
parameters: {
|
|
||||||
'id': cvid,
|
|
||||||
'type': 'read',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PageUtils.handleWebview(patternStr);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user