diff --git a/lib/common/widgets/html_render.dart b/lib/common/widgets/html_render.dart index bf58d78c..b2aa75ff 100644 --- a/lib/common/widgets/html_render.dart +++ b/lib/common/widgets/html_render.dart @@ -1,7 +1,9 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; -import 'package:get/get.dart'; -import 'network_img_layer.dart'; +import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart'; +import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart'; +import 'package:pilipala/utils/highlight.dart'; // ignore: must_be_immutable class HtmlRender extends StatelessWidget { @@ -22,6 +24,20 @@ class HtmlRender extends StatelessWidget { data: htmlContent, onLinkTap: (String? url, Map buildContext, attributes) {}, extensions: [ + TagExtension( + tagsToExtend: {'pre'}, + builder: (ExtensionContext extensionContext) { + final Map attributes = extensionContext.attributes; + final String lang = attributes['data-lang'] as String; + final String code = attributes['codecontent'] as String; + List selectedLanguages = [lang.split('@').first]; + TextSpan? result = highlightExistingText(code, selectedLanguages); + if (result == null) { + return const Center(child: Text('代码块渲染失败')); + } + return SelectableText.rich(result); + }, + ), TagExtension( tagsToExtend: {'img'}, builder: (ExtensionContext extensionContext) { @@ -44,20 +60,52 @@ class HtmlRender extends StatelessWidget { if (isMall) { return const SizedBox(); } - // bool inTable = - // extensionContext.element!.previousElementSibling == null || - // extensionContext.element!.nextElementSibling == null; - // imgUrl = Utils().imageUrl(imgUrl!); - // return Image.network( - // imgUrl, - // width: isEmote ? 22 : null, - // height: isEmote ? 22 : null, - // ); - return NetworkImgLayer( - width: isEmote ? 22 : Get.size.width - 24, - height: isEmote ? 22 : 200, - src: imgUrl, + return InkWell( + onTap: () { + Navigator.of(context).push( + HeroDialogRoute( + builder: (BuildContext context) => + InteractiveviewerGallery( + sources: imgList ?? [imgUrl], + initIndex: imgList?.indexOf(imgUrl) ?? 0, + itemBuilder: ( + BuildContext context, + int index, + bool isFocus, + bool enablePageView, + ) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (enablePageView) { + Navigator.of(context).pop(); + } + }, + child: Center( + child: Hero( + tag: imgList?[index] ?? imgUrl, + child: CachedNetworkImage( + fadeInDuration: + const Duration(milliseconds: 0), + imageUrl: imgList?[index] ?? imgUrl, + fit: BoxFit.contain, + ), + ), + ), + ); + }, + onPageChanged: (int pageIndex) {}, + ), + ), + ); + }, + child: CachedNetworkImage(imageUrl: imgUrl), ); + // return NetworkImgLayer( + // width: isEmote ? 22 : Get.size.width - 24, + // height: isEmote ? 22 : 200, + // src: imgUrl, + // ); } catch (err) { return const SizedBox(); } @@ -66,7 +114,7 @@ class HtmlRender extends StatelessWidget { ], style: { 'html': Style( - fontSize: FontSize.medium, + fontSize: FontSize.large, lineHeight: LineHeight.percent(140), ), 'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero), @@ -78,7 +126,7 @@ class HtmlRender extends StatelessWidget { margin: Margins.only(bottom: 10), ), 'span': Style( - fontSize: FontSize.medium, + fontSize: FontSize.large, height: Height(1.65), ), 'div': Style(height: Height.auto()), diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 7a9ef39c..378c9f75 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -60,17 +60,13 @@ class VideoCardV extends StatelessWidget { // 动态 case 'picture': try { - String dynamicType = 'picture'; String uri = videoItem.uri; - String id = ''; if (videoItem.uri.startsWith('bilibili://article/')) { // https://www.bilibili.com/read/cv27063554 - dynamicType = 'read'; RegExp regex = RegExp(r'\d+'); Match match = regex.firstMatch(videoItem.uri)!; String matchedNumber = match.group(0)!; videoItem.param = int.parse(matchedNumber); - id = 'cv${videoItem.param}'; } if (uri.startsWith('http')) { String path = Uri.parse(uri).path; @@ -88,11 +84,10 @@ class VideoCardV extends StatelessWidget { return; } } - Get.toNamed('/htmlRender', parameters: { - 'url': uri, + Get.toNamed('/read', parameters: { 'title': videoItem.title, - 'id': id, - 'dynamicType': dynamicType + 'id': videoItem.param, + 'articleType': 'read' }); } catch (err) { SmartDialog.showToast(err.toString()); diff --git a/lib/http/api.dart b/lib/http/api.dart index 70488fa3..df0b9c85 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -555,6 +555,10 @@ class Api { static const String messageSystemAPi = '${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify'; + /// 系统通知 个人 + static const String userMessageSystemAPi = + '${HttpString.messageBaseUrl}/x/sys-msg/query_user_notify'; + /// 系统通知标记已读 static const String systemMarkRead = '${HttpString.messageBaseUrl}/x/sys-msg/update_cursor'; @@ -575,4 +579,17 @@ class Api { /// 我的关注 - 正在直播 static const String getFollowingLive = '${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following'; + + /// 稍后再看&收藏夹视频列表 + static const String mediaList = '/x/v2/medialist/resource/list'; + + /// 用户专栏 + static const String opusList = '/x/polymer/web-dynamic/v1/opus/feed/space'; + + /// + static const String getViewInfo = '/x/article/viewinfo'; + + /// 直播间记录 + static const String liveRoomEntry = + '${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction'; } diff --git a/lib/http/live.dart b/lib/http/live.dart index f5fd2a43..1405e9ea 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -142,4 +142,15 @@ class LiveHttp { }; } } + + // 直播历史记录 + static Future liveRoomEntry({required int roomId}) async { + await Request().post(Api.liveRoomEntry, queryParameters: { + 'room_id': roomId, + 'platform': 'pc', + 'csrf_token': await Request.getCsrf(), + 'csrf': await Request.getCsrf(), + 'visit_id': '', + }); + } } diff --git a/lib/http/member.dart b/lib/http/member.dart index e87aa42e..459d6747 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:hive/hive.dart'; +import 'package:html/parser.dart'; +import 'package:pilipala/models/member/article.dart'; import 'package:pilipala/models/member/like.dart'; import '../common/constants.dart'; import '../models/dynamics/result.dart'; @@ -556,4 +559,60 @@ class MemberHttp { }; } } + + static Future getWWebid({required int mid}) async { + var res = await Request().get('https://space.bilibili.com/$mid/article'); + String? headContent = parse(res.data).head?.outerHtml; + final regex = RegExp( + r''); + if (headContent != null) { + final match = regex.firstMatch(headContent); + if (match != null && match.groupCount >= 1) { + final content = match.group(1); + String decodedString = Uri.decodeComponent(content!); + Map map = jsonDecode(decodedString); + return {'status': true, 'data': map['access_id']}; + } else { + return {'status': false, 'data': '请检查登录状态'}; + } + } + return {'status': false, 'data': '请检查登录状态'}; + } + + // 获取用户专栏 + static Future getMemberArticle({ + required int mid, + required int pn, + required String wWebid, + String? offset, + }) async { + Map params = await WbiSign().makSign({ + 'host_mid': mid, + 'page': pn, + 'offset': offset, + 'web_location': 333.999, + 'w_webid': wWebid, + }); + var res = await Request().get(Api.opusList, data: { + 'host_mid': mid, + 'page': pn, + 'offset': offset, + 'web_location': 333.999, + 'w_webid': wWebid, + 'w_rid': params['w_rid'], + 'wts': params['wts'], + }); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': MemberArticleDataModel.fromJson(res.data['data']) + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'] ?? '请求异常', + }; + } + } } diff --git a/lib/http/msg.dart b/lib/http/msg.dart index 2de9cd49..869b5a28 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -330,4 +330,27 @@ class MsgHttp { }; } } + + static Future messageSystemAccount() async { + var res = await Request().get(Api.userMessageSystemAPi, data: { + 'csrf': await Request.getCsrf(), + 'page_size': 20, + 'build': 0, + 'mobi_app': 'web', + }); + if (res.data['code'] == 0) { + try { + return { + 'status': true, + 'data': res.data['data']['system_notify_list'] + .map((e) => MessageSystemModel.fromJson(e)) + .toList(), + }; + } catch (err) { + return {'status': false, 'date': [], 'msg': err.toString()}; + } + } else { + return {'status': false, 'date': [], 'msg': res.data['message']}; + } + } } diff --git a/lib/http/read.dart b/lib/http/read.dart new file mode 100644 index 00000000..68e72e59 --- /dev/null +++ b/lib/http/read.dart @@ -0,0 +1,116 @@ +import 'dart:convert'; +import 'package:html/parser.dart'; +import 'package:pilipala/models/read/opus.dart'; +import 'package:pilipala/models/read/read.dart'; +import 'package:pilipala/utils/wbi_sign.dart'; +import 'index.dart'; + +class ReadHttp { + static List extractScriptContents(String htmlContent) { + RegExp scriptRegExp = RegExp(r'