diff --git a/lib/common/widgets/video_card_h_grpc.dart b/lib/common/widgets/video_card_h_grpc.dart index 94221b03..cc6c5c94 100644 --- a/lib/common/widgets/video_card_h_grpc.dart +++ b/lib/common/widgets/video_card_h_grpc.dart @@ -58,7 +58,7 @@ class VideoCardHGrpc extends StatelessWidget { return; } try { - PiliScheme.routePush(Uri.parse(videoItem.smallCoverV5.base.uri)); + PiliScheme.routePushFromUrl(videoItem.smallCoverV5.base.uri); } catch (err) { SmartDialog.showToast(err.toString()); } diff --git a/lib/pages/dynamics/widgets/rich_node_panel.dart b/lib/pages/dynamics/widgets/rich_node_panel.dart index d568d770..c99b4da8 100644 --- a/lib/pages/dynamics/widgets/rich_node_panel.dart +++ b/lib/pages/dynamics/widgets/rich_node_panel.dart @@ -104,13 +104,12 @@ InlineSpan? richNode(item, context) { return; } if (url.startsWith('//')) { - url = url.replaceFirst('//', 'https://'); - PiliScheme.routePush(Uri.parse(url)); + PiliScheme.routePushFromUrl('https:$url'); return; } - Utils.handleWebview(url.startsWith('//') - ? "https://${url.split('//').last}" - : url); + Utils.handleWebview( + url.startsWith('//') ? "https://$url" : url, + ); }, child: Text( i.text ?? '', diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index 82e4532f..9eb6813e 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -3,7 +3,6 @@ import 'package:PiliPlus/common/widgets/video_progress_indicator.dart'; import 'package:PiliPlus/models/user/history.dart'; import 'package:PiliPlus/pages/common/multi_select_controller.dart'; import 'package:PiliPlus/pages/fav_search/controller.dart'; -import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -53,8 +52,15 @@ class HistoryItem extends StatelessWidget { // 'pageTitle': videoItem.title // }, // ); - PiliScheme.routePush(Uri.parse( - "https://www.bilibili.com/read/cv${videoItem.history.oid}")); + Utils.toDupNamed( + '/htmlRender', + parameters: { + 'url': 'https://www.bilibili.com/read/cv${videoItem.history.oid}', + 'title': '', + 'id': 'cv${videoItem.history.oid}', + 'dynamicType': 'read' + }, + ); } else if (videoItem.history.business == 'live') { if (videoItem.liveStatus == 1) { // LiveItemModel liveItem = LiveItemModel.fromJson({ diff --git a/lib/pages/member/new/content/member_contribute/content/article/member_article.dart b/lib/pages/member/new/content/member_contribute/content/article/member_article.dart index 00cfca34..326e95b1 100644 --- a/lib/pages/member/new/content/member_contribute/content/article/member_article.dart +++ b/lib/pages/member/new/content/member_contribute/content/article/member_article.dart @@ -60,7 +60,7 @@ class _MemberArticleState extends State return ListTile( dense: true, onTap: () { - PiliScheme.routePush(Uri.parse(item.uri ?? '')); + PiliScheme.routePushFromUrl(item.uri ?? ''); }, leading: item.originImageUrls?.isNotEmpty == true ? Container( diff --git a/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart b/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart index 108562f0..913fe93c 100644 --- a/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart +++ b/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart @@ -142,7 +142,7 @@ class _MemberFavoriteState extends State }); } } else if (item1.type == 21) { - PiliScheme.routePush(Uri.parse(item1.link ?? '')); + PiliScheme.routePushFromUrl(item1.link ?? ''); } else if (item1.type == 11) { Get.toNamed( '/subDetail', diff --git a/lib/pages/member/new/content/member_home/member_home.dart b/lib/pages/member/new/content/member_home/member_home.dart index 8ef9242a..aab8d208 100644 --- a/lib/pages/member/new/content/member_home/member_home.dart +++ b/lib/pages/member/new/content/member_home/member_home.dart @@ -118,9 +118,9 @@ class _MemberHomeState extends State child: ListTile( dense: true, onTap: () { - PiliScheme.routePush(Uri.parse( - loadingState.response.article.item.first.uri ?? - '')); + PiliScheme.routePushFromUrl( + loadingState.response.article.item.first.uri ?? '', + ); }, leading: loadingState.response.article.item.first .originImageUrls?.isNotEmpty == diff --git a/lib/pages/msg_feed_top/at_me/view.dart b/lib/pages/msg_feed_top/at_me/view.dart index 213e8c39..d57b2ea3 100644 --- a/lib/pages/msg_feed_top/at_me/view.dart +++ b/lib/pages/msg_feed_top/at_me/view.dart @@ -73,7 +73,7 @@ class _AtMePageState extends State { String? nativeUri = _atMeController.msgFeedAtMeList[i].item?.nativeUri; if (nativeUri != null) { - PiliScheme.routePush(Uri.parse(nativeUri)); + PiliScheme.routePushFromUrl(nativeUri); } // SmartDialog.showToast("跳转至:$nativeUri(暂未实现)"); }, diff --git a/lib/pages/msg_feed_top/like_me/view.dart b/lib/pages/msg_feed_top/like_me/view.dart index a8247d20..5e0d5501 100644 --- a/lib/pages/msg_feed_top/like_me/view.dart +++ b/lib/pages/msg_feed_top/like_me/view.dart @@ -122,7 +122,7 @@ class LikeMeList extends StatelessWidget { onTap: () { String? nativeUri = msgFeedLikeMeList[i].item?.nativeUri; if (nativeUri != null) { - PiliScheme.routePush(Uri.parse(nativeUri)); + PiliScheme.routePushFromUrl(nativeUri); } // SmartDialog.showToast("跳转至:$nativeUri(暂未实现)"); }, diff --git a/lib/pages/msg_feed_top/reply_me/view.dart b/lib/pages/msg_feed_top/reply_me/view.dart index 5d12dd19..9733efb8 100644 --- a/lib/pages/msg_feed_top/reply_me/view.dart +++ b/lib/pages/msg_feed_top/reply_me/view.dart @@ -72,7 +72,7 @@ class _ReplyMePageState extends State { String? nativeUri = _replyMeController .msgFeedReplyMeList[i].item?.nativeUri; if (nativeUri != null) { - PiliScheme.routePush(Uri.parse(nativeUri)); + PiliScheme.routePushFromUrl(nativeUri); } // SmartDialog.showToast("跳转至:$nativeUri(暂未实现)"); }, diff --git a/lib/pages/msg_feed_top/sys_msg/view.dart b/lib/pages/msg_feed_top/sys_msg/view.dart index d7b10d83..8036409e 100644 --- a/lib/pages/msg_feed_top/sys_msg/view.dart +++ b/lib/pages/msg_feed_top/sys_msg/view.dart @@ -184,8 +184,7 @@ class _SysMsgPageState extends State { recognizer: TapGestureRecognizer() ..onTap = () { try { - Uri uri = Uri.parse(match[2]!.replaceAll('"', '')); - PiliScheme.routePush(uri); + PiliScheme.routePushFromUrl(match[2]!.replaceAll('"', '')); } catch (err) { SmartDialog.showToast(err.toString()); } @@ -209,8 +208,7 @@ class _SysMsgPageState extends State { recognizer: TapGestureRecognizer() ..onTap = () { try { - Uri uri = Uri.parse(match[3]!); - PiliScheme.routePush(uri); + PiliScheme.routePushFromUrl(match[3]!); } catch (err) { SmartDialog.showToast(err.toString()); } @@ -231,8 +229,7 @@ class _SysMsgPageState extends State { recognizer: TapGestureRecognizer() ..onTap = () { try { - Uri uri = Uri.parse(match[0]!); - PiliScheme.routePush(uri); + PiliScheme.routePushFromUrl(match[0]!); } catch (err) { SmartDialog.showToast(err.toString()); Utils.copyText(match[0] ?? ''); diff --git a/lib/pages/search_panel/controller.dart b/lib/pages/search_panel/controller.dart index a4f9c440..61b4a4d1 100644 --- a/lib/pages/search_panel/controller.dart +++ b/lib/pages/search_panel/controller.dart @@ -67,18 +67,22 @@ class SearchPanelController extends CommonController { void jump2Video() { if (RegExp(r'^av\d+$', caseSensitive: false).hasMatch(keyword)) { hasJump2Video = true; - PiliScheme.videoPush(int.parse(keyword.substring(2)), null, false); + PiliScheme.videoPush( + int.parse(keyword.substring(2)), + null, + showDialog: false, + ); } else if (RegExp(r'^bv[a-z\d]{10}$', caseSensitive: false) .hasMatch(keyword)) { hasJump2Video = true; - PiliScheme.videoPush(null, keyword, false); + PiliScheme.videoPush(null, keyword, showDialog: false); } } void onPushDetail(resultList) async { int? aid = int.tryParse(keyword); if (aid != null && resultList.first.aid == aid) { - PiliScheme.videoPush(aid, null, false); + PiliScheme.videoPush(aid, null, showDialog: false); } } diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index b17d9574..a3b088d5 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -19,7 +19,6 @@ import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/url_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; -import '../../../../../utils/app_scheme.dart'; import 'zan.dart'; import 'package:html/parser.dart' show parse; @@ -863,56 +862,12 @@ class ReplyItem extends StatelessWidget { }); return; } - final String redirectUrl = - (await UrlUtils.parseRedirectUrl(matchStr)) ?? - matchStr; - // if (redirectUrl == matchStr) { - // Clipboard.setData(ClipboardData(text: matchStr)); - // SmartDialog.showToast('地址可能有误'); - // return; - // } - Uri uri = Uri.parse(redirectUrl); - PiliScheme.routePush(uri); - // final String pathSegment = Uri.parse(redirectUrl).path; - // final String lastPathSegment = - // pathSegment.split('/').last; - // if (lastPathSegment.startsWith('BV')) { - // UrlUtils.matchUrlPush( - // lastPathSegment, - // title, - // redirectUrl, - // ); - // } else { - // Get.toNamed( - // '/webview', - // parameters: { - // 'url': redirectUrl, - // 'type': 'url', - // 'pageTitle': title - // }, - // ); - // } + Utils.handleWebview(matchStr); } } else { if (appUrlSchema.startsWith('bilibili://search')) { Get.toNamed('/searchResult', parameters: {'keyword': title}); - } else if (matchStr.startsWith('https://b23.tv')) { - final String redirectUrl = - (await UrlUtils.parseRedirectUrl(matchStr)) ?? - matchStr; - final String pathSegment = - Uri.parse(redirectUrl).path; - final String lastPathSegment = - pathSegment.split('/').last; - if (lastPathSegment.startsWith('BV')) { - UrlUtils.matchUrlPush( - lastPathSegment, - redirectUrl, - ); - } else { - Utils.handleWebview(redirectUrl); - } } else { Utils.handleWebview(matchStr); } @@ -949,25 +904,8 @@ class ReplyItem extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ), recognizer: TapGestureRecognizer() - ..onTap = () async { - if (matchStr.startsWith('https://b23.tv')) { - final String redirectUrl = - (await UrlUtils.parseRedirectUrl(matchStr)) ?? - matchStr; - final String pathSegment = Uri.parse(redirectUrl).path; - final String lastPathSegment = - pathSegment.split('/').last; - if (lastPathSegment.startsWith('BV')) { - UrlUtils.matchUrlPush( - lastPathSegment, - redirectUrl, - ); - } else { - PiliScheme.routePush(Uri.parse(matchStr)); - } - } else { - PiliScheme.routePush(Uri.parse(matchStr)); - } + ..onTap = () { + Utils.handleWebview(matchStr); }, ), ); diff --git a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart index 213f1757..b716f7ca 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart @@ -20,7 +20,6 @@ import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/url_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; -import '../../../../../utils/app_scheme.dart'; import 'package:html/parser.dart' show parse; class ReplyItemGrpc extends StatelessWidget { @@ -901,56 +900,12 @@ class ReplyItemGrpc extends StatelessWidget { }); return; } - final String redirectUrl = - (await UrlUtils.parseRedirectUrl(matchStr)) ?? - matchStr; - // if (redirectUrl == matchStr) { - // Clipboard.setData(ClipboardData(text: matchStr)); - // SmartDialog.showToast('地址可能有误'); - // return; - // } - Uri uri = Uri.parse(redirectUrl); - PiliScheme.routePush(uri); - // final String pathSegment = Uri.parse(redirectUrl).path; - // final String lastPathSegment = - // pathSegment.split('/').last; - // if (lastPathSegment.startsWith('BV')) { - // UrlUtils.matchUrlPush( - // lastPathSegment, - // title, - // redirectUrl, - // ); - // } else { - // Get.toNamed( - // '/webview', - // parameters: { - // 'url': redirectUrl, - // 'type': 'url', - // 'pageTitle': title - // }, - // ); - // } + Utils.handleWebview(matchStr); } } else { if (appUrlSchema.startsWith('bilibili://search')) { Get.toNamed('/searchResult', parameters: {'keyword': title}); - } else if (matchStr.startsWith('https://b23.tv')) { - final String redirectUrl = - (await UrlUtils.parseRedirectUrl(matchStr)) ?? - matchStr; - final String pathSegment = - Uri.parse(redirectUrl).path; - final String lastPathSegment = - pathSegment.split('/').last; - if (lastPathSegment.startsWith('BV')) { - UrlUtils.matchUrlPush( - lastPathSegment, - redirectUrl, - ); - } else { - Utils.handleWebview(redirectUrl); - } } else { Utils.handleWebview(matchStr); } @@ -987,25 +942,8 @@ class ReplyItemGrpc extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ), recognizer: TapGestureRecognizer() - ..onTap = () async { - if (matchStr.startsWith('https://b23.tv')) { - final String redirectUrl = - (await UrlUtils.parseRedirectUrl(matchStr)) ?? - matchStr; - final String pathSegment = Uri.parse(redirectUrl).path; - final String lastPathSegment = - pathSegment.split('/').last; - if (lastPathSegment.startsWith('BV')) { - UrlUtils.matchUrlPush( - lastPathSegment, - redirectUrl, - ); - } else { - PiliScheme.routePush(Uri.parse(matchStr)); - } - } else { - PiliScheme.routePush(Uri.parse(matchStr)); - } + ..onTap = () { + Utils.handleWebview(matchStr); }, ), ); diff --git a/lib/pages/webview/webview_page.dart b/lib/pages/webview/webview_page.dart index 1c895ffd..3ee50929 100644 --- a/lib/pages/webview/webview_page.dart +++ b/lib/pages/webview/webview_page.dart @@ -5,8 +5,6 @@ import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/cache_manage.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:flutter_inappwebview/flutter_inappwebview.dart'; @@ -237,53 +235,20 @@ class _WebviewPageNewState extends State { } : null, shouldOverrideUrlLoading: (controller, navigationAction) async { - final String? str = - navigationAction.request.url!.pathSegments.getOrNull(0); - if (str != null) { - final Map matchRes = IdUtils.matchAvorBv(input: str); - if (matchRes.isNotEmpty) { - Get.back(); - PiliScheme.videoPush(matchRes['AV'], matchRes['BV']); - return NavigationActionPolicy.CANCEL; - } - } - - var url = navigationAction.request.url!.toString(); - - if (RegExp( - r'^(https?://)?((www|m).)?(bilibili|b23).(com|tv)/video/BV[a-zA-Z\d]+') - .hasMatch(url)) { - try { - String? bvid = - RegExp(r'BV[a-zA-Z\d]+').firstMatch(url)?.group(0); - if (bvid != null) { - Get.back(); - PiliScheme.videoPush(null, bvid); - return NavigationActionPolicy.CANCEL; - } - } catch (_) {} - } else if (RegExp( - r'^(https?://)?((www|m).)?(bilibili|b23).(com|tv)/playlist') - .hasMatch(url)) { - try { - String? bvid = - RegExp(r'bvid=(BV[a-zA-Z\d]+)').firstMatch(url)?.group(1); - if (bvid != null) { - PiliScheme.videoPush(null, bvid); - return NavigationActionPolicy.CANCEL; - } - } catch (_) {} + late String url = navigationAction.request.url.toString(); + bool hasMatch = await PiliScheme.routePush( + navigationAction.request.url?.uriValue ?? Uri(), + selfHandle: true, + off: true, + ); + // debugPrint('webview: [$url], [$hasMatch]'); + if (hasMatch) { + _progressStream.add(1.0); + return NavigationActionPolicy.CANCEL; } else if (RegExp(r'^(?!(https?://))\S+://', caseSensitive: false) .hasMatch(url)) { - if (url.startsWith('bilibili://video/')) { - String? str = - navigationAction.request.url!.pathSegments.getOrNull(0); - Get.offAndToNamed( - '/searchResult', - parameters: {'keyword': str ?? ''}, - ); - } else { - var snackBar = SnackBar( + if (context.mounted) { + SnackBar snackBar = SnackBar( content: const Text('当前网页将要打开外部链接,是否打开'), showCloseIcon: true, action: SnackBarAction( diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index c2630251..9d028562 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -26,240 +26,527 @@ class PiliScheme { }); } - /// 路由跳转 - static void routePush(Uri value) async { - final String scheme = value.scheme; - final String host = value.host; - final String path = value.path; - - if (scheme == 'bilibili') { - debugPrint('$value'); - if (host == 'root') { - Navigator.popUntil( - Get.context!, (Route route) => route.isFirst); - } else if (host == 'space') { - final String mid = path.split('/').last; - Utils.toDupNamed( - '/member?mid=$mid', - arguments: {'face': null}, - ); - } else if (host == 'video') { - String pathQuery = path.split('/').last; - if (value.queryParameters['comment_root_id'] != null) { - Get.to( - () => Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: const Text('评论详情'), - actions: [ - IconButton( - tooltip: '前往原视频', - onPressed: () { - String? enterUri = value.toString().split('?').first; - routePush(Uri.parse(enterUri)); - }, - icon: const Icon(Icons.open_in_new), - ), - ], - ), - body: VideoReplyReplyPanel( - oid: int.tryParse(pathQuery), - rpid: int.tryParse(value.queryParameters['comment_root_id']!), - source: 'routePush', - replyType: ReplyType.video, - firstFloor: null, - ), - ), - ); - return; - } - final numericRegex = RegExp(r'^[0-9]+$'); - if (numericRegex.hasMatch(pathQuery)) { - pathQuery = 'AV$pathQuery'; - } - Map map = IdUtils.matchAvorBv(input: pathQuery); - if (map.isNotEmpty) { - videoPush(map['AV'], map['BV']); - } else { - SmartDialog.showToast('投稿匹配失败'); - } - } else if (host == 'live') { - final String roomId = path.split('/').last; - Utils.toDupNamed('/liveRoom?roomid=$roomId'); - } else if (host == 'bangumi') { - if (path.startsWith('/season')) { - final String seasonId = path.split('/').last; - bangumiPush(int.parse(seasonId), null); - } - } else if (host == 'opus') { - if (path.startsWith('/detail')) { - var opusId = path.split('/').last; - Utils.toDupNamed( - '/webview', - parameters: { - 'url': 'https://www.bilibili.com/opus/$opusId', - 'type': 'url', - 'pageTitle': '', - }, - ); - } - } else if (host == 'search') { - Utils.toDupNamed('/searchResult', parameters: {'keyword': ''}); - } else if (host == 'article') { - final String id = path.split('/').last.split('?').first; - Utils.toDupNamed( - '/htmlRender', - parameters: { - 'url': 'www.bilibili.com/read/cv$id', - 'title': '', - 'id': 'cv$id', - 'dynamicType': 'read' - }, - ); - } else if (host == 'comment' && path.startsWith("/detail/")) { - //bilibili://comment/detail/17/832703053858603029/238686570016/?subType=0&anchor=238686628816&showEnter=1&extraIntentId=0&scene=1&enterName=%E6%9F%A5%E7%9C%8B%E5%8A%A8%E6%80%81%E8%AF%A6%E6%83%85&enterUri=bilibili://following/detail/832703053858603029 - //fmt.Sprintf("bilibili://comment/detail/%d/%d/%d/?subType=%d&anchor=%d&showEnter=1&extraIntentId=%d", rp.Type, rp.Oid, rootID, subType, rp.RpID, extraIntentID) - debugPrint('${value.queryParameters}'); - List pathParts = path.split('/'); - int type = int.parse(pathParts[2]); - int oid = int.parse(pathParts[3]); - int rootId = int.parse(pathParts[4]); - // int subType = int.parse(value.queryParameters['subType'] ?? '0'); - // int rpID = int.parse(value.queryParameters['anchor'] ?? '0'); - // int extraIntentId = - // int.parse(value.queryParameters['extraIntentId'] ?? '0'); - Get.to( - () => Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: const Text('评论详情'), - actions: [ - IconButton( - tooltip: '前往', - onPressed: () { - String? enterUri = value.queryParameters['enterUri']; - if (enterUri != null) { - routePush(Uri.parse(enterUri)); - } - }, - icon: const Icon(Icons.open_in_new), - ), - ], - ), - body: VideoReplyReplyPanel( - oid: oid, - rpid: rootId, // rpID, - source: 'routePush', - replyType: ReplyType.values[type], - firstFloor: null, - ), - ), - ); - } else if (host == 'following' && path.startsWith("/detail/")) { - void getToOpusWeb() async { - String? id = RegExp(r'detail/(\d+)').firstMatch(path)?.group(1); - if (id != null) { - SmartDialog.showLoading(); - dynamic res = await DynamicsHttp.dynamicDetail(id: id); - SmartDialog.dismiss(); - if (res['status']) { - Get.toNamed('/dynamicDetail', arguments: { - 'item': res['data'], - 'floor': 1, - 'action': 'detail' - }); - } else { - SmartDialog.showToast(res['msg']); - } - } else { - var opusId = path.split('/').last; - Utils.toDupNamed( - '/webview', - parameters: { - 'url': 'https://m.bilibili.com/dynamic/$opusId', - 'type': 'url', - 'pageTitle': '', - }, - ); - } - } - - if (value.queryParameters['comment_root_id'] != null) { - Get.to( - () => Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: const Text('评论详情'), - actions: [ - IconButton( - tooltip: '前往', - onPressed: () { - getToOpusWeb(); - }, - icon: const Icon(Icons.open_in_new), - ), - ], - ), - body: VideoReplyReplyPanel( - oid: int.tryParse(path.split('/').last), - rpid: int.tryParse(value.queryParameters['comment_root_id']!), - source: 'routePush', - replyType: ReplyType.dynamics, - firstFloor: null), - ), - ); - } else { - getToOpusWeb(); - } - } else if (host == 'album') { - String? rid = - RegExp(r'album/(\d+)').firstMatch(value.toString())?.group(1); - if (rid != null) { - SmartDialog.showLoading(); - dynamic res = await DynamicsHttp.dynamicDetail(rid: rid, type: 2); - SmartDialog.dismiss(); - if (res['status']) { - Get.toNamed('/dynamicDetail', arguments: { - 'item': res['data'], - 'floor': 1, - 'action': 'detail' - }); - } else { - SmartDialog.showToast(res['msg']); - } - } - } else { - debugPrint('$value'); - SmartDialog.showToast('未知路径:$value,请截图反馈给开发者'); - //Utils.toDupNamed( - // '/webview', - // parameters: { - // 'url': , - // 'type': 'url', - // 'pageTitle': '' - // }, - // ); - } - } else if (['http', 'https'].contains(scheme)) { - fullPathPush(value); - } else if (path.toLowerCase().startsWith('av')) { - try { - videoPush(int.parse(path.substring(2)), null); - } catch (e) { - debugPrint(e.toString()); - } - } else if (path.toLowerCase().startsWith('bv')) { - try { - videoPush(null, path); - } catch (e) { - debugPrint(e.toString()); + static Future routePushFromUrl( + String url, { + bool selfHandle = false, + bool off = false, + }) async { + try { + if (url.startsWith('//')) { + url = 'https:$url'; + } else if (RegExp(r'^\S+://').hasMatch(url).not) { + url = 'https://$url'; } + return await routePush(Uri.parse(url), selfHandle: selfHandle, off: off); + } catch (_) { + return false; } } + /// 路由跳转 + static Future routePush( + Uri uri, { + bool selfHandle = false, + bool off = false, + }) async { + final String scheme = uri.scheme; + final String host = uri.host.toLowerCase(); + final String path = uri.path; + + void launchURL() { + if (selfHandle.not) { + Utils.launchURL(uri.toString()); + } + } + + switch (scheme) { + case 'bilibili': + switch (host) { + case 'root': + Navigator.popUntil( + Get.context!, + (Route route) => route.isFirst, + ); + return true; + case 'space': + // bilibili://space/12345678?frommodule=XX&h5awaken=random + String? mid = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (mid != null) { + Utils.toDupNamed('/member?mid=$mid', off: off); + return true; + } + launchURL(); + return false; + case 'video': + if (uri.queryParameters['comment_root_id'] != null) { + // to check + // to video reply + String? oid = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (oid != null) { + int? rpid = + int.tryParse(uri.queryParameters['comment_root_id']!); + Get.to( + () => Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: const Text('评论详情'), + actions: [ + IconButton( + tooltip: '前往原视频', + onPressed: () { + String? enterUri = + uri.toString().split('?').first; // to check + routePush(Uri.parse(enterUri)); + }, + icon: const Icon(Icons.open_in_new), + ), + ], + ), + body: VideoReplyReplyPanel( + oid: int.parse(oid), + rpid: rpid, + source: 'routePush', + replyType: ReplyType.video, + firstFloor: null, + ), + ), + ); + return true; + } + launchURL(); + return false; + } + + // to video + // bilibili://video/12345678?page=0&h5awaken=random + String? aid = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + String? bvid = RegExp(r'/(BV[a-z\d]{10})', caseSensitive: false) + .firstMatch(path) + ?.group(1); + if (aid != null || bvid != null) { + videoPush( + aid != null ? int.parse(aid) : null, + bvid, + off: off, + ); + return true; + } + launchURL(); + return false; + case 'live': + // bilibili://live/12345678?extra_jump_from=1&from=1&is_room_feed=1&h5awaken=random + String? roomId = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (roomId != null) { + Utils.toDupNamed('/liveRoom?roomid=$roomId', off: off); + return true; + } + launchURL(); + return false; + case 'bangumi': + // to check + if (path.startsWith('/season')) { + String? seasonId = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (seasonId != null) { + Utils.viewBangumi(seasonId: seasonId, epId: null); + return true; + } + } + launchURL(); + return false; + case 'opus': + // bilibili://opus/detail/12345678?h5awaken=random + if (path.startsWith('/detail')) { + bool hasMatch = await _onPushDynDetail(path, off); + if (hasMatch.not) { + launchURL(); + } + return hasMatch; + } + launchURL(); + return false; + case 'search': + Utils.toDupNamed( + '/searchResult', + parameters: {'keyword': ''}, + off: off, + ); + return true; + case 'article': + // bilibili://article/40679479?jump_opus=1&jump_opus_type=1&opus_type=article&h5awaken=random + String? id = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (id != null) { + Utils.toDupNamed( + '/htmlRender', + parameters: { + 'url': 'www.bilibili.com/read/cv$id', + 'title': '', + 'id': 'cv$id', + 'dynamicType': 'read' + }, + off: off, + ); + return true; + } + launchURL(); + return false; + case 'comment': + if (path.startsWith("/detail/")) { + // bilibili://comment/detail/17/832703053858603029/238686570016/?subType=0&anchor=238686628816&showEnter=1&extraIntentId=0&scene=1&enterName=%E6%9F%A5%E7%9C%8B%E5%8A%A8%E6%80%81%E8%AF%A6%E6%83%85&enterUri=bilibili://following/detail/832703053858603029 + List pathSegments = uri.pathSegments; + int type = int.parse(pathSegments[1]); + int oid = int.parse(pathSegments[2]); + int rootId = int.parse(pathSegments[3]); + // int subType = int.parse(value.queryParameters['subType'] ?? '0'); + // int rpID = int.parse(value.queryParameters['anchor'] ?? '0'); + // int extraIntentId = + // int.parse(value.queryParameters['extraIntentId'] ?? '0'); + Get.to( + () => Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: const Text('评论详情'), + actions: [ + IconButton( + tooltip: '前往', + onPressed: () { + String? enterUri = uri.queryParameters['enterUri']; + if (enterUri != null) { + routePush(Uri.parse(enterUri)); + } + }, + icon: const Icon(Icons.open_in_new), + ), + ], + ), + body: VideoReplyReplyPanel( + oid: oid, + rpid: rootId, + source: 'routePush', + replyType: ReplyType.values[type], + firstFloor: null, + ), + ), + ); + return true; + } + launchURL(); + return false; + case 'following': + if (path.startsWith("/detail/")) { + if (uri.queryParameters['comment_root_id'] != null) { + String? oid = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (oid != null) { + int? rpid = + int.tryParse(uri.queryParameters['comment_root_id']!); + Get.to( + () => Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: const Text('评论详情'), + actions: [ + IconButton( + tooltip: '前往', + onPressed: () async { + bool hasMatch = await _onPushDynDetail(path, off); + if (hasMatch.not) { + launchURL(); + } + }, + icon: const Icon(Icons.open_in_new), + ), + ], + ), + body: VideoReplyReplyPanel( + oid: int.tryParse(oid), + rpid: rpid, + source: 'routePush', + replyType: ReplyType.dynamics, + firstFloor: null), + ), + ); + } + return true; + } else { + bool hasMatch = await _onPushDynDetail(path, off); + if (hasMatch.not) { + launchURL(); + } + return hasMatch; + } + } + launchURL(); + return false; + case 'album': + String? rid = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (rid != null) { + SmartDialog.showLoading(); + dynamic res = await DynamicsHttp.dynamicDetail(rid: rid, type: 2); + SmartDialog.dismiss(); + if (res['status']) { + Utils.toDupNamed( + '/dynamicDetail', + arguments: { + 'item': res['data'], + 'floor': 1, + 'action': 'detail' + }, + off: off, + ); + } else { + SmartDialog.showToast(res['msg']); + } + return true; + } + launchURL(); + return false; + default: + if (selfHandle.not) { + debugPrint('$uri'); + SmartDialog.showToast('未知路径:$uri,请截图反馈给开发者'); + } + launchURL(); + return false; + } + case 'http' || 'https': + return await _fullPathPush(uri, selfHandle: selfHandle, off: off); + 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); + if (aid != null || bvid != null) { + videoPush( + aid != null ? int.parse(aid) : null, + bvid, + off: off, + ); + return true; + } + if (selfHandle.not) { + debugPrint('$uri'); + SmartDialog.showToast('未知路径:$uri,请截图反馈给开发者'); + } + launchURL(); + return false; + } + } + + static Future _fullPathPush( + Uri uri, { + bool selfHandle = false, + bool off = false, + }) async { + // https://m.bilibili.com/bangumi/play/ss39708 + // https | m.bilibili.com | /bangumi/play/ss39708 + + String host = uri.host.toLowerCase(); + + if (selfHandle && + host.contains('bilibili.com').not && + host.contains('b23.tv').not) { + return false; + } + + void launchURL() { + if (selfHandle.not) { + _toWebview(uri.toString(), off); + } + } + + // b23.tv + // bilibili.com + // m.bilibili.com + // www.bilibili.com + // space.bilibili.com + // live.bilibili.com + + // redirect + if (host.contains('b23.tv')) { + String? redirectUrl = await UrlUtils.parseRedirectUrl(uri.toString()); + if (redirectUrl != null) { + uri = Uri.parse(redirectUrl); + host = uri.host.toLowerCase(); + } + if (host.contains('bilibili.com').not) { + launchURL(); + return false; + } + } + + final String path = uri.path; + + if (host.contains('t.bilibili.com')) { + bool hasMatch = await _onPushDynDetail(path, off); + if (hasMatch.not) { + launchURL(); + } + return hasMatch; + } + + if (host.contains('live.bilibili.com')) { + String? roomId = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (roomId != null) { + Utils.toDupNamed('/liveRoom?roomid=$roomId', off: off); + return true; + } + launchURL(); + return false; + } + + if (host.contains('space.bilibili.com')) { + String? mid = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (mid != null) { + Utils.toDupNamed('/member?mid=$mid', off: off); + return true; + } + launchURL(); + return false; + } + + List pathSegments = uri.pathSegments; + if (pathSegments.isEmpty) { + launchURL(); + return false; + } + final String? area = pathSegments.first == 'mobile' + ? pathSegments.getOrNull(1) + : pathSegments.first; + switch (area) { + case 'opus': + bool hasMatch = await _onPushDynDetail(path, off); + if (hasMatch.not) { + launchURL(); + } + return hasMatch; + case 'playlist': + String? bvid = uri.queryParameters['bvid'] ?? + RegExp(r'/(BV[a-z\d]{10})', caseSensitive: false) + .firstMatch(path) + ?.group(1); + if (bvid != null) { + videoPush( + null, + bvid, + off: off, + ); + return true; + } + launchURL(); + return false; + case 'bangumi': + debugPrint('番剧'); + String? id = RegExp(r'(ss|ep)\d+').firstMatch(path)?.group(0); + if (id != null) { + bool isSeason = id.startsWith('ss'); + id = id.substring(2); + Utils.viewBangumi( + seasonId: isSeason ? id : null, + epId: isSeason ? null : id, + ); + return true; + } + launchURL(); + return false; + case 'video': + debugPrint('投稿'); + final Map map = IdUtils.matchAvorBv(input: path); + if (map.isNotEmpty) { + videoPush( + map['AV'], + map['BV'], + off: off, + ); + return true; + } + launchURL(); + return false; + case 'read': + debugPrint('专栏'); + String? id = + RegExp(r'cv(\d+)', caseSensitive: false).firstMatch(path)?.group(1); + if (id != null) { + Utils.toDupNamed( + '/htmlRender', + parameters: { + 'url': 'https://www.bilibili.com/read/cv$id', + 'title': '', + 'id': 'cv$id', + 'dynamicType': 'read' + }, + off: off, + ); + return true; + } + launchURL(); + return false; + case 'space': + debugPrint('个人空间'); + String? mid = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (mid != null) { + Utils.toDupNamed( + '/member?mid=$mid', + off: off, + ); + return true; + } + launchURL(); + return false; + default: + Map map = IdUtils.matchAvorBv(input: area?.split('?').first); + if (map.isNotEmpty) { + videoPush( + map['AV'], + map['BV'], + off: off, + ); + return true; + } + launchURL(); + return false; + } + } + + static Future _onPushDynDetail(path, off) async { + String? id = RegExp(r'/(\d+)').firstMatch(path)?.group(1); + if (id != null) { + SmartDialog.showLoading(); + dynamic res = await DynamicsHttp.dynamicDetail(id: id); + SmartDialog.dismiss(); + if (res['status']) { + Utils.toDupNamed( + '/dynamicDetail', + arguments: { + 'item': res['data'], + 'floor': 1, + 'action': 'detail', + }, + off: off, + ); + } else { + SmartDialog.showToast(res['msg']); + } + return true; + } + return false; + } + + static void _toWebview(String url, bool off) { + Utils.toDupNamed( + '/webview', + parameters: {'url': url}, + off: off, + ); + } + // 投稿跳转 - static Future videoPush(int? aid, String? bvid, - [bool showDialog = true]) async { + static Future videoPush( + int? aid, + String? bvid, { + bool showDialog = true, + bool off = false, + }) async { try { aid ??= IdUtils.bv2av(bvid!); bvid ??= IdUtils.av2bv(aid); @@ -272,187 +559,15 @@ class PiliScheme { } Utils.toDupNamed( '/video?bvid=$bvid&cid=$cid', - arguments: { + arguments: { 'pic': null, 'heroTag': Utils.makeHeroTag(aid), }, + off: off, ); } catch (e) { SmartDialog.dismiss(); SmartDialog.showToast('video获取失败: $e'); } } - - // 番剧跳转 - static Future bangumiPush(int? seasonId, int? epId) async { - debugPrint('seasonId: $seasonId, epId: $epId'); - // SmartDialog.showLoading(msg: '获取中...'); - try { - Utils.viewBangumi(seasonId: seasonId, epId: epId); - // var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId); - // if (result['status']) { - // var bangumiDetail = result['data']; - // EpisodeItem episode = result['data'].episodes.first; - // int? epId = result['data'].userStatus?.progress?.lastEpId; - // if (epId == null) { - // epId = episode.epId; - // } else { - // for (var item in result['data'].episodes) { - // if (item.epId == epId) { - // episode = item; - // break; - // } - // } - // } - // String bvid = episode.bvid!; - // int cid = episode.cid!; - // dynamic pic = episode.cover; - // final String heroTag = Utils.makeHeroTag(cid); - // SmartDialog.dismiss().then( - // (e) => Utils.toDupNamed( - // '/video?bvid=$bvid&cid=$cid&seasonId=${bangumiDetail.seasonId}&epId=$epId', - // arguments: { - // 'pic': pic, - // 'heroTag': heroTag, - // 'videoType': SearchType.media_bangumi, - // }, - // ), - // ); - // } else { - // SmartDialog.showToast(result['msg']); - // } - } catch (e) { - SmartDialog.showToast('番剧获取失败:$e'); - } - } - - static Future fullPathPush(Uri value) async { - // https://m.bilibili.com/bangumi/play/ss39708 - // https | m.bilibili.com | /bangumi/play/ss39708 - // final String scheme = value.scheme!; - final String host = value.host; - final String path = value.path; - Map query = value.queryParameters; - RegExp regExp = RegExp(r'^((www\.)|(m\.))?bilibili\.com$'); - if (regExp.hasMatch(host)) { - debugPrint('bilibili.com'); - } else if (host.contains('live')) { - int roomId = int.parse(path.split('/').last); - Utils.toDupNamed('/liveRoom?roomid=$roomId'); - return; - } else if (host.contains('space')) { - var mid = path.split('/').last; - Utils.toDupNamed('/member?mid=$mid', arguments: {'face': ''}); - return; - } else if (host == 'b23.tv') { - final String fullPath = 'https://$host$path'; - final String redirectUrl = - (await UrlUtils.parseRedirectUrl(fullPath)) ?? fullPath; - final String pathSegment = Uri.parse(redirectUrl).path; - final String lastPathSegment = pathSegment.split('/').last; - final RegExp avRegex = RegExp(r'^[aA][vV]\d+', caseSensitive: false); - if (avRegex.hasMatch(lastPathSegment)) { - final Map map = - IdUtils.matchAvorBv(input: lastPathSegment); - if (map.isNotEmpty) { - videoPush(map['AV'], map['BV']); - } else { - SmartDialog.showToast('投稿匹配失败'); - } - } else if (lastPathSegment.startsWith('ep')) { - handleEpisodePath(lastPathSegment, redirectUrl); - } else if (lastPathSegment.startsWith('ss')) { - handleSeasonPath(lastPathSegment, redirectUrl); - } else if (lastPathSegment.startsWith('BV')) { - UrlUtils.matchUrlPush( - lastPathSegment, - redirectUrl, - ); - } else { - Utils.handleWebview(redirectUrl); - } - return; - } - - List pathPart = path.split('/'); - if (pathPart.length < 3) { - Utils.handleWebview(value.toString()); - return; - } - final String area = pathPart[1] == 'mobile' ? pathPart[2] : pathPart[1]; - switch (area) { - case 'bangumi': - debugPrint('番剧'); - for (var pathSegment in pathPart) { - if (pathSegment.startsWith('ss')) { - bangumiPush(matchNum(pathSegment).first, null); - return; - } else if (pathSegment.startsWith('ep')) { - bangumiPush(null, matchNum(pathSegment).first); - return; - } - } - Utils.handleWebview(value.toString()); - break; - case 'video': - debugPrint('投稿'); - final Map map = IdUtils.matchAvorBv(input: path); - if (map.isNotEmpty) { - videoPush(map['AV'], map['BV']); - } else { - SmartDialog.showToast('投稿匹配失败'); - } - break; - case 'read': - debugPrint('专栏'); - late String id; - if (query['id'] != null) { - id = 'cv${matchNum(query['id']!).first}'; - } else { - id = 'cv${matchNum(path).firstOrNull}'; - } - Utils.toDupNamed('/htmlRender', parameters: { - 'url': value.toString(), - 'title': '', - 'id': id, - 'dynamicType': 'read' - }); - break; - case 'space': - debugPrint('个人空间'); - Utils.toDupNamed( - '/member?mid=${pathPart[1] == 'mobile' ? pathPart.getOrNull(3) : pathPart.getOrNull(2)}', - arguments: {'face': ''}); - break; - default: - Map map = IdUtils.matchAvorBv(input: area.split('?').first); - if (map.isNotEmpty) { - videoPush(map['AV'], map['BV']); - } else { - // SmartDialog.showToast('未知路径或匹配错误:$value,先采用浏览器打开'); - Utils.handleWebview(value.toString()); - } - } - } - - static List matchNum(String str) { - final RegExp regExp = RegExp(r'\d+'); - final Iterable matches = regExp.allMatches(str); - - return matches.map((Match match) => int.parse(match.group(0)!)).toList(); - } - - static void handleEpisodePath(String lastPathSegment, String redirectUrl) { - final String seasonId = extractIdFromPath(lastPathSegment); - bangumiPush(null, matchNum(seasonId).first); - } - - static void handleSeasonPath(String lastPathSegment, String redirectUrl) { - final String seasonId = extractIdFromPath(lastPathSegment); - bangumiPush(matchNum(seasonId).first, null); - } - - static String extractIdFromPath(String lastPathSegment) { - return lastPathSegment.split('/').last; - } } diff --git a/lib/utils/url_utils.dart b/lib/utils/url_utils.dart index b1b6cf6c..c792d99f 100644 --- a/lib/utils/url_utils.dart +++ b/lib/utils/url_utils.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -9,8 +10,10 @@ import 'utils.dart'; class UrlUtils { // 302重定向路由截取 - static Future parseRedirectUrl(String url, - [bool returnOri = false]) async { + static Future parseRedirectUrl( + String url, [ + bool returnOri = false, + ]) async { try { final response = await Request().get( url, @@ -23,9 +26,10 @@ class UrlUtils { ); if (response.statusCode == 302 || response.statusCode == 301) { String? redirectUrl = response.headers['location']?.first; + debugPrint('redirectUrl: $redirectUrl'); if (redirectUrl != null) { if (redirectUrl.startsWith('/')) { - return url; + return returnOri ? url : null; } if (redirectUrl.endsWith('/')) { redirectUrl = redirectUrl.substring(0, redirectUrl.length - 1); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index f578b33e..a81e152a 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -370,46 +370,13 @@ class Utils { ); } - static bool _handleInAppWebview(String url) { - if (RegExp( - r'^(https?://)?((www|m).)?(bilibili|b23).(com|tv)/video/BV[a-zA-Z\d]+') - .hasMatch(url)) { - try { - String? bvid = RegExp(r'BV[a-zA-Z\d]+').firstMatch(url)?.group(0); - if (bvid != null) { - PiliScheme.videoPush(null, bvid); - return true; - } - } catch (_) {} - } else if (RegExp( - r'^(https?://)?((www|m).)?(bilibili|b23).(com|tv)/playlist') - .hasMatch(url)) { - try { - String? bvid = - RegExp(r'bvid=(BV[a-zA-Z\d]+)').firstMatch(url)?.group(1); - if (bvid != null) { - PiliScheme.videoPush(null, bvid); - return true; - } - } catch (_) {} - } else if (RegExp(r'^(https?://)?((www|m).)?(bilibili|b23).(com|tv)') - .hasMatch(url)) { - toDupNamed( - '/webview', - parameters: {'url': url}, - ); - return true; - } - return false; - } - static void handleWebview( String url, { bool off = false, bool inApp = false, - }) { + }) async { if (inApp.not && GStorage.openInBrowser) { - if (_handleInAppWebview(url).not) { + if ((await PiliScheme.routePushFromUrl(url, selfHandle: true)).not) { launchURL(url); } } else { @@ -419,12 +386,7 @@ class Utils { parameters: {'url': url}, ); } else { - if (_handleInAppWebview(url).not) { - toDupNamed( - '/webview', - parameters: {'url': url}, - ); - } + PiliScheme.routePushFromUrl(url); } } } @@ -807,13 +769,23 @@ class Utils { String page, { dynamic arguments, Map? parameters, + bool off = false, }) { - Get.toNamed( - page, - arguments: arguments, - parameters: parameters, - preventDuplicates: false, - ); + if (off) { + Get.offNamed( + page, + arguments: arguments, + parameters: parameters, + preventDuplicates: false, + ); + } else { + Get.toNamed( + page, + arguments: arguments, + parameters: parameters, + preventDuplicates: false, + ); + } } static Future copyText(