Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-06-26 15:07:12 +08:00
parent 81f72e2c4a
commit 12c13cd25a
22 changed files with 139 additions and 213 deletions

View File

@@ -16,7 +16,6 @@ import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
// 视频卡片 - 垂直布局
@@ -30,11 +29,6 @@ class VideoCardV extends StatelessWidget {
this.onRemove,
});
bool isStringNumeric(String str) {
RegExp numericRegex = RegExp(r'^\d+$');
return numericRegex.hasMatch(str);
}
Future<void> onPushDetail(String heroTag) async {
String? goto = videoItem.goto;
switch (goto) {
@@ -58,31 +52,7 @@ class VideoCardV extends StatelessWidget {
// 动态
case 'picture':
try {
String type = 'picture';
String uri = videoItem.uri!;
String id = '';
if (uri.startsWith('bilibili://article/')) {
type = 'read';
RegExp regex = RegExp(r'\d+');
Match match = regex.firstMatch(uri)!;
String matchedNumber = match.group(0)!;
videoItem.param = int.parse(matchedNumber);
id = '${videoItem.param}';
}
if (uri.startsWith('http')) {
String id = Uri.parse(uri).path.split('/')[1];
if (isStringNumeric(id)) {
PageUtils.pushDynFromId(id: id);
return;
}
}
Get.toNamed(
'/articlePage',
parameters: {
'id': id,
'type': type,
},
);
PiliScheme.routePushFromUrl(videoItem.uri!);
} catch (err) {
SmartDialog.showToast(err.toString());
}

View File

@@ -27,8 +27,6 @@ class Request {
static late AccountManager accountManager;
static late final Dio dio;
factory Request() => _instance;
// static final _rand = Random();
// static final RegExp _spmPrefixExp = RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
/// 设置cookie
static Future<void> setCookie() async {

View File

@@ -125,9 +125,8 @@ class _DanmakuBlockPageState extends State<DanmakuBlockPage> {
initialValue: filter,
onChanged: (value) => filter = value,
keyboardType: isUid ? TextInputType.number : null,
inputFormatters: isUid
? [FilteringTextInputFormatter.allow(RegExp(r'\d+'))]
: null,
inputFormatters:
isUid ? [FilteringTextInputFormatter.digitsOnly] : null,
)
],
),

View File

@@ -342,9 +342,8 @@ class _LiveDmBlockPageState extends State<LiveDmBlockPage> {
onChanged: (val) => value = val,
decoration: isKeyword ? null : const InputDecoration(hintText: 'UID'),
keyboardType: isKeyword ? null : TextInputType.number,
inputFormatters: isKeyword
? null
: [FilteringTextInputFormatter.allow(RegExp(r'\d+'))],
inputFormatters:
isKeyword ? null : [FilteringTextInputFormatter.digitsOnly],
),
onConfirm: () {
if (value.isNotEmpty) {

View File

@@ -1,6 +1,7 @@
import 'package:PiliPlus/models/common/live_search_type.dart';
import 'package:PiliPlus/pages/live_search/child/controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -35,11 +36,9 @@ class LiveSearchController extends GetxController
}
}
late final regex = RegExp(r'^\d+$');
void submit() {
if (editingController.text.isNotEmpty) {
if (regex.hasMatch(editingController.text)) {
if (IdUtils.digitOnlyRegExp.hasMatch(editingController.text)) {
Get.toNamed('/liveRoom?roomid=${editingController.text}');
} else {
hasData.value = true;

View File

@@ -141,46 +141,39 @@ class MemberVideoCtr
}
Future<void> toViewPlayAll() async {
if (episodicButton.value.text == '继续播放' &&
episodicButton.value.uri?.isNotEmpty == true) {
final params = Uri.parse(episodicButton.value.uri!).queryParameters;
String? oid = params['oid'];
if (oid != null) {
var bvid = IdUtils.av2bv(int.parse(oid));
var cid = await SearchHttp.ab2c(aid: oid, bvid: bvid);
PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
arguments: {
'heroTag': Utils.makeHeroTag(oid),
'sourceType': 'archive',
'mediaId': seasonId ?? seriesId ?? mid,
'oid': oid,
'favTitle':
'$username: ${title ?? episodicButton.value.text ?? '播放全部'}',
if (seriesId == null) 'count': count.value,
if (seasonId != null || seriesId != null)
'mediaType': params['page_type'],
'desc': params['desc'] == '1',
'sortField': params['sort_field'],
'isContinuePlaying': true,
},
);
}
return;
}
if (loadingState.value.isSuccess) {
List<SpaceArchiveItem>? list = loadingState.value.data;
if (list.isNullOrEmpty) return;
if (episodicButton.value.text == '继续播放') {
String? oid = RegExp(r'oid=(\d+)')
.firstMatch('${episodicButton.value.uri}')
?.group(1);
if (oid != null) {
var bvid = IdUtils.av2bv(int.parse(oid));
var cid = await SearchHttp.ab2c(aid: oid, bvid: bvid);
PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
arguments: {
'heroTag': Utils.makeHeroTag(oid),
'sourceType': 'archive',
'mediaId': seasonId ?? seriesId ?? mid,
'oid': oid,
'favTitle':
'$username: ${title ?? episodicButton.value.text ?? '播放全部'}',
if (seriesId == null) 'count': count.value,
if (seasonId != null || seriesId != null)
'mediaType': RegExp(r'page_type=([\d]+)')
.firstMatch('${episodicButton.value.uri}')
?.group(1),
'desc': RegExp(r'desc=([\d]+)')
.firstMatch('${episodicButton.value.uri}')
?.group(1) ==
'1',
'sortField': RegExp(r'sort_field=([\d]+)')
.firstMatch('${episodicButton.value.uri}')
?.group(1),
'isContinuePlaying': true,
},
);
}
return;
}
for (SpaceArchiveItem element in list!) {
if (element.cid == null) {
continue;
@@ -207,9 +200,8 @@ class MemberVideoCtr
'$username: ${title ?? episodicButton.value.text ?? '播放全部'}',
if (seriesId == null) 'count': count.value,
if (seasonId != null || seriesId != null)
'mediaType': RegExp(r'page_type=([\d]+)')
.firstMatch('${episodicButton.value.uri}')
?.group(1),
'mediaType': Uri.parse(episodicButton.value.uri!)
.queryParameters['page_type'],
'desc': desc,
if (type == ContributeType.video)
'sortField': order.value == 'click' ? 2 : 1,

View File

@@ -6,6 +6,7 @@ import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/search/suggest.dart';
import 'package:PiliPlus/models_new/search/search_rcmd/data.dart';
import 'package:PiliPlus/models_new/search/search_trending/data.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter/material.dart';
@@ -25,7 +26,6 @@ class SSearchController extends GetxController {
// uid
final RxBool showUidBtn = false.obs;
late final digitOnlyRegExp = RegExp(r'^\d+$');
// history
final RxBool recordSearchHistory = Pref.recordSearchHistory.obs;
@@ -82,7 +82,7 @@ class SSearchController extends GetxController {
}
void validateUid() {
showUidBtn.value = digitOnlyRegExp.hasMatch(controller.text);
showUidBtn.value = IdUtils.digitOnlyRegExp.hasMatch(controller.text);
}
void onChange(String value) {

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/models/common/search_type.dart';
import 'package:PiliPlus/models/search/result.dart';
import 'package:PiliPlus/pages/search_panel/controller.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/id_utils.dart';
class SearchAllController
extends SearchPanelController<SearchAllData, dynamic> {
@@ -61,15 +62,14 @@ class SearchAllController
}
void jump2Video() {
if (RegExp(r'^av\d+$', caseSensitive: false).hasMatch(keyword)) {
if (IdUtils.avRegexExact.hasMatch(keyword)) {
hasJump2Video = true;
PiliScheme.videoPush(
int.parse(keyword.substring(2)),
null,
showDialog: false,
);
} else if (RegExp(r'^bv[a-z\d]{10}$', caseSensitive: false)
.hasMatch(keyword)) {
} else if (IdUtils.bvRegexExact.hasMatch(keyword)) {
hasJump2Video = true;
PiliScheme.videoPush(null, keyword, showDialog: false);
}

View File

@@ -23,7 +23,7 @@ class SearchArticleController
void jump2Article() {
String? cvid = RegExp(r'^cv(id)?(\d+)$', caseSensitive: false)
.firstMatch(keyword)
.matchAsPrefix(keyword)
?.group(2);
if (cvid != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {

View File

@@ -7,6 +7,7 @@ import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/pages/search_panel/controller.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/date_util.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -71,15 +72,14 @@ class SearchVideoController
}
void jump2Video() {
if (RegExp(r'^av\d+$', caseSensitive: false).hasMatch(keyword)) {
if (IdUtils.avRegexExact.hasMatch(keyword)) {
hasJump2Video = true;
PiliScheme.videoPush(
int.parse(keyword.substring(2)),
null,
showDialog: false,
);
} else if (RegExp(r'^bv[a-z\d]{10}$', caseSensitive: false)
.hasMatch(keyword)) {
} else if (IdUtils.bvRegexExact.hasMatch(keyword)) {
hasJump2Video = true;
PiliScheme.videoPush(null, keyword, showDialog: false);
}

View File

@@ -74,9 +74,7 @@ List<SettingsModel> get extraSettings => [
onChanged: (value) {
dynamicPeriod = int.tryParse(value) ?? 5;
},
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'\d+')),
],
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: const InputDecoration(suffixText: 'min'),
),
actions: [
@@ -187,9 +185,7 @@ List<SettingsModel> get extraSettings => [
onChanged: (value) {
replyLengthLimit = value;
},
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'\d+')),
],
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: const InputDecoration(suffixText: ''),
),
actions: [

View File

@@ -184,9 +184,7 @@ SettingsModel getVideoFilterSelectModel({
autofocus: true,
onChanged: (value) => valueStr = value,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'\d+')),
],
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(suffixText: suffix),
),
actions: [

View File

@@ -515,7 +515,6 @@ List<SettingsModel> get styleSettings => [
title: '滑动动画弹簧参数',
leading: const Icon(Icons.chrome_reader_mode_outlined),
onTap: (setState) {
final numberRegExp = RegExp(r'[\d\.]+');
List<String> springDescription = CustomSpringDescription
.springDescription
.map((i) => i.toString())
@@ -538,7 +537,7 @@ List<SettingsModel> get styleSettings => [
springDescription[index] = value;
},
inputFormatters: [
FilteringTextInputFormatter.allow(numberRegExp)
FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+'))
],
decoration: InputDecoration(
labelText: const [

View File

@@ -632,56 +632,27 @@ class _VideoInfoState extends State<VideoInfo> {
);
}
static final RegExp urlRegExp = RegExp(
'${Constants.urlRegex.pattern}|av\\d+|bv[a-z\\d]{10}',
caseSensitive: false,
);
InlineSpan buildContent(ThemeData theme, VideoDetailData content) {
final List descV2 = content.descV2!;
if (content.descV2.isNullOrEmpty) {
return const TextSpan();
}
// type
// 1 普通文本
// 2 @用户
final List<TextSpan> spanChildren = List.generate(descV2.length, (index) {
final currentDesc = descV2[index];
final List<TextSpan> spanChildren = content.descV2!.map((currentDesc) {
switch (currentDesc.type) {
case 1:
final List<InlineSpan> spanChildren = <InlineSpan>[];
final RegExp urlRegExp = RegExp(
'${Constants.urlRegex.pattern}|av\\d+|bv[a-z\\d]{10}',
caseSensitive: false,
);
(currentDesc.rawText as String).splitMapJoin(
urlRegExp,
onMatch: (Match match) {
String matchStr = match[0]!;
if (RegExp(r'^av\d+$', caseSensitive: false).hasMatch(matchStr)) {
try {
int aid = int.parse(matchStr.substring(2));
IdUtils.av2bv(aid);
spanChildren.add(
TextSpan(
text: matchStr,
style: TextStyle(color: theme.colorScheme.primary),
recognizer: TapGestureRecognizer()
..onTap = () => PiliScheme.videoPush(aid, null),
),
);
} catch (e) {
spanChildren.add(TextSpan(text: matchStr));
}
} else if (RegExp(r'^bv[a-z\d]{10}$', caseSensitive: false)
.hasMatch(matchStr)) {
try {
IdUtils.bv2av(matchStr);
spanChildren.add(
TextSpan(
text: matchStr,
style: TextStyle(color: theme.colorScheme.primary),
recognizer: TapGestureRecognizer()
..onTap = () => PiliScheme.videoPush(null, matchStr),
),
);
} catch (e) {
spanChildren.add(TextSpan(text: matchStr));
}
} else {
if (matchStr.toLowerCase().startsWith('http')) {
spanChildren.add(
TextSpan(
text: matchStr,
@@ -696,6 +667,35 @@ class _VideoInfoState extends State<VideoInfo> {
},
),
);
} else if (matchStr.startsWith('av')) {
try {
int aid = int.parse(matchStr.substring(2));
IdUtils.av2bv(aid);
spanChildren.add(
TextSpan(
text: matchStr,
style: TextStyle(color: theme.colorScheme.primary),
recognizer: TapGestureRecognizer()
..onTap = () => PiliScheme.videoPush(aid, null),
),
);
} catch (e) {
spanChildren.add(TextSpan(text: matchStr));
}
} else {
try {
IdUtils.bv2av(matchStr);
spanChildren.add(
TextSpan(
text: matchStr,
style: TextStyle(color: theme.colorScheme.primary),
recognizer: TapGestureRecognizer()
..onTap = () => PiliScheme.videoPush(null, matchStr),
),
);
} catch (e) {
spanChildren.add(TextSpan(text: matchStr));
}
}
return '';
},
@@ -716,7 +716,7 @@ class _VideoInfoState extends State<VideoInfo> {
default:
return const TextSpan();
}
});
}).toList();
return TextSpan(children: spanChildren);
}

View File

@@ -631,13 +631,12 @@ class ReplyItemGrpc extends StatelessWidget {
.hasMatch(matchStr)) {
UrlUtils.matchUrlPush(matchStr, '');
} else {
RegExpMatch? firstMatch = RegExp(
RegExpMatch? match = RegExp(
r'^cv(\d+)$|/read/cv(\d+)|note-app/view\?cvid=(\d+)',
caseSensitive: false,
).firstMatch(matchStr);
String? cvid = firstMatch?.group(1) ??
firstMatch?.group(2) ??
firstMatch?.group(3);
String? cvid =
match?.group(1) ?? match?.group(2) ?? match?.group(3);
if (cvid != null) {
Get.toNamed(
'/articlePage',

View File

@@ -171,9 +171,7 @@ class _WebviewPageState extends State<WebviewPage> {
callback: (args) async {
WebUri? uri = await controller.getUrl();
if (uri != null) {
String? oid = RegExp(r'oid=(\d+)')
.firstMatch(uri.toString())
?.group(1);
String? oid = uri.queryParameters['oid'];
if (oid != null) {
PiliScheme.videoPush(int.parse(oid), null);
}

View File

@@ -289,12 +289,9 @@ class ChatItem extends StatelessWidget {
for (var i in content['sub_cards'])
GestureDetector(
onTap: () async {
RegExp bvRegex =
RegExp(r'BV[0-9A-Za-z]{10}', caseSensitive: false);
Iterable<Match> matches = bvRegex.allMatches(i['jump_url']);
if (matches.isNotEmpty) {
Match match = matches.first;
String bvid = match.group(0)!;
String? bvid =
IdUtils.bvRegex.firstMatch(i['jump_url'])?.group(0);
if (bvid != null) {
try {
SmartDialog.showLoading();
final int? cid = await SearchHttp.ab2c(bvid: bvid);
@@ -612,15 +609,16 @@ class ChatItem extends StatelessWidget {
content['content'].splitMapJoin(
RegExp(r"\[[^\[\]]+\]"),
onMatch: (Match match) {
final String emojiKey = match[0]!;
final size = emojiMap[emojiKey]!['size'];
if (emojiMap.containsKey(emojiKey)) {
final emojiKey = match[0]!;
final emoji = emojiMap[emojiKey];
if (emoji != null) {
final size = emoji['size'];
children.add(
WidgetSpan(
child: NetworkImgLayer(
width: size,
height: size,
src: emojiMap[emojiKey]!['url'],
src: emoji['url'],
type: ImageType.emote,
),
),

View File

@@ -17,6 +17,7 @@ class PiliScheme {
static late AppLinks appLinks;
static StreamSubscription? listener;
static final uriDigitRegExp = RegExp(r'/(\d+)');
static final _prefixRegex = RegExp(r'^\S+://');
static void init() {
// Register our protocol only on Windows platform
@@ -40,7 +41,7 @@ class PiliScheme {
try {
if (url.startsWith('//')) {
url = 'https:$url';
} else if (!RegExp(r'^\S+://').hasMatch(url)) {
} else if (!_prefixRegex.hasMatch(url)) {
url = 'https://$url';
}
return routePush(
@@ -160,9 +161,7 @@ class PiliScheme {
// to video
// bilibili://video/12345678?page=0&h5awaken=random
String? aid = uriDigitRegExp.firstMatch(path)?.group(1);
String? bvid = RegExp(r'/(BV[a-z\d]{10})', caseSensitive: false)
.firstMatch(path)
?.group(1);
String? bvid = IdUtils.bvRegex.firstMatch(path)?.group(0);
if (aid != null || bvid != null) {
if (queryParameters['cid'] != null) {
bvid ??= IdUtils.av2bv(int.parse(aid!));
@@ -349,7 +348,7 @@ class PiliScheme {
// bilibili://following/detail/832703053858603029 (dynId)
// bilibili://following/detail/12345678?comment_root_id=654321\u0026comment_on=1
String? cvid = RegExp(r'^/detail/cv(\d+)', caseSensitive: false)
.firstMatch(path)
.matchAsPrefix(path)
?.group(1);
if (cvid != null) {
PageUtils.toDupNamed(
@@ -473,12 +472,8 @@ class PiliScheme {
parameters: parameters,
);
default:
String? aid = RegExp(r'^av(\d+)', caseSensitive: false)
.firstMatch(path)
?.group(1);
String? bvid = RegExp(r'^BV[a-z\d]{10}', caseSensitive: false)
.firstMatch(path)
?.group(0);
String? aid = IdUtils.avRegexExact.matchAsPrefix(path)?.group(1);
String? bvid = IdUtils.bvRegexExact.matchAsPrefix(path)?.group(0);
if (aid != null || bvid != null) {
videoPush(
aid != null ? int.parse(aid) : null,
@@ -621,9 +616,7 @@ class PiliScheme {
.firstMatch(path)
?.group(1);
String? bvid = uri.queryParameters['bvid'] ??
RegExp(r'/(BV[a-z\d]{10})', caseSensitive: false)
.firstMatch(path)
?.group(1);
IdUtils.bvRegex.firstMatch(path)?.group(0);
if (bvid != null) {
if (mediaId != null) {
final int? cid = await SearchHttp.ab2c(bvid: bvid);
@@ -651,15 +644,11 @@ class PiliScheme {
case 'bangumi':
// www.bilibili.com/bangumi/play/ep{eid}?start_progress={offset}&thumb_up_dm_id={dmid}
if (kDebugMode) debugPrint('番剧');
String? id = RegExp(r'(ss|ep)\d+').firstMatch(path)?.group(0);
if (id != null) {
bool isSeason = id.startsWith('ss');
id = id.substring(2);
PageUtils.viewPgc(
seasonId: isSeason ? id : null,
epId: isSeason ? null : id,
progress: uri.queryParameters['start_progress'],
);
bool hasMatch = PageUtils.viewPgcFromUri(
path,
progress: uri.queryParameters['start_progress'],
);
if (hasMatch) {
return true;
}
launchURL();

View File

@@ -15,8 +15,12 @@ class IdUtils {
'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf';
static final invData = {for (var (i, c) in data.codeUnits.indexed) c: i};
static final bvRegex = RegExp(r'bv(1[0-9A-Za-z]{9})', caseSensitive: false);
static final bvRegex = RegExp(r'bv[0-9a-zA-Z]{10}', caseSensitive: false);
static final bvRegexExact =
RegExp(r'^bv[0-9a-zA-Z]{10}$', caseSensitive: false);
static final avRegex = RegExp(r'av(\d+)', caseSensitive: false);
static final avRegexExact = RegExp(r'^av(\d+)$', caseSensitive: false);
static final digitOnlyRegExp = RegExp(r'^\d+$');
static void swap<T>(List<T> list, int idx1, int idx2) {
final idx1Value = list[idx1];
@@ -58,12 +62,12 @@ class IdUtils {
if (input == null || input.isEmpty) {
return result;
}
String? bvid = bvRegex.firstMatch(input)?.group(1);
String? bvid = bvRegex.firstMatch(input)?.group(0);
late String? aid = avRegex.firstMatch(input)?.group(1);
if (bvid != null) {
result['BV'] = 'BV$bvid';
result['BV'] = bvid;
} else if (aid != null) {
result['AV'] = int.parse(aid);
}

View File

@@ -232,14 +232,13 @@ class ImageUtil {
}
}
static final regExp =
static final _thumbRegex =
RegExp(r'(@(\d+[a-z]_?)*)(\..*)?$', caseSensitive: false);
static String thumbnailUrl(String? src, [int? quality]) {
if (src != null && quality != 100) {
bool hasMatch = false;
src = src.splitMapJoin(
regExp,
_thumbRegex,
onMatch: (Match match) {
hasMatch = true;
String suffix = match.group(3) ?? '.webp';

View File

@@ -102,9 +102,7 @@ class PageUtils {
autofocus: true,
onChanged: (value) => duration = value,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'\d+')),
],
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: const InputDecoration(suffixText: 'min'),
),
actions: [
@@ -414,24 +412,13 @@ class PageUtils {
/// 专栏文章查看
case 'DYNAMIC_TYPE_ARTICLE':
String? url = item.modules.moduleDynamic?.major?.opus?.jumpUrl;
if (url != null) {
if (url.contains('opus') || url.contains('read')) {
RegExp digitRegExp = RegExp(r'\d+');
Iterable<Match> matches = digitRegExp.allMatches(url);
String number = matches.first.group(0)!;
toDupNamed(
'/articlePage',
parameters: {
'id': number,
'type': url.split('//').last.split('/')[1],
},
);
} else {
handleWebview('https:$url');
}
}
toDupNamed(
'/articlePage',
parameters: {
'id': item.idStr,
'type': 'opus',
},
);
break;
case 'DYNAMIC_TYPE_PGC':
if (kDebugMode) debugPrint('番剧');
@@ -675,14 +662,16 @@ class PageUtils {
}
}
static bool viewPgcFromUri(String uri) {
String? id = RegExp(r'(ep|ss)\d+').firstMatch(uri)?.group(0);
if (id != null) {
bool isSeason = id.startsWith('ss');
id = id.substring(2);
static final _pgcRegex = RegExp(r'(ep|ss)(\d+)');
static bool viewPgcFromUri(String uri, {String? progress}) {
RegExpMatch? match = _pgcRegex.firstMatch(uri);
if (match != null) {
bool isSeason = match.group(1) == 'ss';
String id = match.group(2)!;
viewPgc(
seasonId: isSeason ? id : null,
epId: isSeason ? null : id,
progress: progress,
);
return true;
}

View File

@@ -53,8 +53,8 @@ class Utils {
return absolutePaths.join(':');
}
static final numericRegex = RegExp(r'^[\d\.]+$');
static bool isStringNumeric(String str) {
RegExp numericRegex = RegExp(r'^[\d\.]+$');
return numericRegex.hasMatch(str);
}