opt pages

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-06-05 14:57:54 +08:00
parent b960359a39
commit 707d2f4b07
66 changed files with 1165 additions and 481 deletions

View File

@@ -56,7 +56,7 @@ class NetworkImgLayer extends StatelessWidget {
: getPlaceHolder?.call() ?? placeholder(context);
}
Widget _buildImage(context) {
Widget _buildImage(BuildContext context) {
int? memCacheWidth, memCacheHeight;
if (height == null || callback?.call() == true || width <= height!) {
memCacheWidth = width.cacheSize(context);

View File

@@ -21,7 +21,6 @@ class VideoCardH extends StatelessWidget {
const VideoCardH({
super.key,
required this.videoItem,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
this.showDanmaku = true,
@@ -31,7 +30,6 @@ class VideoCardH extends StatelessWidget {
this.onViewLater,
});
final BaseVideoItemModel videoItem;
final String source;
final bool showOwner;
final bool showView;
final bool showDanmaku;
@@ -42,8 +40,6 @@ class VideoCardH extends StatelessWidget {
@override
Widget build(BuildContext context) {
final int aid = videoItem.aid!;
final String bvid = videoItem.bvid!;
String type = 'video';
if (videoItem case SearchVideoItemModel item) {
var typeOrNull = item.type;
@@ -90,19 +86,16 @@ class VideoCardH extends StatelessWidget {
}
try {
final int? cid = videoItem.cid ??
await SearchHttp.ab2c(aid: aid, bvid: bvid);
await SearchHttp.ab2c(
aid: videoItem.aid, bvid: videoItem.bvid);
if (cid != null) {
if (source == 'later') {
onViewLater!(cid);
} else {
PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
arguments: {
'videoItem': videoItem,
'heroTag': Utils.makeHeroTag(aid)
},
);
}
PageUtils.toVideoPage(
'bvid=${videoItem.bvid}&cid=$cid',
arguments: {
'videoItem': videoItem,
'heroTag': Utils.makeHeroTag(videoItem.aid)
},
);
}
} catch (err) {
SmartDialog.showToast(err.toString());
@@ -181,28 +174,27 @@ class VideoCardH extends StatelessWidget {
),
),
const SizedBox(width: 10),
videoContent(context),
content(context),
],
),
),
),
),
if (source == 'normal')
Positioned(
bottom: 0,
right: 12,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
videoItem: videoItem,
),
Positioned(
bottom: 0,
right: 12,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
videoItem: videoItem,
),
),
],
),
);
}
Widget videoContent(BuildContext context) {
Widget content(BuildContext context) {
final theme = Theme.of(context);
String pubdate = showPubdate
? Utils.dateFormat(videoItem.pubdate!, formatType: 'day')
@@ -262,23 +254,20 @@ class VideoCardH extends StatelessWidget {
),
const SizedBox(height: 3),
Row(
spacing: 8,
children: [
if (showView) ...[
if (showView)
StatView(
context: context,
theme: 'gray',
value: videoItem.stat.viewStr,
),
const SizedBox(width: 8),
],
if (showDanmaku)
StatDanMu(
context: context,
theme: 'gray',
value: videoItem.stat.danmuStr,
),
const Spacer(),
if (source == 'normal') const SizedBox(width: 24),
],
),
],

View File

@@ -32,7 +32,7 @@ class VideoCardV extends StatelessWidget {
return numericRegex.hasMatch(str);
}
Future<void> onPushDetail(heroTag) async {
Future<void> onPushDetail(String heroTag) async {
String? goto = videoItem.goto;
switch (goto) {
case 'bangumi':
@@ -136,7 +136,7 @@ class VideoCardV extends StatelessWidget {
);
}),
),
videoContent(context)
content(context)
],
),
),
@@ -157,7 +157,7 @@ class VideoCardV extends StatelessWidget {
);
}
Widget videoContent(context) {
Widget content(BuildContext context) {
final theme = Theme.of(context);
return Expanded(
child: Padding(

View File

@@ -41,36 +41,36 @@ class DynamicsHttp {
};
var res = await Request().get(Api.followDynamic, queryParameters: data);
if (res.data['code'] == 0) {
// try {
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
final antiGoodsDyn = GStorage.antiGoodsDyn;
final filterWord = banWordForDyn.pattern.isNotEmpty;
try {
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
final antiGoodsDyn = GStorage.antiGoodsDyn;
final filterWord = banWordForDyn.pattern.isNotEmpty;
data.items?.removeWhere(
(item) =>
(antiGoodsDyn &&
(item.orig?.modules.moduleDynamic?.additional?.type ==
'ADDITIONAL_TYPE_GOODS' ||
item.modules.moduleDynamic?.additional?.type ==
'ADDITIONAL_TYPE_GOODS')) ||
(filterWord &&
(item.orig?.modules.moduleDynamic?.major?.opus?.summary?.text
?.contains(banWordForDyn) ==
true ||
item.modules.moduleDynamic?.major?.opus?.summary?.text
?.contains(banWordForDyn) ==
true ||
item.orig?.modules.moduleDynamic?.desc?.text
?.contains(banWordForDyn) ==
true ||
item.modules.moduleDynamic?.desc?.text
?.contains(banWordForDyn) ==
true)),
);
return Success(data);
// } catch (err) {
// return Error(err.toString());
// }
data.items?.removeWhere(
(item) =>
(antiGoodsDyn &&
(item.orig?.modules.moduleDynamic?.additional?.type ==
'ADDITIONAL_TYPE_GOODS' ||
item.modules.moduleDynamic?.additional?.type ==
'ADDITIONAL_TYPE_GOODS')) ||
(filterWord &&
(item.orig?.modules.moduleDynamic?.major?.opus?.summary?.text
?.contains(banWordForDyn) ==
true ||
item.modules.moduleDynamic?.major?.opus?.summary?.text
?.contains(banWordForDyn) ==
true ||
item.orig?.modules.moduleDynamic?.desc?.text
?.contains(banWordForDyn) ==
true ||
item.modules.moduleDynamic?.desc?.text
?.contains(banWordForDyn) ==
true)),
);
return Success(data);
} catch (err) {
return Error(err.toString());
}
} else {
return Error(res.data['message']);
}

View File

@@ -2,10 +2,10 @@ import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models/user/info.dart';
import 'package:PiliPlus/models/user/stat.dart';
import 'package:PiliPlus/models_new/history/data.dart';
import 'package:PiliPlus/models_new/later/data.dart';
import 'package:PiliPlus/models_new/media_list/data.dart';
import 'package:PiliPlus/models_new/sub/sub/data.dart';
import 'package:PiliPlus/models_new/sub/sub/list.dart';
@@ -48,7 +48,7 @@ class UserHttp {
}
// 稍后再看
static Future<LoadingState<Map>> seeYouLater({
static Future<LoadingState<LaterData>> seeYouLater({
required int page,
int viewed = 0,
String keyword = '',
@@ -67,19 +67,7 @@ class UserHttp {
}),
);
if (res.data['code'] == 0) {
if (res.data['data']['count'] == 0) {
return const Success({'count': 0});
}
List<HotVideoItemModel> list = <HotVideoItemModel>[];
if (res.data['data']?['list'] != null) {
for (var i in res.data['data']['list']) {
list.add(HotVideoItemModel.fromJson(i));
}
}
return Success({
'list': list,
'count': res.data['data']?['count'] ?? 0,
});
return Success(LaterData.fromJson(res.data['data']));
} else {
return Error(res.data['message']);
}

View File

@@ -0,0 +1,33 @@
import 'package:PiliPlus/models_new/later/season.dart';
class Bangumi {
int? epId;
String? title;
String? longTitle;
int? episodeStatus;
int? follow;
String? cover;
Season? season;
Bangumi({
this.epId,
this.title,
this.longTitle,
this.episodeStatus,
this.follow,
this.cover,
this.season,
});
factory Bangumi.fromJson(Map<String, dynamic> json) => Bangumi(
epId: json['ep_id'] as int?,
title: json['title'] as String?,
longTitle: json['long_title'] as String?,
episodeStatus: json['episode_status'] as int?,
follow: json['follow'] as int?,
cover: json['cover'] as String?,
season: json['season'] == null
? null
: Season.fromJson(json['season'] as Map<String, dynamic>),
);
}

View File

@@ -0,0 +1,15 @@
import 'package:PiliPlus/models_new/later/list.dart';
class LaterData {
int? count;
List<LaterItemModel>? list;
LaterData({this.count, this.list});
factory LaterData.fromJson(Map<String, dynamic> json) => LaterData(
count: json['count'] as int?,
list: (json['list'] as List<dynamic>?)
?.map((e) => LaterItemModel.fromJson(e as Map<String, dynamic>))
.toList(),
);
}

View File

@@ -0,0 +1,13 @@
class Dimension {
int? width;
int? height;
int? rotate;
Dimension({this.width, this.height, this.rotate});
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
width: json['width'] as int?,
height: json['height'] as int?,
rotate: json['rotate'] as int?,
);
}

View File

@@ -0,0 +1,164 @@
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/models_new/later/bangumi.dart';
import 'package:PiliPlus/models_new/later/dimension.dart';
import 'package:PiliPlus/models_new/later/owner.dart';
import 'package:PiliPlus/models_new/later/page.dart';
import 'package:PiliPlus/models_new/later/rights.dart';
import 'package:PiliPlus/models_new/later/stat.dart';
class LaterItemModel with MultiSelectData {
int? aid;
int? videos;
int? tid;
String? tname;
int? copyright;
String? pic;
String? title;
String? subtitle;
int? pubdate;
int? ctime;
String? desc;
int? state;
int? duration;
String? redirectUrl;
Rights? rights;
Owner? owner;
Stat? stat;
String? dynam1c;
Dimension? dimension;
String? shortLinkV2;
int? upFromV2;
String? pubLocation;
String? cover43;
int? tidv2;
String? tnamev2;
int? pidV2;
String? pidNameV2;
List<Page>? pages;
Bangumi? bangumi;
int? cid;
int? progress;
int? addAt;
String? bvid;
String? uri;
bool? viewed;
int? seq;
int? enableVt;
String? viewText1;
bool? isPgc;
String? pgcLabel;
bool? isPugv;
int? missionId;
String? firstFrame;
int? seasonId;
LaterItemModel({
this.aid,
this.videos,
this.tid,
this.tname,
this.copyright,
this.pic,
this.title,
this.subtitle,
this.pubdate,
this.ctime,
this.desc,
this.state,
this.duration,
this.redirectUrl,
this.rights,
this.owner,
this.stat,
this.dynam1c,
this.dimension,
this.shortLinkV2,
this.upFromV2,
this.pubLocation,
this.cover43,
this.tidv2,
this.tnamev2,
this.pidV2,
this.pidNameV2,
this.pages,
this.bangumi,
this.cid,
this.progress,
this.addAt,
this.bvid,
this.uri,
this.viewed,
this.seq,
this.enableVt,
this.viewText1,
this.isPgc,
this.pgcLabel,
this.isPugv,
this.missionId,
this.firstFrame,
this.seasonId,
});
factory LaterItemModel.fromJson(Map<String, dynamic> json) => LaterItemModel(
aid: json['aid'] as int?,
videos: json['videos'] as int?,
tid: json['tid'] as int?,
tname: json['tname'] as String?,
copyright: json['copyright'] as int?,
pic: json['pic'] as String?,
title: json['title'] as String?,
pubdate: json['pubdate'] as int?,
ctime: json['ctime'] as int?,
desc: json['desc'] as String?,
state: json['state'] as int?,
duration: json['duration'] as int?,
redirectUrl: json['redirect_url'] as String?,
rights: json['rights'] == null
? null
: Rights.fromJson(json['rights'] as Map<String, dynamic>),
owner: json['owner'] == null
? null
: Owner.fromJson(json['owner'] as Map<String, dynamic>),
stat: json['stat'] == null
? null
: Stat.fromJson(json['stat'] as Map<String, dynamic>),
dynam1c: json['dynamic'] as String?,
dimension: json['dimension'] == null
? null
: Dimension.fromJson(json['dimension'] as Map<String, dynamic>),
shortLinkV2: json['short_link_v2'] as String?,
upFromV2: json['up_from_v2'] as int?,
pubLocation: json['pub_location'] as String?,
cover43: json['cover43'] as String?,
tidv2: json['tidv2'] as int?,
tnamev2: json['tnamev2'] as String?,
pidV2: json['pid_v2'] as int?,
pidNameV2: json['pid_name_v2'] as String?,
pages: (json['pages'] as List<dynamic>?)
?.map((e) => Page.fromJson(e as Map<String, dynamic>))
.toList(),
bangumi: json['bangumi'] == null
? null
: Bangumi.fromJson(json['bangumi'] as Map<String, dynamic>),
subtitle: json['bangumi'] == null
? null
: (json['title'] as String)
.replaceFirst('${json['bangumi']['season']['title']} ', ''),
cid: json['cid'] as int?,
progress: json['progress'] as int?,
addAt: json['add_at'] as int?,
bvid: json['bvid'] as String?,
uri: json['uri'] as String?,
viewed: json['viewed'] as bool?,
seq: json['seq'] as int?,
enableVt: json['enable_vt'] as int?,
viewText1: json['view_text_1'] as String?,
isPgc: json['is_pgc'] as bool?,
pgcLabel: json['pgc_label'] as String?,
isPugv: json['is_pugv'] as bool?,
missionId: json['mission_id'] as int?,
firstFrame: json['first_frame'] as String?,
seasonId: json['season_id'] as int?,
);
}

View File

@@ -0,0 +1,13 @@
class Owner {
int? mid;
String? name;
String? face;
Owner({this.mid, this.name, this.face});
factory Owner.fromJson(Map<String, dynamic> json) => Owner(
mid: json['mid'] as int?,
name: json['name'] as String?,
face: json['face'] as String?,
);
}

View File

@@ -0,0 +1,39 @@
import 'package:PiliPlus/models_new/later/dimension.dart';
class Page {
int? cid;
int? page;
String? from;
String? part;
int? duration;
String? vid;
String? weblink;
Dimension? dimension;
int? ctime;
Page({
this.cid,
this.page,
this.from,
this.part,
this.duration,
this.vid,
this.weblink,
this.dimension,
this.ctime,
});
factory Page.fromJson(Map<String, dynamic> json) => Page(
cid: json['cid'] as int?,
page: json['page'] as int?,
from: json['from'] as String?,
part: json['part'] as String?,
duration: json['duration'] as int?,
vid: json['vid'] as String?,
weblink: json['weblink'] as String?,
dimension: json['dimension'] == null
? null
: Dimension.fromJson(json['dimension'] as Map<String, dynamic>),
ctime: json['ctime'] as int?,
);
}

View File

@@ -0,0 +1,50 @@
class Rights {
int? bp;
int? elec;
int? download;
int? movie;
int? pay;
int? hd5;
int? noReprint;
int? autoplay;
int? ugcPay;
int? isCooperation;
int? ugcPayPreview;
int? noBackground;
int? arcPay;
int? payFreeWatch;
Rights({
this.bp,
this.elec,
this.download,
this.movie,
this.pay,
this.hd5,
this.noReprint,
this.autoplay,
this.ugcPay,
this.isCooperation,
this.ugcPayPreview,
this.noBackground,
this.arcPay,
this.payFreeWatch,
});
factory Rights.fromJson(Map<String, dynamic> json) => Rights(
bp: json['bp'] as int?,
elec: json['elec'] as int?,
download: json['download'] as int?,
movie: json['movie'] as int?,
pay: json['pay'] as int?,
hd5: json['hd5'] as int?,
noReprint: json['no_reprint'] as int?,
autoplay: json['autoplay'] as int?,
ugcPay: json['ugc_pay'] as int?,
isCooperation: json['is_cooperation'] as int?,
ugcPayPreview: json['ugc_pay_preview'] as int?,
noBackground: json['no_background'] as int?,
arcPay: json['arc_pay'] as int?,
payFreeWatch: json['pay_free_watch'] as int?,
);
}

View File

@@ -0,0 +1,32 @@
class Season {
int? seasonId;
String? title;
int? seasonStatus;
int? isFinish;
int? totalCount;
int? newestEpId;
String? newestEpIndex;
int? seasonType;
Season({
this.seasonId,
this.title,
this.seasonStatus,
this.isFinish,
this.totalCount,
this.newestEpId,
this.newestEpIndex,
this.seasonType,
});
factory Season.fromJson(Map<String, dynamic> json) => Season(
seasonId: json['season_id'] as int?,
title: json['title'] as String?,
seasonStatus: json['season_status'] as int?,
isFinish: json['is_finish'] as int?,
totalCount: json['total_count'] as int?,
newestEpId: json['newest_ep_id'] as int?,
newestEpIndex: json['newest_ep_index'] as String?,
seasonType: json['season_type'] as int?,
);
}

View File

@@ -0,0 +1,47 @@
class Stat {
int? aid;
int? view;
int? danmaku;
int? reply;
int? favorite;
int? coin;
int? share;
int? nowRank;
int? hisRank;
int? like;
int? dislike;
int? vt;
int? vv;
Stat({
this.aid,
this.view,
this.danmaku,
this.reply,
this.favorite,
this.coin,
this.share,
this.nowRank,
this.hisRank,
this.like,
this.dislike,
this.vt,
this.vv,
});
factory Stat.fromJson(Map<String, dynamic> json) => Stat(
aid: json['aid'] as int?,
view: json['view'] as int?,
danmaku: json['danmaku'] as int?,
reply: json['reply'] as int?,
favorite: json['favorite'] as int?,
coin: json['coin'] as int?,
share: json['share'] as int?,
nowRank: json['now_rank'] as int?,
hisRank: json['his_rank'] as int?,
like: json['like'] as int?,
dislike: json['dislike'] as int?,
vt: json['vt'] as int?,
vv: json['vv'] as int?,
);
}

View File

@@ -193,7 +193,7 @@ class _ArticlePageState extends State<ArticlePage>
id: id,
oid: oid,
rpid: rpid,
source: 'dynamic',
isVideoDetail: false,
replyType: _articleCtr.commentType,
firstFloor: replyItem,
onDispose: onDispose,

View File

@@ -22,7 +22,7 @@ import 'package:get/get.dart';
class AuthorPanel extends StatelessWidget {
final DynamicItemModel item;
final Function? addBannedList;
final String? source;
final bool isDetail;
final ValueChanged? onRemove;
final bool isSave;
final Function(bool isTop, dynamic dynId)? onSetTop;
@@ -32,7 +32,7 @@ class AuthorPanel extends StatelessWidget {
super.key,
required this.item,
this.addBannedList,
this.source,
this.isDetail = false,
this.onRemove,
this.isSave = false,
this.onSetTop,
@@ -119,7 +119,7 @@ class AuthorPanel extends StatelessWidget {
),
Align(
alignment: Alignment.centerRight,
child: source != 'detail' && item.modules.moduleTag?.text != null
child: !isDetail && item.modules.moduleTag?.text != null
? Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@@ -11,7 +11,7 @@ Widget content(
bool isSave,
BuildContext context,
DynamicItemModel item,
String? source,
bool isDetail,
Function(List<String>, int)? callback, {
floor = 1,
}) {
@@ -53,7 +53,7 @@ Widget content(
style: TextStyle(
fontSize: floor != 1
? 14
: source == 'detail' && !isSave
: isDetail && !isSave
? 16
: 15,
color: theme.colorScheme.primary,
@@ -61,7 +61,7 @@ Widget content(
),
),
if (richNodes != null)
source == 'detail' && floor == 1
isDetail && floor == 1
? SelectableText.rich(
richNodes,
style: isSave

View File

@@ -12,7 +12,7 @@ import 'package:get/get.dart';
class DynamicPanel extends StatelessWidget {
final DynamicItemModel item;
final String? source;
final bool isDetail;
final ValueChanged? onRemove;
final Function(List<String>, int)? callback;
final bool isSave;
@@ -22,7 +22,7 @@ class DynamicPanel extends StatelessWidget {
const DynamicPanel({
super.key,
required this.item,
this.source,
this.isDetail = false,
this.onRemove,
this.callback,
this.isSave = false,
@@ -35,7 +35,7 @@ class DynamicPanel extends StatelessWidget {
final theme = Theme.of(context);
final authorWidget = AuthorPanel(
item: item,
source: source,
isDetail: isDetail,
onRemove: onRemove,
isSave: isSave,
onSetTop: onSetTop,
@@ -45,7 +45,7 @@ class DynamicPanel extends StatelessWidget {
elevation: 0,
color: Colors.transparent,
child: InkWell(
onTap: source == 'detail' &&
onTap: isDetail &&
!const {
'DYNAMIC_TYPE_AV',
'DYNAMIC_TYPE_UGC_SEASON',
@@ -67,22 +67,21 @@ class DynamicPanel extends StatelessWidget {
child: authorWidget,
),
if (item.type != 'DYNAMIC_TYPE_NONE')
content(theme, isSave, context, item, source, callback),
module(theme, isSave, item, context, source, callback),
content(theme, isSave, context, item, isDetail, callback),
module(theme, isSave, item, context, isDetail, callback),
if (item.modules.moduleDynamic?.additional != null)
addWidget(theme, item, context),
if (item.modules.moduleDynamic?.major?.blocked != null)
blockedItem(theme, item.modules.moduleDynamic!.major!.blocked!),
const SizedBox(height: 2),
if (source == null) ActionPanel(item: item),
if (source == 'detail' && !isSave) const SizedBox(height: 12),
if (!isDetail) ActionPanel(item: item),
if (isDetail && !isSave) const SizedBox(height: 12),
],
),
),
);
if (isSave ||
(source == 'detail' &&
Get.context!.orientation == Orientation.landscape)) {
(isDetail && Get.context!.orientation == Orientation.landscape)) {
return child;
}
return DecoratedBox(

View File

@@ -6,7 +6,7 @@ import 'package:get/get.dart';
Widget livePanel(
ThemeData theme,
String? source,
bool isDetail,
DynamicItemModel item,
BuildContext context, {
int floor = 1,
@@ -45,8 +45,8 @@ Widget livePanel(
children: [
Text(
content.live!.title!,
maxLines: source == 'detail' ? null : 2,
overflow: source == 'detail' ? null : TextOverflow.ellipsis,
maxLines: isDetail ? null : 2,
overflow: isDetail ? null : TextOverflow.ellipsis,
),
const SizedBox(height: 4),
if (content.live?.descFirst != null)

View File

@@ -8,7 +8,7 @@ import 'package:get/get.dart';
Widget livePanelSub(
ThemeData theme,
String? source,
bool isDetail,
DynamicItemModel item,
BuildContext context, {
int floor = 1,
@@ -113,9 +113,9 @@ Widget livePanelSub(
const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
child: Text(
content.title!,
maxLines: source == 'detail' ? null : 1,
maxLines: isDetail ? null : 1,
style: const TextStyle(fontWeight: FontWeight.bold),
overflow: source == 'detail' ? null : TextOverflow.ellipsis,
overflow: isDetail ? null : TextOverflow.ellipsis,
),
),
const SizedBox(height: 2),

View File

@@ -8,7 +8,7 @@ import 'package:flutter/material.dart';
Widget liveRcmdPanel(
ThemeData theme,
String? source,
bool isDetail,
DynamicItemModel item,
BuildContext context, {
int floor = 1,
@@ -113,9 +113,9 @@ Widget liveRcmdPanel(
const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
child: Text(
liveRcmd.title!,
maxLines: source == 'detail' ? null : 1,
maxLines: isDetail ? null : 1,
style: const TextStyle(fontWeight: FontWeight.bold),
overflow: source == 'detail' ? null : TextOverflow.ellipsis,
overflow: isDetail ? null : TextOverflow.ellipsis,
),
),
const SizedBox(height: 2),

View File

@@ -22,7 +22,7 @@ Widget module(
bool isSave,
DynamicItemModel item,
BuildContext context,
String? source,
bool isDetail,
Function(List<String>, int)? callback, {
floor = 1,
}) {
@@ -36,7 +36,7 @@ Widget module(
// 视频
case 'DYNAMIC_TYPE_AV':
return videoSeasonWidget(
theme, isSave, source, item, context, 'archive', callback,
theme, isSave, isDetail, item, context, 'archive', callback,
floor: floor);
// 转发
case 'DYNAMIC_TYPE_FORWARD':
@@ -45,108 +45,134 @@ Widget module(
orig.modules.moduleDynamic?.major?.type == 'MAJOR_TYPE_NONE';
late final isNormalAuth =
orig.modules.moduleAuthor!.type == 'AUTHOR_TYPE_NORMAL';
return InkWell(
onTap:
isNoneMajor ? null : () => PageUtils.pushDynDetail(orig, floor + 1),
onLongPress: isNoneMajor
? null
: () {
late String? title, cover;
late var origMajor = orig.modules.moduleDynamic?.major;
late var major = item.modules.moduleDynamic?.major;
switch (orig.type) {
case 'DYNAMIC_TYPE_AV':
title = origMajor?.archive?.title;
cover = origMajor?.archive?.cover;
break;
case 'DYNAMIC_TYPE_UGC_SEASON':
title = origMajor?.ugcSeason?.title;
cover = origMajor?.ugcSeason?.cover;
break;
case 'DYNAMIC_TYPE_PGC' || 'DYNAMIC_TYPE_PGC_UNION':
title = origMajor?.pgc?.title;
cover = origMajor?.pgc?.cover;
break;
case 'DYNAMIC_TYPE_LIVE_RCMD':
title = major?.liveRcmd?.title;
cover = major?.liveRcmd?.cover;
break;
case 'DYNAMIC_TYPE_LIVE':
title = major?.live?.title;
cover = major?.live?.cover;
break;
default:
return;
}
imageSaveDialog(
title: title,
cover: cover,
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
color: theme.dividerColor.withValues(alpha: 0.08),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (orig.type != 'DYNAMIC_TYPE_NONE') ...[
Row(
children: [
GestureDetector(
onTap: isNormalAuth
? () => Get.toNamed(
'/member?mid=${orig.modules.moduleAuthor!.mid}',
arguments: {
'face': orig.modules.moduleAuthor!.face
},
)
: null,
child: Text(
'${isNormalAuth ? '@' : ''}${orig.modules.moduleAuthor!.name}',
style: TextStyle(color: theme.colorScheme.primary),
return orig.type == 'DYNAMIC_TYPE_NONE'
? const SizedBox.shrink()
: InkWell(
onTap: isNoneMajor
? null
: () => PageUtils.pushDynDetail(orig, floor + 1),
onLongPress: isNoneMajor
? null
: () {
late String? title, cover;
late var origMajor = orig.modules.moduleDynamic?.major;
late var major = item.modules.moduleDynamic?.major;
switch (orig.type) {
case 'DYNAMIC_TYPE_AV':
title = origMajor?.archive?.title;
cover = origMajor?.archive?.cover;
break;
case 'DYNAMIC_TYPE_UGC_SEASON':
title = origMajor?.ugcSeason?.title;
cover = origMajor?.ugcSeason?.cover;
break;
case 'DYNAMIC_TYPE_PGC' || 'DYNAMIC_TYPE_PGC_UNION':
title = origMajor?.pgc?.title;
cover = origMajor?.pgc?.cover;
break;
case 'DYNAMIC_TYPE_LIVE_RCMD':
title = major?.liveRcmd?.title;
cover = major?.liveRcmd?.cover;
break;
case 'DYNAMIC_TYPE_LIVE':
title = major?.live?.title;
cover = major?.live?.cover;
break;
default:
return;
}
imageSaveDialog(
title: title,
cover: cover,
);
},
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
color: theme.dividerColor.withValues(alpha: 0.08),
child: isNoneMajor
? Row(
children: [
Icon(
Icons.error,
size: 18,
color: theme.colorScheme.outline,
),
const SizedBox(width: 5),
Text(
orig.modules.moduleDynamic?.major?.none?.tips ??
'NONE',
style: TextStyle(color: theme.colorScheme.outline),
),
],
)
: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
GestureDetector(
onTap: isNormalAuth
? () => Get.toNamed(
'/member?mid=${orig.modules.moduleAuthor!.mid}',
arguments: {
'face':
orig.modules.moduleAuthor!.face
},
)
: null,
child: Text(
'${isNormalAuth ? '@' : ''}${orig.modules.moduleAuthor!.name}',
style: TextStyle(
color: theme.colorScheme.primary),
),
),
const SizedBox(width: 6),
Text(
Utils.dateFormat(
orig.modules.moduleAuthor!.pubTs),
style: TextStyle(
color: theme.colorScheme.outline,
fontSize:
theme.textTheme.labelSmall!.fontSize),
),
],
),
const SizedBox(height: 5),
content(
theme, isSave, context, orig, isDetail, callback,
floor: floor + 1),
module(
theme, isSave, orig, context, isDetail, callback,
floor: floor + 1),
if (orig.modules.moduleDynamic?.additional != null)
addWidget(theme, orig, context, floor: floor + 1),
if (orig.modules.moduleDynamic?.major?.blocked !=
null)
blockedItem(theme,
orig.modules.moduleDynamic!.major!.blocked!),
],
),
),
const SizedBox(width: 6),
Text(
Utils.dateFormat(orig.modules.moduleAuthor!.pubTs),
style: TextStyle(
color: theme.colorScheme.outline,
fontSize: theme.textTheme.labelSmall!.fontSize),
),
],
),
const SizedBox(height: 5),
content(theme, isSave, context, orig, source, callback,
floor: floor + 1),
],
module(theme, isSave, orig, context, source, callback,
floor: floor + 1),
if (orig.modules.moduleDynamic?.additional != null)
addWidget(theme, orig, context, floor: floor + 1),
if (orig.modules.moduleDynamic?.major?.blocked != null)
blockedItem(theme, orig.modules.moduleDynamic!.major!.blocked!),
],
),
),
);
),
);
// 直播
case 'DYNAMIC_TYPE_LIVE_RCMD':
return liveRcmdPanel(theme, source, item, context, floor: floor);
return liveRcmdPanel(theme, isDetail, item, context, floor: floor);
// 直播
case 'DYNAMIC_TYPE_LIVE':
return livePanel(theme, source, item, context, floor: floor);
return livePanel(theme, isDetail, item, context, floor: floor);
// 合集
case 'DYNAMIC_TYPE_UGC_SEASON':
return videoSeasonWidget(
theme, isSave, source, item, context, 'ugcSeason', callback);
theme, isSave, isDetail, item, context, 'ugcSeason', callback);
case 'DYNAMIC_TYPE_PGC':
return videoSeasonWidget(
theme, isSave, source, item, context, 'pgc', callback,
theme, isSave, isDetail, item, context, 'pgc', callback,
floor: floor);
case 'DYNAMIC_TYPE_PGC_UNION':
return videoSeasonWidget(
theme, isSave, source, item, context, 'pgc', callback,
theme, isSave, isDetail, item, context, 'pgc', callback,
floor: floor);
case 'DYNAMIC_TYPE_NONE':
return Row(
@@ -342,7 +368,7 @@ Widget module(
case 'DYNAMIC_TYPE_SUBSCRIPTION_NEW'
when item.modules.moduleDynamic?.major?.type ==
'MAJOR_TYPE_SUBSCRIPTION_NEW':
return livePanelSub(theme, source, item, context, floor: floor);
return livePanelSub(theme, isDetail, item, context, floor: floor);
default:
return Padding(

View File

@@ -10,7 +10,7 @@ import 'package:flutter/material.dart';
Widget videoSeasonWidget(
ThemeData theme,
bool isSave,
String? source,
bool isDetail,
DynamicItemModel item,
BuildContext context,
String type,
@@ -29,9 +29,7 @@ Widget videoSeasonWidget(
const SizedBox(width: 5),
Text(
item.modules.moduleDynamic!.major!.none!.tips!,
style: TextStyle(
color: theme.colorScheme.outline,
),
style: TextStyle(color: theme.colorScheme.outline),
),
],
)
@@ -159,9 +157,9 @@ Widget videoSeasonWidget(
: EdgeInsets.zero,
child: Text(
itemContent.title!,
maxLines: source == 'detail' ? null : 1,
maxLines: isDetail ? null : 1,
style: const TextStyle(fontWeight: FontWeight.bold),
overflow: source == 'detail' ? null : TextOverflow.ellipsis,
overflow: isDetail ? null : TextOverflow.ellipsis,
),
),
],

View File

@@ -137,7 +137,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
id: id,
oid: oid,
rpid: rpid,
source: 'dynamic',
isVideoDetail: false,
replyType: _controller.replyType,
firstFloor: replyItem,
onDispose: onDispose,
@@ -253,7 +253,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
duration: const Duration(milliseconds: 300),
child: AuthorPanel(
item: _controller.dynItem,
source: 'detail', //to remove tag
isDetail: true,
),
);
},
@@ -332,7 +332,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
SliverToBoxAdapter(
child: DynamicPanel(
item: _controller.dynItem,
source: 'detail',
isDetail: true,
callback: _getImageCallback,
),
),
@@ -363,7 +363,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
sliver: SliverToBoxAdapter(
child: DynamicPanel(
item: _controller.dynItem,
source: 'detail',
isDetail: true,
callback: _getImageCallback,
),
),

View File

@@ -512,20 +512,19 @@ class _EpisodePanelState extends CommonSlidePageState<EpisodePanel> {
if (view != null) ...[
const SizedBox(height: 2),
Row(
spacing: 8,
children: [
StatView(
context: context,
theme: 'gray',
value: view,
),
if (danmaku != null) ...[
const SizedBox(width: 8),
if (danmaku != null)
StatDanMu(
context: context,
theme: 'gray',
value: danmaku,
),
],
],
),
],

View File

@@ -72,7 +72,7 @@ class _FavVideoPageState extends State<FavVideoPage>
String heroTag = Utils.makeHeroTag(item.fid);
return FavVideoItem(
heroTag: heroTag,
favFolderItem: item,
item: item,
onTap: () async {
var res = await Get.toNamed(
'/favDetail',

View File

@@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
class FavVideoItem extends StatelessWidget {
final String heroTag;
final FavVideoItemModel favFolderItem;
final FavVideoItemModel item;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
@@ -16,7 +16,7 @@ class FavVideoItem extends StatelessWidget {
this.onTap,
this.onLongPress,
required this.heroTag,
required this.favFolderItem,
required this.item,
});
@override
@@ -27,8 +27,8 @@ class FavVideoItem extends StatelessWidget {
(onTap == null
? null
: () => imageSaveDialog(
title: favFolderItem.title,
cover: favFolderItem.cover,
title: item.title,
cover: item.cover,
)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
@@ -43,7 +43,7 @@ class FavVideoItem extends StatelessWidget {
return Hero(
tag: heroTag,
child: NetworkImgLayer(
src: favFolderItem.cover,
src: item.cover,
width: boxConstraints.maxWidth,
height: boxConstraints.maxHeight,
),
@@ -52,14 +52,14 @@ class FavVideoItem extends StatelessWidget {
),
),
const SizedBox(width: 10),
videoContent(context),
content(context),
],
),
),
);
}
Widget videoContent(context) {
Widget content(BuildContext context) {
final theme = Theme.of(context);
final fontSize = theme.textTheme.labelMedium!.fontSize;
final color = theme.colorScheme.outline;
@@ -68,22 +68,22 @@ class FavVideoItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
favFolderItem.title ?? '',
item.title!,
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
),
if (favFolderItem.intro?.isNotEmpty == true)
if (item.intro?.isNotEmpty == true)
Text(
favFolderItem.intro!,
item.intro!,
style: TextStyle(
fontSize: fontSize,
color: color,
),
),
Text(
'${favFolderItem.mediaCount}个内容',
'${item.mediaCount}个内容',
style: TextStyle(
fontSize: fontSize,
color: color,
@@ -91,7 +91,7 @@ class FavVideoItem extends StatelessWidget {
),
const Spacer(),
Text(
Utils.isPublicFavText(favFolderItem.attr ?? 0),
Utils.isPublicFavText(item.attr ?? 0),
style: TextStyle(
fontSize: fontSize,
color: color,

View File

@@ -9,6 +9,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/data.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/media.dart';
import 'package:PiliPlus/models_new/fav/fav_video/list.dart';
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
import 'package:PiliPlus/pages/fav_detail/controller.dart';
import 'package:PiliPlus/pages/fav_detail/widget/fav_video_card.dart';
import 'package:PiliPlus/pages/fav_sort/view.dart';
@@ -134,6 +135,16 @@ class _FavDetailPageState extends State<FavDetailPage> {
),
icon: const Icon(Icons.search_outlined),
),
Obx(
() => Utils.isPublicFav(_favDetailController.item.value.attr ?? 0)
? IconButton(
tooltip: '分享',
onPressed: () => Utils.shareText(
'https://www.bilibili.com/medialist/detail/ml${_favDetailController.mediaId}'),
icon: const Icon(Icons.share),
)
: const SizedBox.shrink(),
),
Obx(
() => _favDetailController.isOwner.value
? PopupMenuButton(
@@ -150,6 +161,23 @@ class _FavDetailPageState extends State<FavDetailPage> {
}),
child: const Text('编辑信息'),
),
if (Utils.isPublicFav(
_favDetailController.item.value.attr ?? 0))
PopupMenuItem(
onTap: () => showModalBottomSheet(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (context) => RepostPanel(
rid: _favDetailController.mediaId,
dynType: 4300,
pic: _favDetailController.item.value.cover,
title: _favDetailController.item.value.title,
uname: _favDetailController.item.value.upper?.name,
),
),
child: const Text('分享至动态'),
),
PopupMenuItem(
onTap: () =>
FavHttp.cleanFav(mediaId: mediaId).then((data) {
@@ -225,7 +253,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
)
: const SizedBox.shrink(),
),
const SizedBox(width: 12),
const SizedBox(width: 10),
];
List<Widget> _selectActions(ThemeData theme) => [
@@ -410,7 +438,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
children: [
Positioned.fill(
child: FavVideoCardH(
videoItem: item,
item: item,
onDelFav: _favDetailController.isOwner.value
? () => _favDetailController.onCancelFav(
index,

View File

@@ -13,7 +13,7 @@ import 'package:get/get.dart';
// 收藏视频卡片 - 水平布局
class FavVideoCardH extends StatelessWidget {
final FavDetailItemModel videoItem;
final FavDetailItemModel item;
final GestureTapCallback? onTap;
final GestureLongPressCallback? onLongPress;
final VoidCallback? onDelFav;
@@ -22,7 +22,7 @@ class FavVideoCardH extends StatelessWidget {
const FavVideoCardH({
super.key,
required this.videoItem,
required this.item,
this.onDelFav,
this.onTap,
this.onLongPress,
@@ -37,16 +37,16 @@ class FavVideoCardH extends StatelessWidget {
? null
: onTap ??
() {
if (!const [0, 16].contains(videoItem.attr)) {
Get.toNamed('/member?mid=${videoItem.upper?.mid}');
if (!const [0, 16].contains(item.attr)) {
Get.toNamed('/member?mid=${item.upper?.mid}');
return;
}
// pgc
if (videoItem.type == 24) {
if (item.type == 24) {
PageUtils.viewPgc(
seasonId: videoItem.ogv!.seasonId,
epId: videoItem.id,
seasonId: item.ogv!.seasonId,
epId: item.id,
);
return;
}
@@ -57,8 +57,8 @@ class FavVideoCardH extends StatelessWidget {
? null
: onLongPress ??
() => imageSaveDialog(
title: videoItem.title,
cover: videoItem.cover,
title: item.title,
cover: item.cover,
),
child: Padding(
padding: const EdgeInsets.symmetric(
@@ -79,18 +79,18 @@ class FavVideoCardH extends StatelessWidget {
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
src: videoItem.cover,
src: item.cover,
width: maxWidth,
height: maxHeight,
),
PBadge(
text: Utils.timeFormat(videoItem.duration),
text: Utils.timeFormat(item.duration),
right: 6.0,
bottom: 6.0,
type: PBadgeType.gray,
),
PBadge(
text: videoItem.ogv?.typeName,
text: item.ogv?.typeName,
top: 6.0,
right: 6.0,
bottom: null,
@@ -102,35 +102,45 @@ class FavVideoCardH extends StatelessWidget {
),
),
const SizedBox(width: 10),
videoContent(context),
content(context),
],
),
),
);
}
Widget videoContent(BuildContext context) {
Widget content(BuildContext context) {
final theme = Theme.of(context);
return Expanded(
child: Stack(
clipBehavior: Clip.none,
children: [
Column(
spacing: 3,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
videoItem.title!,
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
Text(
item.title!,
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
if (item.type == 24 && item.intro?.isNotEmpty == true)
Text(
item.intro!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: theme.colorScheme.outline,
),
),
),
const Spacer(),
Text(
'${Utils.dateFormat(videoItem.favTime)} ${videoItem.upper?.name}',
'${Utils.dateFormat(item.favTime)} ${item.upper?.name}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
@@ -139,23 +149,22 @@ class FavVideoCardH extends StatelessWidget {
color: theme.colorScheme.outline,
),
),
const SizedBox(height: 3),
Row(
children: [
StatView(
context: context,
theme: 'gray',
value: Utils.numFormat(videoItem.cntInfo?.play),
),
const SizedBox(width: 8),
StatDanMu(
context: context,
theme: 'gray',
value: Utils.numFormat(videoItem.cntInfo?.danmaku),
),
const Spacer(),
],
),
if (item.type != 24)
Row(
spacing: 8,
children: [
StatView(
context: context,
theme: 'gray',
value: Utils.numFormat(item.cntInfo?.play),
),
StatDanMu(
context: context,
theme: 'gray',
value: Utils.numFormat(item.cntInfo?.danmaku),
),
],
),
],
),
if (onDelFav != null)

View File

@@ -128,7 +128,7 @@ class _FavFolderSortPageState extends State<FavFolderSortPage> {
height: 98,
child: FavVideoItem(
heroTag: key,
favFolderItem: item,
item: item,
onLongPress:
index == 0 ? () => SmartDialog.showToast('默认收藏夹不支持排序') : null,
),

View File

@@ -36,7 +36,7 @@ class _FavSearchPageState extends CommonSearchPageState<FavSearchPage,
}
final item = list[index];
return FavVideoCardH(
videoItem: item,
item: item,
onDelFav: controller.isOwner == true
? () => controller.onCancelFav(
index,

View File

@@ -135,7 +135,7 @@ class _FavSortPageState extends State<FavSortPage> {
height: 98,
child: FavVideoCardH(
isSort: true,
videoItem: item,
item: item,
),
);
},

View File

@@ -275,7 +275,7 @@ class _HistoryPageState extends State<HistoryPage>
}
final item = response[index];
return HistoryItem(
videoItem: item,
item: item,
ctr: _historyController.baseCtr,
onChoose: () => _historyController.onSelect(index),
onDelete: (kid, business) =>

View File

@@ -20,14 +20,14 @@ import 'package:get/get.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
class HistoryItem extends StatelessWidget {
final HistoryItemModel videoItem;
final HistoryItemModel item;
final dynamic ctr;
final Function? onChoose;
final Function(dynamic kid, dynamic business) onDelete;
const HistoryItem({
super.key,
required this.videoItem,
required this.item,
this.ctr,
this.onChoose,
required this.onDelete,
@@ -36,8 +36,8 @@ class HistoryItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
int aid = videoItem.history.oid!;
String bvid = videoItem.history.bvid ?? IdUtils.av2bv(aid);
int aid = item.history.oid!;
String bvid = item.history.bvid ?? IdUtils.av2bv(aid);
return InkWell(
onTap: () async {
if (ctr is MultiSelectController || ctr is HistoryBaseController) {
@@ -46,37 +46,37 @@ class HistoryItem extends StatelessWidget {
return;
}
}
if (videoItem.history.business?.contains('article') == true) {
if (item.history.business?.contains('article') == true) {
PageUtils.toDupNamed(
'/articlePage',
parameters: {
'id': videoItem.history.business == 'article-list'
? '${videoItem.history.cid}'
: '${videoItem.history.oid}',
'id': item.history.business == 'article-list'
? '${item.history.cid}'
: '${item.history.oid}',
'type': 'read',
},
);
} else if (videoItem.history.business == 'live') {
if (videoItem.liveStatus == 1) {
Get.toNamed('/liveRoom?roomid=${videoItem.history.oid}');
} else if (item.history.business == 'live') {
if (item.liveStatus == 1) {
Get.toNamed('/liveRoom?roomid=${item.history.oid}');
} else {
SmartDialog.showToast('直播未开播');
}
} else if (videoItem.history.business == 'pgc') {
PageUtils.viewPgc(epId: videoItem.history.epid);
} else if (item.history.business == 'pgc') {
PageUtils.viewPgc(epId: item.history.epid);
} else {
int? cid = videoItem.history.cid ??
int? cid = item.history.cid ??
await SearchHttp.ab2c(
aid: aid,
bvid: bvid,
part: videoItem.history.page,
part: item.history.page,
);
if (cid != null) {
PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
arguments: {
'heroTag': Utils.makeHeroTag(aid),
'pic': videoItem.cover,
'pic': item.cover,
},
);
}
@@ -91,8 +91,8 @@ class HistoryItem extends StatelessWidget {
return;
}
imageSaveDialog(
title: videoItem.title,
cover: videoItem.cover,
title: item.title,
cover: item.cover,
);
},
child: Stack(
@@ -117,51 +117,51 @@ class HistoryItem extends StatelessWidget {
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
src: videoItem.cover?.isNotEmpty == true
? videoItem.cover
: videoItem.covers?.firstOrNull ?? '',
src: item.cover?.isNotEmpty == true
? item.cover
: item.covers?.firstOrNull ?? '',
width: maxWidth,
height: maxHeight,
),
if (!HistoryBusinessType.hiddenDurationType
.contains(videoItem.history.business))
.contains(item.history.business))
PBadge(
text: videoItem.progress == -1
text: item.progress == -1
? '已看完'
: '${Utils.timeFormat(videoItem.progress)}/${Utils.timeFormat(videoItem.duration!)}',
: '${Utils.timeFormat(item.progress)}/${Utils.timeFormat(item.duration!)}',
right: 6.0,
bottom: 8.0,
type: PBadgeType.gray,
),
// 右上角
if (HistoryBusinessType.showBadge
.contains(videoItem.history.business) ||
videoItem.history.business ==
.contains(item.history.business) ||
item.history.business ==
HistoryBusinessType.live.type)
PBadge(
text: videoItem.badge,
text: item.badge,
top: 6.0,
right: 6.0,
bottom: null,
left: null,
),
if (videoItem.duration != null &&
videoItem.duration != 0 &&
videoItem.progress != null &&
videoItem.progress != 0)
if (item.duration != null &&
item.duration != 0 &&
item.progress != null &&
item.progress != 0)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: videoProgressIndicator(
videoItem.progress == -1
item.progress == -1
? 1
: videoItem.progress! / videoItem.duration!,
: item.progress! / item.duration!,
),
),
Positioned.fill(
child: AnimatedOpacity(
opacity: videoItem.checked == true ? 1 : 0,
opacity: item.checked == true ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
alignment: Alignment.center,
@@ -173,7 +173,7 @@ class HistoryItem extends StatelessWidget {
width: 34,
height: 34,
child: AnimatedScale(
scale: videoItem.checked == true ? 1 : 0,
scale: item.checked == true ? 1 : 0,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
child: IconButton(
@@ -207,7 +207,7 @@ class HistoryItem extends StatelessWidget {
),
),
const SizedBox(width: 10),
videoContent(theme),
content(theme),
],
),
),
@@ -227,13 +227,13 @@ class HistoryItem extends StatelessWidget {
),
position: PopupMenuPosition.under,
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
if (videoItem.authorMid != null &&
videoItem.authorName?.isNotEmpty == true)
if (item.authorMid != null &&
item.authorName?.isNotEmpty == true)
PopupMenuItem<String>(
onTap: () => Get.toNamed(
'/member?mid=${videoItem.authorMid}',
'/member?mid=${item.authorMid}',
arguments: {
'heroTag': '${videoItem.authorMid}',
'heroTag': '${item.authorMid}',
},
),
height: 35,
@@ -242,21 +242,21 @@ class HistoryItem extends StatelessWidget {
const Icon(MdiIcons.accountCircleOutline, size: 16),
const SizedBox(width: 6),
Text(
'访问:${videoItem.authorName}',
'访问:${item.authorName}',
style: const TextStyle(fontSize: 13),
)
],
),
),
if (videoItem.history.business != 'pgc' &&
videoItem.badge != '番剧' &&
videoItem.tagName?.contains('动画') != true &&
videoItem.history.business != 'live' &&
videoItem.history.business?.contains('article') != true)
if (item.history.business != 'pgc' &&
item.badge != '番剧' &&
item.tagName?.contains('动画') != true &&
item.history.business != 'live' &&
item.history.business?.contains('article') != true)
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
bvid: videoItem.history.bvid);
var res =
await UserHttp.toViewLater(bvid: item.history.bvid);
SmartDialog.showToast(res['msg']);
},
height: 35,
@@ -269,8 +269,7 @@ class HistoryItem extends StatelessWidget {
),
),
PopupMenuItem<String>(
onTap: () =>
onDelete(videoItem.kid, videoItem.history.business),
onTap: () => onDelete(item.kid, item.history.business),
height: 35,
child: const Row(
children: [
@@ -289,27 +288,37 @@ class HistoryItem extends StatelessWidget {
);
}
Widget videoContent(ThemeData theme) {
Widget content(ThemeData theme) {
return Expanded(
child: Column(
spacing: 2,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
videoItem.title!,
textAlign: TextAlign.start,
Text(
item.title!,
style: TextStyle(
fontSize: theme.textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: item.videos! > 1 ? 1 : 2,
overflow: TextOverflow.ellipsis,
),
if (item.history.business == 'pgc' &&
item.showTitle?.isNotEmpty == true)
Text(
item.showTitle!,
style: TextStyle(
fontSize: theme.textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
fontSize: 13,
color: theme.colorScheme.outline,
),
maxLines: videoItem.videos! > 1 ? 1 : 2,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
if (videoItem.authorName != '')
const Spacer(),
if (item.authorName?.isNotEmpty == true)
Text(
videoItem.authorName!,
item.authorName!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
@@ -317,9 +326,8 @@ class HistoryItem extends StatelessWidget {
color: theme.colorScheme.outline,
),
),
const SizedBox(height: 2),
Text(
Utils.dateFormat(videoItem.viewAt!),
Utils.dateFormat(item.viewAt!),
style: TextStyle(
fontSize: theme.textTheme.labelMedium!.fontSize,
color: theme.colorScheme.outline,

View File

@@ -35,7 +35,7 @@ class _HistorySearchPageState extends CommonSearchPageState<HistorySearchPage,
}
final item = list[index];
return HistoryItem(
videoItem: item,
item: item,
ctr: controller,
onChoose: null,
onDelete: (kid, business) {

View File

@@ -93,7 +93,7 @@ class HomeController extends GetxController
} catch (_) {}
}
void showUserInfoDialog(context) {
void showUserInfoDialog(BuildContext context) {
feedBack();
showDialog(
context: context,

View File

@@ -3,11 +3,11 @@ import 'package:PiliPlus/common/skeleton/video_card_h.dart';
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/later_view_type.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models_new/later/list.dart';
import 'package:PiliPlus/pages/later/controller.dart';
import 'package:PiliPlus/pages/later/widgets/video_card_h_later.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
@@ -56,7 +56,7 @@ class _LaterViewChildPageState extends State<LaterViewChildPage>
);
}
Widget _buildBody(LoadingState<List<HotVideoItemModel>?> loadingState) {
Widget _buildBody(LoadingState<List<LaterItemModel>?> loadingState) {
final theme = Theme.of(context);
return switch (loadingState) {
Loading() => SliverGrid(
@@ -80,9 +80,8 @@ class _LaterViewChildPageState extends State<LaterViewChildPage>
return Stack(
clipBehavior: Clip.none,
children: [
VideoCardH(
VideoCardHLater(
videoItem: videoItem,
source: 'later',
onViewLater: (cid) {
PageUtils.toVideoPage(
'bvid=${videoItem.bvid}&cid=$cid',

View File

@@ -1,8 +1,10 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/models/common/later_view_type.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models_new/later/data.dart';
import 'package:PiliPlus/models_new/later/list.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/pages/later/base_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
@@ -13,8 +15,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
LaterController(this.laterViewType);
class LaterController extends MultiSelectController<LaterData, LaterItemModel> {
LaterController(
this.laterViewType,
);
final LaterViewType laterViewType;
dynamic mid;
@@ -23,7 +27,7 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
final LaterBaseController baseCtr = Get.put(LaterBaseController());
@override
Future<LoadingState<Map>> customGetData() => UserHttp.seeYouLater(
Future<LoadingState<LaterData>> customGetData() => UserHttp.seeYouLater(
page: page,
viewed: laterViewType.type,
asc: asc.value,
@@ -31,7 +35,7 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
@override
void onSelect(int index, [bool disableSelect = true]) {
List<HotVideoItemModel> list = loadingState.value.data!;
List<LaterItemModel> list = loadingState.value.data!;
list[index].checked = !(list[index].checked ?? false);
baseCtr.checkedCount.value =
list.where((item) => item.checked == true).length;
@@ -44,9 +48,9 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
@override
void handleSelect([bool checked = false, bool disableSelect = true]) {
if (loadingState.value.isSuccess) {
List<HotVideoItemModel>? list = loadingState.value.data;
List<LaterItemModel>? list = loadingState.value.data;
if (list?.isNotEmpty == true) {
for (HotVideoItemModel item in list!) {
for (LaterItemModel item in list!) {
item.checked = checked;
}
baseCtr.checkedCount.value = checked ? list.length : 0;
@@ -66,8 +70,9 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
}
@override
List<HotVideoItemModel>? getDataList(response) {
return response['list'];
List<LaterItemModel>? getDataList(response) {
baseCtr.counts[laterViewType] = response.count ?? 0;
return response.list;
}
@override
@@ -77,12 +82,6 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
}
}
@override
bool customHandleResponse(bool isRefresh, Success response) {
baseCtr.counts[laterViewType] = response.response['count'];
return false;
}
// single
void toViewDel(BuildContext context, int index, int? aid) {
showDialog(
@@ -180,12 +179,12 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
);
}
Future<void> _onDelete(List<HotVideoItemModel> result) async {
Future<void> _onDelete(List<LaterItemModel> result) async {
SmartDialog.showLoading(msg: '请求中');
List<int?> aids = result.map((item) => item.aid).toList();
var res = await UserHttp.toViewDel(aids: aids);
if (res['status']) {
Set<HotVideoItemModel> remainList =
Set<LaterItemModel> remainList =
loadingState.value.data!.toSet().difference(result.toSet());
baseCtr.counts[laterViewType] =
baseCtr.counts[laterViewType]! - aids.length;
@@ -202,10 +201,10 @@ class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
// 稍后再看播放全部
void toViewPlayAll() {
if (loadingState.value.isSuccess) {
List<HotVideoItemModel>? list = loadingState.value.data;
List<LaterItemModel>? list = loadingState.value.data;
if (list.isNullOrEmpty) return;
for (HotVideoItemModel item in list!) {
for (LaterItemModel item in list!) {
if (item.cid == null || item.pgcLabel?.isNotEmpty == true) {
continue;
} else {

View File

@@ -1,6 +1,7 @@
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/models/common/later_view_type.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models_new/later/data.dart';
import 'package:PiliPlus/models_new/later/list.dart';
import 'package:PiliPlus/pages/history/view.dart' show AppBarWidget;
import 'package:PiliPlus/pages/later/base_controller.dart';
import 'package:PiliPlus/pages/later/controller.dart';
@@ -266,7 +267,7 @@ class _LaterPageState extends State<LaterPage>
),
onPressed: () {
final ctr = currCtr();
RequestUtils.onCopyOrMove<Map, HotVideoItemModel>(
RequestUtils.onCopyOrMove<LaterData, LaterItemModel>(
context: context,
isCopy: true,
ctr: ctr,
@@ -287,7 +288,7 @@ class _LaterPageState extends State<LaterPage>
),
onPressed: () {
final ctr = currCtr();
RequestUtils.onCopyOrMove<Map, HotVideoItemModel>(
RequestUtils.onCopyOrMove<LaterData, LaterItemModel>(
context: context,
isCopy: false,
ctr: ctr,

View File

@@ -0,0 +1,226 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/image/image_save.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/search/result.dart';
import 'package:PiliPlus/models_new/later/list.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
// 视频卡片 - 水平布局
class VideoCardHLater extends StatelessWidget {
const VideoCardHLater({
super.key,
required this.videoItem,
this.onTap,
this.onLongPress,
this.onViewLater,
});
final LaterItemModel videoItem;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final ValueChanged<int>? onViewLater;
@override
Widget build(BuildContext context) {
String type = 'video';
if (videoItem case SearchVideoItemModel item) {
var typeOrNull = item.type;
if (typeOrNull?.isNotEmpty == true) {
type = typeOrNull!;
}
}
return Material(
color: Colors.transparent,
child: InkWell(
onLongPress: onLongPress ??
() => imageSaveDialog(
title: videoItem.title,
cover: videoItem.pic,
),
onTap: onTap ??
() async {
if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放');
return;
}
if (videoItem.isPgc == true) {
if (videoItem.bangumi?.epId != null) {
PageUtils.viewPgc(epId: videoItem.bangumi!.epId);
} else if (videoItem.redirectUrl?.isNotEmpty == true) {
PageUtils.viewPgcFromUri(videoItem.redirectUrl!);
}
return;
}
try {
final int? cid = videoItem.cid ??
await SearchHttp.ab2c(
aid: videoItem.aid, bvid: videoItem.bvid);
if (cid != null) {
onViewLater!(cid);
}
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: 5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
num? progress = videoItem.progress;
return Stack(
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
),
PBadge(
text: videoItem.pgcLabel,
top: 6.0,
right: 6.0,
),
if (progress != null && progress != 0) ...[
PBadge(
text: progress == -1
? '已看完'
: '${Utils.timeFormat(progress)}/${Utils.timeFormat(videoItem.duration)}',
right: 6,
bottom: 8,
type: PBadgeType.gray,
),
Positioned(
left: 0,
bottom: 0,
right: 0,
child: videoProgressIndicator(
progress == -1
? 1
: progress / videoItem.duration!,
),
)
] else if (videoItem.duration! > 0)
PBadge(
text: Utils.timeFormat(videoItem.duration),
right: 6.0,
bottom: 6.0,
type: PBadgeType.gray,
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: PBadgeType.primary,
),
],
);
},
),
),
const SizedBox(width: 10),
content(context),
],
),
),
),
);
}
Widget content(BuildContext context) {
final theme = Theme.of(context);
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (videoItem.isPgc == true && videoItem.bangumi != null) ...[
Text(
videoItem.bangumi!.season!.title!,
style: TextStyle(
fontSize: theme.textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 3),
Text(
videoItem.subtitle!,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 13,
color: theme.colorScheme.outline,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
StatView(
context: context,
theme: 'gray',
value: Utils.numFormat(videoItem.stat?.view),
),
] else ...[
Expanded(
child: Text(
videoItem.title!,
style: TextStyle(
fontSize: theme.textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Text(
videoItem.owner!.name!,
maxLines: 1,
style: TextStyle(
fontSize: 12,
height: 1,
color: theme.colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const SizedBox(height: 3),
Row(
spacing: 8,
children: [
StatView(
context: context,
theme: 'gray',
value: Utils.numFormat(videoItem.stat?.view),
),
StatDanMu(
context: context,
theme: 'gray',
value: Utils.numFormat(videoItem.stat?.danmaku),
),
],
),
]
],
),
);
}
}

View File

@@ -1,25 +1,26 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models_new/later/data.dart';
import 'package:PiliPlus/models_new/later/list.dart';
import 'package:PiliPlus/pages/common/common_search_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class LaterSearchController
extends CommonSearchController<Map, HotVideoItemModel> {
extends CommonSearchController<LaterData, LaterItemModel> {
dynamic mid = Get.arguments['mid'];
dynamic count = Get.arguments['count'];
@override
Future<LoadingState<Map>> customGetData() => UserHttp.seeYouLater(
Future<LoadingState<LaterData>> customGetData() => UserHttp.seeYouLater(
page: page,
keyword: editController.value.text,
);
@override
List<HotVideoItemModel>? getDataList(Map response) {
return response['list'];
List<LaterItemModel>? getDataList(LaterData response) {
return response.list;
}
Future<void> toViewDel(BuildContext context, int index, aid) async {

View File

@@ -1,7 +1,8 @@
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models_new/later/data.dart';
import 'package:PiliPlus/models_new/later/list.dart';
import 'package:PiliPlus/pages/common/common_search_page.dart';
import 'package:PiliPlus/pages/later/widgets/video_card_h_later.dart';
import 'package:PiliPlus/pages/later_search/controller.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/page_utils.dart';
@@ -17,7 +18,7 @@ class LaterSearchPage extends CommonSearchPage {
}
class _LaterSearchPageState
extends CommonSearchPageState<LaterSearchPage, Map, HotVideoItemModel> {
extends CommonSearchPageState<LaterSearchPage, LaterData, LaterItemModel> {
@override
final LaterSearchController controller = Get.put(
LaterSearchController(),
@@ -25,7 +26,7 @@ class _LaterSearchPageState
);
@override
Widget buildList(List<HotVideoItemModel> list) {
Widget buildList(List<LaterItemModel> list) {
return SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110),
delegate: SliverChildBuilderDelegate(
@@ -38,9 +39,8 @@ class _LaterSearchPageState
return Stack(
clipBehavior: Clip.none,
children: [
VideoCardH(
VideoCardHLater(
videoItem: item,
source: 'later',
onViewLater: (cid) {
PageUtils.toVideoPage(
'bvid=${item.bvid}&cid=$cid',

View File

@@ -66,7 +66,7 @@ class LiveCardVApp extends StatelessWidget {
);
}
Widget liveContent(context) {
Widget liveContent(BuildContext context) {
final theme = Theme.of(context);
return Expanded(
flex: 1,
@@ -107,7 +107,7 @@ class LiveCardVApp extends StatelessWidget {
);
}
Widget videoStat(context) {
Widget videoStat(BuildContext context) {
return Container(
height: 50,
padding: const EdgeInsets.only(top: 26, left: 10, right: 10),

View File

@@ -66,7 +66,7 @@ class LiveCardVFollow extends StatelessWidget {
);
}
Widget liveContent(context) {
Widget liveContent(BuildContext context) {
final theme = Theme.of(context);
return Expanded(
flex: 1,
@@ -107,7 +107,7 @@ class LiveCardVFollow extends StatelessWidget {
);
}
Widget videoStat(context) {
Widget videoStat(BuildContext context) {
return Container(
height: 50,
padding: const EdgeInsets.only(top: 26, left: 10, right: 10),

View File

@@ -105,7 +105,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
}
}
double _getFontSize(isFullScreen) {
double _getFontSize(bool isFullScreen) {
return isFullScreen == false || _isPipMode == true
? 15 * plPlayerController.fontSize
: 15 * plPlayerController.fontSizeFS;

View File

@@ -78,7 +78,7 @@ class LiveCardVSearch extends StatelessWidget {
);
}
Widget videoStat(context) {
Widget videoStat(BuildContext context) {
return Container(
height: 50,
padding: const EdgeInsets.only(top: 26, left: 10, right: 10),

View File

@@ -95,14 +95,14 @@ class VideoCardVMemberHome extends StatelessWidget {
},
),
),
videoContent(context)
content(context)
],
),
),
);
}
Widget videoContent(BuildContext context) {
Widget content(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(6, 5, 6, 5),

View File

@@ -58,14 +58,14 @@ class SeasonSeriesCard extends StatelessWidget {
),
),
const SizedBox(width: 10),
videoContent(context),
content(context),
],
),
),
);
}
Widget videoContent(context) {
Widget content(BuildContext context) {
final theme = Theme.of(context);
return Expanded(
child: Column(

View File

@@ -165,7 +165,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
),
),
const SizedBox(width: 10),
videoContent(context, theme),
content(context, theme),
],
);
},
@@ -185,7 +185,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
);
}
Widget videoContent(BuildContext context, ThemeData theme) {
Widget content(BuildContext context, ThemeData theme) {
final isCurr = fromViewAid == videoItem.param ||
(videoItem.bvid != null && videoItem.bvid == bvid);
return Expanded(
@@ -221,13 +221,13 @@ class VideoCardHMemberVideo extends StatelessWidget {
),
const SizedBox(height: 3),
Row(
spacing: 8,
children: [
StatView(
context: context,
theme: 'gray',
value: videoItem.stat.viewStr,
),
const SizedBox(width: 8),
StatDanMu(
context: context,
theme: 'gray',

View File

@@ -66,14 +66,14 @@ class PgcCardV extends StatelessWidget {
);
}),
),
bagumiContent(context)
content(context)
],
),
),
);
}
Widget bagumiContent(context) {
Widget content(BuildContext context) {
final theme = Theme.of(context);
final style = TextStyle(
fontSize: theme.textTheme.labelMedium!.fontSize,

View File

@@ -59,14 +59,14 @@ class PgcCardVTimeline extends StatelessWidget {
);
}),
),
bagumiContent(context)
content(context)
],
),
),
);
}
Widget bagumiContent(context) {
Widget content(BuildContext context) {
final theme = Theme.of(context);
return Expanded(
child: Padding(

View File

@@ -61,14 +61,14 @@ class PgcCardVPgcIndex extends StatelessWidget {
);
}),
),
bagumiContent(context)
conetent(context)
],
),
),
);
}
Widget bagumiContent(context) {
Widget conetent(BuildContext context) {
final theme = Theme.of(context);
return Expanded(
child: Padding(

View File

@@ -323,7 +323,7 @@ class _SavePanelState extends State<SavePanel> {
IgnorePointer(
child: DynamicPanel(
item: _item,
source: 'detail',
isDetail: true,
isSave: true,
),
),

View File

@@ -88,14 +88,14 @@ class SubItem extends StatelessWidget {
),
),
const SizedBox(width: 10),
videoContent(context),
content(context),
],
),
),
);
}
Widget videoContent(context) {
Widget content(BuildContext context) {
final theme = Theme.of(context);
final style = TextStyle(
fontSize: 13,

View File

@@ -24,17 +24,15 @@ class SubVideoCardH extends StatelessWidget {
@override
Widget build(BuildContext context) {
int id = videoItem.id!;
String bvid = videoItem.bvid!;
return InkWell(
onTap: () async {
int? cid = await SearchHttp.ab2c(bvid: bvid);
int? cid = await SearchHttp.ab2c(bvid: videoItem.bvid);
if (cid != null) {
PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
'bvid=${videoItem.bvid}&cid=$cid',
arguments: {
'videoItem': videoItem,
'heroTag': Utils.makeHeroTag(id),
'heroTag': Utils.makeHeroTag(videoItem.id),
'videoType': SearchType.video,
},
);
@@ -79,21 +77,21 @@ class SubVideoCardH extends StatelessWidget {
),
),
const SizedBox(width: 10),
videoContent(context),
content(context),
],
),
),
);
}
Widget videoContent(context) {
Widget content(BuildContext context) {
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
'${videoItem.title}',
videoItem.title!,
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
@@ -111,19 +109,18 @@ class SubVideoCardH extends StatelessWidget {
),
const SizedBox(height: 3),
Row(
spacing: 8,
children: [
StatView(
context: context,
theme: 'gray',
value: Utils.numFormat(videoItem.cntInfo?.play),
),
const SizedBox(width: 8),
StatDanMu(
context: context,
theme: 'gray',
value: Utils.numFormat(videoItem.cntInfo?.danmaku),
),
const Spacer(),
],
),
],

View File

@@ -6,19 +6,19 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AiDetail extends CommonCollapseSlidePage {
final AiConclusionResult modelResult;
class AiConclusionPanel extends CommonCollapseSlidePage {
final AiConclusionResult item;
const AiDetail({
const AiConclusionPanel({
super.key,
required this.modelResult,
required this.item,
});
@override
State<AiDetail> createState() => _AiDetailState();
State<AiConclusionPanel> createState() => _AiDetailState();
}
class _AiDetailState extends CommonCollapseSlidePageState<AiDetail> {
class _AiDetailState extends CommonCollapseSlidePageState<AiConclusionPanel> {
@override
Widget buildPage(ThemeData theme) {
return Material(
@@ -56,12 +56,12 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiDetail> {
controller: ScrollController(),
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
if (widget.modelResult.summary?.isNotEmpty == true) ...[
if (widget.item.summary?.isNotEmpty == true) ...[
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: SelectableText(
widget.modelResult.summary!,
widget.item.summary!,
style: const TextStyle(
fontSize: 15,
height: 1.5,
@@ -69,7 +69,7 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiDetail> {
),
),
),
if (widget.modelResult.outline?.isNotEmpty == true)
if (widget.item.outline?.isNotEmpty == true)
SliverToBoxAdapter(
child: Divider(
height: 20,
@@ -78,7 +78,7 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiDetail> {
),
),
],
if (widget.modelResult.outline?.isNotEmpty == true)
if (widget.item.outline?.isNotEmpty == true)
SliverPadding(
padding: EdgeInsets.only(
left: 14,
@@ -86,14 +86,14 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiDetail> {
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: SliverList.builder(
itemCount: widget.modelResult.outline!.length,
itemCount: widget.item.outline!.length,
itemBuilder: (context, index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (index != 0) const SizedBox(height: 10),
SelectableText(
widget.modelResult.outline![index].title!,
widget.item.outline![index].title!,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
@@ -101,10 +101,9 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiDetail> {
),
),
const SizedBox(height: 6),
if (widget.modelResult.outline![index].partOutline
?.isNotEmpty ==
if (widget.item.outline![index].partOutline?.isNotEmpty ==
true)
...widget.modelResult.outline![index].partOutline!.map(
...widget.item.outline![index].partOutline!.map(
(item) => Wrap(
children: [
SelectableText.rich(
@@ -122,7 +121,6 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiDetail> {
),
recognizer: TapGestureRecognizer()
..onTap = () {
// 跳转到指定位置
try {
Get.find<VideoDetailController>(
tag: Get.arguments['heroTag'])

View File

@@ -220,7 +220,7 @@ class PgcIntroController extends GetxController {
}
// 分享视频
void actionShareVideo(context) {
void actionShareVideo(BuildContext context) {
showDialog(
context: context,
builder: (_) {
@@ -360,7 +360,7 @@ class PgcIntroController extends GetxController {
}
// 修改分P或番剧分集
void changeSeasonOrbangu(epId, bvid, cid, aid, cover) {
void changeSeasonOrbangu(dynamic epId, bvid, cid, aid, cover) {
// 重新获取视频资源
this.epId = epId;
this.bvid = bvid;
@@ -420,7 +420,7 @@ class PgcIntroController extends GetxController {
SmartDialog.showToast(result['msg']);
}
Future<void> pgcUpdate(status) async {
Future<void> pgcUpdate(int status) async {
var result = await VideoHttp.pgcUpdate(
seasonId: [pgcItem.seasonId],
status: status,

View File

@@ -19,13 +19,13 @@ import 'package:flutter/services.dart' show HapticFeedback;
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
class PgcIntroPanel extends StatefulWidget {
class PgcIntroPage extends StatefulWidget {
final int? cid;
final String heroTag;
final Function showEpisodes;
final Function showIntroDetail;
const PgcIntroPanel({
const PgcIntroPage({
super.key,
this.cid,
required this.heroTag,
@@ -34,10 +34,10 @@ class PgcIntroPanel extends StatefulWidget {
});
@override
State<PgcIntroPanel> createState() => _PgcIntroPanelState();
State<PgcIntroPage> createState() => _PgcIntroPageState();
}
class _PgcIntroPanelState extends State<PgcIntroPanel>
class _PgcIntroPageState extends State<PgcIntroPage>
with AutomaticKeepAliveClientMixin {
late PgcIntroController pgcIntroController;
late VideoDetailController videoDetailCtr;

View File

@@ -13,11 +13,11 @@ import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart' hide TabBarView;
import 'package:get/get.dart';
class IntroDetail extends CommonCollapseSlidePage {
class PgcIntroPanel extends CommonCollapseSlidePage {
final PgcInfoModel item;
final List<VideoTagItem>? videoTags;
const IntroDetail({
const PgcIntroPanel({
super.key,
required this.item,
super.enableSlide = false,
@@ -25,10 +25,10 @@ class IntroDetail extends CommonCollapseSlidePage {
});
@override
State<IntroDetail> createState() => _IntroDetailState();
State<PgcIntroPanel> createState() => _IntroDetailState();
}
class _IntroDetailState extends CommonCollapseSlidePageState<IntroDetail> {
class _IntroDetailState extends CommonCollapseSlidePageState<PgcIntroPanel> {
late final _tabController = TabController(length: 2, vsync: this);
final _controller = ScrollController();
@@ -106,13 +106,13 @@ class _IntroDetailState extends CommonCollapseSlidePageState<IntroDetail> {
),
const SizedBox(height: 4),
Row(
spacing: 6,
children: [
StatView(
context: context,
theme: 'gray',
value: Utils.numFormat(widget.item.stat!.views),
),
const SizedBox(width: 6),
StatDanMu(
context: context,
theme: 'gray',

View File

@@ -551,6 +551,7 @@ class _VideoInfoState extends State<VideoInfo> {
clipBehavior: Clip.none,
children: [
Row(
spacing: 10,
children: [
StatView(
context: context,
@@ -560,7 +561,6 @@ class _VideoInfoState extends State<VideoInfo> {
: videoItem['stat']?.view ?? '-'),
textColor: theme.colorScheme.outline,
),
const SizedBox(width: 10),
StatDanMu(
context: context,
theme: 'gray',
@@ -569,7 +569,6 @@ class _VideoInfoState extends State<VideoInfo> {
: videoItem['stat']?.danmu ?? '-'),
textColor: theme.colorScheme.outline,
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(
!widget.isLoading
@@ -581,16 +580,13 @@ class _VideoInfoState extends State<VideoInfo> {
color: theme.colorScheme.outline,
),
),
if (MineController.anonymity.value) ...<Widget>[
const SizedBox(width: 10),
if (MineController.anonymity.value)
Icon(
MdiIcons.incognito,
size: 15,
color: theme.colorScheme.outline,
semanticLabel: '无痕',
),
],
const SizedBox(width: 10),
if (videoIntroController.isShowOnlineTotal)
Obx(
() => Text(

View File

@@ -198,6 +198,16 @@ class _MediaListPanelState
width: boxConstraints.maxWidth,
height: boxConstraints.maxHeight,
),
if (item.badge?.text?.isNotEmpty == true)
PBadge(
text: item.badge?.text,
right: 6.0,
top: 6.0,
type: switch (item.badge?.text) {
'充电专属' => PBadgeType.error,
_ => PBadgeType.primary,
},
),
PBadge(
text: Utils.timeFormat(item.duration!),
right: 6.0,
@@ -216,7 +226,6 @@ class _MediaListPanelState
children: [
Text(
item.title!,
textAlign: TextAlign.start,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
@@ -227,6 +236,19 @@ class _MediaListPanelState
: 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!,
@@ -237,24 +259,26 @@ class _MediaListPanelState
color: theme.colorScheme.outline,
),
),
const SizedBox(height: 3),
Row(
children: [
StatView(
context: context,
theme: 'gray',
value: Utils.numFormat(
item.cntInfo!.play!),
),
const SizedBox(width: 8),
StatDanMu(
context: context,
theme: 'gray',
value: Utils.numFormat(
item.cntInfo!.danmaku!),
),
],
),
if (item.type == 2) ...[
const SizedBox(height: 3),
Row(
spacing: 8,
children: [
StatView(
context: context,
theme: 'gray',
value: Utils.numFormat(
item.cntInfo!.play!),
),
StatDanMu(
context: context,
theme: 'gray',
value: Utils.numFormat(
item.cntInfo!.danmaku!),
),
],
),
],
],
),
),

View File

@@ -218,7 +218,7 @@ class _PayCoinsPageState extends State<PayCoinsPage>
});
}
Widget _buildBody(isV) => Stack(
Widget _buildBody(bool isV) => Stack(
key: _key,
clipBehavior: Clip.none,
alignment: Alignment.center,

View File

@@ -25,7 +25,7 @@ class VideoReplyReplyPanel extends CommonSlidePage {
required this.rpid,
this.dialog,
this.firstFloor,
this.source,
required this.isVideoDetail,
required this.replyType,
this.isDialogue = false,
this.onViewImage,
@@ -37,7 +37,7 @@ class VideoReplyReplyPanel extends CommonSlidePage {
final int rpid;
final int? dialog;
final ReplyInfo? firstFloor;
final String? source;
final bool isVideoDetail;
final int replyType;
final bool isDialogue;
final VoidCallback? onViewImage;
@@ -118,7 +118,7 @@ class _VideoReplyReplyPanelState
resizeToAvoidBottomInset: false,
body: Column(
children: [
widget.source == 'videoDetail'
widget.isVideoDetail
? Container(
height: 45,
decoration: BoxDecoration(
@@ -439,7 +439,7 @@ class _VideoReplyReplyPanelState
rpid: replyItem.root.toInt(),
dialog: replyItem.dialog.toInt(),
replyType: widget.replyType,
source: 'videoDetail',
isVideoDetail: true,
isDialogue: true,
),
),

View File

@@ -1799,7 +1799,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
] else if (videoDetailController.videoType ==
SearchType.media_bangumi)
Obx(
() => PgcIntroPanel(
() => PgcIntroPage(
key: pgcPanelKey,
heroTag: heroTag,
cid: videoDetailController.cid.value,
@@ -2027,7 +2027,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
rpid: rpid,
firstFloor: replyItem,
replyType: 1,
source: 'videoDetail',
isVideoDetail: true,
onViewImage: videoDetailController.onViewImage,
onDismissed: videoDetailController.onDismissed,
),
@@ -2040,7 +2040,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
(context) =>
AiDetail(modelResult: videoIntroController.aiConclusionResult!),
AiConclusionPanel(item: videoIntroController.aiConclusionResult!),
);
}
@@ -2048,7 +2048,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
PgcInfoModel videoDetail, List<VideoTagItem>? videoTags) {
videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
(context) => IntroDetail(
(context) => PgcIntroPanel(
item: videoDetail,
videoTags: videoTags,
),

View File

@@ -998,14 +998,7 @@ class PlPlayerController {
if (event.startsWith("Failed to open .") ||
event.startsWith("Cannot open") ||
event.startsWith("Can not open")) {
List list = [
if (dataSource.videoSource.isNullOrEmpty) '视频',
if (dataSource.audioSource.isNullOrEmpty) '音频',
];
if (list.isNotEmpty) {
SmartDialog.showToast('${list.join('')}源为空');
return;
}
return;
}
SmartDialog.showToast('视频加载错误, $event');
if (kDebugMode) debugPrint('视频加载错误, $event');

View File

@@ -142,7 +142,7 @@ class PiliScheme {
enableSlide: false,
oid: int.parse(oid),
rpid: rpid,
source: 'routePush',
isVideoDetail: false,
replyType: 1,
firstFloor: null,
id: commentSecondaryId != null
@@ -284,7 +284,7 @@ class PiliScheme {
oid: oid,
rpid: rootId,
id: rpId,
source: 'routePush',
isVideoDetail: false,
replyType: type,
firstFloor: null,
),
@@ -333,7 +333,7 @@ class PiliScheme {
enableSlide: false,
oid: oid,
rpid: rpId,
source: 'routePush',
isVideoDetail: false,
replyType: type,
firstFloor: null,
),
@@ -397,7 +397,7 @@ class PiliScheme {
enableSlide: false,
oid: oid ?? int.parse(dynId),
rpid: rpid,
source: 'routePush',
isVideoDetail: false,
replyType: businessId ?? 17,
firstFloor: null,
id: commentSecondaryId != null
@@ -788,7 +788,7 @@ class PiliScheme {
enableSlide: false,
oid: int.parse(oid),
rpid: int.parse(root),
source: 'routePush',
isVideoDetail: false,
replyType: int.parse(pageType),
firstFloor: null,
id: commentSecondaryId != null