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/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart
index d2772478..0b715a89 100644
--- a/lib/common/widgets/network_img_layer.dart
+++ b/lib/common/widgets/network_img_layer.dart
@@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/extension.dart';
-import 'package:pilipala/utils/global_data.dart';
+import 'package:pilipala/utils/global_data_cache.dart';
import '../../utils/storage.dart';
import '../constants.dart';
@@ -33,7 +33,7 @@ class NetworkImgLayer extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final int defaultImgQuality = GlobalData().imgQuality;
+ final int defaultImgQuality = GlobalDataCache().imgQuality;
if (src == '' || src == null) {
return placeholder(context);
}
diff --git a/lib/common/widgets/stat/danmu.dart b/lib/common/widgets/stat/danmu.dart
index 511839a0..9ea05301 100644
--- a/lib/common/widgets/stat/danmu.dart
+++ b/lib/common/widgets/stat/danmu.dart
@@ -6,7 +6,7 @@ class StatDanMu extends StatelessWidget {
final dynamic danmu;
final String? size;
- const StatDanMu({Key? key, this.theme, this.danmu, this.size})
+ const StatDanMu({Key? key, this.theme = 'gray', this.danmu, this.size})
: super(key: key);
@override
@@ -17,21 +17,46 @@ class StatDanMu extends StatelessWidget {
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
};
Color color = colorObject[theme]!;
+ return StatIconText(
+ icon: Icons.subtitles_outlined,
+ text: Utils.numFormat(danmu!),
+ color: color,
+ size: size,
+ );
+ }
+}
+
+class StatIconText extends StatelessWidget {
+ final IconData icon;
+ final String text;
+ final Color color;
+ final String? size;
+
+ const StatIconText({
+ Key? key,
+ required this.icon,
+ required this.text,
+ required this.color,
+ this.size,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
return Row(
children: [
Icon(
- Icons.subtitles_outlined,
+ icon,
size: 14,
color: color,
),
const SizedBox(width: 2),
Text(
- Utils.numFormat(danmu!),
+ text,
style: TextStyle(
fontSize: size == 'medium' ? 12 : 11,
color: color,
),
- )
+ ),
],
);
}
diff --git a/lib/common/widgets/stat/view.dart b/lib/common/widgets/stat/view.dart
index 5359c979..85bec816 100644
--- a/lib/common/widgets/stat/view.dart
+++ b/lib/common/widgets/stat/view.dart
@@ -6,8 +6,12 @@ class StatView extends StatelessWidget {
final dynamic view;
final String? size;
- const StatView({Key? key, this.theme, this.view, this.size})
- : super(key: key);
+ const StatView({
+ Key? key,
+ this.theme = 'gray',
+ this.view,
+ this.size,
+ }) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -17,16 +21,41 @@ class StatView extends StatelessWidget {
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
};
Color color = colorObject[theme]!;
+ return StatIconText(
+ icon: Icons.play_circle_outlined,
+ text: Utils.numFormat(view!),
+ color: color,
+ size: size,
+ );
+ }
+}
+
+class StatIconText extends StatelessWidget {
+ final IconData icon;
+ final String text;
+ final Color color;
+ final String? size;
+
+ const StatIconText({
+ Key? key,
+ required this.icon,
+ required this.text,
+ required this.color,
+ this.size,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
return Row(
children: [
Icon(
- Icons.play_circle_outlined,
+ icon,
size: 13,
color: color,
),
const SizedBox(width: 2),
Text(
- Utils.numFormat(view!),
+ text,
style: TextStyle(
fontSize: size == 'medium' ? 12 : 11,
color: color,
diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart
index 1265477f..78c4ba87 100644
--- a/lib/common/widgets/video_card_h.dart
+++ b/lib/common/widgets/video_card_h.dart
@@ -266,17 +266,11 @@ class VideoContent extends StatelessWidget {
Row(
children: [
if (showView) ...[
- StatView(
- theme: 'gray',
- view: videoItem.stat.view as int,
- ),
+ StatView(view: videoItem.stat.view as int),
const SizedBox(width: 8),
],
if (showDanmaku)
- StatDanMu(
- theme: 'gray',
- danmu: videoItem.stat.danmaku as int,
- ),
+ StatDanMu(danmu: videoItem.stat.danmaku as int),
const Spacer(),
if (source == 'normal')
SizedBox(
diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart
index d8e1bb2c..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());
@@ -287,9 +282,9 @@ class VideoStat extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: [
- StatView(theme: 'gray', view: videoItem.stat.view),
+ StatView(view: videoItem.stat.view),
const SizedBox(width: 8),
- StatDanMu(theme: 'gray', danmu: videoItem.stat.danmu),
+ StatDanMu(danmu: videoItem.stat.danmu),
if (videoItem is RecVideoItemModel) ...[
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
RichText(
diff --git a/lib/http/api.dart b/lib/http/api.dart
index 93226946..df0b9c85 100644
--- a/lib/http/api.dart
+++ b/lib/http/api.dart
@@ -555,14 +555,41 @@ 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';
+ /// 编辑收藏夹
+ static const String editFavFolder = '/x/v3/fav/folder/edit';
+
+ /// 新建收藏夹
+ static const String addFavFolder = '/x/v3/fav/folder/add';
+
/// 直播间弹幕信息
static const String getDanmuInfo =
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getDanmuInfo';
/// 直播间发送弹幕
static const String sendLiveMsg = '${HttpString.liveBaseUrl}/msg/send';
+
+ /// 我的关注 - 正在直播
+ 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/fav.dart b/lib/http/fav.dart
new file mode 100644
index 00000000..6f49d68a
--- /dev/null
+++ b/lib/http/fav.dart
@@ -0,0 +1,67 @@
+import 'index.dart';
+
+class FavHttp {
+ /// 编辑收藏夹
+ static Future editFolder({
+ required String title,
+ required String intro,
+ required String mediaId,
+ String? cover,
+ int? privacy,
+ }) async {
+ var res = await Request().post(
+ Api.editFavFolder,
+ queryParameters: {
+ 'title': title,
+ 'intro': intro,
+ 'media_id': mediaId,
+ 'cover': cover ?? '',
+ 'privacy': privacy ?? 0,
+ 'csrf': await Request.getCsrf(),
+ },
+ );
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': res.data['data'],
+ };
+ } else {
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': res.data['message'],
+ };
+ }
+ }
+
+ /// 新建收藏夹
+ static Future addFolder({
+ required String title,
+ required String intro,
+ String? cover,
+ int? privacy,
+ }) async {
+ var res = await Request().post(
+ Api.addFavFolder,
+ queryParameters: {
+ 'title': title,
+ 'intro': intro,
+ 'cover': cover ?? '',
+ 'privacy': privacy ?? 0,
+ 'csrf': await Request.getCsrf(),
+ },
+ );
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': res.data['data'],
+ };
+ } else {
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': res.data['message'],
+ };
+ }
+ }
+}
diff --git a/lib/http/live.dart b/lib/http/live.dart
index f6fc4ea4..1405e9ea 100644
--- a/lib/http/live.dart
+++ b/lib/http/live.dart
@@ -1,3 +1,5 @@
+import 'package:pilipala/models/live/follow.dart';
+
import '../models/live/item.dart';
import '../models/live/room_info.dart';
import '../models/live/room_info_h5.dart';
@@ -117,4 +119,38 @@ class LiveHttp {
};
}
}
+
+ // 我的关注 正在直播
+ static Future liveFollowing({int? pn, int? ps}) async {
+ var res = await Request().get(Api.getFollowingLive, data: {
+ 'page': pn,
+ 'page_size': ps,
+ 'platform': 'web',
+ 'ignoreRecord': 1,
+ 'hit_ab': true,
+ });
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': LiveFollowingModel.fromJson(res.data['data'])
+ };
+ } else {
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': res.data['message'],
+ };
+ }
+ }
+
+ // 直播历史记录
+ 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'