mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
refa: later view page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -223,7 +223,7 @@ class Api {
|
||||
'${HttpString.tUrl}/dynamic_like/v1/dynamic_like/thumb';
|
||||
|
||||
// 获取稍后再看
|
||||
static const String seeYouLater = '/x/v2/history/toview';
|
||||
static const String seeYouLater = '/x/v2/history/toview/web';
|
||||
|
||||
// 获取历史记录
|
||||
static const String historyList = '/x/web-interface/history/cursor';
|
||||
@@ -381,7 +381,7 @@ class Api {
|
||||
static const String toViewLater = '/x/v2/history/toview/add';
|
||||
|
||||
// 移除已观看
|
||||
static const String toViewDel = '/x/v2/history/toview/del';
|
||||
static const String toViewDel = '/x/v2/history/toview/v2/dels';
|
||||
|
||||
// 清空稍后再看
|
||||
static const String toViewClear = '/x/v2/history/toview/clear';
|
||||
|
||||
@@ -97,7 +97,6 @@ class DanmakuHttp {
|
||||
if (response.statusCode != 200) {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '弹幕发送失败,状态码:${response.statusCode}',
|
||||
};
|
||||
}
|
||||
@@ -109,7 +108,6 @@ class DanmakuHttp {
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': "${response.data['code']}: ${response.data['message']}",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ class DanmakuFilterHttp {
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,11 +53,7 @@ class DynamicsHttp {
|
||||
'data': FollowUpModel.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,11 +76,7 @@ class DynamicsHttp {
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,7 @@ class FollowHttp {
|
||||
'data': FollowDataModel.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,11 +82,7 @@ class LiveHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': RoomInfoModel.fromJson(res.data['data'])};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +96,7 @@ class LiveHttp {
|
||||
'data': RoomInfoH5Model.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,11 +107,7 @@ class LiveHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']['room']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,11 +118,7 @@ class LiveHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': LiveDanmakuInfo.fromJson(res.data)};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -300,11 +300,7 @@ class MemberHttp {
|
||||
'data': MemberInfoModel.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,11 +309,7 @@ class MemberHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,11 +324,7 @@ class MemberHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,11 +464,7 @@ class MemberHttp {
|
||||
.toList()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,13 +507,9 @@ class MemberHttp {
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': [], 'msg': '操作成功'};
|
||||
return {'status': true, 'msg': '操作成功'};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,11 +535,7 @@ class MemberHttp {
|
||||
.toList()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,11 +550,7 @@ class MemberHttp {
|
||||
.toList()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,11 +567,7 @@ class MemberHttp {
|
||||
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,11 +655,7 @@ class MemberHttp {
|
||||
debugPrint(err.toString());
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,11 +666,7 @@ class MemberHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,11 +110,7 @@ class MsgHttp {
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,21 +430,13 @@ class MsgHttp {
|
||||
try {
|
||||
return {
|
||||
'status': true,
|
||||
'data': SessionDataModel.fromJson(res.data['data']),
|
||||
'data': SessionDataModel.fromJson(res.data['data']).sessionList,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': err.toString(),
|
||||
};
|
||||
return {'status': false, 'msg': err.toString()};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,11 +458,7 @@ class MsgHttp {
|
||||
debugPrint('err🔟: $err');
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,11 +484,7 @@ class MsgHttp {
|
||||
debugPrint(err.toString());
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +512,6 @@ class MsgHttp {
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': "message: ${res.data['message']},"
|
||||
" msg: ${res.data['msg']},"
|
||||
" code: ${res.data['code']}",
|
||||
@@ -581,11 +560,7 @@ class MsgHttp {
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'] ?? res.data['msg'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -351,11 +351,7 @@ class ReplyHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,11 +375,7 @@ class ReplyHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,7 @@ class SearchHttp {
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '请求错误',
|
||||
};
|
||||
return {'status': false, 'msg': '请求错误'};
|
||||
}
|
||||
|
||||
// 获取搜索建议
|
||||
@@ -50,19 +46,17 @@ class SearchHttp {
|
||||
'status': true,
|
||||
'data': resultMap['result'] is Map
|
||||
? SearchSuggestModel.fromJson(resultMap['result'])
|
||||
: [],
|
||||
: null,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '请求错误 🙅',
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '请求错误 🙅',
|
||||
};
|
||||
}
|
||||
@@ -210,11 +204,7 @@ class SearchHttp {
|
||||
'data': BangumiInfoModel.fromJson(res.data['result']),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/video/later.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:PiliPlus/utils/wbi_sign.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import '../common/constants.dart';
|
||||
@@ -43,7 +44,7 @@ class UserHttp {
|
||||
UserStat data = UserStat.fromJson(res.data['data']);
|
||||
return {'status': true, 'data': data};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,16 +198,29 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 稍后再看
|
||||
static Future<LoadingState<Map>> seeYouLater() async {
|
||||
var res = await Request().get(Api.seeYouLater);
|
||||
static Future<LoadingState<Map>> seeYouLater({
|
||||
required int page,
|
||||
int viewed = 0,
|
||||
String keyword = '',
|
||||
bool asc = false,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.seeYouLater,
|
||||
queryParameters: await WbiSign.makSign({
|
||||
'pn': page,
|
||||
'ps': 20,
|
||||
'viewed': viewed,
|
||||
'key': keyword,
|
||||
'asc': asc,
|
||||
'need_split': true,
|
||||
'web_location': 333.881,
|
||||
}),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
if (res.data['data']['count'] == 0) {
|
||||
return LoadingState.success({
|
||||
'list': [],
|
||||
'count': 0,
|
||||
});
|
||||
return LoadingState.success({'count': 0});
|
||||
}
|
||||
List<HotVideoItemModel> list = [];
|
||||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||
if (res.data['data']?['list'] != null) {
|
||||
for (var i in res.data['data']['list']) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
@@ -260,7 +274,7 @@ class UserHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,11 +310,10 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 移除已观看
|
||||
static Future toViewDel({List<int?>? aids}) async {
|
||||
static Future toViewDel({required List<int?> aids}) async {
|
||||
final Map<String, dynamic> params = {
|
||||
'jsonp': 'jsonp',
|
||||
'csrf': await Request.getCsrf(),
|
||||
if (aids != null) 'aid': aids.join(',') else 'viewed': true
|
||||
'resources': aids.join(',')
|
||||
};
|
||||
dynamic res = await Request().post(
|
||||
Api.toViewDel,
|
||||
@@ -333,12 +346,12 @@ class UserHttp {
|
||||
}
|
||||
}
|
||||
|
||||
// 清空稍后再看
|
||||
static Future toViewClear() async {
|
||||
// 清空稍后再看 // clean_type: null->all, 1->invalid, 2->viewed
|
||||
static Future toViewClear([int? cleanType]) async {
|
||||
var res = await Request().post(
|
||||
Api.toViewClear,
|
||||
queryParameters: {
|
||||
'jsonp': 'jsonp',
|
||||
if (cleanType != null) 'clean_type': cleanType,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
@@ -631,7 +644,7 @@ class UserHttp {
|
||||
static List<String> extractScriptContents(String htmlContent) {
|
||||
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
|
||||
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);
|
||||
List<String> scriptContents = [];
|
||||
List<String> scriptContents = <String>[];
|
||||
for (Match match in matches) {
|
||||
String scriptContent = match.group(1)!;
|
||||
scriptContents.add(scriptContent);
|
||||
@@ -675,7 +688,7 @@ class UserHttp {
|
||||
.map<MediaVideoItemModel>(
|
||||
(e) => MediaVideoItemModel.fromJson(e))
|
||||
.toList()
|
||||
: []
|
||||
: <MediaVideoItemModel>[]
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
|
||||
@@ -50,7 +50,7 @@ class VideoHttp {
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemModel> list = [];
|
||||
List<RecVideoItemModel> list = <RecVideoItemModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['item']) {
|
||||
//过滤掉live与ad,以及拉黑用户
|
||||
@@ -119,7 +119,7 @@ class VideoHttp {
|
||||
}),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemAppModel> list = [];
|
||||
List<RecVideoItemAppModel> list = <RecVideoItemAppModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['items']) {
|
||||
// 屏蔽推广和拉黑用户
|
||||
@@ -153,7 +153,7 @@ class VideoHttp {
|
||||
queryParameters: {'pn': pn, 'ps': ps},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMids.contains(i['owner']['mid']) &&
|
||||
@@ -177,7 +177,7 @@ class VideoHttp {
|
||||
static Future<LoadingState> hotVideoListGrpc({required int idx}) async {
|
||||
dynamic res = await GrpcRepo.popular(idx);
|
||||
if (res['status']) {
|
||||
List<card.Card> list = [];
|
||||
List<card.Card> list = <card.Card>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (card.Card item in res['data']) {
|
||||
if (!blackMids.contains(item.smallCoverV5.up.id.toInt())) {
|
||||
@@ -259,13 +259,12 @@ class VideoHttp {
|
||||
}
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'code': res.data['code'],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
return {'status': false, 'data': [], 'msg': err};
|
||||
return {'status': false, 'msg': err};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +381,7 @@ class VideoHttp {
|
||||
// if (res.data['code'] == 0) {
|
||||
// return {'status': true, 'data': res.data['data']};
|
||||
// } else {
|
||||
// return {'status': false, 'data': []};
|
||||
// return {'status': false, 'msg': res.data['message']};
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -393,7 +392,7 @@ class VideoHttp {
|
||||
// if (res.data['code'] == 0) {
|
||||
// return {'status': true, 'data': res.data['data']};
|
||||
// } else {
|
||||
// return {'status': false, 'data': []};
|
||||
// return {'status': false, 'msg': res.data['message']};
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -417,7 +416,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +427,7 @@ class VideoHttp {
|
||||
// if (res.data['code'] == 0) {
|
||||
// return {'status': true, 'data': res.data['data']};
|
||||
// } else {
|
||||
// return {'status': false, 'data': []};
|
||||
// return {'status': false, 'msg': res.data['message']};
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -452,7 +451,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,7 +479,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +501,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -719,7 +718,7 @@ class VideoHttp {
|
||||
FavFolderData data = FavFolderData.fromJson(res.data['data']);
|
||||
return {'status': true, 'data': data};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,7 +740,7 @@ class VideoHttp {
|
||||
bool? syncToDynamic,
|
||||
}) async {
|
||||
if (message == '') {
|
||||
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
||||
return {'status': false, 'msg': '请输入评论内容'};
|
||||
}
|
||||
Map<String, dynamic> data = {
|
||||
'type': type.index,
|
||||
@@ -761,7 +760,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -973,7 +972,7 @@ class VideoHttp {
|
||||
'data': AiConclusionModel.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1053,7 +1052,7 @@ class VideoHttp {
|
||||
var rankApi = "${Api.getRankApi}?rid=$rid&type=all";
|
||||
var res = await Request().get(rankApi);
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMids.contains(i['owner']['mid']) &&
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:PiliPlus/http/user.dart';
|
||||
import 'package:PiliPlus/models/user/fav_detail.dart';
|
||||
import 'package:PiliPlus/models/user/fav_folder.dart';
|
||||
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -142,8 +143,10 @@ class FavDetailController
|
||||
|
||||
void toViewPlayAll() {
|
||||
if (loadingState.value is Success) {
|
||||
List<FavDetailItemData> list = (loadingState.value as Success).response;
|
||||
for (FavDetailItemData element in list) {
|
||||
List<FavDetailItemData>? list = (loadingState.value as Success).response;
|
||||
if (list.isNullOrEmpty) return;
|
||||
|
||||
for (FavDetailItemData element in list!) {
|
||||
if (element.cid == null) {
|
||||
continue;
|
||||
} else {
|
||||
|
||||
@@ -135,13 +135,16 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
visualDensity:
|
||||
VisualDensity(horizontal: -2, vertical: -2),
|
||||
),
|
||||
onPressed: () =>
|
||||
Utils.onCopyOrMove<FavDetailItemData>(
|
||||
onPressed: () {
|
||||
Utils.onCopyOrMove<FavDetailData,
|
||||
FavDetailItemData>(
|
||||
context: context,
|
||||
isCopy: true,
|
||||
ctr: _favDetailController,
|
||||
mediaId: _favDetailController.mediaId,
|
||||
),
|
||||
mid: _favDetailController.mid,
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'复制',
|
||||
style: TextStyle(
|
||||
@@ -156,13 +159,16 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
visualDensity:
|
||||
VisualDensity(horizontal: -2, vertical: -2),
|
||||
),
|
||||
onPressed: () =>
|
||||
Utils.onCopyOrMove<FavDetailItemData>(
|
||||
onPressed: () {
|
||||
Utils.onCopyOrMove<FavDetailData,
|
||||
FavDetailItemData>(
|
||||
context: context,
|
||||
isCopy: false,
|
||||
ctr: _favDetailController,
|
||||
mediaId: _favDetailController.mediaId,
|
||||
),
|
||||
mid: _favDetailController.mid,
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'移动',
|
||||
style: TextStyle(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/member.dart';
|
||||
import 'package:PiliPlus/models/model_hot_video_item.dart';
|
||||
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
||||
import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType;
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -47,14 +48,18 @@ class FavSearchController extends CommonListController {
|
||||
|
||||
@override
|
||||
List? getDataList(response) {
|
||||
if (searchType == SearchType.later) {
|
||||
return response['list'];
|
||||
}
|
||||
return response.list;
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(bool isRefresh, Success response) {
|
||||
isEnd = searchType == SearchType.fav
|
||||
? response.response.hasMore == false
|
||||
: response.response.list == null || response.response.list.isEmpty;
|
||||
if (searchType == SearchType.fav && response.response.hasMore == false) {
|
||||
isEnd = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -92,6 +97,10 @@ class FavSearchController extends CommonListController {
|
||||
pn: currentPage,
|
||||
keyword: controller.value.text,
|
||||
),
|
||||
SearchType.later => UserHttp.seeYouLater(
|
||||
page: currentPage,
|
||||
keyword: controller.value.text,
|
||||
),
|
||||
};
|
||||
|
||||
@override
|
||||
@@ -117,4 +126,14 @@ class FavSearchController extends CommonListController {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
Future toViewDel(BuildContext context, int index, aid) async {
|
||||
var res = await UserHttp.toViewDel(aids: [aid]);
|
||||
if (res['status']) {
|
||||
List<HotVideoItemModel> list = (loadingState.value as Success).response;
|
||||
list.removeAt(index);
|
||||
loadingState.refresh();
|
||||
}
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_card_h.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/pages/follow/widgets/follow_item.dart';
|
||||
import 'package:PiliPlus/pages/history/widgets/item.dart';
|
||||
@@ -11,7 +13,7 @@ import 'package:PiliPlus/pages/fav_detail/widget/fav_video_card.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
enum SearchType { fav, follow, history }
|
||||
enum SearchType { fav, follow, history, later }
|
||||
|
||||
class FavSearchPage extends StatefulWidget {
|
||||
const FavSearchPage({super.key});
|
||||
@@ -51,7 +53,12 @@ class _FavSearchPageState extends State<FavSearchPage> {
|
||||
suffixIcon: IconButton(
|
||||
tooltip: '清空',
|
||||
icon: const Icon(Icons.clear, size: 22),
|
||||
onPressed: _favSearchCtr.onClear,
|
||||
onPressed: () {
|
||||
_favSearchCtr
|
||||
..loadingState.value = LoadingState.loading()
|
||||
..onClear()
|
||||
..searchFocusNode.requestFocus();
|
||||
},
|
||||
),
|
||||
),
|
||||
onSubmitted: (value) => _favSearchCtr.onReload(),
|
||||
@@ -149,6 +156,143 @@ class _FavSearchPageState extends State<FavSearchPage> {
|
||||
);
|
||||
}),
|
||||
),
|
||||
SearchType.later => CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: _favSearchCtr.scrollController,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 80,
|
||||
),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: 2,
|
||||
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.2,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index == loadingState.response!.length - 1) {
|
||||
_favSearchCtr.onLoadMore();
|
||||
}
|
||||
var videoItem = loadingState.response![index];
|
||||
return Stack(
|
||||
children: [
|
||||
VideoCardH(
|
||||
videoItem: videoItem,
|
||||
source: 'later',
|
||||
onViewLater: (cid) {
|
||||
Utils.toViewPage(
|
||||
'bvid=${videoItem.bvid}&cid=$cid',
|
||||
arguments: {
|
||||
'videoItem': videoItem,
|
||||
'oid': videoItem.aid,
|
||||
'heroTag':
|
||||
Utils.makeHeroTag(videoItem.bvid),
|
||||
'sourceType': 'watchLater',
|
||||
'count': Get.arguments['count'],
|
||||
'favTitle': '稍后再看',
|
||||
'mediaId': _favSearchCtr.mid,
|
||||
'desc': false,
|
||||
'isContinuePlaying': index != 0,
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
top: 5,
|
||||
left: 12,
|
||||
bottom: 5,
|
||||
child: IgnorePointer(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) =>
|
||||
AnimatedOpacity(
|
||||
opacity:
|
||||
videoItem.checked == true ? 1 : 0,
|
||||
duration:
|
||||
const Duration(milliseconds: 200),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: constraints.maxHeight,
|
||||
width: constraints.maxHeight *
|
||||
StyleString.aspectRatio,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(10),
|
||||
color:
|
||||
Colors.black.withOpacity(0.6),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: AnimatedScale(
|
||||
scale: videoItem.checked == true
|
||||
? 1
|
||||
: 0,
|
||||
duration: const Duration(
|
||||
milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
child: IconButton(
|
||||
tooltip: '取消选择',
|
||||
style: ButtonStyle(
|
||||
padding:
|
||||
WidgetStateProperty.all(
|
||||
EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
WidgetStateProperty
|
||||
.resolveWith(
|
||||
(states) {
|
||||
return Theme.of(context)
|
||||
.colorScheme
|
||||
.surface
|
||||
.withOpacity(0.8);
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: null,
|
||||
icon: Icon(
|
||||
Icons.done_all_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: 0,
|
||||
child: iconButton(
|
||||
tooltip: '移除',
|
||||
context: context,
|
||||
onPressed: () {
|
||||
_favSearchCtr.toViewDel(
|
||||
context,
|
||||
index,
|
||||
videoItem.aid,
|
||||
);
|
||||
},
|
||||
icon: Icons.clear,
|
||||
iconColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
bgColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
childCount: loadingState.response!.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
}
|
||||
: errorWidget(
|
||||
callback: _favSearchCtr.onReload,
|
||||
|
||||
@@ -31,13 +31,12 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
tag: widget.type ?? 'all',
|
||||
);
|
||||
|
||||
HistoryController get currCtr {
|
||||
HistoryController currCtr([int? index]) {
|
||||
try {
|
||||
if (_historyController.tabController != null &&
|
||||
_historyController.tabController!.index != 0) {
|
||||
index ??= _historyController.tabController!.index;
|
||||
if (index != 0) {
|
||||
return Get.find<HistoryController>(
|
||||
tag: _historyController
|
||||
.tabs[_historyController.tabController!.index - 1].type,
|
||||
tag: _historyController.tabs[index - 1].type,
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
@@ -65,7 +64,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
canPop: enableMultiSelect.not,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (enableMultiSelect) {
|
||||
currCtr.handleSelect();
|
||||
currCtr().handleSelect();
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
@@ -115,7 +114,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
});
|
||||
break;
|
||||
case 'del':
|
||||
currCtr.onDelHistory();
|
||||
currCtr().onDelHistory();
|
||||
break;
|
||||
case 'multiple':
|
||||
_historyController
|
||||
@@ -157,7 +156,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
leading: IconButton(
|
||||
tooltip: '取消',
|
||||
onPressed: () {
|
||||
currCtr.handleSelect();
|
||||
currCtr().handleSelect();
|
||||
},
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
),
|
||||
@@ -168,12 +167,12 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => currCtr.handleSelect(true),
|
||||
onPressed: () => currCtr().handleSelect(true),
|
||||
child: const Text('全选'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
currCtr.onDelCheckedHistory(context),
|
||||
currCtr().onDelCheckedHistory(context),
|
||||
child: Text(
|
||||
'删除',
|
||||
style: TextStyle(
|
||||
@@ -191,28 +190,15 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
children: [
|
||||
TabBar(
|
||||
controller: _historyController.tabController,
|
||||
onTap: (value) {
|
||||
onTap: (index) {
|
||||
if (_historyController
|
||||
.tabController!.indexIsChanging.not) {
|
||||
currCtr.scrollController.animToTop();
|
||||
currCtr().scrollController.animToTop();
|
||||
} else {
|
||||
if (enableMultiSelect) {
|
||||
if (_historyController
|
||||
.tabController!.previousIndex ==
|
||||
0) {
|
||||
_historyController.handleSelect();
|
||||
} else {
|
||||
try {
|
||||
Get.find<HistoryController>(
|
||||
tag: _historyController
|
||||
.tabs[_historyController
|
||||
.tabController!
|
||||
.previousIndex -
|
||||
1]
|
||||
.type)
|
||||
currCtr(_historyController
|
||||
.tabController!.previousIndex)
|
||||
.handleSelect();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
10
lib/pages/later/base_controller.dart
Normal file
10
lib/pages/later/base_controller.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
import 'package:PiliPlus/pages/later/view.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class LaterBaseController extends GetxController {
|
||||
RxBool enableMultiSelect = false.obs;
|
||||
RxInt checkedCount = 0.obs;
|
||||
|
||||
RxMap<LaterViewType, int> counts =
|
||||
{for (final item in LaterViewType.values) item: -1}.obs;
|
||||
}
|
||||
219
lib/pages/later/child_view.dart
Normal file
219
lib/pages/later/child_view.dart
Normal file
@@ -0,0 +1,219 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_card_h.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/model_hot_video_item.dart';
|
||||
import 'package:PiliPlus/pages/later/controller.dart';
|
||||
import 'package:PiliPlus/pages/later/view.dart'
|
||||
show LaterViewType, LaterViewTypeExt;
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class LaterViewChildPage extends StatefulWidget {
|
||||
const LaterViewChildPage({
|
||||
super.key,
|
||||
required this.laterViewType,
|
||||
});
|
||||
|
||||
final LaterViewType laterViewType;
|
||||
|
||||
@override
|
||||
State<LaterViewChildPage> createState() => _LaterViewChildPageState();
|
||||
}
|
||||
|
||||
class _LaterViewChildPageState extends State<LaterViewChildPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late final LaterController _laterController = Get.put(
|
||||
LaterController(widget.laterViewType),
|
||||
tag: widget.laterViewType.type.toString(),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return refreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _laterController.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: _laterController.scrollController,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 7,
|
||||
bottom: MediaQuery.of(context).padding.bottom + 85,
|
||||
),
|
||||
sliver: Obx(
|
||||
() => _buildBody(_laterController.loadingState.value),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(LoadingState<List<HotVideoItemModel>?> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: 2,
|
||||
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.2,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
},
|
||||
childCount: 10,
|
||||
),
|
||||
),
|
||||
Success() => loadingState.response?.isNotEmpty == true
|
||||
? SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: 2,
|
||||
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.2,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index == loadingState.response!.length - 1) {
|
||||
_laterController.onLoadMore();
|
||||
}
|
||||
var videoItem = loadingState.response![index];
|
||||
return Stack(
|
||||
children: [
|
||||
VideoCardH(
|
||||
videoItem: videoItem,
|
||||
source: 'later',
|
||||
onViewLater: (cid) {
|
||||
Utils.toViewPage(
|
||||
'bvid=${videoItem.bvid}&cid=$cid',
|
||||
arguments: {
|
||||
'videoItem': videoItem,
|
||||
'oid': videoItem.aid,
|
||||
'heroTag': Utils.makeHeroTag(videoItem.bvid),
|
||||
'sourceType': 'watchLater',
|
||||
'count': loadingState.response!.length,
|
||||
'favTitle': '稍后再看',
|
||||
'mediaId': _laterController.mid,
|
||||
'desc': false,
|
||||
'isContinuePlaying': index != 0,
|
||||
},
|
||||
);
|
||||
},
|
||||
onTap:
|
||||
_laterController.baseCtr.enableMultiSelect.value.not
|
||||
? null
|
||||
: () {
|
||||
_laterController.onSelect(index);
|
||||
},
|
||||
onLongPress: () {
|
||||
if (_laterController
|
||||
.baseCtr.enableMultiSelect.value.not) {
|
||||
_laterController.baseCtr.enableMultiSelect.value =
|
||||
true;
|
||||
_laterController.onSelect(index);
|
||||
}
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
top: 5,
|
||||
left: 12,
|
||||
bottom: 5,
|
||||
child: IgnorePointer(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => AnimatedOpacity(
|
||||
opacity: videoItem.checked == true ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: constraints.maxHeight,
|
||||
width: constraints.maxHeight *
|
||||
StyleString.aspectRatio,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.black.withOpacity(0.6),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: AnimatedScale(
|
||||
scale: videoItem.checked == true ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
child: IconButton(
|
||||
tooltip: '取消选择',
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
WidgetStateProperty.resolveWith(
|
||||
(states) {
|
||||
return Theme.of(context)
|
||||
.colorScheme
|
||||
.surface
|
||||
.withOpacity(0.8);
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: null,
|
||||
icon: Icon(
|
||||
Icons.done_all_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: 0,
|
||||
child: iconButton(
|
||||
tooltip: '移除',
|
||||
context: context,
|
||||
onPressed: () {
|
||||
_laterController.toViewDel(
|
||||
context,
|
||||
index,
|
||||
videoItem.aid,
|
||||
);
|
||||
},
|
||||
icon: Icons.clear,
|
||||
iconColor:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
bgColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
childCount: loadingState.response!.length,
|
||||
),
|
||||
)
|
||||
: HttpError(
|
||||
callback: _laterController.onReload,
|
||||
),
|
||||
Error() => HttpError(
|
||||
errMsg: loadingState.errMsg,
|
||||
callback: _laterController.onReload,
|
||||
),
|
||||
LoadingState() => throw UnimplementedError(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
@@ -2,6 +2,10 @@ import 'package:PiliPlus/common/widgets/dialog.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/model_hot_video_item.dart';
|
||||
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
|
||||
import 'package:PiliPlus/pages/later/base_controller.dart';
|
||||
import 'package:PiliPlus/pages/later/view.dart'
|
||||
show LaterViewType, LaterViewTypeExt;
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -10,9 +14,49 @@ import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/http/user.dart';
|
||||
|
||||
class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
||||
RxInt count = (-1).obs;
|
||||
LaterController(this.laterViewType);
|
||||
final LaterViewType laterViewType;
|
||||
|
||||
dynamic mid;
|
||||
final RxBool asc = false.obs;
|
||||
|
||||
final LaterBaseController baseCtr = Get.put(LaterBaseController());
|
||||
|
||||
@override
|
||||
Future<LoadingState<Map>> customGetData() => UserHttp.seeYouLater(
|
||||
page: currentPage,
|
||||
viewed: laterViewType.type,
|
||||
asc: asc.value,
|
||||
);
|
||||
|
||||
@override
|
||||
onSelect(int index, [bool disableSelect = true]) {
|
||||
List<HotVideoItemModel> list = (loadingState.value as Success).response;
|
||||
list[index].checked = !(list[index].checked ?? false);
|
||||
baseCtr.checkedCount.value =
|
||||
list.where((item) => item.checked == true).length;
|
||||
loadingState.refresh();
|
||||
if (baseCtr.checkedCount.value == 0) {
|
||||
baseCtr.enableMultiSelect.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleSelect([bool checked = false, bool disableSelect = true]) {
|
||||
if (loadingState.value is Success) {
|
||||
List<HotVideoItemModel>? list = (loadingState.value as Success).response;
|
||||
if (list?.isNotEmpty == true) {
|
||||
for (HotVideoItemModel item in list!) {
|
||||
item.checked = checked;
|
||||
}
|
||||
baseCtr.checkedCount.value = checked ? list.length : 0;
|
||||
loadingState.refresh();
|
||||
}
|
||||
}
|
||||
if (checked.not) {
|
||||
baseCtr.enableMultiSelect.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -28,25 +72,25 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
||||
|
||||
@override
|
||||
void checkIsEnd(int length) {
|
||||
if (length >= count.value) {
|
||||
if (length >= baseCtr.counts[laterViewType]!) {
|
||||
isEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(bool isRefresh, Success response) {
|
||||
count.value = response.response['count'];
|
||||
baseCtr.counts[laterViewType] = response.response['count'];
|
||||
return false;
|
||||
}
|
||||
|
||||
Future toViewDel(BuildContext context, {index, aid}) async {
|
||||
// single
|
||||
Future toViewDel(BuildContext context, int index, int? aid) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: Text(
|
||||
aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
|
||||
content: Text('即将移除该视频,确定是否移除'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
@@ -57,23 +101,19 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var res =
|
||||
await UserHttp.toViewDel(aids: aid != null ? [aid] : null);
|
||||
Get.back();
|
||||
var res = await UserHttp.toViewDel(aids: [aid]);
|
||||
if (res['status']) {
|
||||
if (aid != null) {
|
||||
List<HotVideoItemModel> list =
|
||||
(loadingState.value as Success).response;
|
||||
list.removeAt(index);
|
||||
count.value -= 1;
|
||||
baseCtr.counts[laterViewType] =
|
||||
baseCtr.counts[laterViewType]! - 1;
|
||||
loadingState.refresh();
|
||||
} else {
|
||||
onReload();
|
||||
}
|
||||
}
|
||||
Get.back();
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
child: Text(aid != null ? '确认移除' : '确认删除'),
|
||||
child: Text('确认移除'),
|
||||
)
|
||||
],
|
||||
);
|
||||
@@ -82,24 +122,33 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
||||
}
|
||||
|
||||
// 一键清空
|
||||
void toViewClear(BuildContext context) {
|
||||
void toViewClear(BuildContext context, [int? cleanType]) {
|
||||
String content = switch (cleanType) {
|
||||
1 => '确定清空已失效视频吗?',
|
||||
2 => '确定清空已看完视频吗?',
|
||||
_ => '确定清空稍后再看列表吗?',
|
||||
};
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
title: '清空确认',
|
||||
content: '确定要清空你的稍后再看列表吗?',
|
||||
title: '确认',
|
||||
content: content,
|
||||
onConfirm: () async {
|
||||
var res = await UserHttp.toViewClear();
|
||||
var res = await UserHttp.toViewClear(cleanType);
|
||||
if (res['status']) {
|
||||
loadingState.value = LoadingState.success(null);
|
||||
onReload();
|
||||
final restTypes = List<LaterViewType>.from(LaterViewType.values)
|
||||
..remove(laterViewType);
|
||||
for (final item in restTypes) {
|
||||
try {
|
||||
Get.find<LaterController>(tag: item.type.toString()).onReload();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState<Map>> customGetData() => UserHttp.seeYouLater();
|
||||
|
||||
onDelChecked(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
@@ -142,11 +191,12 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
||||
((loadingState.value as Success).response as List<HotVideoItemModel>)
|
||||
.toSet()
|
||||
.difference(result.toSet());
|
||||
count.value -= aids.length;
|
||||
baseCtr.counts[laterViewType] =
|
||||
baseCtr.counts[laterViewType]! - aids.length;
|
||||
loadingState.value = LoadingState.success(remainList.toList());
|
||||
if (enableMultiSelect.value) {
|
||||
checkedCount.value = 0;
|
||||
enableMultiSelect.value = false;
|
||||
if (baseCtr.enableMultiSelect.value) {
|
||||
baseCtr.checkedCount.value = 0;
|
||||
baseCtr.enableMultiSelect.value = false;
|
||||
}
|
||||
}
|
||||
SmartDialog.dismiss();
|
||||
@@ -156,9 +206,10 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
||||
// 稍后再看播放全部
|
||||
void toViewPlayAll() {
|
||||
if (loadingState.value is Success) {
|
||||
List<HotVideoItemModel> list = List<HotVideoItemModel>.from(
|
||||
(loadingState.value as Success).response);
|
||||
for (HotVideoItemModel item in list) {
|
||||
List<HotVideoItemModel>? list = (loadingState.value as Success).response;
|
||||
if (list.isNullOrEmpty) return;
|
||||
|
||||
for (HotVideoItemModel item in list!) {
|
||||
if (item.cid == null || item.pgcLabel?.isNotEmpty == true) {
|
||||
continue;
|
||||
} else {
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/model_hot_video_item.dart';
|
||||
import 'package:PiliPlus/pages/fav_search/view.dart';
|
||||
import 'package:PiliPlus/pages/history/view.dart' show AppBarWidget;
|
||||
import 'package:PiliPlus/pages/later/base_controller.dart';
|
||||
import 'package:PiliPlus/pages/later/child_view.dart';
|
||||
import 'package:PiliPlus/pages/later/controller.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_card_h.dart';
|
||||
import 'package:PiliPlus/pages/later/index.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
import '../../common/constants.dart';
|
||||
import '../../utils/grid.dart';
|
||||
enum LaterViewType { all, toView, unfinished, viewed }
|
||||
|
||||
extension LaterViewTypeExt on LaterViewType {
|
||||
int get type => index;
|
||||
|
||||
String get title => ['全部', '未看', '未看完', '已看完'][index];
|
||||
|
||||
Widget get page => LaterViewChildPage(laterViewType: this);
|
||||
}
|
||||
|
||||
class LaterPage extends StatefulWidget {
|
||||
const LaterPage({super.key});
|
||||
@@ -22,51 +30,216 @@ class LaterPage extends StatefulWidget {
|
||||
State<LaterPage> createState() => _LaterPageState();
|
||||
}
|
||||
|
||||
class _LaterPageState extends State<LaterPage> {
|
||||
final LaterController _laterController = Get.put(LaterController());
|
||||
class _LaterPageState extends State<LaterPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final LaterBaseController _baseCtr = Get.put(LaterBaseController());
|
||||
late final TabController _tabController = TabController(
|
||||
length: LaterViewType.values.length,
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
LaterController currCtr([int? index]) {
|
||||
final type = LaterViewType.values[index ?? _tabController.index];
|
||||
return Get.put(
|
||||
LaterController(type),
|
||||
tag: type.type.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
final sortKey = GlobalKey();
|
||||
void listener() {
|
||||
(sortKey.currentContext as Element?)?.markNeedsBuild();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController.addListener(listener);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.removeListener(listener);
|
||||
_tabController.dispose();
|
||||
Get.delete<LaterBaseController>();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(
|
||||
() => PopScope(
|
||||
canPop: _laterController.enableMultiSelect.value.not,
|
||||
canPop: _baseCtr.enableMultiSelect.value.not,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (_laterController.enableMultiSelect.value) {
|
||||
_laterController.handleSelect();
|
||||
if (_baseCtr.enableMultiSelect.value) {
|
||||
currCtr().handleSelect();
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBarWidget(
|
||||
visible: _laterController.enableMultiSelect.value,
|
||||
appBar: _buildAppbar,
|
||||
floatingActionButton: Obx(
|
||||
() => currCtr().loadingState.value is Success
|
||||
? FloatingActionButton.extended(
|
||||
onPressed: currCtr().toViewPlayAll,
|
||||
label: const Text('播放全部'),
|
||||
icon: const Icon(Icons.playlist_play),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
tabs: LaterViewType.values.map((item) {
|
||||
final count = _baseCtr.counts[item];
|
||||
return Tab(
|
||||
text: '${item.title}${count != -1 ? '($count)' : ''}');
|
||||
}).toList(),
|
||||
onTap: (_) {
|
||||
if (_tabController.indexIsChanging.not) {
|
||||
currCtr().scrollController.animToTop();
|
||||
} else {
|
||||
if (_baseCtr.enableMultiSelect.value) {
|
||||
currCtr(_tabController.previousIndex).handleSelect();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
physics: _baseCtr.enableMultiSelect.value
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: const CustomTabBarViewScrollPhysics(),
|
||||
controller: _tabController,
|
||||
children:
|
||||
LaterViewType.values.map((item) => item.page).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
PreferredSizeWidget get _buildAppbar {
|
||||
Color color = Theme.of(context).colorScheme.secondary;
|
||||
|
||||
return AppBarWidget(
|
||||
visible: _baseCtr.enableMultiSelect.value,
|
||||
child1: AppBar(
|
||||
title: Obx(
|
||||
() => Text(
|
||||
'稍后再看${_laterController.count.value == -1 ? '' : ' (${_laterController.count.value})'}',
|
||||
),
|
||||
),
|
||||
title: const Text('稍后再看'),
|
||||
actions: [
|
||||
Obx(
|
||||
() => _laterController.count.value != -1
|
||||
? TextButton(
|
||||
onPressed: () => _laterController.toViewDel(context),
|
||||
child: const Text('移除已看'),
|
||||
)
|
||||
: const SizedBox(),
|
||||
IconButton(
|
||||
tooltip: '搜索',
|
||||
onPressed: () {
|
||||
final mid = Accounts.main.mid;
|
||||
Get.toNamed(
|
||||
'/favSearch',
|
||||
arguments: {
|
||||
'type': 0,
|
||||
'mediaId': mid,
|
||||
'mid': mid,
|
||||
'title': '稍后再看',
|
||||
'count': _baseCtr.counts[LaterViewType.all],
|
||||
'searchType': SearchType.later,
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
Obx(
|
||||
() => _laterController.count.value != -1
|
||||
? IconButton(
|
||||
tooltip: '一键清空',
|
||||
onPressed: () =>
|
||||
_laterController.toViewClear(context),
|
||||
icon: Icon(
|
||||
Icons.clear_all_outlined,
|
||||
size: 21,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Builder(
|
||||
key: sortKey,
|
||||
builder: (context) {
|
||||
final value = currCtr().asc.value;
|
||||
return PopupMenuButton(
|
||||
initialValue: value,
|
||||
tooltip: '排序',
|
||||
onSelected: (value) {
|
||||
currCtr()
|
||||
..asc.value = value
|
||||
..onReload();
|
||||
},
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: value ? '最早添加' : '最近添加',
|
||||
),
|
||||
WidgetSpan(
|
||||
child: Icon(
|
||||
size: 16,
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
),
|
||||
),
|
||||
itemBuilder: (BuildContext context) => [
|
||||
PopupMenuItem(
|
||||
value: false,
|
||||
child: const Text('最近添加'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: true,
|
||||
child: const Text('最早添加'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: PopupMenuButton(
|
||||
tooltip: '清空',
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '清空',
|
||||
),
|
||||
WidgetSpan(
|
||||
child: Icon(
|
||||
size: 16,
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
),
|
||||
),
|
||||
itemBuilder: (BuildContext context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => currCtr().toViewClear(context, 1),
|
||||
child: const Text('清空失效'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => currCtr().toViewClear(context, 2),
|
||||
child: const Text('清空看完'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => currCtr().toViewClear(context),
|
||||
child: const Text('清空全部'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
@@ -74,12 +247,12 @@ class _LaterPageState extends State<LaterPage> {
|
||||
child2: AppBar(
|
||||
leading: IconButton(
|
||||
tooltip: '取消',
|
||||
onPressed: _laterController.handleSelect,
|
||||
onPressed: currCtr().handleSelect,
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
),
|
||||
title: Obx(
|
||||
() => Text(
|
||||
'已选: ${_laterController.checkedCount.value}',
|
||||
'已选: ${_baseCtr.checkedCount.value}',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
@@ -87,19 +260,23 @@ class _LaterPageState extends State<LaterPage> {
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
||||
),
|
||||
onPressed: () => _laterController.handleSelect(true),
|
||||
onPressed: () => currCtr().handleSelect(true),
|
||||
child: const Text('全选'),
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
||||
),
|
||||
onPressed: () => Utils.onCopyOrMove<HotVideoItemModel>(
|
||||
onPressed: () {
|
||||
final ctr = currCtr();
|
||||
Utils.onCopyOrMove<Map, HotVideoItemModel>(
|
||||
context: context,
|
||||
isCopy: true,
|
||||
ctr: _laterController,
|
||||
ctr: ctr,
|
||||
mediaId: null,
|
||||
),
|
||||
mid: ctr.mid,
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'复制',
|
||||
style: TextStyle(
|
||||
@@ -111,12 +288,16 @@ class _LaterPageState extends State<LaterPage> {
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
||||
),
|
||||
onPressed: () => Utils.onCopyOrMove<HotVideoItemModel>(
|
||||
onPressed: () {
|
||||
final ctr = currCtr();
|
||||
Utils.onCopyOrMove<Map, HotVideoItemModel>(
|
||||
context: context,
|
||||
isCopy: false,
|
||||
ctr: _laterController,
|
||||
ctr: ctr,
|
||||
mediaId: null,
|
||||
),
|
||||
mid: ctr.mid,
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'移动',
|
||||
style: TextStyle(
|
||||
@@ -128,196 +309,15 @@ class _LaterPageState extends State<LaterPage> {
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
||||
),
|
||||
onPressed: () => _laterController.onDelChecked(context),
|
||||
onPressed: () => currCtr().onDelChecked(context),
|
||||
child: Text(
|
||||
'移除',
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: Obx(
|
||||
() => _laterController.loadingState.value is Success
|
||||
? FloatingActionButton.extended(
|
||||
onPressed: _laterController.toViewPlayAll,
|
||||
label: const Text('播放全部'),
|
||||
icon: const Icon(Icons.playlist_play),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
body: refreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _laterController.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: _laterController.scrollController,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 85,
|
||||
),
|
||||
sliver: Obx(
|
||||
() => _buildBody(_laterController.loadingState.value),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(LoadingState<List<HotVideoItemModel>?> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: 2,
|
||||
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.2,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
},
|
||||
childCount: 10,
|
||||
),
|
||||
),
|
||||
Success() => loadingState.response?.isNotEmpty == true
|
||||
? SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: 2,
|
||||
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.2,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
var videoItem = loadingState.response![index];
|
||||
return Stack(
|
||||
children: [
|
||||
VideoCardH(
|
||||
videoItem: videoItem,
|
||||
source: 'later',
|
||||
onViewLater: (cid) {
|
||||
Utils.toViewPage(
|
||||
'bvid=${videoItem.bvid}&cid=$cid',
|
||||
arguments: {
|
||||
'videoItem': videoItem,
|
||||
'oid': videoItem.aid,
|
||||
'heroTag': Utils.makeHeroTag(videoItem.bvid),
|
||||
'sourceType': 'watchLater',
|
||||
'count': loadingState.response!.length,
|
||||
'favTitle': '稍后再看',
|
||||
'mediaId': _laterController.mid,
|
||||
'desc': false,
|
||||
'isContinuePlaying': index != 0,
|
||||
},
|
||||
);
|
||||
},
|
||||
onTap: _laterController.enableMultiSelect.value.not
|
||||
? null
|
||||
: () {
|
||||
_laterController.onSelect(index);
|
||||
},
|
||||
onLongPress: () {
|
||||
if (_laterController.enableMultiSelect.value.not) {
|
||||
_laterController.enableMultiSelect.value = true;
|
||||
_laterController.onSelect(index);
|
||||
}
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
top: 5,
|
||||
left: 12,
|
||||
bottom: 5,
|
||||
child: IgnorePointer(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => AnimatedOpacity(
|
||||
opacity: videoItem.checked == true ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: constraints.maxHeight,
|
||||
width: constraints.maxHeight *
|
||||
StyleString.aspectRatio,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.black.withOpacity(0.6),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: AnimatedScale(
|
||||
scale: videoItem.checked == true ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
child: IconButton(
|
||||
tooltip: '取消选择',
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
WidgetStateProperty.resolveWith(
|
||||
(states) {
|
||||
return Theme.of(context)
|
||||
.colorScheme
|
||||
.surface
|
||||
.withOpacity(0.8);
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: null,
|
||||
icon: Icon(
|
||||
Icons.done_all_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: 0,
|
||||
child: iconButton(
|
||||
tooltip: '移除',
|
||||
context: context,
|
||||
onPressed: () {
|
||||
_laterController.toViewDel(
|
||||
context,
|
||||
index: index,
|
||||
aid: videoItem.aid,
|
||||
);
|
||||
},
|
||||
icon: Icons.clear,
|
||||
iconColor:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
bgColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
childCount: loadingState.response!.length,
|
||||
),
|
||||
)
|
||||
: HttpError(
|
||||
callback: _laterController.onReload,
|
||||
),
|
||||
Error() => HttpError(
|
||||
errMsg: loadingState.errMsg,
|
||||
callback: _laterController.onReload,
|
||||
),
|
||||
LoadingState() => throw UnimplementedError(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,9 @@ class MemberVideoCtr extends CommonListController<Data, Item> {
|
||||
|
||||
void toViewPlayAll() async {
|
||||
if (loadingState.value is Success) {
|
||||
List<Item> list = (loadingState.value as Success).response;
|
||||
List<Item>? list = (loadingState.value as Success).response;
|
||||
|
||||
if (list.isNullOrEmpty) return;
|
||||
|
||||
if (episodicButton.value.text == '继续播放') {
|
||||
dynamic oid = RegExp(r'oid=([\d]+)')
|
||||
@@ -173,7 +175,7 @@ class MemberVideoCtr extends CommonListController<Data, Item> {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Item element in list) {
|
||||
for (Item element in list!) {
|
||||
if (element.cid == null) {
|
||||
continue;
|
||||
} else {
|
||||
|
||||
@@ -102,9 +102,13 @@ class WhisperController extends GetxController {
|
||||
Future querySessionList(String? type) async {
|
||||
if (isLoading) return;
|
||||
var res = await MsgHttp.sessionList(
|
||||
endTs: type == 'onLoad' ? sessionList.last.sessionTs : null);
|
||||
if (res['data'].sessionList != null && res['data'].sessionList.isNotEmpty) {
|
||||
await queryAccountList(res['data'].sessionList);
|
||||
endTs: type == 'onLoad' ? sessionList.last.sessionTs : null,
|
||||
);
|
||||
if (res['status']) {
|
||||
List<SessionList>? sessionList = res['data'];
|
||||
if (sessionList != null) {
|
||||
if (sessionList.isNotEmpty) {
|
||||
await queryAccountList(sessionList);
|
||||
// 将 accountList 转换为 Map 结构
|
||||
Map<int, dynamic> accountMap = {};
|
||||
for (var j in accountList) {
|
||||
@@ -112,7 +116,7 @@ class WhisperController extends GetxController {
|
||||
}
|
||||
|
||||
// 遍历 sessionList,通过 mid 查找并赋值 accountInfo
|
||||
for (var i in res['data'].sessionList) {
|
||||
for (var i in sessionList) {
|
||||
var accountInfo = accountMap[i.talkerId];
|
||||
if (accountInfo != null) {
|
||||
i.accountInfo = accountInfo;
|
||||
@@ -126,11 +130,12 @@ class WhisperController extends GetxController {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (res['status'] && res['data'].sessionList != null) {
|
||||
|
||||
if (type == 'onLoad') {
|
||||
sessionList.addAll(res['data'].sessionList);
|
||||
this.sessionList.addAll(sessionList);
|
||||
} else {
|
||||
sessionList.value = res['data'].sessionList;
|
||||
this.sessionList.value = sessionList;
|
||||
}
|
||||
}
|
||||
}
|
||||
isLoading = false;
|
||||
|
||||
@@ -78,9 +78,11 @@ class _WhisperPageState extends State<WhisperPage> {
|
||||
_whisperController.onRefresh(),
|
||||
]);
|
||||
},
|
||||
// TODO: refactor
|
||||
child: ListView(
|
||||
padding: EdgeInsets.only(bottom: 80),
|
||||
controller: _scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:PiliPlus/models/common/search_type.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/models/live/item.dart';
|
||||
import 'package:PiliPlus/models/user/fav_folder.dart';
|
||||
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/tab/controller.dart';
|
||||
import 'package:PiliPlus/pages/later/controller.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/fav_panel.dart';
|
||||
@@ -670,13 +671,14 @@ class Utils {
|
||||
);
|
||||
}
|
||||
|
||||
static void onCopyOrMove<T>({
|
||||
static void onCopyOrMove<R, T extends MultiSelectData>({
|
||||
required BuildContext context,
|
||||
required bool isCopy,
|
||||
required dynamic ctr,
|
||||
required MultiSelectController<R, T> ctr,
|
||||
required dynamic mediaId,
|
||||
required dynamic mid,
|
||||
}) {
|
||||
VideoHttp.allFavFolders(ctr.mid).then((res) {
|
||||
VideoHttp.allFavFolders(mid).then((res) {
|
||||
if (context.mounted &&
|
||||
res['status'] &&
|
||||
(res['data'].list as List?)?.isNotEmpty == true) {
|
||||
@@ -720,8 +722,8 @@ class Utils {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (checkedId != null) {
|
||||
List resources =
|
||||
((ctr.loadingState.value as Success).response as List)
|
||||
List resources = ((ctr.loadingState.value as Success)
|
||||
.response as List<T>)
|
||||
.where((e) => e.checked == true)
|
||||
.toList();
|
||||
SmartDialog.showLoading();
|
||||
@@ -735,7 +737,7 @@ class Utils {
|
||||
? item.aid
|
||||
: '${item.id}:${item.type}')
|
||||
.toList(),
|
||||
mid: isCopy ? ctr.mid : null,
|
||||
mid: isCopy ? mid : null,
|
||||
).then((res) {
|
||||
if (res['status']) {
|
||||
ctr.handleSelect(false);
|
||||
|
||||
Reference in New Issue
Block a user