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';
|
'${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';
|
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 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';
|
static const String toViewClear = '/x/v2/history/toview/clear';
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ class DanmakuHttp {
|
|||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
|
||||||
'msg': '弹幕发送失败,状态码:${response.statusCode}',
|
'msg': '弹幕发送失败,状态码:${response.statusCode}',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -109,7 +108,6 @@ class DanmakuHttp {
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
|
||||||
'msg': "${response.data['code']}: ${response.data['message']}",
|
'msg': "${response.data['code']}: ${response.data['message']}",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class DanmakuFilterHttp {
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
'msg': res.data['message'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,7 @@ class DynamicsHttp {
|
|||||||
'data': FollowUpModel.fromJson(res.data['data']),
|
'data': FollowUpModel.fromJson(res.data['data']),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,11 +76,7 @@ class DynamicsHttp {
|
|||||||
'data': res.data['data'],
|
'data': res.data['data'],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,7 @@ class FollowHttp {
|
|||||||
'data': FollowDataModel.fromJson(res.data['data'])
|
'data': FollowDataModel.fromJson(res.data['data'])
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,11 +82,7 @@ class LiveHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': RoomInfoModel.fromJson(res.data['data'])};
|
return {'status': true, 'data': RoomInfoModel.fromJson(res.data['data'])};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,11 +96,7 @@ class LiveHttp {
|
|||||||
'data': RoomInfoH5Model.fromJson(res.data['data'])
|
'data': RoomInfoH5Model.fromJson(res.data['data'])
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +107,7 @@ class LiveHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']['room']};
|
return {'status': true, 'data': res.data['data']['room']};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,11 +118,7 @@ class LiveHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': LiveDanmakuInfo.fromJson(res.data)};
|
return {'status': true, 'data': LiveDanmakuInfo.fromJson(res.data)};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -300,11 +300,7 @@ class MemberHttp {
|
|||||||
'data': MemberInfoModel.fromJson(res.data['data'])
|
'data': MemberInfoModel.fromJson(res.data['data'])
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,11 +309,7 @@ class MemberHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,11 +324,7 @@ class MemberHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,11 +464,7 @@ class MemberHttp {
|
|||||||
.toList()
|
.toList()
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,13 +507,9 @@ class MemberHttp {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': [], 'msg': '操作成功'};
|
return {'status': true, 'msg': '操作成功'};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,11 +535,7 @@ class MemberHttp {
|
|||||||
.toList()
|
.toList()
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,11 +550,7 @@ class MemberHttp {
|
|||||||
.toList()
|
.toList()
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,11 +567,7 @@ class MemberHttp {
|
|||||||
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
|
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,11 +655,7 @@ class MemberHttp {
|
|||||||
debugPrint(err.toString());
|
debugPrint(err.toString());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,11 +666,7 @@ class MemberHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,11 +110,7 @@ class MsgHttp {
|
|||||||
'data': res.data['data'],
|
'data': res.data['data'],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,21 +430,13 @@ class MsgHttp {
|
|||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': SessionDataModel.fromJson(res.data['data']),
|
'data': SessionDataModel.fromJson(res.data['data']).sessionList,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return {'status': false, 'msg': err.toString()};
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': err.toString(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,11 +458,7 @@ class MsgHttp {
|
|||||||
debugPrint('err🔟: $err');
|
debugPrint('err🔟: $err');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,11 +484,7 @@ class MsgHttp {
|
|||||||
debugPrint(err.toString());
|
debugPrint(err.toString());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,7 +512,6 @@ class MsgHttp {
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'date': [],
|
|
||||||
'msg': "message: ${res.data['message']},"
|
'msg': "message: ${res.data['message']},"
|
||||||
" msg: ${res.data['msg']},"
|
" msg: ${res.data['msg']},"
|
||||||
" code: ${res.data['code']}",
|
" code: ${res.data['code']}",
|
||||||
@@ -581,11 +560,7 @@ class MsgHttp {
|
|||||||
'data': res.data['data'],
|
'data': res.data['data'],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': res.data['message'] ?? res.data['msg'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -351,11 +351,7 @@ class ReplyHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,11 +375,7 @@ class ReplyHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,7 @@ class SearchHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {'status': false, 'msg': '请求错误'};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': '请求错误',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取搜索建议
|
// 获取搜索建议
|
||||||
@@ -50,19 +46,17 @@ class SearchHttp {
|
|||||||
'status': true,
|
'status': true,
|
||||||
'data': resultMap['result'] is Map
|
'data': resultMap['result'] is Map
|
||||||
? SearchSuggestModel.fromJson(resultMap['result'])
|
? SearchSuggestModel.fromJson(resultMap['result'])
|
||||||
: [],
|
: null,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
|
||||||
'msg': '请求错误 🙅',
|
'msg': '请求错误 🙅',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
|
||||||
'msg': '请求错误 🙅',
|
'msg': '请求错误 🙅',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -210,11 +204,7 @@ class SearchHttp {
|
|||||||
'data': BangumiInfoModel.fromJson(res.data['result']),
|
'data': BangumiInfoModel.fromJson(res.data['result']),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'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/models/video/later.dart';
|
||||||
import 'package:PiliPlus/utils/global_data.dart';
|
import 'package:PiliPlus/utils/global_data.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
|
import 'package:PiliPlus/utils/wbi_sign.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import '../common/constants.dart';
|
import '../common/constants.dart';
|
||||||
@@ -43,7 +44,7 @@ class UserHttp {
|
|||||||
UserStat data = UserStat.fromJson(res.data['data']);
|
UserStat data = UserStat.fromJson(res.data['data']);
|
||||||
return {'status': true, 'data': data};
|
return {'status': true, 'data': data};
|
||||||
} else {
|
} 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 {
|
static Future<LoadingState<Map>> seeYouLater({
|
||||||
var res = await Request().get(Api.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['code'] == 0) {
|
||||||
if (res.data['data']['count'] == 0) {
|
if (res.data['data']['count'] == 0) {
|
||||||
return LoadingState.success({
|
return LoadingState.success({'count': 0});
|
||||||
'list': [],
|
|
||||||
'count': 0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
List<HotVideoItemModel> list = [];
|
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||||
if (res.data['data']?['list'] != null) {
|
if (res.data['data']?['list'] != null) {
|
||||||
for (var i in res.data['data']['list']) {
|
for (var i in res.data['data']['list']) {
|
||||||
list.add(HotVideoItemModel.fromJson(i));
|
list.add(HotVideoItemModel.fromJson(i));
|
||||||
@@ -260,7 +274,7 @@ class UserHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} 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 = {
|
final Map<String, dynamic> params = {
|
||||||
'jsonp': 'jsonp',
|
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
if (aids != null) 'aid': aids.join(',') else 'viewed': true
|
'resources': aids.join(',')
|
||||||
};
|
};
|
||||||
dynamic res = await Request().post(
|
dynamic res = await Request().post(
|
||||||
Api.toViewDel,
|
Api.toViewDel,
|
||||||
@@ -333,12 +346,12 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空稍后再看
|
// 清空稍后再看 // clean_type: null->all, 1->invalid, 2->viewed
|
||||||
static Future toViewClear() async {
|
static Future toViewClear([int? cleanType]) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.toViewClear,
|
Api.toViewClear,
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'jsonp': 'jsonp',
|
if (cleanType != null) 'clean_type': cleanType,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -631,7 +644,7 @@ class UserHttp {
|
|||||||
static List<String> extractScriptContents(String htmlContent) {
|
static List<String> extractScriptContents(String htmlContent) {
|
||||||
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
|
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
|
||||||
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);
|
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);
|
||||||
List<String> scriptContents = [];
|
List<String> scriptContents = <String>[];
|
||||||
for (Match match in matches) {
|
for (Match match in matches) {
|
||||||
String scriptContent = match.group(1)!;
|
String scriptContent = match.group(1)!;
|
||||||
scriptContents.add(scriptContent);
|
scriptContents.add(scriptContent);
|
||||||
@@ -675,7 +688,7 @@ class UserHttp {
|
|||||||
.map<MediaVideoItemModel>(
|
.map<MediaVideoItemModel>(
|
||||||
(e) => MediaVideoItemModel.fromJson(e))
|
(e) => MediaVideoItemModel.fromJson(e))
|
||||||
.toList()
|
.toList()
|
||||||
: []
|
: <MediaVideoItemModel>[]
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class VideoHttp {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
List<RecVideoItemModel> list = [];
|
List<RecVideoItemModel> list = <RecVideoItemModel>[];
|
||||||
Set<int> blackMids = GStorage.blackMids;
|
Set<int> blackMids = GStorage.blackMids;
|
||||||
for (var i in res.data['data']['item']) {
|
for (var i in res.data['data']['item']) {
|
||||||
//过滤掉live与ad,以及拉黑用户
|
//过滤掉live与ad,以及拉黑用户
|
||||||
@@ -119,7 +119,7 @@ class VideoHttp {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
List<RecVideoItemAppModel> list = [];
|
List<RecVideoItemAppModel> list = <RecVideoItemAppModel>[];
|
||||||
Set<int> blackMids = GStorage.blackMids;
|
Set<int> blackMids = GStorage.blackMids;
|
||||||
for (var i in res.data['data']['items']) {
|
for (var i in res.data['data']['items']) {
|
||||||
// 屏蔽推广和拉黑用户
|
// 屏蔽推广和拉黑用户
|
||||||
@@ -153,7 +153,7 @@ class VideoHttp {
|
|||||||
queryParameters: {'pn': pn, 'ps': ps},
|
queryParameters: {'pn': pn, 'ps': ps},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
List<HotVideoItemModel> list = [];
|
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||||
Set<int> blackMids = GStorage.blackMids;
|
Set<int> blackMids = GStorage.blackMids;
|
||||||
for (var i in res.data['data']['list']) {
|
for (var i in res.data['data']['list']) {
|
||||||
if (!blackMids.contains(i['owner']['mid']) &&
|
if (!blackMids.contains(i['owner']['mid']) &&
|
||||||
@@ -177,7 +177,7 @@ class VideoHttp {
|
|||||||
static Future<LoadingState> hotVideoListGrpc({required int idx}) async {
|
static Future<LoadingState> hotVideoListGrpc({required int idx}) async {
|
||||||
dynamic res = await GrpcRepo.popular(idx);
|
dynamic res = await GrpcRepo.popular(idx);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
List<card.Card> list = [];
|
List<card.Card> list = <card.Card>[];
|
||||||
Set<int> blackMids = GStorage.blackMids;
|
Set<int> blackMids = GStorage.blackMids;
|
||||||
for (card.Card item in res['data']) {
|
for (card.Card item in res['data']) {
|
||||||
if (!blackMids.contains(item.smallCoverV5.up.id.toInt())) {
|
if (!blackMids.contains(item.smallCoverV5.up.id.toInt())) {
|
||||||
@@ -259,13 +259,12 @@ class VideoHttp {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
|
||||||
'code': res.data['code'],
|
'code': res.data['code'],
|
||||||
'msg': res.data['message'],
|
'msg': res.data['message'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {'status': false, 'data': [], 'msg': err};
|
return {'status': false, 'msg': err};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +381,7 @@ class VideoHttp {
|
|||||||
// if (res.data['code'] == 0) {
|
// if (res.data['code'] == 0) {
|
||||||
// return {'status': true, 'data': res.data['data']};
|
// return {'status': true, 'data': res.data['data']};
|
||||||
// } else {
|
// } else {
|
||||||
// return {'status': false, 'data': []};
|
// return {'status': false, 'msg': res.data['message']};
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -393,7 +392,7 @@ class VideoHttp {
|
|||||||
// if (res.data['code'] == 0) {
|
// if (res.data['code'] == 0) {
|
||||||
// return {'status': true, 'data': res.data['data']};
|
// return {'status': true, 'data': res.data['data']};
|
||||||
// } else {
|
// } else {
|
||||||
// return {'status': false, 'data': []};
|
// return {'status': false, 'msg': res.data['message']};
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -417,7 +416,7 @@ class VideoHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} 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) {
|
// if (res.data['code'] == 0) {
|
||||||
// return {'status': true, 'data': res.data['data']};
|
// return {'status': true, 'data': res.data['data']};
|
||||||
// } else {
|
// } else {
|
||||||
// return {'status': false, 'data': []};
|
// return {'status': false, 'msg': res.data['message']};
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -452,7 +451,7 @@ class VideoHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} 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) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} 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) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} 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']);
|
FavFolderData data = FavFolderData.fromJson(res.data['data']);
|
||||||
return {'status': true, 'data': data};
|
return {'status': true, 'data': data};
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'data': []};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,7 +740,7 @@ class VideoHttp {
|
|||||||
bool? syncToDynamic,
|
bool? syncToDynamic,
|
||||||
}) async {
|
}) async {
|
||||||
if (message == '') {
|
if (message == '') {
|
||||||
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
return {'status': false, 'msg': '请输入评论内容'};
|
||||||
}
|
}
|
||||||
Map<String, dynamic> data = {
|
Map<String, dynamic> data = {
|
||||||
'type': type.index,
|
'type': type.index,
|
||||||
@@ -761,7 +760,7 @@ class VideoHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} 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']),
|
'data': AiConclusionModel.fromJson(res.data['data']),
|
||||||
};
|
};
|
||||||
} else {
|
} 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 rankApi = "${Api.getRankApi}?rid=$rid&type=all";
|
||||||
var res = await Request().get(rankApi);
|
var res = await Request().get(rankApi);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
List<HotVideoItemModel> list = [];
|
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||||
Set<int> blackMids = GStorage.blackMids;
|
Set<int> blackMids = GStorage.blackMids;
|
||||||
for (var i in res.data['data']['list']) {
|
for (var i in res.data['data']['list']) {
|
||||||
if (!blackMids.contains(i['owner']['mid']) &&
|
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_detail.dart';
|
||||||
import 'package:PiliPlus/models/user/fav_folder.dart';
|
import 'package:PiliPlus/models/user/fav_folder.dart';
|
||||||
import 'package:PiliPlus/pages/common/multi_select_controller.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/storage.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -142,8 +143,10 @@ class FavDetailController
|
|||||||
|
|
||||||
void toViewPlayAll() {
|
void toViewPlayAll() {
|
||||||
if (loadingState.value is Success) {
|
if (loadingState.value is Success) {
|
||||||
List<FavDetailItemData> list = (loadingState.value as Success).response;
|
List<FavDetailItemData>? list = (loadingState.value as Success).response;
|
||||||
for (FavDetailItemData element in list) {
|
if (list.isNullOrEmpty) return;
|
||||||
|
|
||||||
|
for (FavDetailItemData element in list!) {
|
||||||
if (element.cid == null) {
|
if (element.cid == null) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -135,13 +135,16 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
visualDensity:
|
visualDensity:
|
||||||
VisualDensity(horizontal: -2, vertical: -2),
|
VisualDensity(horizontal: -2, vertical: -2),
|
||||||
),
|
),
|
||||||
onPressed: () =>
|
onPressed: () {
|
||||||
Utils.onCopyOrMove<FavDetailItemData>(
|
Utils.onCopyOrMove<FavDetailData,
|
||||||
|
FavDetailItemData>(
|
||||||
context: context,
|
context: context,
|
||||||
isCopy: true,
|
isCopy: true,
|
||||||
ctr: _favDetailController,
|
ctr: _favDetailController,
|
||||||
mediaId: _favDetailController.mediaId,
|
mediaId: _favDetailController.mediaId,
|
||||||
),
|
mid: _favDetailController.mid,
|
||||||
|
);
|
||||||
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'复制',
|
'复制',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -156,13 +159,16 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
visualDensity:
|
visualDensity:
|
||||||
VisualDensity(horizontal: -2, vertical: -2),
|
VisualDensity(horizontal: -2, vertical: -2),
|
||||||
),
|
),
|
||||||
onPressed: () =>
|
onPressed: () {
|
||||||
Utils.onCopyOrMove<FavDetailItemData>(
|
Utils.onCopyOrMove<FavDetailData,
|
||||||
|
FavDetailItemData>(
|
||||||
context: context,
|
context: context,
|
||||||
isCopy: false,
|
isCopy: false,
|
||||||
ctr: _favDetailController,
|
ctr: _favDetailController,
|
||||||
mediaId: _favDetailController.mediaId,
|
mediaId: _favDetailController.mediaId,
|
||||||
),
|
mid: _favDetailController.mid,
|
||||||
|
);
|
||||||
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'移动',
|
'移动',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/http/member.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/common/common_list_controller.dart';
|
||||||
import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType;
|
import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -47,14 +48,18 @@ class FavSearchController extends CommonListController {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List? getDataList(response) {
|
List? getDataList(response) {
|
||||||
|
if (searchType == SearchType.later) {
|
||||||
|
return response['list'];
|
||||||
|
}
|
||||||
return response.list;
|
return response.list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool customHandleResponse(bool isRefresh, Success response) {
|
bool customHandleResponse(bool isRefresh, Success response) {
|
||||||
isEnd = searchType == SearchType.fav
|
if (searchType == SearchType.fav && response.response.hasMore == false) {
|
||||||
? response.response.hasMore == false
|
isEnd = true;
|
||||||
: response.response.list == null || response.response.list.isEmpty;
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +97,10 @@ class FavSearchController extends CommonListController {
|
|||||||
pn: currentPage,
|
pn: currentPage,
|
||||||
keyword: controller.value.text,
|
keyword: controller.value.text,
|
||||||
),
|
),
|
||||||
|
SearchType.later => UserHttp.seeYouLater(
|
||||||
|
page: currentPage,
|
||||||
|
keyword: controller.value.text,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -117,4 +126,14 @@ class FavSearchController extends CommonListController {
|
|||||||
SmartDialog.showToast(res['msg']);
|
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/constants.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
||||||
import 'package:PiliPlus/common/widgets/loading_widget.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/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/pages/follow/widgets/follow_item.dart';
|
import 'package:PiliPlus/pages/follow/widgets/follow_item.dart';
|
||||||
import 'package:PiliPlus/pages/history/widgets/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';
|
import 'controller.dart';
|
||||||
|
|
||||||
enum SearchType { fav, follow, history }
|
enum SearchType { fav, follow, history, later }
|
||||||
|
|
||||||
class FavSearchPage extends StatefulWidget {
|
class FavSearchPage extends StatefulWidget {
|
||||||
const FavSearchPage({super.key});
|
const FavSearchPage({super.key});
|
||||||
@@ -51,7 +53,12 @@ class _FavSearchPageState extends State<FavSearchPage> {
|
|||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
tooltip: '清空',
|
tooltip: '清空',
|
||||||
icon: const Icon(Icons.clear, size: 22),
|
icon: const Icon(Icons.clear, size: 22),
|
||||||
onPressed: _favSearchCtr.onClear,
|
onPressed: () {
|
||||||
|
_favSearchCtr
|
||||||
|
..loadingState.value = LoadingState.loading()
|
||||||
|
..onClear()
|
||||||
|
..searchFocusNode.requestFocus();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onSubmitted: (value) => _favSearchCtr.onReload(),
|
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(
|
: errorWidget(
|
||||||
callback: _favSearchCtr.onReload,
|
callback: _favSearchCtr.onReload,
|
||||||
|
|||||||
@@ -31,13 +31,12 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
tag: widget.type ?? 'all',
|
tag: widget.type ?? 'all',
|
||||||
);
|
);
|
||||||
|
|
||||||
HistoryController get currCtr {
|
HistoryController currCtr([int? index]) {
|
||||||
try {
|
try {
|
||||||
if (_historyController.tabController != null &&
|
index ??= _historyController.tabController!.index;
|
||||||
_historyController.tabController!.index != 0) {
|
if (index != 0) {
|
||||||
return Get.find<HistoryController>(
|
return Get.find<HistoryController>(
|
||||||
tag: _historyController
|
tag: _historyController.tabs[index - 1].type,
|
||||||
.tabs[_historyController.tabController!.index - 1].type,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
@@ -65,7 +64,7 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
canPop: enableMultiSelect.not,
|
canPop: enableMultiSelect.not,
|
||||||
onPopInvokedWithResult: (didPop, result) {
|
onPopInvokedWithResult: (didPop, result) {
|
||||||
if (enableMultiSelect) {
|
if (enableMultiSelect) {
|
||||||
currCtr.handleSelect();
|
currCtr().handleSelect();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
@@ -115,7 +114,7 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'del':
|
case 'del':
|
||||||
currCtr.onDelHistory();
|
currCtr().onDelHistory();
|
||||||
break;
|
break;
|
||||||
case 'multiple':
|
case 'multiple':
|
||||||
_historyController
|
_historyController
|
||||||
@@ -157,7 +156,7 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
tooltip: '取消',
|
tooltip: '取消',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
currCtr.handleSelect();
|
currCtr().handleSelect();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.close_outlined),
|
icon: const Icon(Icons.close_outlined),
|
||||||
),
|
),
|
||||||
@@ -168,12 +167,12 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => currCtr.handleSelect(true),
|
onPressed: () => currCtr().handleSelect(true),
|
||||||
child: const Text('全选'),
|
child: const Text('全选'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
currCtr.onDelCheckedHistory(context),
|
currCtr().onDelCheckedHistory(context),
|
||||||
child: Text(
|
child: Text(
|
||||||
'删除',
|
'删除',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -191,28 +190,15 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
children: [
|
children: [
|
||||||
TabBar(
|
TabBar(
|
||||||
controller: _historyController.tabController,
|
controller: _historyController.tabController,
|
||||||
onTap: (value) {
|
onTap: (index) {
|
||||||
if (_historyController
|
if (_historyController
|
||||||
.tabController!.indexIsChanging.not) {
|
.tabController!.indexIsChanging.not) {
|
||||||
currCtr.scrollController.animToTop();
|
currCtr().scrollController.animToTop();
|
||||||
} else {
|
} else {
|
||||||
if (enableMultiSelect) {
|
if (enableMultiSelect) {
|
||||||
if (_historyController
|
currCtr(_historyController
|
||||||
.tabController!.previousIndex ==
|
.tabController!.previousIndex)
|
||||||
0) {
|
|
||||||
_historyController.handleSelect();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
Get.find<HistoryController>(
|
|
||||||
tag: _historyController
|
|
||||||
.tabs[_historyController
|
|
||||||
.tabController!
|
|
||||||
.previousIndex -
|
|
||||||
1]
|
|
||||||
.type)
|
|
||||||
.handleSelect();
|
.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/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/models/model_hot_video_item.dart';
|
import 'package:PiliPlus/models/model_hot_video_item.dart';
|
||||||
import 'package:PiliPlus/pages/common/multi_select_controller.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/storage.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -10,9 +14,49 @@ import 'package:get/get.dart';
|
|||||||
import 'package:PiliPlus/http/user.dart';
|
import 'package:PiliPlus/http/user.dart';
|
||||||
|
|
||||||
class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
||||||
RxInt count = (-1).obs;
|
LaterController(this.laterViewType);
|
||||||
|
final LaterViewType laterViewType;
|
||||||
|
|
||||||
dynamic mid;
|
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
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@@ -28,25 +72,25 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void checkIsEnd(int length) {
|
void checkIsEnd(int length) {
|
||||||
if (length >= count.value) {
|
if (length >= baseCtr.counts[laterViewType]!) {
|
||||||
isEnd = true;
|
isEnd = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool customHandleResponse(bool isRefresh, Success response) {
|
bool customHandleResponse(bool isRefresh, Success response) {
|
||||||
count.value = response.response['count'];
|
baseCtr.counts[laterViewType] = response.response['count'];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future toViewDel(BuildContext context, {index, aid}) async {
|
// single
|
||||||
|
Future toViewDel(BuildContext context, int index, int? aid) async {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('提示'),
|
title: const Text('提示'),
|
||||||
content: Text(
|
content: Text('即将移除该视频,确定是否移除'),
|
||||||
aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: Get.back,
|
onPressed: Get.back,
|
||||||
@@ -57,23 +101,19 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var res =
|
Get.back();
|
||||||
await UserHttp.toViewDel(aids: aid != null ? [aid] : null);
|
var res = await UserHttp.toViewDel(aids: [aid]);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
if (aid != null) {
|
|
||||||
List<HotVideoItemModel> list =
|
List<HotVideoItemModel> list =
|
||||||
(loadingState.value as Success).response;
|
(loadingState.value as Success).response;
|
||||||
list.removeAt(index);
|
list.removeAt(index);
|
||||||
count.value -= 1;
|
baseCtr.counts[laterViewType] =
|
||||||
|
baseCtr.counts[laterViewType]! - 1;
|
||||||
loadingState.refresh();
|
loadingState.refresh();
|
||||||
} else {
|
|
||||||
onReload();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Get.back();
|
|
||||||
SmartDialog.showToast(res['msg']);
|
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(
|
showConfirmDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: '清空确认',
|
title: '确认',
|
||||||
content: '确定要清空你的稍后再看列表吗?',
|
content: content,
|
||||||
onConfirm: () async {
|
onConfirm: () async {
|
||||||
var res = await UserHttp.toViewClear();
|
var res = await UserHttp.toViewClear(cleanType);
|
||||||
if (res['status']) {
|
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']);
|
SmartDialog.showToast(res['msg']);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<LoadingState<Map>> customGetData() => UserHttp.seeYouLater();
|
|
||||||
|
|
||||||
onDelChecked(BuildContext context) {
|
onDelChecked(BuildContext context) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -142,11 +191,12 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
|||||||
((loadingState.value as Success).response as List<HotVideoItemModel>)
|
((loadingState.value as Success).response as List<HotVideoItemModel>)
|
||||||
.toSet()
|
.toSet()
|
||||||
.difference(result.toSet());
|
.difference(result.toSet());
|
||||||
count.value -= aids.length;
|
baseCtr.counts[laterViewType] =
|
||||||
|
baseCtr.counts[laterViewType]! - aids.length;
|
||||||
loadingState.value = LoadingState.success(remainList.toList());
|
loadingState.value = LoadingState.success(remainList.toList());
|
||||||
if (enableMultiSelect.value) {
|
if (baseCtr.enableMultiSelect.value) {
|
||||||
checkedCount.value = 0;
|
baseCtr.checkedCount.value = 0;
|
||||||
enableMultiSelect.value = false;
|
baseCtr.enableMultiSelect.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
@@ -156,9 +206,10 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
|
|||||||
// 稍后再看播放全部
|
// 稍后再看播放全部
|
||||||
void toViewPlayAll() {
|
void toViewPlayAll() {
|
||||||
if (loadingState.value is Success) {
|
if (loadingState.value is Success) {
|
||||||
List<HotVideoItemModel> list = List<HotVideoItemModel>.from(
|
List<HotVideoItemModel>? list = (loadingState.value as Success).response;
|
||||||
(loadingState.value as Success).response);
|
if (list.isNullOrEmpty) return;
|
||||||
for (HotVideoItemModel item in list) {
|
|
||||||
|
for (HotVideoItemModel item in list!) {
|
||||||
if (item.cid == null || item.pgcLabel?.isNotEmpty == true) {
|
if (item.cid == null || item.pgcLabel?.isNotEmpty == true) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/models/model_hot_video_item.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/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/extension.dart';
|
||||||
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.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 '../../common/constants.dart';
|
enum LaterViewType { all, toView, unfinished, viewed }
|
||||||
import '../../utils/grid.dart';
|
|
||||||
|
extension LaterViewTypeExt on LaterViewType {
|
||||||
|
int get type => index;
|
||||||
|
|
||||||
|
String get title => ['全部', '未看', '未看完', '已看完'][index];
|
||||||
|
|
||||||
|
Widget get page => LaterViewChildPage(laterViewType: this);
|
||||||
|
}
|
||||||
|
|
||||||
class LaterPage extends StatefulWidget {
|
class LaterPage extends StatefulWidget {
|
||||||
const LaterPage({super.key});
|
const LaterPage({super.key});
|
||||||
@@ -22,51 +30,216 @@ class LaterPage extends StatefulWidget {
|
|||||||
State<LaterPage> createState() => _LaterPageState();
|
State<LaterPage> createState() => _LaterPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LaterPageState extends State<LaterPage> {
|
class _LaterPageState extends State<LaterPage>
|
||||||
final LaterController _laterController = Get.put(LaterController());
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Obx(
|
return Obx(
|
||||||
() => PopScope(
|
() => PopScope(
|
||||||
canPop: _laterController.enableMultiSelect.value.not,
|
canPop: _baseCtr.enableMultiSelect.value.not,
|
||||||
onPopInvokedWithResult: (didPop, result) {
|
onPopInvokedWithResult: (didPop, result) {
|
||||||
if (_laterController.enableMultiSelect.value) {
|
if (_baseCtr.enableMultiSelect.value) {
|
||||||
_laterController.handleSelect();
|
currCtr().handleSelect();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
appBar: AppBarWidget(
|
appBar: _buildAppbar,
|
||||||
visible: _laterController.enableMultiSelect.value,
|
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(
|
child1: AppBar(
|
||||||
title: Obx(
|
title: const Text('稍后再看'),
|
||||||
() => Text(
|
|
||||||
'稍后再看${_laterController.count.value == -1 ? '' : ' (${_laterController.count.value})'}',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
Obx(
|
IconButton(
|
||||||
() => _laterController.count.value != -1
|
tooltip: '搜索',
|
||||||
? TextButton(
|
onPressed: () {
|
||||||
onPressed: () => _laterController.toViewDel(context),
|
final mid = Accounts.main.mid;
|
||||||
child: const Text('移除已看'),
|
Get.toNamed(
|
||||||
)
|
'/favSearch',
|
||||||
: const SizedBox(),
|
arguments: {
|
||||||
|
'type': 0,
|
||||||
|
'mediaId': mid,
|
||||||
|
'mid': mid,
|
||||||
|
'title': '稍后再看',
|
||||||
|
'count': _baseCtr.counts[LaterViewType.all],
|
||||||
|
'searchType': SearchType.later,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
),
|
),
|
||||||
Obx(
|
Material(
|
||||||
() => _laterController.count.value != -1
|
clipBehavior: Clip.hardEdge,
|
||||||
? IconButton(
|
borderRadius: BorderRadius.circular(20),
|
||||||
tooltip: '一键清空',
|
child: Builder(
|
||||||
onPressed: () =>
|
key: sortKey,
|
||||||
_laterController.toViewClear(context),
|
builder: (context) {
|
||||||
icon: Icon(
|
final value = currCtr().asc.value;
|
||||||
Icons.clear_all_outlined,
|
return PopupMenuButton(
|
||||||
size: 21,
|
initialValue: value,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
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),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
@@ -74,12 +247,12 @@ class _LaterPageState extends State<LaterPage> {
|
|||||||
child2: AppBar(
|
child2: AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
tooltip: '取消',
|
tooltip: '取消',
|
||||||
onPressed: _laterController.handleSelect,
|
onPressed: currCtr().handleSelect,
|
||||||
icon: const Icon(Icons.close_outlined),
|
icon: const Icon(Icons.close_outlined),
|
||||||
),
|
),
|
||||||
title: Obx(
|
title: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
'已选: ${_laterController.checkedCount.value}',
|
'已选: ${_baseCtr.checkedCount.value}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -87,19 +260,23 @@ class _LaterPageState extends State<LaterPage> {
|
|||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
||||||
),
|
),
|
||||||
onPressed: () => _laterController.handleSelect(true),
|
onPressed: () => currCtr().handleSelect(true),
|
||||||
child: const Text('全选'),
|
child: const Text('全选'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
||||||
),
|
),
|
||||||
onPressed: () => Utils.onCopyOrMove<HotVideoItemModel>(
|
onPressed: () {
|
||||||
|
final ctr = currCtr();
|
||||||
|
Utils.onCopyOrMove<Map, HotVideoItemModel>(
|
||||||
context: context,
|
context: context,
|
||||||
isCopy: true,
|
isCopy: true,
|
||||||
ctr: _laterController,
|
ctr: ctr,
|
||||||
mediaId: null,
|
mediaId: null,
|
||||||
),
|
mid: ctr.mid,
|
||||||
|
);
|
||||||
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'复制',
|
'复制',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -111,12 +288,16 @@ class _LaterPageState extends State<LaterPage> {
|
|||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
||||||
),
|
),
|
||||||
onPressed: () => Utils.onCopyOrMove<HotVideoItemModel>(
|
onPressed: () {
|
||||||
|
final ctr = currCtr();
|
||||||
|
Utils.onCopyOrMove<Map, HotVideoItemModel>(
|
||||||
context: context,
|
context: context,
|
||||||
isCopy: false,
|
isCopy: false,
|
||||||
ctr: _laterController,
|
ctr: ctr,
|
||||||
mediaId: null,
|
mediaId: null,
|
||||||
),
|
mid: ctr.mid,
|
||||||
|
);
|
||||||
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'移动',
|
'移动',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -128,196 +309,15 @@ class _LaterPageState extends State<LaterPage> {
|
|||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
|
||||||
),
|
),
|
||||||
onPressed: () => _laterController.onDelChecked(context),
|
onPressed: () => currCtr().onDelChecked(context),
|
||||||
child: Text(
|
child: Text(
|
||||||
'移除',
|
'移除',
|
||||||
style:
|
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||||
TextStyle(color: Theme.of(context).colorScheme.error),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
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 {
|
void toViewPlayAll() async {
|
||||||
if (loadingState.value is Success) {
|
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 == '继续播放') {
|
if (episodicButton.value.text == '继续播放') {
|
||||||
dynamic oid = RegExp(r'oid=([\d]+)')
|
dynamic oid = RegExp(r'oid=([\d]+)')
|
||||||
@@ -173,7 +175,7 @@ class MemberVideoCtr extends CommonListController<Data, Item> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Item element in list) {
|
for (Item element in list!) {
|
||||||
if (element.cid == null) {
|
if (element.cid == null) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -102,9 +102,13 @@ class WhisperController extends GetxController {
|
|||||||
Future querySessionList(String? type) async {
|
Future querySessionList(String? type) async {
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
var res = await MsgHttp.sessionList(
|
var res = await MsgHttp.sessionList(
|
||||||
endTs: type == 'onLoad' ? sessionList.last.sessionTs : null);
|
endTs: type == 'onLoad' ? sessionList.last.sessionTs : null,
|
||||||
if (res['data'].sessionList != null && res['data'].sessionList.isNotEmpty) {
|
);
|
||||||
await queryAccountList(res['data'].sessionList);
|
if (res['status']) {
|
||||||
|
List<SessionList>? sessionList = res['data'];
|
||||||
|
if (sessionList != null) {
|
||||||
|
if (sessionList.isNotEmpty) {
|
||||||
|
await queryAccountList(sessionList);
|
||||||
// 将 accountList 转换为 Map 结构
|
// 将 accountList 转换为 Map 结构
|
||||||
Map<int, dynamic> accountMap = {};
|
Map<int, dynamic> accountMap = {};
|
||||||
for (var j in accountList) {
|
for (var j in accountList) {
|
||||||
@@ -112,7 +116,7 @@ class WhisperController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 遍历 sessionList,通过 mid 查找并赋值 accountInfo
|
// 遍历 sessionList,通过 mid 查找并赋值 accountInfo
|
||||||
for (var i in res['data'].sessionList) {
|
for (var i in sessionList) {
|
||||||
var accountInfo = accountMap[i.talkerId];
|
var accountInfo = accountMap[i.talkerId];
|
||||||
if (accountInfo != null) {
|
if (accountInfo != null) {
|
||||||
i.accountInfo = accountInfo;
|
i.accountInfo = accountInfo;
|
||||||
@@ -126,11 +130,12 @@ class WhisperController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (res['status'] && res['data'].sessionList != null) {
|
|
||||||
if (type == 'onLoad') {
|
if (type == 'onLoad') {
|
||||||
sessionList.addAll(res['data'].sessionList);
|
this.sessionList.addAll(sessionList);
|
||||||
} else {
|
} else {
|
||||||
sessionList.value = res['data'].sessionList;
|
this.sessionList.value = sessionList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
|||||||
@@ -78,9 +78,11 @@ class _WhisperPageState extends State<WhisperPage> {
|
|||||||
_whisperController.onRefresh(),
|
_whisperController.onRefresh(),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
// TODO: refactor
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: EdgeInsets.only(bottom: 80),
|
padding: EdgeInsets.only(bottom: 80),
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
children: [
|
children: [
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
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/dynamics/result.dart';
|
||||||
import 'package:PiliPlus/models/live/item.dart';
|
import 'package:PiliPlus/models/live/item.dart';
|
||||||
import 'package:PiliPlus/models/user/fav_folder.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/dynamics/tab/controller.dart';
|
||||||
import 'package:PiliPlus/pages/later/controller.dart';
|
import 'package:PiliPlus/pages/later/controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/fav_panel.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 BuildContext context,
|
||||||
required bool isCopy,
|
required bool isCopy,
|
||||||
required dynamic ctr,
|
required MultiSelectController<R, T> ctr,
|
||||||
required dynamic mediaId,
|
required dynamic mediaId,
|
||||||
|
required dynamic mid,
|
||||||
}) {
|
}) {
|
||||||
VideoHttp.allFavFolders(ctr.mid).then((res) {
|
VideoHttp.allFavFolders(mid).then((res) {
|
||||||
if (context.mounted &&
|
if (context.mounted &&
|
||||||
res['status'] &&
|
res['status'] &&
|
||||||
(res['data'].list as List?)?.isNotEmpty == true) {
|
(res['data'].list as List?)?.isNotEmpty == true) {
|
||||||
@@ -720,8 +722,8 @@ class Utils {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (checkedId != null) {
|
if (checkedId != null) {
|
||||||
List resources =
|
List resources = ((ctr.loadingState.value as Success)
|
||||||
((ctr.loadingState.value as Success).response as List)
|
.response as List<T>)
|
||||||
.where((e) => e.checked == true)
|
.where((e) => e.checked == true)
|
||||||
.toList();
|
.toList();
|
||||||
SmartDialog.showLoading();
|
SmartDialog.showLoading();
|
||||||
@@ -735,7 +737,7 @@ class Utils {
|
|||||||
? item.aid
|
? item.aid
|
||||||
: '${item.id}:${item.type}')
|
: '${item.id}:${item.type}')
|
||||||
.toList(),
|
.toList(),
|
||||||
mid: isCopy ? ctr.mid : null,
|
mid: isCopy ? mid : null,
|
||||||
).then((res) {
|
).then((res) {
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
ctr.handleSelect(false);
|
ctr.handleSelect(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user