mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-16 06:10:13 +08:00
feat: fav article
Closes #727 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -16,8 +16,7 @@ Widget htmlRender({
|
|||||||
Function(List<String>, int)? callback,
|
Function(List<String>, int)? callback,
|
||||||
}) {
|
}) {
|
||||||
debugPrint('htmlRender');
|
debugPrint('htmlRender');
|
||||||
return SelectionArea(
|
return Html(
|
||||||
child: Html(
|
|
||||||
data: htmlContent,
|
data: htmlContent,
|
||||||
onLinkTap: (String? url, Map<String, String> buildContext, attributes) {},
|
onLinkTap: (String? url, Map<String, String> buildContext, attributes) {},
|
||||||
extensions: [
|
extensions: [
|
||||||
@@ -124,5 +123,5 @@ Widget htmlRender({
|
|||||||
margin: Margins.zero,
|
margin: Margins.zero,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -773,4 +773,6 @@ class Api {
|
|||||||
|
|
||||||
static const String searchRecommend =
|
static const String searchRecommend =
|
||||||
'${HttpString.appBaseUrl}/x/v2/search/recommend';
|
'${HttpString.appBaseUrl}/x/v2/search/recommend';
|
||||||
|
|
||||||
|
static const String articleInfo = '/x/article/viewinfo';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
|
import 'package:PiliPlus/utils/wbi_sign.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
import '../models/dynamics/result.dart';
|
import '../models/dynamics/result.dart';
|
||||||
@@ -137,4 +138,23 @@ class DynamicsHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future articleInfo({
|
||||||
|
required dynamic cvId,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.articleInfo,
|
||||||
|
queryParameters: await WbiSign.makSign({
|
||||||
|
'id': cvId,
|
||||||
|
'mobi_app': 'pc',
|
||||||
|
'from': 'web',
|
||||||
|
'gaia_source': 'main_web',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,14 +72,18 @@ class HtmlHttp {
|
|||||||
// List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
|
// List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
|
||||||
|
|
||||||
dynamic mid;
|
dynamic mid;
|
||||||
|
Map? favorite;
|
||||||
try {
|
try {
|
||||||
final regex = RegExp(r'window\.__INITIAL_STATE__\s*=\s*(\{.*?\});');
|
final regex = RegExp(r'window\.__INITIAL_STATE__\s*=\s*(\{.*?\});');
|
||||||
final match = regex.firstMatch(response.data);
|
final match = regex.firstMatch(response.data);
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
final json = jsonDecode(match.group(1)!);
|
final json = jsonDecode(match.group(1)!);
|
||||||
mid = json['detail']['basic']['uid'];
|
mid = json['detail']['basic']['uid'];
|
||||||
|
favorite = json['detail']['modules'].last['module_stat']['favorite'];
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (e) {
|
||||||
|
debugPrint('req html: $e');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
@@ -90,6 +94,7 @@ class HtmlHttp {
|
|||||||
'content': (test ?? '') + opusContent,
|
'content': (test ?? '') + opusContent,
|
||||||
'commentType': int.parse(comment[1]),
|
'commentType': int.parse(comment[1]),
|
||||||
'commentId': int.parse(comment[2]),
|
'commentId': int.parse(comment[2]),
|
||||||
|
'favorite': favorite,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
debugPrint('err: $err');
|
debugPrint('err: $err');
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future addFavArticle({
|
static Future addFavArticle({
|
||||||
required int id,
|
required dynamic id,
|
||||||
}) async {
|
}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.addFavArticle,
|
Api.addFavArticle,
|
||||||
@@ -516,7 +516,7 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future delFavArticle({
|
static Future delFavArticle({
|
||||||
required int id,
|
required dynamic id,
|
||||||
}) async {
|
}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.delFavArticle,
|
Api.delFavArticle,
|
||||||
|
|||||||
@@ -792,9 +792,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
_dynamicDetailController.onLoadMore();
|
_dynamicDetailController.onLoadMore();
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
padding: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
bottom: MediaQuery.of(context).padding.bottom),
|
bottom: MediaQuery.of(context).padding.bottom),
|
||||||
height: MediaQuery.of(context).padding.bottom + 100,
|
height: 125,
|
||||||
child: Text(
|
child: Text(
|
||||||
_dynamicDetailController.isEnd.not
|
_dynamicDetailController.isEnd.not
|
||||||
? '加载中...'
|
? '加载中...'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||||
import 'package:PiliPlus/http/dynamics.dart';
|
import 'package:PiliPlus/http/dynamics.dart';
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/http/user.dart';
|
||||||
import 'package:PiliPlus/http/video.dart';
|
import 'package:PiliPlus/http/video.dart';
|
||||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||||
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
||||||
@@ -8,6 +9,7 @@ import 'package:PiliPlus/pages/mine/controller.dart';
|
|||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
import 'package:PiliPlus/utils/url_utils.dart';
|
import 'package:PiliPlus/utils/url_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:PiliPlus/http/html.dart';
|
import 'package:PiliPlus/http/html.dart';
|
||||||
import 'package:PiliPlus/http/reply.dart';
|
import 'package:PiliPlus/http/reply.dart';
|
||||||
@@ -24,7 +26,8 @@ class HtmlRenderController extends ReplyController<MainListReply> {
|
|||||||
|
|
||||||
Rx<DynamicItemModel> item = DynamicItemModel().obs;
|
Rx<DynamicItemModel> item = DynamicItemModel().obs;
|
||||||
|
|
||||||
RxBool loaded = false.obs;
|
final RxMap favStat = <dynamic, dynamic>{'status': false}.obs;
|
||||||
|
final RxBool loaded = false.obs;
|
||||||
|
|
||||||
late final horizontalPreview = GStorage.horizontalPreview;
|
late final horizontalPreview = GStorage.horizontalPreview;
|
||||||
late final showDynActionBar = GStorage.showDynActionBar;
|
late final showDynActionBar = GStorage.showDynActionBar;
|
||||||
@@ -40,13 +43,22 @@ class HtmlRenderController extends ReplyController<MainListReply> {
|
|||||||
type = dynamicType == 'picture' ? 11 : 12;
|
type = dynamicType == 'picture' ? 11 : 12;
|
||||||
|
|
||||||
if (showDynActionBar) {
|
if (showDynActionBar) {
|
||||||
if (RegExp(r'^cv', caseSensitive: false).hasMatch(id)) {
|
if (dynamicType == 'read') {
|
||||||
UrlUtils.parseRedirectUrl('https://www.bilibili.com/read/$id/')
|
UrlUtils.parseRedirectUrl('https://www.bilibili.com/read/$id/')
|
||||||
.then((url) {
|
.then((url) {
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
_queryDyn(url.split('/').last);
|
_queryDyn(url.split('/').last);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
DynamicsHttp.articleInfo(cvId: id.substring(2)).then((res) {
|
||||||
|
if (res['status']) {
|
||||||
|
favStat.value = {
|
||||||
|
'status': true,
|
||||||
|
'isFav': res['data']?['favorite'] ?? false,
|
||||||
|
'favNum': res['data']?['stats']?['favorite'] ?? 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
_queryDyn(id);
|
_queryDyn(id);
|
||||||
}
|
}
|
||||||
@@ -72,6 +84,13 @@ class HtmlRenderController extends ReplyController<MainListReply> {
|
|||||||
res = await HtmlHttp.reqHtml(id, dynamicType);
|
res = await HtmlHttp.reqHtml(id, dynamicType);
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
type = res['commentType'];
|
type = res['commentType'];
|
||||||
|
if (res['favorite'] != null) {
|
||||||
|
favStat.value = {
|
||||||
|
'status': true,
|
||||||
|
'isFav': res['favorite']['status'] ?? false,
|
||||||
|
'favNum': res['favorite']['count'] ?? 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res = await HtmlHttp.reqReadHtml(id, dynamicType);
|
res = await HtmlHttp.reqReadHtml(id, dynamicType);
|
||||||
@@ -80,7 +99,7 @@ class HtmlRenderController extends ReplyController<MainListReply> {
|
|||||||
response = res;
|
response = res;
|
||||||
mid = res['mid'];
|
mid = res['mid'];
|
||||||
oid.value = res['commentId'];
|
oid.value = res['commentId'];
|
||||||
if (Accounts.main.isLogin && !MineController.anonymity.value) {
|
if (isLogin && !MineController.anonymity.value) {
|
||||||
VideoHttp.historyReport(aid: oid.value, type: 5);
|
VideoHttp.historyReport(aid: oid.value, type: 5);
|
||||||
}
|
}
|
||||||
queryData();
|
queryData();
|
||||||
@@ -107,4 +126,24 @@ class HtmlRenderController extends ReplyController<MainListReply> {
|
|||||||
antiGoodsReply: antiGoodsReply,
|
antiGoodsReply: antiGoodsReply,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future onFav() async {
|
||||||
|
bool isFav = favStat['isFav'] == true;
|
||||||
|
final res = dynamicType == 'read'
|
||||||
|
? isFav
|
||||||
|
? await UserHttp.delFavArticle(id: id.substring(2))
|
||||||
|
: await UserHttp.addFavArticle(id: id.substring(2))
|
||||||
|
: await UserHttp.communityAction(opusId: id, action: isFav ? 4 : 3);
|
||||||
|
if (res['status']) {
|
||||||
|
favStat['isFav'] = !isFav;
|
||||||
|
if (isFav) {
|
||||||
|
favStat['favNum'] -= 1;
|
||||||
|
} else {
|
||||||
|
favStat['favNum'] += 1;
|
||||||
|
}
|
||||||
|
SmartDialog.showToast('${isFav ? '取消' : ''}收藏成功');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -636,6 +636,46 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
|||||||
label: const Text('分享'),
|
label: const Text('分享'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_htmlRenderCtr
|
||||||
|
.favStat['status'])
|
||||||
|
Expanded(
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
_htmlRenderCtr.onFav();
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
_htmlRenderCtr.favStat[
|
||||||
|
'isFav'] ==
|
||||||
|
true
|
||||||
|
? FontAwesomeIcons
|
||||||
|
.solidStar
|
||||||
|
: FontAwesomeIcons.star,
|
||||||
|
size: 16,
|
||||||
|
color:
|
||||||
|
_htmlRenderCtr.favStat[
|
||||||
|
'isFav'] ==
|
||||||
|
true
|
||||||
|
? Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
semanticLabel: "收藏",
|
||||||
|
),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets
|
||||||
|
.fromLTRB(15, 0, 15, 0),
|
||||||
|
foregroundColor:
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
),
|
||||||
|
label: Text(_htmlRenderCtr
|
||||||
|
.favStat['favNum']
|
||||||
|
.toString()),
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
@@ -781,9 +821,9 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
|||||||
_htmlRenderCtr.onLoadMore();
|
_htmlRenderCtr.onLoadMore();
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
padding: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
bottom: MediaQuery.of(context).padding.bottom),
|
bottom: MediaQuery.of(context).padding.bottom),
|
||||||
height: MediaQuery.of(context).padding.bottom + 100,
|
height: 125,
|
||||||
child: Text(
|
child: Text(
|
||||||
_htmlRenderCtr.isEnd.not
|
_htmlRenderCtr.isEnd.not
|
||||||
? '加载中...'
|
? '加载中...'
|
||||||
|
|||||||
@@ -407,9 +407,9 @@ class _VideoReplyReplyPanelState
|
|||||||
_videoReplyReplyController.onLoadMore();
|
_videoReplyReplyController.onLoadMore();
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
padding: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
bottom: MediaQuery.of(context).padding.bottom),
|
bottom: MediaQuery.of(context).padding.bottom),
|
||||||
height: MediaQuery.of(context).padding.bottom + 100,
|
height: 125,
|
||||||
child: Text(
|
child: Text(
|
||||||
_videoReplyReplyController.isEnd.not
|
_videoReplyReplyController.isEnd.not
|
||||||
? '加载中...'
|
? '加载中...'
|
||||||
|
|||||||
Reference in New Issue
Block a user