mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
opt ui
opt req Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -33,7 +33,7 @@ class MultiSelectAppBarWidget extends StatelessWidget
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
onPressed: () => ctr.handleSelect(true),
|
||||
onPressed: () => ctr.handleSelect(checked: true),
|
||||
child: const Text('全选'),
|
||||
),
|
||||
...?children,
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget selectMask(ThemeData theme, bool checked) {
|
||||
Widget selectMask(
|
||||
ThemeData theme,
|
||||
bool checked, {
|
||||
BorderRadiusGeometry borderRadius = StyleString.mdRadius,
|
||||
}) {
|
||||
return AnimatedOpacity(
|
||||
opacity: checked ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: StyleString.mdRadius,
|
||||
borderRadius: borderRadius,
|
||||
color: Colors.black.withValues(alpha: 0.6),
|
||||
),
|
||||
child: AnimatedScale(
|
||||
|
||||
@@ -134,9 +134,9 @@ class Api {
|
||||
// aid num 稿件avid 必要(可选) avid与bvid任选一个
|
||||
// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
|
||||
// csrf str CSRF Token(位于cookie) 必要
|
||||
static const String oneThree = '/x/web-interface/archive/like/triple';
|
||||
static const String ugcTriple = '/x/web-interface/archive/like/triple';
|
||||
|
||||
static const String triple = '/pgc/season/episode/like/triple';
|
||||
static const String pgcTriple = '/pgc/season/episode/like/triple';
|
||||
|
||||
// 获取指定用户创建的所有收藏夹信息
|
||||
// 该接口也能查询目标内容id存在于那些收藏夹中
|
||||
|
||||
@@ -27,7 +27,7 @@ class DanmakuHttp {
|
||||
// assert(aid != null || bvid != null);
|
||||
// assert(csrf != null || access_key != null);
|
||||
// 构建参数对象
|
||||
var params = <String, dynamic>{
|
||||
var data = <String, dynamic>{
|
||||
'type': type,
|
||||
'oid': oid,
|
||||
'msg': msg,
|
||||
@@ -47,10 +47,8 @@ class DanmakuHttp {
|
||||
|
||||
var response = await Request().post(
|
||||
Api.shootDanmaku,
|
||||
data: params,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
data: data,
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
return {
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/models/user/danmaku_block.dart';
|
||||
import 'package:PiliPlus/utils/accounts.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
class DanmakuFilterHttp {
|
||||
static Future danmakuFilter() async {
|
||||
@@ -22,10 +23,11 @@ class DanmakuFilterHttp {
|
||||
static Future danmakuFilterDel({required int ids}) async {
|
||||
var res = await Request().post(
|
||||
Api.danmakuFilterDel,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'ids': ids,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -43,11 +45,12 @@ class DanmakuFilterHttp {
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.danmakuFilterAdd,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'type': type,
|
||||
'filter': filter,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
|
||||
@@ -103,18 +103,20 @@ class FavHttp {
|
||||
var res = type == 11
|
||||
? await Request().post(
|
||||
Api.unfavFolder,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'media_id': id,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
)
|
||||
: await Request().post(
|
||||
Api.unfavSeason,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'platform': 'web',
|
||||
'season_id': id,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
|
||||
@@ -420,9 +420,7 @@ class LiveHttp {
|
||||
var res = await Request().post(
|
||||
Api.setLiveFavTag,
|
||||
data: data,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
|
||||
@@ -28,7 +28,6 @@ import 'package:PiliPlus/utils/accounts.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:PiliPlus/utils/wbi_sign.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MemberHttp {
|
||||
static Future reportMember(
|
||||
@@ -38,16 +37,14 @@ class MemberHttp {
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
HttpString.spaceBaseUrl + Api.reportMember,
|
||||
data: FormData.fromMap(
|
||||
{
|
||||
'mid': mid,
|
||||
'reason': reason,
|
||||
'reason_v2': ?reasonV2,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
),
|
||||
data: {
|
||||
'mid': mid,
|
||||
'reason': reason,
|
||||
'reason_v2': ?reasonV2,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
debugPrint('report: ${res.data}');
|
||||
return {
|
||||
'status': res.data['status'],
|
||||
'msg': res.data['message'] ?? res.data['data'],
|
||||
@@ -510,9 +507,7 @@ class MemberHttp {
|
||||
'fid': fid,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -538,9 +533,7 @@ class MemberHttp {
|
||||
'csrf': Accounts.main.csrf,
|
||||
// 'cross_domain': true
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'msg': '操作成功'};
|
||||
@@ -591,9 +584,7 @@ class MemberHttp {
|
||||
'tag': tagName,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -614,9 +605,7 @@ class MemberHttp {
|
||||
'name': name,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -636,9 +625,7 @@ class MemberHttp {
|
||||
'tagid': tagid,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
|
||||
@@ -187,16 +187,14 @@ class MsgHttp {
|
||||
String? biz,
|
||||
CancelToken? cancelToken,
|
||||
}) async {
|
||||
final file = await MultipartFile.fromFile(path);
|
||||
Map<String, dynamic> data = {
|
||||
'file_up': file,
|
||||
'category': ?category,
|
||||
'biz': ?biz,
|
||||
'csrf': Accounts.main.csrf,
|
||||
};
|
||||
var res = await Request().post(
|
||||
Api.uploadBfs,
|
||||
data: FormData.fromMap(data),
|
||||
data: FormData.fromMap({
|
||||
'file_up': await MultipartFile.fromFile(path),
|
||||
'category': ?category,
|
||||
'biz': ?biz,
|
||||
'csrf': Accounts.main.csrf,
|
||||
}),
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -226,7 +224,8 @@ class MsgHttp {
|
||||
});
|
||||
var res = await Request().post(
|
||||
HttpString.tUrl + Api.createTextDynamic,
|
||||
data: FormData.fromMap(data),
|
||||
data: data,
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -272,7 +271,8 @@ class MsgHttp {
|
||||
});
|
||||
var res = await Request().post(
|
||||
HttpString.tUrl + Api.removeMsg,
|
||||
data: FormData.fromMap(data),
|
||||
data: data,
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -296,9 +296,7 @@ class MsgHttp {
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -354,7 +352,8 @@ class MsgHttp {
|
||||
});
|
||||
var res = await Request().post(
|
||||
HttpString.tUrl + Api.setTop,
|
||||
data: FormData.fromMap(data),
|
||||
data: data,
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -407,7 +406,7 @@ class MsgHttp {
|
||||
}) async {
|
||||
String csrf = Accounts.main.csrf;
|
||||
final devId = getDevId();
|
||||
Map<String, dynamic> base = {
|
||||
Map<String, dynamic> data = {
|
||||
'msg': {
|
||||
'sender_uid': senderUid,
|
||||
'receiver_id': receiverId,
|
||||
@@ -425,7 +424,7 @@ class MsgHttp {
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
};
|
||||
Map<String, dynamic> params = await WbiSign.makSign(base);
|
||||
Map<String, dynamic> params = await WbiSign.makSign(data);
|
||||
var res = await Request().post(
|
||||
Api.sendMsg,
|
||||
queryParameters: <String, dynamic>{
|
||||
@@ -435,7 +434,7 @@ class MsgHttp {
|
||||
'w_rid': params['w_rid'],
|
||||
'wts': params['wts'],
|
||||
},
|
||||
data: base,
|
||||
data: data,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
|
||||
@@ -196,9 +196,7 @@ class ReplyHttp {
|
||||
'action': action,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
@@ -216,13 +214,14 @@ class ReplyHttp {
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.likeReply,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'type': type,
|
||||
'oid': oid,
|
||||
'rpid': rpid,
|
||||
'action': action,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
@@ -263,9 +262,7 @@ class ReplyHttp {
|
||||
'action': isUpTop ? 0 : 1,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
|
||||
@@ -106,12 +106,15 @@ class UserHttp {
|
||||
account ??= Accounts.history;
|
||||
var res = await Request().post(
|
||||
Api.pauseHistory,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'switch': switchStatus,
|
||||
'jsonp': 'jsonp',
|
||||
'csrf': account.csrf,
|
||||
},
|
||||
options: Options(extra: {'account': account}),
|
||||
options: Options(
|
||||
extra: {'account': account},
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
return res;
|
||||
}
|
||||
@@ -134,11 +137,14 @@ class UserHttp {
|
||||
account ??= Accounts.history;
|
||||
var res = await Request().post(
|
||||
Api.clearHistory,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'jsonp': 'jsonp',
|
||||
'csrf': account.csrf,
|
||||
},
|
||||
options: Options(extra: {'account': account}),
|
||||
options: Options(
|
||||
extra: {'account': account},
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
return res;
|
||||
}
|
||||
@@ -147,11 +153,12 @@ class UserHttp {
|
||||
static Future toViewLater({String? bvid, dynamic aid}) async {
|
||||
var res = await Request().post(
|
||||
Api.toViewLater,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'aid': ?aid,
|
||||
'bvid': ?bvid,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'msg': 'yeah!稍后再看'};
|
||||
@@ -201,10 +208,11 @@ class UserHttp {
|
||||
static Future toViewClear([int? cleanType]) async {
|
||||
var res = await Request().post(
|
||||
Api.toViewClear,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'clean_type': ?cleanType,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'msg': '操作完成'};
|
||||
@@ -369,9 +377,7 @@ class UserHttp {
|
||||
"reason_type": reasonType,
|
||||
"reason_desc": reasonType == 0 ? reasonDesc : null,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
return res.data as Map;
|
||||
}
|
||||
|
||||
@@ -360,9 +360,9 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 一键三连 pgc
|
||||
static Future triple({dynamic epId, required dynamic seasonId}) async {
|
||||
static Future pgcTriple({dynamic epId, required dynamic seasonId}) async {
|
||||
var res = await Request().post(
|
||||
Api.triple,
|
||||
Api.pgcTriple,
|
||||
data: {
|
||||
'ep_id': epId,
|
||||
'csrf': Accounts.main.csrf,
|
||||
@@ -384,9 +384,9 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 一键三连
|
||||
static Future oneThree({required String bvid}) async {
|
||||
static Future ugcTriple({required String bvid}) async {
|
||||
var res = await Request().post(
|
||||
Api.oneThree,
|
||||
Api.ugcTriple,
|
||||
data: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
'eab_x': 2,
|
||||
@@ -394,6 +394,8 @@ class VideoHttp {
|
||||
'source': 'web_normal',
|
||||
'ga': 1,
|
||||
'csrf': Accounts.main.csrf,
|
||||
'spmid': '333.788.0.0',
|
||||
'statistics': '{"appId":100,"platform":5}',
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
@@ -529,7 +531,7 @@ class VideoHttp {
|
||||
if (message == '') {
|
||||
return {'status': false, 'msg': '请输入评论内容'};
|
||||
}
|
||||
Map<String, dynamic> data = {
|
||||
final data = {
|
||||
'type': type,
|
||||
'oid': oid,
|
||||
if (root != null && root != 0) 'root': root,
|
||||
@@ -560,12 +562,13 @@ class VideoHttp {
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.replyDel,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'type': type, //type.index
|
||||
'oid': oid,
|
||||
'rpid': rpid,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -637,11 +640,12 @@ class VideoHttp {
|
||||
}) async {
|
||||
await Request().post(
|
||||
Api.historyReport,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'aid': ?aid,
|
||||
'type': ?type,
|
||||
'csrf': Accounts.heartbeat.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -659,7 +663,7 @@ class VideoHttp {
|
||||
final isPugv = videoType == VideoType.pugv;
|
||||
await Request().post(
|
||||
Api.heartBeat,
|
||||
queryParameters: {
|
||||
data: {
|
||||
if (isPugv) 'aid': ?aid else 'bvid': ?bvid,
|
||||
'cid': cid,
|
||||
'epid': ?epid,
|
||||
@@ -669,6 +673,7 @@ class VideoHttp {
|
||||
'played_time': progress,
|
||||
'csrf': Accounts.heartbeat.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -679,12 +684,13 @@ class VideoHttp {
|
||||
}) async {
|
||||
await Request().post(
|
||||
Api.mediaListHistory,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'desc': desc,
|
||||
'oid': oid,
|
||||
'upper_mid': upperMid,
|
||||
'csrf': Accounts.heartbeat.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -692,10 +698,11 @@ class VideoHttp {
|
||||
static Future pgcAdd({int? seasonId}) async {
|
||||
var res = await Request().post(
|
||||
Api.pgcAdd,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'season_id': seasonId,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
@@ -718,10 +725,11 @@ class VideoHttp {
|
||||
static Future pgcDel({int? seasonId}) async {
|
||||
var res = await Request().post(
|
||||
Api.pgcDel,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'season_id': seasonId,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
|
||||
@@ -16,7 +16,6 @@ import 'package:PiliPlus/utils/accounts.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
import 'package:PiliPlus/utils/url_utils.dart';
|
||||
import 'package:flutter/rendering.dart' show ScrollDirection;
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
@@ -227,22 +226,6 @@ class ArticleController extends CommonDynController<MainListReply> {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void listener() {
|
||||
showTitle.value = scrollController.positions.last.pixels >= 45;
|
||||
final ScrollDirection direction1 =
|
||||
scrollController.positions.first.userScrollDirection;
|
||||
late final ScrollDirection direction2 =
|
||||
scrollController.positions.last.userScrollDirection;
|
||||
if (direction1 == ScrollDirection.forward ||
|
||||
direction2 == ScrollDirection.forward) {
|
||||
showFab();
|
||||
} else if (direction1 == ScrollDirection.reverse ||
|
||||
direction2 == ScrollDirection.reverse) {
|
||||
hideFab();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Summary {
|
||||
|
||||
@@ -81,112 +81,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
double padding = max(
|
||||
context.width / 2 - Grid.smallCardWidth,
|
||||
0,
|
||||
);
|
||||
if (isPortrait) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxWidth =
|
||||
constraints.maxWidth - 2 * padding - 24;
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
_buildContent(theme, maxWidth),
|
||||
SliverToBoxAdapter(
|
||||
child: Divider(
|
||||
thickness: 8,
|
||||
color: theme.dividerColor.withValues(
|
||||
alpha: 0.05,
|
||||
),
|
||||
),
|
||||
),
|
||||
buildReplyHeader(theme),
|
||||
Obx(
|
||||
() => _buildReplyList(
|
||||
theme,
|
||||
controller.loadingState.value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: controller.ratio[0].toInt(),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxWidth =
|
||||
constraints.maxWidth - padding / 4 - 24;
|
||||
return CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
left: padding / 4,
|
||||
bottom:
|
||||
MediaQuery.paddingOf(context).bottom +
|
||||
80,
|
||||
),
|
||||
sliver: _buildContent(theme, maxWidth),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
VerticalDivider(
|
||||
thickness: 8,
|
||||
color: theme.dividerColor.withValues(alpha: 0.05),
|
||||
),
|
||||
Expanded(
|
||||
flex: controller.ratio[1].toInt(),
|
||||
child: Scaffold(
|
||||
key: scaffoldKey,
|
||||
backgroundColor: Colors.transparent,
|
||||
body: refreshIndicator(
|
||||
onRefresh: controller.onRefresh,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: padding / 4),
|
||||
child: CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics:
|
||||
const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
buildReplyHeader(theme),
|
||||
Obx(
|
||||
() => _buildReplyList(
|
||||
theme,
|
||||
controller.loadingState.value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
_buildPage(theme, isPortrait),
|
||||
_buildBottom(theme),
|
||||
],
|
||||
),
|
||||
@@ -194,6 +89,103 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPage(ThemeData theme, bool isPortrait) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
double padding = max(
|
||||
context.width / 2 - Grid.smallCardWidth,
|
||||
0,
|
||||
);
|
||||
|
||||
if (isPortrait) {
|
||||
final maxWidth = constraints.maxWidth - 2 * padding - 24;
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
_buildContent(theme, maxWidth),
|
||||
SliverToBoxAdapter(
|
||||
child: Divider(
|
||||
thickness: 8,
|
||||
color: theme.dividerColor.withValues(
|
||||
alpha: 0.05,
|
||||
),
|
||||
),
|
||||
),
|
||||
buildReplyHeader(theme),
|
||||
Obx(
|
||||
() => _buildReplyList(
|
||||
theme,
|
||||
controller.loadingState.value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
padding = padding / 4;
|
||||
final flex = controller.ratio[0].toInt();
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: flex,
|
||||
child: CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
left: padding,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: _buildContent(
|
||||
theme,
|
||||
constraints.maxWidth * flex - padding - 24,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
VerticalDivider(
|
||||
thickness: 8,
|
||||
color: theme.dividerColor.withValues(alpha: 0.05),
|
||||
),
|
||||
Expanded(
|
||||
flex: controller.ratio[1].toInt(),
|
||||
child: Scaffold(
|
||||
key: scaffoldKey,
|
||||
backgroundColor: Colors.transparent,
|
||||
body: refreshIndicator(
|
||||
onRefresh: controller.onRefresh,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: padding),
|
||||
child: CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
buildReplyHeader(theme),
|
||||
Obx(
|
||||
() => _buildReplyList(
|
||||
theme,
|
||||
controller.loadingState.value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(ThemeData theme, double maxWidth) => SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
sliver: Obx(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/image_view.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/models/common/image_preview_type.dart';
|
||||
@@ -73,75 +72,71 @@ class OpusContent extends StatelessWidget {
|
||||
switch (element.paraType) {
|
||||
case 1 || 4:
|
||||
final isQuote = element.paraType == 4;
|
||||
Widget widget = SelectionArea(
|
||||
child: Text.rich(
|
||||
textAlign: element.align == 1 ? TextAlign.center : null,
|
||||
TextSpan(
|
||||
children: element.text?.nodes?.map((item) {
|
||||
switch (item.type) {
|
||||
case 'TEXT_NODE_TYPE_RICH' when (item.rich != null):
|
||||
Rich rich = item.rich!;
|
||||
switch (rich.type) {
|
||||
case 'RICH_TEXT_NODE_TYPE_EMOJI':
|
||||
Emoji emoji = rich.emoji!;
|
||||
final size = 20.0 * emoji.size;
|
||||
return WidgetSpan(
|
||||
child: NetworkImgLayer(
|
||||
width: size,
|
||||
height: size,
|
||||
src: emoji.url,
|
||||
type: ImageType.emote,
|
||||
),
|
||||
);
|
||||
default:
|
||||
return TextSpan(
|
||||
text:
|
||||
'${rich.type == 'RICH_TEXT_NODE_TYPE_WEB' ? '\u{1F517}' : ''}${item.rich!.text}',
|
||||
style: _getStyle(
|
||||
rich.style,
|
||||
rich.type == 'RICH_TEXT_NODE_TYPE_TEXT'
|
||||
? null
|
||||
: colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
switch (rich.type) {
|
||||
case 'RICH_TEXT_NODE_TYPE_AT':
|
||||
Get.toNamed('/member?mid=${rich.rid}');
|
||||
// case 'RICH_TEXT_NODE_TYPE_TOPIC':
|
||||
default:
|
||||
if (rich.jumpUrl != null) {
|
||||
PiliScheme.routePushFromUrl(
|
||||
rich.jumpUrl!,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
case 'TEXT_NODE_TYPE_FORMULA'
|
||||
when (item.formula != null):
|
||||
return WidgetSpan(
|
||||
child: CachedNetworkSVGImage(
|
||||
height: 65,
|
||||
'https://api.bilibili.com/x/web-frontend/mathjax/tex?formula=${Uri.encodeComponent(item.formula!.latexContent!)}',
|
||||
colorFilter: ColorFilter.mode(
|
||||
colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn,
|
||||
Widget widget = SelectableText.rich(
|
||||
textAlign: element.align == 1 ? TextAlign.center : null,
|
||||
TextSpan(
|
||||
children: element.text?.nodes?.map((item) {
|
||||
switch (item.type) {
|
||||
case 'TEXT_NODE_TYPE_RICH' when (item.rich != null):
|
||||
Rich rich = item.rich!;
|
||||
switch (rich.type) {
|
||||
case 'RICH_TEXT_NODE_TYPE_EMOJI':
|
||||
Emoji emoji = rich.emoji!;
|
||||
final size = 20.0 * emoji.size;
|
||||
return WidgetSpan(
|
||||
child: NetworkImgLayer(
|
||||
width: size,
|
||||
height: size,
|
||||
src: emoji.url,
|
||||
type: ImageType.emote,
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
placeholderBuilder: (_) =>
|
||||
const SizedBox.shrink(),
|
||||
);
|
||||
default:
|
||||
return TextSpan(
|
||||
text:
|
||||
'${rich.type == 'RICH_TEXT_NODE_TYPE_WEB' ? '\u{1F517}' : ''}${item.rich!.text}',
|
||||
style: _getStyle(
|
||||
rich.style,
|
||||
rich.type == 'RICH_TEXT_NODE_TYPE_TEXT'
|
||||
? null
|
||||
: colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
switch (rich.type) {
|
||||
case 'RICH_TEXT_NODE_TYPE_AT':
|
||||
Get.toNamed('/member?mid=${rich.rid}');
|
||||
// case 'RICH_TEXT_NODE_TYPE_TOPIC':
|
||||
default:
|
||||
if (rich.jumpUrl != null) {
|
||||
PiliScheme.routePushFromUrl(
|
||||
rich.jumpUrl!,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
case 'TEXT_NODE_TYPE_FORMULA' when (item.formula != null):
|
||||
return WidgetSpan(
|
||||
child: CachedNetworkSVGImage(
|
||||
height: 65,
|
||||
'https://api.bilibili.com/x/web-frontend/mathjax/tex?formula=${Uri.encodeComponent(item.formula!.latexContent!)}',
|
||||
colorFilter: ColorFilter.mode(
|
||||
colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
);
|
||||
default:
|
||||
return _getSpan(
|
||||
item.word,
|
||||
isQuote ? colorScheme.onSurfaceVariant : null,
|
||||
);
|
||||
}
|
||||
}).toList(),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
placeholderBuilder: (_) => const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return _getSpan(
|
||||
item.word,
|
||||
isQuote ? colorScheme.onSurfaceVariant : null,
|
||||
);
|
||||
}
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
if (isQuote) {
|
||||
@@ -223,25 +218,23 @@ class OpusContent extends StatelessWidget {
|
||||
imageUrl: ImageUtil.thumbnailUrl(element.line!.pic!.url!),
|
||||
);
|
||||
case 5 when (element.list != null):
|
||||
return SelectionArea(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: element.list!.items?.indexed.map((entry) {
|
||||
return TextSpan(
|
||||
children: [
|
||||
const WidgetSpan(
|
||||
child: Icon(MdiIcons.circleMedium),
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
),
|
||||
...entry.$2.nodes!.map((item) {
|
||||
return _getSpan(item.word);
|
||||
}),
|
||||
if (entry.$1 < element.list!.items!.length - 1)
|
||||
const TextSpan(text: '\n'),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
return SelectableText.rich(
|
||||
TextSpan(
|
||||
children: element.list!.items?.indexed.map((entry) {
|
||||
return TextSpan(
|
||||
children: [
|
||||
const WidgetSpan(
|
||||
child: Icon(MdiIcons.circleMedium),
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
),
|
||||
...entry.$2.nodes!.map((item) {
|
||||
return _getSpan(item.word);
|
||||
}),
|
||||
if (entry.$1 < element.list!.items!.length - 1)
|
||||
const TextSpan(text: '\n'),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
case 6:
|
||||
@@ -292,7 +285,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: element.linkCard!.card!.ugc!.cover,
|
||||
),
|
||||
@@ -331,7 +324,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: element.linkCard!.card!.common!.cover,
|
||||
),
|
||||
@@ -368,7 +361,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: element.linkCard!.card!.live!.cover,
|
||||
),
|
||||
@@ -405,7 +398,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: element.linkCard!.card!.opus!.cover,
|
||||
),
|
||||
@@ -466,7 +459,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: element.linkCard!.card!.music!.cover,
|
||||
),
|
||||
@@ -504,7 +497,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: e.cover,
|
||||
),
|
||||
@@ -570,41 +563,35 @@ class OpusContent extends StatelessWidget {
|
||||
color: colorScheme.onInverseSurface,
|
||||
),
|
||||
width: double.infinity,
|
||||
child: SelectionArea(child: Text.rich(renderer.span!)),
|
||||
child: SelectableText.rich(renderer.span!),
|
||||
);
|
||||
default:
|
||||
if (kDebugMode) debugPrint('unknown type ${element.paraType}');
|
||||
if (element.text?.nodes?.isNotEmpty == true) {
|
||||
return SelectionArea(
|
||||
child: Text.rich(
|
||||
textAlign: element.align == 1 ? TextAlign.center : null,
|
||||
TextSpan(
|
||||
children: element.text!.nodes!
|
||||
.map<TextSpan>((item) => _getSpan(item.word))
|
||||
.toList(),
|
||||
),
|
||||
return SelectableText.rich(
|
||||
textAlign: element.align == 1 ? TextAlign.center : null,
|
||||
TextSpan(
|
||||
children: element.text!.nodes!
|
||||
.map<TextSpan>((item) => _getSpan(item.word))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SelectionArea(
|
||||
child: Text(
|
||||
'不支持的类型 (${element.paraType})',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
),
|
||||
return SelectableText(
|
||||
'不支持的类型 (${element.paraType})',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return SelectionArea(
|
||||
child: Text(
|
||||
'错误的类型 $e',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
),
|
||||
return SelectableText(
|
||||
'错误的类型 $e',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -676,7 +663,7 @@ Widget moduleBlockedItem(
|
||||
CachedNetworkImage(
|
||||
height: 16,
|
||||
color: Colors.white,
|
||||
imageUrl: moduleBlocked.button!.icon!,
|
||||
imageUrl: moduleBlocked.button!.icon!.http2https,
|
||||
),
|
||||
Text(moduleBlocked.button!.text ?? ''),
|
||||
],
|
||||
|
||||
@@ -12,8 +12,8 @@ abstract class MultiSelectBase<T> {
|
||||
|
||||
int get checkedCount;
|
||||
|
||||
void onSelect(T item, [bool disableSelect = true]);
|
||||
void handleSelect([bool checked = false, bool disableSelect = true]);
|
||||
void onSelect(T item);
|
||||
void handleSelect({bool checked = false, bool disableSelect = true});
|
||||
void onRemove();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ mixin CommonMultiSelectMixin<T extends MultiSelectData>
|
||||
loadingState.value.data!.where((v) => v.checked == true);
|
||||
|
||||
@override
|
||||
void onSelect(T item, [bool disableSelect = true]) {
|
||||
void onSelect(T item) {
|
||||
List<T> list = loadingState.value.data!;
|
||||
item.checked = !(item.checked ?? false);
|
||||
if (item.checked!) {
|
||||
@@ -43,17 +43,15 @@ mixin CommonMultiSelectMixin<T extends MultiSelectData>
|
||||
rxCount.value--;
|
||||
}
|
||||
loadingState.refresh();
|
||||
if (disableSelect) {
|
||||
if (checkedCount == 0) {
|
||||
enableMultiSelect.value = false;
|
||||
}
|
||||
if (checkedCount == 0) {
|
||||
enableMultiSelect.value = false;
|
||||
} else {
|
||||
allSelected.value = checkedCount == list.length;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleSelect([bool checked = false, bool disableSelect = true]) {
|
||||
void handleSelect({bool checked = false, bool disableSelect = true}) {
|
||||
if (loadingState.value.isSuccess) {
|
||||
final list = loadingState.value.data;
|
||||
if (list?.isNotEmpty == true) {
|
||||
|
||||
@@ -14,7 +14,7 @@ Widget livePanelSub(
|
||||
int floor = 1,
|
||||
required double maxWidth,
|
||||
}) {
|
||||
maxWidth -= StyleString.safeSpace * 2;
|
||||
maxWidth -= 24;
|
||||
SubscriptionNew? subItem = item.modules.moduleDynamic!.major?.subscriptionNew;
|
||||
LivePlayInfo? content = subItem?.liveRcmd?.content?.livePlayInfo;
|
||||
if (subItem == null || content == null) {
|
||||
|
||||
@@ -14,7 +14,7 @@ Widget liveRcmdPanel(
|
||||
int floor = 1,
|
||||
required double maxWidth,
|
||||
}) {
|
||||
maxWidth -= StyleString.safeSpace * 2;
|
||||
maxWidth -= 24;
|
||||
DynamicLiveModel? liveRcmd = item.modules.moduleDynamic?.major?.liveRcmd;
|
||||
if (liveRcmd == null) {
|
||||
return const SizedBox.shrink();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/dynamic_card.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
@@ -28,46 +26,11 @@ class DynamicsTabPage extends CommonPage {
|
||||
|
||||
@override
|
||||
State<DynamicsTabPage> createState() => _DynamicsTabPageState();
|
||||
|
||||
static Widget dynSkeleton(bool dynamicsWaterfallFlow) {
|
||||
if (!dynamicsWaterfallFlow) {
|
||||
return SliverCrossAxisGroup(
|
||||
slivers: [
|
||||
const SliverFillRemaining(),
|
||||
SliverConstrainedCrossAxis(
|
||||
maxExtent: Grid.smallCardWidth * 2,
|
||||
sliver: SliverList.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return const DynamicCardSkeleton();
|
||||
},
|
||||
itemCount: 10,
|
||||
),
|
||||
),
|
||||
const SliverFillRemaining(),
|
||||
],
|
||||
);
|
||||
}
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
crossAxisSpacing: StyleString.cardSpace / 2,
|
||||
mainAxisSpacing: StyleString.cardSpace / 2,
|
||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio,
|
||||
mainAxisExtent: 50,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return const DynamicCardSkeleton();
|
||||
},
|
||||
childCount: 10,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DynamicsTabPageState
|
||||
extends CommonPageState<DynamicsTabPage, DynamicsTabController>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
with AutomaticKeepAliveClientMixin, DynMixin {
|
||||
StreamSubscription? _listener;
|
||||
late final MainController _mainController = Get.find<MainController>();
|
||||
|
||||
@@ -135,23 +98,14 @@ class _DynamicsTabPageState
|
||||
);
|
||||
}
|
||||
|
||||
late double _maxWidth;
|
||||
|
||||
Widget _buildBody(LoadingState<List<DynamicItemModel>?> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => DynamicsTabPage.dynSkeleton(
|
||||
GlobalData().dynamicsWaterfallFlow,
|
||||
),
|
||||
Loading() => dynSkeleton,
|
||||
Success(:var response) =>
|
||||
response?.isNotEmpty == true
|
||||
? GlobalData().dynamicsWaterfallFlow
|
||||
? SliverWaterfallFlow(
|
||||
gridDelegate:
|
||||
SliverWaterfallFlowDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||
crossAxisSpacing: StyleString.cardSpace / 2,
|
||||
callback: (value) => _maxWidth = value,
|
||||
),
|
||||
gridDelegate: gridDelegate,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(_, index) {
|
||||
if (index == response.length - 1) {
|
||||
@@ -162,7 +116,7 @@ class _DynamicsTabPageState
|
||||
onRemove: (idStr) =>
|
||||
controller.onRemove(index, idStr),
|
||||
onBlock: () => controller.onBlock(index),
|
||||
maxWidth: _maxWidth,
|
||||
maxWidth: maxWidth,
|
||||
);
|
||||
},
|
||||
childCount: response!.length,
|
||||
@@ -184,7 +138,7 @@ class _DynamicsTabPageState
|
||||
onRemove: (idStr) =>
|
||||
controller.onRemove(index, idStr),
|
||||
onBlock: () => controller.onBlock(index),
|
||||
maxWidth: _maxWidth,
|
||||
maxWidth: maxWidth,
|
||||
);
|
||||
},
|
||||
itemCount: response!.length,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
|
||||
import 'package:PiliPlus/common/widgets/dynamic_sliver_appbar_medium.dart';
|
||||
@@ -12,7 +11,6 @@ import 'package:PiliPlus/models_new/dynamic/dyn_topic_feed/item.dart';
|
||||
import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/top_details.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics_create/view.dart';
|
||||
import 'package:PiliPlus/pages/dynamics_tab/view.dart';
|
||||
import 'package:PiliPlus/pages/dynamics_topic/controller.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
@@ -35,7 +33,7 @@ class DynTopicPage extends StatefulWidget {
|
||||
State<DynTopicPage> createState() => _DynTopicPageState();
|
||||
}
|
||||
|
||||
class _DynTopicPageState extends State<DynTopicPage> {
|
||||
class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
final DynTopicController _controller = Get.put(
|
||||
DynTopicController(),
|
||||
tag: Utils.generateRandomString(8),
|
||||
@@ -346,23 +344,14 @@ class _DynTopicPageState extends State<DynTopicPage> {
|
||||
};
|
||||
}
|
||||
|
||||
late double _maxWidth;
|
||||
|
||||
Widget _buildBody(LoadingState<List<TopicCardItem>?> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => DynamicsTabPage.dynSkeleton(
|
||||
GlobalData().dynamicsWaterfallFlow,
|
||||
),
|
||||
Loading() => dynSkeleton,
|
||||
Success(:var response) =>
|
||||
response?.isNotEmpty == true
|
||||
? GlobalData().dynamicsWaterfallFlow
|
||||
? SliverWaterfallFlow(
|
||||
gridDelegate:
|
||||
SliverWaterfallFlowDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||
crossAxisSpacing: StyleString.cardSpace / 2,
|
||||
callback: (value) => _maxWidth = value,
|
||||
),
|
||||
gridDelegate: gridDelegate,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(_, index) {
|
||||
if (index == response.length - 1) {
|
||||
@@ -373,7 +362,7 @@ class _DynTopicPageState extends State<DynTopicPage> {
|
||||
if (item.dynamicCardItem != null) {
|
||||
return DynamicPanel(
|
||||
item: item.dynamicCardItem!,
|
||||
maxWidth: _maxWidth,
|
||||
maxWidth: maxWidth,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -396,7 +385,7 @@ class _DynTopicPageState extends State<DynTopicPage> {
|
||||
if (item.dynamicCardItem != null) {
|
||||
return DynamicPanel(
|
||||
item: item.dynamicCardItem!,
|
||||
maxWidth: _maxWidth,
|
||||
maxWidth: maxWidth,
|
||||
);
|
||||
} else {
|
||||
return Text(item.topicType ?? 'err');
|
||||
|
||||
@@ -246,6 +246,7 @@ class _EpisodePanelState extends CommonCollapseSlidePageState<EpisodePanel> {
|
||||
}
|
||||
|
||||
Widget _buildBody(ThemeData theme, int tabIndex, episodes) {
|
||||
final isCurrTab = tabIndex == widget.initialTabIndex;
|
||||
return KeepAliveWrapper(
|
||||
builder: (context) => ScrollablePositionedList.separated(
|
||||
padding: EdgeInsets.only(
|
||||
@@ -254,7 +255,7 @@ class _EpisodePanelState extends CommonCollapseSlidePageState<EpisodePanel> {
|
||||
),
|
||||
reverse: _isReversed[tabIndex],
|
||||
itemCount: episodes.length,
|
||||
initialScrollIndex: _currentItemIndex,
|
||||
initialScrollIndex: isCurrTab ? _currentItemIndex : 0,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (BuildContext context, int itemIndex) {
|
||||
final episode = episodes[itemIndex];
|
||||
@@ -263,9 +264,7 @@ class _EpisodePanelState extends CommonCollapseSlidePageState<EpisodePanel> {
|
||||
episode: episode,
|
||||
index: itemIndex,
|
||||
length: episodes.length,
|
||||
isCurrentIndex: tabIndex == widget.initialTabIndex
|
||||
? itemIndex == _currentItemIndex
|
||||
: false,
|
||||
isCurrentIndex: isCurrTab ? itemIndex == _currentItemIndex : false,
|
||||
);
|
||||
return widget.type == EpisodeType.season &&
|
||||
widget.showTitle &&
|
||||
@@ -394,47 +393,40 @@ class _EpisodePanelState extends CommonCollapseSlidePageState<EpisodePanel> {
|
||||
spacing: 10,
|
||||
children: [
|
||||
if (cover?.isNotEmpty == true)
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: cover,
|
||||
width: boxConstraints.maxWidth,
|
||||
height: boxConstraints.maxHeight,
|
||||
),
|
||||
if (duration != null && duration > 0)
|
||||
PBadge(
|
||||
text: DurationUtil.formatDuration(duration),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: PBadgeType.gray,
|
||||
),
|
||||
if (isCharging == true)
|
||||
const PBadge(
|
||||
text: '充电专属',
|
||||
top: 6,
|
||||
right: 6,
|
||||
type: PBadgeType.error,
|
||||
)
|
||||
else if (episode.badge != null)
|
||||
PBadge(
|
||||
text: episode.badge,
|
||||
top: 6,
|
||||
right: 6,
|
||||
type: switch (episode.badge) {
|
||||
'会员' => PBadgeType.primary,
|
||||
'限免' => PBadgeType.free,
|
||||
_ => PBadgeType.gray,
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: cover,
|
||||
width: 140.8,
|
||||
height: 88,
|
||||
),
|
||||
if (duration != null && duration > 0)
|
||||
PBadge(
|
||||
text: DurationUtil.formatDuration(duration),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: PBadgeType.gray,
|
||||
),
|
||||
if (isCharging == true)
|
||||
const PBadge(
|
||||
text: '充电专属',
|
||||
top: 6,
|
||||
right: 6,
|
||||
type: PBadgeType.error,
|
||||
)
|
||||
else if (episode.badge != null)
|
||||
PBadge(
|
||||
text: episode.badge,
|
||||
top: 6,
|
||||
right: 6,
|
||||
type: switch (episode.badge) {
|
||||
'会员' => PBadgeType.primary,
|
||||
'限免' => PBadgeType.free,
|
||||
_ => PBadgeType.gray,
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
else if (isCurrentIndex)
|
||||
Image.asset(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
@@ -38,7 +37,7 @@ class _FavArticlePageState extends State<FavArticlePage>
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
top: StyleString.safeSpace - 5,
|
||||
top: 7,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: Obx(
|
||||
|
||||
@@ -32,108 +32,109 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
|
||||
super.build(context);
|
||||
final theme = Theme.of(context);
|
||||
final padding = MediaQuery.paddingOf(context);
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
refreshIndicator(
|
||||
onRefresh: _favNoteController.onRefresh,
|
||||
child: CustomScrollView(
|
||||
controller: _favNoteController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(bottom: padding.bottom + 80),
|
||||
sliver: Obx(
|
||||
() => _buildBody(_favNoteController.loadingState.value),
|
||||
),
|
||||
final bottomH = 50 + padding.bottom;
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
refreshIndicator(
|
||||
onRefresh: _favNoteController.onRefresh,
|
||||
child: CustomScrollView(
|
||||
controller: _favNoteController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(bottom: padding.bottom + 80),
|
||||
sliver: Obx(
|
||||
() => _buildBody(_favNoteController.loadingState.value),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
top: constraints.maxHeight,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Obx(
|
||||
() => AnimatedSlide(
|
||||
offset: _favNoteController.enableMultiSelect.value
|
||||
? const Offset(0, -1)
|
||||
: Offset.zero,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Container(
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 0.5,
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: -bottomH,
|
||||
child: Obx(
|
||||
() => AnimatedSlide(
|
||||
offset: _favNoteController.enableMultiSelect.value
|
||||
? const Offset(0, -1)
|
||||
: Offset.zero,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Container(
|
||||
height: bottomH,
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 0.5,
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
iconButton(
|
||||
size: 32,
|
||||
tooltip: '取消',
|
||||
context: context,
|
||||
icon: Icons.clear,
|
||||
onPressed: _favNoteController.onDisable,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Obx(
|
||||
() => Checkbox(
|
||||
value: _favNoteController.allSelected.value,
|
||||
onChanged: (value) {
|
||||
_favNoteController.handleSelect(
|
||||
!_favNoteController.allSelected.value,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _favNoteController.handleSelect(
|
||||
!_favNoteController.allSelected.value,
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 14,
|
||||
bottom: 14,
|
||||
right: 12,
|
||||
),
|
||||
child: Text('全选'),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
FilledButton.tonal(
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
if (_favNoteController.checkedCount != 0) {
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
title: '确定删除已选中的笔记吗?',
|
||||
onConfirm: _favNoteController.onRemove,
|
||||
);
|
||||
}
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
iconButton(
|
||||
size: 32,
|
||||
tooltip: '取消',
|
||||
context: context,
|
||||
icon: Icons.clear,
|
||||
onPressed: _favNoteController.onDisable,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Obx(
|
||||
() => Checkbox(
|
||||
value: _favNoteController.allSelected.value,
|
||||
onChanged: (value) {
|
||||
_favNoteController.handleSelect(
|
||||
checked: !_favNoteController.allSelected.value,
|
||||
);
|
||||
},
|
||||
child: const Text('删除'),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _favNoteController.handleSelect(
|
||||
checked: !_favNoteController.allSelected.value,
|
||||
disableSelect: false,
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 14,
|
||||
bottom: 14,
|
||||
right: 12,
|
||||
),
|
||||
child: Text('全选'),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
FilledButton.tonal(
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
if (_favNoteController.checkedCount != 0) {
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
title: '确定删除已选中的笔记吗?',
|
||||
onConfirm: _favNoteController.onRemove,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('删除'),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,9 @@ class FavNoteController
|
||||
}
|
||||
|
||||
@override
|
||||
void onSelect(FavNoteItemModel item, [bool disableSelect = true]) {
|
||||
super.onSelect(item, false);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleSelect([bool checked = false, bool disableSelect = true]) {
|
||||
void handleSelect({bool checked = false, bool disableSelect = true}) {
|
||||
allSelected.value = checked;
|
||||
super.handleSelect(checked, false);
|
||||
super.handleSelect(checked: checked, disableSelect: disableSelect);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/widgets/select_mask.dart';
|
||||
import 'package:PiliPlus/models_new/fav/fav_note/list.dart';
|
||||
import 'package:PiliPlus/pages/fav/note/controller.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
@@ -64,62 +65,9 @@ class FavNoteItem extends StatelessWidget {
|
||||
height: boxConstraints.maxHeight,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) =>
|
||||
AnimatedOpacity(
|
||||
opacity: item.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: StyleString.mdRadius,
|
||||
color: Colors.black.withValues(
|
||||
alpha: 0.6,
|
||||
),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: AnimatedScale(
|
||||
scale: item.checked == true ? 1 : 0,
|
||||
duration: const Duration(
|
||||
milliseconds: 250,
|
||||
),
|
||||
curve: Curves.easeInOut,
|
||||
child: IconButton(
|
||||
tooltip: '取消选择',
|
||||
style: ButtonStyle(
|
||||
padding:
|
||||
WidgetStateProperty.all(
|
||||
EdgeInsets.zero,
|
||||
),
|
||||
backgroundColor:
|
||||
WidgetStatePropertyAll(
|
||||
theme.colorScheme.surface
|
||||
.withValues(
|
||||
alpha: 0.8,
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: null,
|
||||
icon: Icon(
|
||||
Icons.done_all_outlined,
|
||||
color:
|
||||
theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: selectMask(
|
||||
theme,
|
||||
item.checked == true,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -37,127 +37,128 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
|
||||
super.build(context);
|
||||
final theme = Theme.of(context);
|
||||
final padding = MediaQuery.paddingOf(context);
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
refreshIndicator(
|
||||
onRefresh: _favPgcController.onRefresh,
|
||||
child: CustomScrollView(
|
||||
controller: _favPgcController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(bottom: padding.bottom + 80),
|
||||
sliver: Obx(
|
||||
() => _buildBody(_favPgcController.loadingState.value),
|
||||
),
|
||||
final bottomH = 50 + padding.bottom;
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
refreshIndicator(
|
||||
onRefresh: _favPgcController.onRefresh,
|
||||
child: CustomScrollView(
|
||||
controller: _favPgcController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(bottom: padding.bottom + 80),
|
||||
sliver: Obx(
|
||||
() => _buildBody(_favPgcController.loadingState.value),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
top: constraints.maxHeight,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Obx(
|
||||
() => AnimatedSlide(
|
||||
offset: _favPgcController.enableMultiSelect.value
|
||||
? const Offset(0, -1)
|
||||
: Offset.zero,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Container(
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 0.5,
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: -bottomH,
|
||||
child: Obx(
|
||||
() => AnimatedSlide(
|
||||
offset: _favPgcController.enableMultiSelect.value
|
||||
? const Offset(0, -1)
|
||||
: Offset.zero,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Container(
|
||||
height: bottomH,
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 0.5,
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
iconButton(
|
||||
size: 32,
|
||||
tooltip: '取消',
|
||||
context: context,
|
||||
icon: Icons.clear,
|
||||
onPressed: _favPgcController.onDisable,
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
iconButton(
|
||||
size: 32,
|
||||
tooltip: '取消',
|
||||
context: context,
|
||||
icon: Icons.clear,
|
||||
onPressed: _favPgcController.onDisable,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Obx(
|
||||
() => Checkbox(
|
||||
value: _favPgcController.allSelected.value,
|
||||
onChanged: (value) {
|
||||
_favPgcController.handleSelect(
|
||||
checked: !_favPgcController.allSelected.value,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Obx(
|
||||
() => Checkbox(
|
||||
value: _favPgcController.allSelected.value,
|
||||
onChanged: (value) {
|
||||
_favPgcController.handleSelect(
|
||||
!_favPgcController.allSelected.value,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _favPgcController.handleSelect(
|
||||
checked: !_favPgcController.allSelected.value,
|
||||
disableSelect: false,
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _favPgcController.handleSelect(
|
||||
!_favPgcController.allSelected.value,
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 14,
|
||||
bottom: 14,
|
||||
right: 12,
|
||||
),
|
||||
child: Text('全选'),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 14,
|
||||
bottom: 14,
|
||||
right: 12,
|
||||
),
|
||||
child: Text('全选'),
|
||||
),
|
||||
const Spacer(),
|
||||
...const [
|
||||
(followStatus: 1, title: '想看'),
|
||||
(followStatus: 2, title: '在看'),
|
||||
(followStatus: 3, title: '看过'),
|
||||
]
|
||||
.where(
|
||||
(item) => item.followStatus != widget.followStatus,
|
||||
)
|
||||
.map(
|
||||
(item) => Padding(
|
||||
padding: const EdgeInsets.only(left: 25),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (_favPgcController.checkedCount != 0) {
|
||||
_favPgcController.onUpdateList(
|
||||
item.followStatus,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 14,
|
||||
horizontal: 5,
|
||||
),
|
||||
child: Text(
|
||||
'标记为${item.title}',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
...const [
|
||||
(followStatus: 1, title: '想看'),
|
||||
(followStatus: 2, title: '在看'),
|
||||
(followStatus: 3, title: '看过'),
|
||||
]
|
||||
.where(
|
||||
(item) => item.followStatus != widget.followStatus,
|
||||
)
|
||||
.map(
|
||||
(item) => Padding(
|
||||
padding: const EdgeInsets.only(left: 25),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (_favPgcController.checkedCount != 0) {
|
||||
_favPgcController.onUpdateList(
|
||||
item.followStatus,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 14,
|
||||
horizontal: 5,
|
||||
),
|
||||
child: Text(
|
||||
'标记为${item.title}',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,14 +24,9 @@ class FavPgcController
|
||||
}
|
||||
|
||||
@override
|
||||
void onSelect(FavPgcItemModel item, [bool disableSelect = true]) {
|
||||
super.onSelect(item, false);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleSelect([bool checked = false, bool disableSelect = true]) {
|
||||
void handleSelect({bool checked = false, bool disableSelect = true}) {
|
||||
allSelected.value = checked;
|
||||
super.handleSelect(checked, false);
|
||||
super.handleSelect(checked: checked, disableSelect: disableSelect);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -88,10 +83,10 @@ class FavPgcController
|
||||
..refresh();
|
||||
ctr.allSelected.value = false;
|
||||
}
|
||||
afterDelete(removeList);
|
||||
} catch (e) {
|
||||
if (kDebugMode) debugPrint('fav pgc onUpdate: $e');
|
||||
}
|
||||
afterDelete(removeList);
|
||||
}
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/widgets/select_mask.dart';
|
||||
import 'package:PiliPlus/models/common/badge_type.dart';
|
||||
import 'package:PiliPlus/models_new/fav/fav_pgc/list.dart';
|
||||
import 'package:PiliPlus/pages/common/multi_select/base.dart';
|
||||
@@ -81,71 +82,11 @@ class FavPgcItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) =>
|
||||
AnimatedOpacity(
|
||||
opacity: item.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:
|
||||
const BorderRadius.all(
|
||||
Radius.circular(4),
|
||||
),
|
||||
color: Colors.black.withValues(
|
||||
alpha: 0.6,
|
||||
),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: AnimatedScale(
|
||||
scale: item.checked == true
|
||||
? 1
|
||||
: 0,
|
||||
duration: const Duration(
|
||||
milliseconds: 250,
|
||||
),
|
||||
curve: Curves.easeInOut,
|
||||
child: IconButton(
|
||||
tooltip: '取消选择',
|
||||
style: ButtonStyle(
|
||||
padding:
|
||||
WidgetStateProperty.all(
|
||||
EdgeInsets.zero,
|
||||
),
|
||||
backgroundColor:
|
||||
WidgetStatePropertyAll(
|
||||
theme
|
||||
.colorScheme
|
||||
.surface
|
||||
.withValues(
|
||||
alpha: 0.8,
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: null,
|
||||
icon: Icon(
|
||||
Icons.done_all_outlined,
|
||||
color: theme
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: selectMask(
|
||||
theme,
|
||||
item.checked == true,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
@@ -36,7 +35,7 @@ class _FavVideoPageState extends State<FavVideoPage>
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
top: StyleString.safeSpace - 5,
|
||||
top: 7,
|
||||
bottom: 80 + MediaQuery.paddingOf(context).bottom,
|
||||
),
|
||||
sliver: Obx(
|
||||
|
||||
@@ -246,20 +246,16 @@ class _CreateFavPageState extends State<CreateFavPage> {
|
||||
if (_cover?.isNotEmpty == true)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: ImageUtil.thumbnailUrl(_cover!),
|
||||
height: constraints.maxHeight,
|
||||
width: constraints.maxHeight * 16 / 9,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: ImageUtil.thumbnailUrl(_cover!),
|
||||
height: 55,
|
||||
width: 88,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
|
||||
@@ -279,7 +279,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
onPressed: () => _favDetailController.handleSelect(true),
|
||||
onPressed: () => _favDetailController.handleSelect(checked: true),
|
||||
child: const Text('全选'),
|
||||
),
|
||||
TextButton(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPlus/common/widgets/appbar/appbar.dart';
|
||||
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
||||
@@ -60,7 +59,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
top: StyleString.safeSpace - 5,
|
||||
top: 7,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: Obx(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
@@ -138,7 +137,7 @@ class _HotPageState extends CommonPageState<HotPage, HotController>
|
||||
),
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
top: StyleString.safeSpace - 5,
|
||||
top: 7,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: Obx(
|
||||
|
||||
@@ -52,7 +52,7 @@ class _MemberCoinArcPageState extends State<MemberCoinArcPage> {
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
top: StyleString.safeSpace - 5,
|
||||
top: 7,
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics_tab/view.dart';
|
||||
import 'package:PiliPlus/pages/member_dynamics/controller.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
@@ -25,7 +23,7 @@ class MemberDynamicsPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MemberDynamicsPageState extends State<MemberDynamicsPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
with AutomaticKeepAliveClientMixin, DynMixin {
|
||||
late MemberDynamicsController _memberDynamicController;
|
||||
late int mid;
|
||||
|
||||
@@ -74,23 +72,14 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage>
|
||||
),
|
||||
);
|
||||
|
||||
late double _maxWidth;
|
||||
|
||||
Widget _buildContent(LoadingState<List<DynamicItemModel>?> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => DynamicsTabPage.dynSkeleton(
|
||||
GlobalData().dynamicsWaterfallFlow,
|
||||
),
|
||||
Loading() => dynSkeleton,
|
||||
Success(:var response) =>
|
||||
response?.isNotEmpty == true
|
||||
? GlobalData().dynamicsWaterfallFlow
|
||||
? SliverWaterfallFlow(
|
||||
gridDelegate:
|
||||
SliverWaterfallFlowDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||
crossAxisSpacing: StyleString.cardSpace / 2,
|
||||
callback: (value) => _maxWidth = value,
|
||||
),
|
||||
gridDelegate: gridDelegate,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(_, index) {
|
||||
if (index == response.length - 1) {
|
||||
@@ -100,7 +89,7 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage>
|
||||
item: response[index],
|
||||
onRemove: _memberDynamicController.onRemove,
|
||||
onSetTop: _memberDynamicController.onSetTop,
|
||||
maxWidth: _maxWidth,
|
||||
maxWidth: maxWidth,
|
||||
);
|
||||
},
|
||||
childCount: response!.length,
|
||||
@@ -120,7 +109,7 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage>
|
||||
item: response[index],
|
||||
onRemove: _memberDynamicController.onRemove,
|
||||
onSetTop: _memberDynamicController.onSetTop,
|
||||
maxWidth: _maxWidth,
|
||||
maxWidth: maxWidth,
|
||||
);
|
||||
},
|
||||
itemCount: response!.length,
|
||||
|
||||
@@ -56,34 +56,27 @@ class MemberFavItem extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: item.cover,
|
||||
width: boxConstraints.maxWidth,
|
||||
height: boxConstraints.maxHeight,
|
||||
),
|
||||
if (item.type == 21)
|
||||
const PBadge(
|
||||
right: 6,
|
||||
top: 6,
|
||||
text: '合集',
|
||||
)
|
||||
else if (item.type == 11)
|
||||
const PBadge(
|
||||
right: 6,
|
||||
top: 6,
|
||||
text: '收藏夹',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: item.cover,
|
||||
width: 140.8,
|
||||
height: 88,
|
||||
),
|
||||
if (item.type == 21)
|
||||
const PBadge(
|
||||
right: 6,
|
||||
top: 6,
|
||||
text: '合集',
|
||||
)
|
||||
else if (item.type == 11)
|
||||
const PBadge(
|
||||
right: 6,
|
||||
top: 6,
|
||||
text: '收藏夹',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
|
||||
@@ -37,17 +37,10 @@ class MemberFavItem extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return NetworkImgLayer(
|
||||
src: item.cover,
|
||||
width: boxConstraints.maxWidth,
|
||||
height: boxConstraints.maxHeight,
|
||||
);
|
||||
},
|
||||
),
|
||||
NetworkImgLayer(
|
||||
src: item.cover,
|
||||
width: 140.8,
|
||||
height: 88,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
|
||||
@@ -52,7 +52,7 @@ class _MemberLikeArcPageState extends State<MemberLikeArcPage> {
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
top: StyleString.safeSpace - 5,
|
||||
top: 7,
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
@@ -6,7 +5,6 @@ import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/member/search_type.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics_tab/view.dart';
|
||||
import 'package:PiliPlus/pages/member_search/child/controller.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
@@ -31,7 +29,7 @@ class MemberSearchChildPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MemberSearchChildPageState extends State<MemberSearchChildPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
with AutomaticKeepAliveClientMixin, DynMixin {
|
||||
MemberSearchChildController get _controller => widget.controller;
|
||||
|
||||
@override
|
||||
@@ -66,14 +64,10 @@ class _MemberSearchChildPageState extends State<MemberSearchChildPage>
|
||||
childCount: 10,
|
||||
),
|
||||
),
|
||||
MemberSearchType.dynamic => DynamicsTabPage.dynSkeleton(
|
||||
GlobalData().dynamicsWaterfallFlow,
|
||||
),
|
||||
MemberSearchType.dynamic => dynSkeleton,
|
||||
};
|
||||
}
|
||||
|
||||
late double _maxWidth;
|
||||
|
||||
Widget _buildBody(LoadingState<List?> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => _buildLoading,
|
||||
@@ -99,12 +93,7 @@ class _MemberSearchChildPageState extends State<MemberSearchChildPage>
|
||||
MemberSearchType.dynamic =>
|
||||
GlobalData().dynamicsWaterfallFlow
|
||||
? SliverWaterfallFlow(
|
||||
gridDelegate:
|
||||
SliverWaterfallFlowDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
callback: (value) => _maxWidth = value,
|
||||
),
|
||||
gridDelegate: gridDelegate,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(_, index) {
|
||||
if (index == response.length - 1) {
|
||||
@@ -112,7 +101,7 @@ class _MemberSearchChildPageState extends State<MemberSearchChildPage>
|
||||
}
|
||||
return DynamicPanel(
|
||||
item: response[index],
|
||||
maxWidth: _maxWidth,
|
||||
maxWidth: maxWidth,
|
||||
);
|
||||
},
|
||||
childCount: response!.length,
|
||||
@@ -130,7 +119,7 @@ class _MemberSearchChildPageState extends State<MemberSearchChildPage>
|
||||
}
|
||||
return DynamicPanel(
|
||||
item: response[index],
|
||||
maxWidth: _maxWidth,
|
||||
maxWidth: maxWidth,
|
||||
);
|
||||
},
|
||||
itemCount: response!.length,
|
||||
|
||||
@@ -54,17 +54,13 @@ class FavFolderItem extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, box) {
|
||||
return Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: item.cover,
|
||||
width: box.maxWidth,
|
||||
height: box.maxHeight,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: item.cover,
|
||||
width: 176,
|
||||
height: 110,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
@@ -44,7 +43,7 @@ class _ZonePageState extends CommonPageState<ZonePage, ZoneController>
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
top: StyleString.safeSpace - 5,
|
||||
top: 7,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: Obx(() => _buildBody(controller.loadingState.value)),
|
||||
|
||||
@@ -115,11 +115,9 @@ class _RcmdPageState extends CommonPageState<RcmdPage, RcmdController>
|
||||
} else {
|
||||
return VideoCardV(
|
||||
videoItem: response[index],
|
||||
onRemove: () {
|
||||
controller.loadingState
|
||||
..value.data!.removeAt(index)
|
||||
..refresh();
|
||||
},
|
||||
onRemove: () => controller.loadingState
|
||||
..value.data!.removeAt(index)
|
||||
..refresh(),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -423,7 +423,7 @@ class PgcIntroController extends CommonIntroController {
|
||||
SmartDialog.showToast('已三连');
|
||||
return;
|
||||
}
|
||||
var result = await VideoHttp.triple(epId: epId, seasonId: seasonId);
|
||||
var result = await VideoHttp.pgcTriple(epId: epId, seasonId: seasonId);
|
||||
if (result['status']) {
|
||||
PgcTriple data = result['data'];
|
||||
late final stat = pgcItem.stat!;
|
||||
@@ -431,7 +431,8 @@ class PgcIntroController extends CommonIntroController {
|
||||
stat.like++;
|
||||
hasLike.value = true;
|
||||
}
|
||||
if ((data.coin == 1) != hasCoin) {
|
||||
final hasCoin = data.coin == 1;
|
||||
if (this.hasCoin != hasCoin) {
|
||||
stat.coin += 2;
|
||||
coinNum.value = 2;
|
||||
GlobalData().afterCoin(2);
|
||||
@@ -440,7 +441,11 @@ class PgcIntroController extends CommonIntroController {
|
||||
stat.favorite++;
|
||||
hasFav.value = true;
|
||||
}
|
||||
SmartDialog.showToast('三连成功');
|
||||
if (!hasCoin) {
|
||||
SmartDialog.showToast('投币失败');
|
||||
} else {
|
||||
SmartDialog.showToast('三连成功');
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ class _PgcIntroPageState extends TripleState<PgcIntroPage>
|
||||
Widget? _buildBreif(PgcInfoModel item) {
|
||||
final img = item.brief?.img;
|
||||
if (img != null && img.isNotEmpty) {
|
||||
final maxWidth = widget.maxWidth - 2 * StyleString.safeSpace;
|
||||
final maxWidth = widget.maxWidth - 24;
|
||||
double padding = max(0, maxWidth - 400);
|
||||
final imgWidth = maxWidth - padding;
|
||||
padding = padding / 2;
|
||||
|
||||
@@ -193,7 +193,7 @@ class UgcIntroController extends CommonIntroController with ReloadMixin {
|
||||
SmartDialog.showToast('已三连');
|
||||
return;
|
||||
}
|
||||
var result = await VideoHttp.oneThree(bvid: bvid);
|
||||
var result = await VideoHttp.ugcTriple(bvid: bvid);
|
||||
if (result['status']) {
|
||||
UgcTriple data = result['data'];
|
||||
late final stat = videoDetail.value.stat!;
|
||||
@@ -211,7 +211,11 @@ class UgcIntroController extends CommonIntroController with ReloadMixin {
|
||||
hasFav.value = true;
|
||||
}
|
||||
hasDislike.value = false;
|
||||
SmartDialog.showToast('三连成功');
|
||||
if (data.coin != true) {
|
||||
SmartDialog.showToast('投币失败');
|
||||
} else {
|
||||
SmartDialog.showToast('三连成功');
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||
@@ -51,7 +50,6 @@ class MediaListPanel extends CommonCollapseSlidePage {
|
||||
class _MediaListPanelState
|
||||
extends CommonCollapseSlidePageState<MediaListPanel> {
|
||||
late final int _index;
|
||||
late final RxBool desc = widget.desc.obs;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -74,19 +72,14 @@ class _MediaListPanelState
|
||||
title: Text(widget.panelTitle ?? '稍后再看'),
|
||||
backgroundColor: Colors.transparent,
|
||||
actions: [
|
||||
Obx(
|
||||
() {
|
||||
final desc = this.desc.value;
|
||||
return mediumButton(
|
||||
tooltip: desc ? '顺序播放' : '倒序播放',
|
||||
icon: desc
|
||||
? MdiIcons.sortAscending
|
||||
: MdiIcons.sortDescending,
|
||||
onPressed: () {
|
||||
widget.onReverse();
|
||||
this.desc.value = !desc;
|
||||
},
|
||||
);
|
||||
mediumButton(
|
||||
tooltip: widget.desc ? '顺序播放' : '倒序播放',
|
||||
icon: widget.desc
|
||||
? MdiIcons.sortAscending
|
||||
: MdiIcons.sortDescending,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
widget.onReverse();
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
@@ -131,169 +124,170 @@ class _MediaListPanelState
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
itemBuilder: ((context, index) {
|
||||
var item = widget.mediaList[index];
|
||||
if (index == widget.mediaList.length - 1 &&
|
||||
(widget.count == null ||
|
||||
widget.mediaList.length < widget.count!)) {
|
||||
widget.loadMoreMedia();
|
||||
}
|
||||
var item = widget.mediaList[index];
|
||||
final isCurr = item.bvid == widget.getBvId();
|
||||
return SizedBox(
|
||||
height: 98,
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (item.type != 2) {
|
||||
SmartDialog.showToast('不支持播放该类型视频');
|
||||
return;
|
||||
}
|
||||
Get.back();
|
||||
widget.onChangeEpisode(item);
|
||||
},
|
||||
onLongPress: () => imageSaveDialog(
|
||||
title: item.title,
|
||||
cover: item.cover,
|
||||
aid: item.aid,
|
||||
bvid: item.bvid,
|
||||
),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: item.cover,
|
||||
width: boxConstraints.maxWidth,
|
||||
height: boxConstraints.maxHeight,
|
||||
),
|
||||
if (item.badge?.isNotEmpty == true)
|
||||
PBadge(
|
||||
text: item.badge,
|
||||
right: 6.0,
|
||||
top: 6.0,
|
||||
type: switch (item.badge) {
|
||||
'充电专属' => PBadgeType.error,
|
||||
_ => PBadgeType.primary,
|
||||
},
|
||||
),
|
||||
PBadge(
|
||||
text: DurationUtil.formatDuration(
|
||||
item.duration,
|
||||
),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: PBadgeType.gray,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.title!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: isCurr ? FontWeight.bold : null,
|
||||
color: isCurr
|
||||
? theme.colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
),
|
||||
if (item.type == 24 &&
|
||||
item.intro?.isNotEmpty == true) ...[
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
item.intro!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
Text(
|
||||
item.upper!.name!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
if (item.type == 2) ...[
|
||||
const SizedBox(height: 3),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
StatWidget(
|
||||
type: StatType.play,
|
||||
value: item.cntInfo!.play,
|
||||
),
|
||||
StatWidget(
|
||||
type: StatType.danmaku,
|
||||
value: item.cntInfo!.danmaku,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (showDelBtn && !isCurr)
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: -6,
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () => showConfirmDialog(
|
||||
context: context,
|
||||
title: '确定移除该视频?',
|
||||
onConfirm: () => widget.onDelete!(item, index),
|
||||
),
|
||||
onLongPress: () => widget.onDelete!(item, index),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(9),
|
||||
child: Icon(
|
||||
Icons.clear,
|
||||
size: 18,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return _buildItem(theme, index, item, isCurr, showDelBtn);
|
||||
}),
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 2),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Widget _buildItem(
|
||||
ThemeData theme,
|
||||
int index,
|
||||
MediaListItemModel item,
|
||||
bool isCurr,
|
||||
bool showDelBtn,
|
||||
) {
|
||||
return SizedBox(
|
||||
height: 98,
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (item.type != 2) {
|
||||
SmartDialog.showToast('不支持播放该类型视频');
|
||||
return;
|
||||
}
|
||||
Get.back();
|
||||
widget.onChangeEpisode(item);
|
||||
},
|
||||
onLongPress: () => imageSaveDialog(
|
||||
title: item.title,
|
||||
cover: item.cover,
|
||||
aid: item.aid,
|
||||
bvid: item.bvid,
|
||||
),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: item.cover,
|
||||
width: 140.8,
|
||||
height: 88,
|
||||
),
|
||||
if (item.badge?.isNotEmpty == true)
|
||||
PBadge(
|
||||
text: item.badge,
|
||||
right: 6.0,
|
||||
top: 6.0,
|
||||
type: switch (item.badge) {
|
||||
'充电专属' => PBadgeType.error,
|
||||
_ => PBadgeType.primary,
|
||||
},
|
||||
),
|
||||
PBadge(
|
||||
text: DurationUtil.formatDuration(
|
||||
item.duration,
|
||||
),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: PBadgeType.gray,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.title!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: isCurr ? FontWeight.bold : null,
|
||||
color: isCurr ? theme.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
if (item.type == 24 &&
|
||||
item.intro?.isNotEmpty == true) ...[
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
item.intro!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
Text(
|
||||
item.upper!.name!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
if (item.type == 2) ...[
|
||||
const SizedBox(height: 3),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
StatWidget(
|
||||
type: StatType.play,
|
||||
value: item.cntInfo!.play,
|
||||
),
|
||||
StatWidget(
|
||||
type: StatType.danmaku,
|
||||
value: item.cntInfo!.danmaku,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (showDelBtn && !isCurr)
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: -6,
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () => showConfirmDialog(
|
||||
context: context,
|
||||
title: '确定移除该视频?',
|
||||
onConfirm: () => widget.onDelete!(item, index),
|
||||
),
|
||||
onLongPress: () => widget.onDelete!(item, index),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(9),
|
||||
child: Icon(
|
||||
Icons.clear,
|
||||
size: 18,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,20 +208,16 @@ class _PayCoinsPageState extends State<PayCoinsPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
bool isV = constraints.maxHeight > constraints.maxWidth;
|
||||
return isV
|
||||
? _buildBody(isV)
|
||||
: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(flex: 3, child: _buildBody(isV)),
|
||||
const Spacer(),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
bool isPortrait = context.isPortrait;
|
||||
return isPortrait
|
||||
? _buildBody(isPortrait)
|
||||
: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(flex: 3, child: _buildBody(isPortrait)),
|
||||
const Spacer(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(bool isV) => Stack(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart';
|
||||
@@ -31,7 +30,7 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
|
||||
super.build(context);
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: StyleString.safeSpace - 5,
|
||||
top: 7,
|
||||
bottom: 80,
|
||||
),
|
||||
sliver: Obx(() => _buildBody(_relatedController.loadingState.value)),
|
||||
|
||||
@@ -50,7 +50,7 @@ class _ViewPointsPageState
|
||||
toolbarHeight: 45,
|
||||
actions: [
|
||||
const Text(
|
||||
'分段进度条',
|
||||
'分段进度条 ',
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
Obx(
|
||||
@@ -116,63 +116,71 @@ class _ViewPointsPageState
|
||||
currentIndex = index;
|
||||
}
|
||||
}
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: segment.from != null
|
||||
? () {
|
||||
currentIndex = index;
|
||||
plPlayerController?.danmakuController?.clear();
|
||||
plPlayerController?.videoPlayerController?.seek(
|
||||
Duration(seconds: segment.from!),
|
||||
);
|
||||
Get.back();
|
||||
}
|
||||
: null,
|
||||
leading: segment.url?.isNotEmpty == true
|
||||
? Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: currentIndex == index
|
||||
? BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
),
|
||||
border: Border.all(
|
||||
width: 1.8,
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => NetworkImgLayer(
|
||||
radius: 6,
|
||||
src: segment.url,
|
||||
width: constraints.maxHeight * StyleString.aspectRatio,
|
||||
height: constraints.maxHeight,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
title: Text(
|
||||
segment.title ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: currentIndex == index ? FontWeight.bold : null,
|
||||
color: currentIndex == index ? theme.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${segment.from != null ? DurationUtil.formatDuration(segment.from) : ''} - ${segment.to != null ? DurationUtil.formatDuration(segment.to) : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: currentIndex == index
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
final isCurr = currentIndex == index;
|
||||
return _buildItem(theme, segment, isCurr);
|
||||
},
|
||||
separatorBuilder: (context, index) => divider,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(ThemeData theme, Segment segment, bool isCurr) {
|
||||
final theme = Theme.of(context);
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: segment.from != null
|
||||
? () {
|
||||
Get.back();
|
||||
plPlayerController
|
||||
?..danmakuController?.clear()
|
||||
..videoPlayerController?.seek(
|
||||
Duration(seconds: segment.from!),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: segment.url,
|
||||
width: 140.8,
|
||||
height: 88,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
segment.title ?? '',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: isCurr
|
||||
? TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
Text(
|
||||
'${segment.from != null ? DurationUtil.formatDuration(segment.from) : ''} - '
|
||||
'${segment.to != null ? DurationUtil.formatDuration(segment.to) : ''}',
|
||||
style: TextStyle(color: theme.colorScheme.outline),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ class RequestUtils {
|
||||
mid: isCopy ? mid : null,
|
||||
).then((res) {
|
||||
if (res.isSuccess) {
|
||||
ctr.handleSelect(false);
|
||||
ctr.handleSelect(checked: false);
|
||||
if (!isCopy) {
|
||||
ctr.loadingState
|
||||
..value.data!.removeWhere(removeList.contains)
|
||||
|
||||
@@ -1,8 +1,55 @@
|
||||
import 'package:flutter/material.dart' show ValueChanged;
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/dynamic_card.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart' show SliverConstraints;
|
||||
import 'package:waterfall_flow/waterfall_flow.dart'
|
||||
show SliverWaterfallFlowDelegate;
|
||||
|
||||
mixin DynMixin {
|
||||
late double maxWidth;
|
||||
|
||||
late final gridDelegate = SliverWaterfallFlowDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||
crossAxisSpacing: 4,
|
||||
callback: (value) => maxWidth = value,
|
||||
);
|
||||
|
||||
late final skeDelegate = SliverGridDelegateWithExtentAndRatio(
|
||||
crossAxisSpacing: 4,
|
||||
mainAxisSpacing: 4,
|
||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio,
|
||||
mainAxisExtent: 50,
|
||||
);
|
||||
|
||||
Widget get dynSkeleton {
|
||||
if (!GlobalData().dynamicsWaterfallFlow) {
|
||||
return SliverCrossAxisGroup(
|
||||
slivers: [
|
||||
const SliverFillRemaining(),
|
||||
SliverConstrainedCrossAxis(
|
||||
maxExtent: Grid.smallCardWidth * 2,
|
||||
sliver: SliverList.builder(
|
||||
itemBuilder: (_, _) => const DynamicCardSkeleton(),
|
||||
itemCount: 10,
|
||||
),
|
||||
),
|
||||
const SliverFillRemaining(),
|
||||
],
|
||||
);
|
||||
}
|
||||
return SliverGrid(
|
||||
gridDelegate: skeDelegate,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(_, _) => const DynamicCardSkeleton(),
|
||||
childCount: 10,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SliverWaterfallFlowDelegateWithMaxCrossAxisExtent
|
||||
extends SliverWaterfallFlowDelegate {
|
||||
/// Creates a delegate that makes masonry layouts with tiles that have a maximum
|
||||
|
||||
Reference in New Issue
Block a user