refa: video model (#523)

This commit is contained in:
My-Responsitories
2025-03-25 10:12:44 +08:00
committed by GitHub
parent bf464994df
commit 7a6085e923
52 changed files with 761 additions and 1494 deletions

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/widgets/no_splash_factory.dart'; import 'package:PiliPlus/common/widgets/no_splash_factory.dart';
import 'package:PiliPlus/common/widgets/overlay_pop.dart'; import 'package:PiliPlus/common/widgets/overlay_pop.dart';
import 'package:PiliPlus/models/model_video.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AnimatedDialog extends StatefulWidget { class AnimatedDialog extends StatefulWidget {
@@ -9,7 +10,7 @@ class AnimatedDialog extends StatefulWidget {
required this.closeFn, required this.closeFn,
}); });
final dynamic videoItem; final BaseVideoItemModel videoItem;
final Function closeFn; final Function closeFn;
@override @override

View File

@@ -42,13 +42,10 @@ abstract class _StatItemBase extends StatelessWidget {
const SizedBox(width: 2), const SizedBox(width: 2),
Text( Text(
Utils.numFormat(value), Utils.numFormat(value),
style: TextStyle( style: TextStyle(fontSize: size == 'medium' ? 12 : 11, color: color),
fontSize: size == 'medium' ? 12 : 11,
color: color,
),
overflow: TextOverflow.clip, overflow: TextOverflow.clip,
semanticsLabel: semanticsLabel, semanticsLabel: semanticsLabel,
), )
], ],
); );
} }

View File

@@ -1,5 +1,7 @@
import 'package:PiliPlus/common/widgets/image_save.dart'; import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart'; import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/models/search/result.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../../http/search.dart'; import '../../http/search.dart';
@@ -24,7 +26,7 @@ class VideoCardH extends StatelessWidget {
this.onLongPress, this.onLongPress,
this.onViewLater, this.onViewLater,
}); });
final dynamic videoItem; final BaseVideoItemModel videoItem;
final String source; final String source;
final bool showOwner; final bool showOwner;
final bool showView; final bool showView;
@@ -36,12 +38,18 @@ class VideoCardH extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final int aid = videoItem.aid; final int aid = videoItem.aid!;
final String bvid = videoItem.bvid; final String bvid = videoItem.bvid!;
String type = 'video'; String type = 'video';
try { // try {
type = videoItem.type; // type = videoItem.type;
} catch (_) {} // } catch (_) {}
if (videoItem is SearchVideoItemModel) {
var typeOrNull = (videoItem as SearchVideoItemModel).type;
if (typeOrNull?.isNotEmpty == true) {
type = typeOrNull!;
}
}
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: Stack( child: Stack(
@@ -61,13 +69,7 @@ class VideoCardH extends StatelessWidget {
} else { } else {
imageSaveDialog( imageSaveDialog(
context: context, context: context,
title: videoItem.title is String title: videoItem.title,
? videoItem.title
: videoItem.title is List
? (videoItem.title as List)
.map((item) => item['text'])
.join()
: '',
cover: videoItem.pic, cover: videoItem.pic,
); );
} }
@@ -81,9 +83,11 @@ class VideoCardH extends StatelessWidget {
SmartDialog.showToast('课堂视频暂不支持播放'); SmartDialog.showToast('课堂视频暂不支持播放');
return; return;
} }
if (videoItem is HotVideoItemModel && if ((videoItem is HotVideoItemModel) &&
videoItem.redirectUrl?.isNotEmpty == true) { (videoItem as HotVideoItemModel).redirectUrl?.isNotEmpty ==
if (Utils.viewPgcFromUri(videoItem.redirectUrl!)) { true) {
if (Utils.viewPgcFromUri(
(videoItem as HotVideoItemModel).redirectUrl!)) {
return; return;
} }
} }
@@ -124,20 +128,24 @@ class VideoCardH extends StatelessWidget {
return Stack( return Stack(
children: [ children: [
NetworkImgLayer( NetworkImgLayer(
src: videoItem.pic as String, src: videoItem.pic,
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
), ),
if (videoItem is HotVideoItemModel && if (videoItem is HotVideoItemModel &&
videoItem.pgcLabel?.isNotEmpty == true) (videoItem as HotVideoItemModel)
.pgcLabel
?.isNotEmpty ==
true)
PBadge( PBadge(
text: videoItem.pgcLabel, text:
(videoItem as HotVideoItemModel).pgcLabel,
top: 6.0, top: 6.0,
right: 6.0, right: 6.0,
), ),
if (videoItem.duration != 0) if (videoItem.duration > 0)
PBadge( PBadge(
text: Utils.timeFormat(videoItem.duration!), text: Utils.timeFormat(videoItem.duration),
right: 6.0, right: 6.0,
bottom: 6.0, bottom: 6.0,
type: 'gray', type: 'gray',
@@ -180,7 +188,7 @@ class VideoCardH extends StatelessWidget {
); );
} }
Widget videoContent(context) { Widget videoContent(BuildContext context) {
String pubdate = showPubdate String pubdate = showPubdate
? Utils.dateFormat(videoItem.pubdate!, formatType: 'day') ? Utils.dateFormat(videoItem.pubdate!, formatType: 'day')
: ''; : '';
@@ -189,7 +197,33 @@ class VideoCardH extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (videoItem.title is String) if ((videoItem is SearchVideoItemModel) &&
(videoItem as SearchVideoItemModel).titleList?.isNotEmpty == true)
Expanded(
child: Text.rich(
overflow: TextOverflow.ellipsis,
maxLines: 2,
TextSpan(
children: [
for (var i
in (videoItem as SearchVideoItemModel).titleList!)
TextSpan(
text: i['text'],
style: TextStyle(
fontSize:
Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
),
],
),
),
)
else
Expanded( Expanded(
child: Text( child: Text(
videoItem.title, videoItem.title,
@@ -202,31 +236,6 @@ class VideoCardH extends StatelessWidget {
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
)
else
Expanded(
child: Text.rich(
overflow: TextOverflow.ellipsis,
maxLines: 2,
TextSpan(
children: [
for (final i in videoItem.title) ...[
TextSpan(
text: i['text'] as String,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
),
]
],
),
),
), ),
// const Spacer(), // const Spacer(),
// if (videoItem.rcmdReason != null && // if (videoItem.rcmdReason != null &&
@@ -267,7 +276,7 @@ class VideoCardH extends StatelessWidget {
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: videoItem.stat.view!, value: videoItem.stat.viewStr,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
], ],
@@ -275,7 +284,7 @@ class VideoCardH extends StatelessWidget {
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: videoItem.stat.danmu!, value: videoItem.stat.danmuStr,
), ),
const Spacer(), const Spacer(),
if (source == 'normal') const SizedBox(width: 24), if (source == 'normal') const SizedBox(width: 24),

View File

@@ -42,12 +42,12 @@ class VideoCardHMemberVideo extends StatelessWidget {
return; return;
} }
} }
if (videoItem.bvid == null || videoItem.firstCid == null) { if (videoItem.bvid == null || videoItem.cid == null) {
return; return;
} }
try { try {
Utils.toViewPage( Utils.toViewPage(
'bvid=${videoItem.bvid}&cid=${videoItem.firstCid}', 'bvid=${videoItem.bvid}&cid=${videoItem.cid}',
arguments: { arguments: {
'heroTag': Utils.makeHeroTag(videoItem.bvid), 'heroTag': Utils.makeHeroTag(videoItem.bvid),
}, },
@@ -90,7 +90,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
right: 6.0, right: 6.0,
top: 6.0, top: 6.0,
), ),
if (videoItem.duration != null) if (videoItem.duration > 0)
PBadge( PBadge(
text: Utils.timeFormat(videoItem.duration), text: Utils.timeFormat(videoItem.duration),
right: 6.0, right: 6.0,
@@ -147,7 +147,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
Expanded( Expanded(
child: Text( child: Text(
// videoItem.season?['title'] ?? videoItem.title ?? '', // videoItem.season?['title'] ?? videoItem.title ?? '',
videoItem.title ?? '', videoItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: TextStyle(
fontWeight: videoItem.bvid != null && videoItem.bvid == bvid fontWeight: videoItem.bvid != null && videoItem.bvid == bvid
@@ -184,14 +184,14 @@ class VideoCardHMemberVideo extends StatelessWidget {
theme: 'gray', theme: 'gray',
// view: videoItem.season?['view_content'] ?? // view: videoItem.season?['view_content'] ??
// videoItem.viewContent, // videoItem.viewContent,
value: videoItem.viewContent!, value: videoItem.stat.viewStr,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
// danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku, // danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku,
value: videoItem.danmaku!, value: videoItem.stat.danmuStr,
), ),
], ],
), ),

View File

@@ -16,7 +16,7 @@ import 'video_popup_menu.dart';
// 视频卡片 - 垂直布局 // 视频卡片 - 垂直布局
class VideoCardV extends StatelessWidget { class VideoCardV extends StatelessWidget {
final dynamic videoItem; final BaseRecVideoItemModel videoItem;
final VoidCallback? onRemove; final VoidCallback? onRemove;
const VideoCardV({ const VideoCardV({
@@ -31,14 +31,14 @@ class VideoCardV extends StatelessWidget {
} }
void onPushDetail(heroTag) async { void onPushDetail(heroTag) async {
String goto = videoItem.goto; String goto = videoItem.goto!;
switch (goto) { switch (goto) {
case 'bangumi': case 'bangumi':
Utils.viewBangumi(epId: videoItem.param); Utils.viewBangumi(epId: videoItem.param!);
break; break;
case 'av': case 'av':
String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid); String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid!);
int cid = videoItem.cid; int cid = videoItem.cid!;
if (cid == -1) { if (cid == -1) {
cid = await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid); cid = await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid);
} }
@@ -55,13 +55,13 @@ class VideoCardV extends StatelessWidget {
case 'picture': case 'picture':
try { try {
String dynamicType = 'picture'; String dynamicType = 'picture';
String uri = videoItem.uri; String uri = videoItem.uri!;
String id = ''; String id = '';
if (videoItem.uri.startsWith('bilibili://article/')) { if (uri.startsWith('bilibili://article/')) {
// https://www.bilibili.com/read/cv27063554 // https://www.bilibili.com/read/cv27063554
dynamicType = 'read'; dynamicType = 'read';
RegExp regex = RegExp(r'\d+'); RegExp regex = RegExp(r'\d+');
Match match = regex.firstMatch(videoItem.uri)!; Match match = regex.firstMatch(uri)!;
String matchedNumber = match.group(0)!; String matchedNumber = match.group(0)!;
videoItem.param = int.parse(matchedNumber); videoItem.param = int.parse(matchedNumber);
id = 'cv${videoItem.param}'; id = 'cv${videoItem.param}';
@@ -95,8 +95,8 @@ class VideoCardV extends StatelessWidget {
} }
break; break;
default: default:
SmartDialog.showToast(videoItem.goto); SmartDialog.showToast(goto);
Utils.handleWebview(videoItem.uri); Utils.handleWebview(videoItem.uri!);
} }
} }
@@ -114,7 +114,7 @@ class VideoCardV extends StatelessWidget {
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
child: InkWell( child: InkWell(
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.id)), onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.aid)),
onLongPress: () => imageSaveDialog( onLongPress: () => imageSaveDialog(
context: context, context: context,
title: videoItem.title, title: videoItem.title,
@@ -179,7 +179,7 @@ class VideoCardV extends StatelessWidget {
Row( Row(
children: [ children: [
Expanded( Expanded(
child: Text(videoItem.title + "\n", child: Text("${videoItem.title}\n",
// semanticsLabel: "${videoItem.title}", // semanticsLabel: "${videoItem.title}",
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -223,7 +223,7 @@ class VideoCardV extends StatelessWidget {
), ),
const SizedBox(width: 2), const SizedBox(width: 2),
], ],
if (videoItem.isFollowed == 1) ...[ if (videoItem.isFollowed) ...[
const PBadge( const PBadge(
text: '已关注', text: '已关注',
stack: 'normal', stack: 'normal',
@@ -262,7 +262,7 @@ class VideoCardV extends StatelessWidget {
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: videoItem.stat.view!, value: videoItem.stat.viewStr,
goto: videoItem.goto, goto: videoItem.goto,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
@@ -270,7 +270,7 @@ class VideoCardV extends StatelessWidget {
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: videoItem.stat.danmu!, value: videoItem.stat.danmuStr,
), ),
if (videoItem is RecVideoItemModel) ...<Widget>[ if (videoItem is RecVideoItemModel) ...<Widget>[
const Spacer(), const Spacer(),
@@ -294,7 +294,7 @@ class VideoCardV extends StatelessWidget {
], ],
if (videoItem is RecVideoItemAppModel && if (videoItem is RecVideoItemAppModel &&
videoItem.desc != null && videoItem.desc != null &&
videoItem.desc.contains(' · ')) ...<Widget>[ videoItem.desc!.contains(' · ')) ...<Widget>[
const Spacer(), const Spacer(),
Expanded( Expanded(
flex: 0, flex: 0,
@@ -310,7 +310,7 @@ class VideoCardV extends StatelessWidget {
.withOpacity(0.8), .withOpacity(0.8),
), ),
text: Utils.shortenChineseDateString( text: Utils.shortenChineseDateString(
videoItem.desc.split(' · ').last)), videoItem.desc!.split(' · ').last)),
)), )),
const SizedBox(width: 2), const SizedBox(width: 2),
] ]

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -21,16 +22,16 @@ class VideoCustomAction {
} }
class VideoCustomActions { class VideoCustomActions {
dynamic videoItem; BaseSimpleVideoItemModel videoItem;
BuildContext context; BuildContext context;
late List<VideoCustomAction> actions; late List<VideoCustomAction> actions;
VoidCallback? onRemove; VoidCallback? onRemove;
VideoCustomActions(this.videoItem, this.context, [this.onRemove]) { VideoCustomActions(this.videoItem, this.context, [this.onRemove]) {
actions = [ actions = [
if ((videoItem.bvid as String?)?.isNotEmpty == true) ...[ if (videoItem.bvid?.isNotEmpty == true) ...[
VideoCustomAction( VideoCustomAction(
videoItem.bvid, videoItem.bvid!,
'copy', 'copy',
Stack( Stack(
children: [ children: [
@@ -39,7 +40,7 @@ class VideoCustomActions {
], ],
), ),
() { () {
Utils.copyText(videoItem.bvid); Utils.copyText(videoItem.bvid!);
}, },
), ),
VideoCustomAction( VideoCustomAction(
@@ -84,7 +85,7 @@ class VideoCustomActions {
SmartDialog.showToast("未能获取dislikeReasons或feedbacks"); SmartDialog.showToast("未能获取dislikeReasons或feedbacks");
return; return;
} }
Widget actionButton(DislikeReason? r, FeedbackReason? f) { Widget actionButton(Reason? r, Reason? f) {
return SearchText( return SearchText(
text: r?.name ?? f?.name ?? '未知', text: r?.name ?? f?.name ?? '未知',
onTap: (_) async { onTap: (_) async {
@@ -258,11 +259,11 @@ class VideoCustomActions {
TextButton( TextButton(
onPressed: () async { onPressed: () async {
var res = await VideoHttp.relationMod( var res = await VideoHttp.relationMod(
mid: videoItem.owner.mid, mid: videoItem.owner.mid!,
act: 5, act: 5,
reSrc: 11, reSrc: 11,
); );
GStorage.setBlackMid(videoItem.owner.mid); GStorage.setBlackMid(videoItem.owner.mid!);
Get.back(); Get.back();
SmartDialog.showToast(res['msg'] ?? '成功'); SmartDialog.showToast(res['msg'] ?? '成功');
}, },

View File

@@ -19,22 +19,19 @@ import 'constants.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web; import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
class Request { class Request {
static const gzipDecoder = GZipDecoder(); static const _gzipDecoder = GZipDecoder();
static const brotilDecoder = BrotliDecoder(); static const _brotilDecoder = BrotliDecoder();
static final Request _instance = Request._internal(); static final Request _instance = Request._internal();
static late AccountManager accountManager; static late AccountManager accountManager;
static late final Dio dio; static late final Dio dio;
factory Request() => _instance; factory Request() => _instance;
late bool enableSystemProxy;
late String systemProxyHost;
late String systemProxyPort;
static final _rand = Random(); static final _rand = Random();
static final RegExp spmPrefixExp = static final RegExp _spmPrefixExp =
RegExp(r'<meta name="spm_prefix" content="([^"]+?)">'); RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
/// 设置cookie /// 设置cookie
static setCookie() async { static Future<void> setCookie() async {
accountManager = AccountManager(); accountManager = AccountManager();
dio.interceptors.add(accountManager); dio.interceptors.add(accountManager);
await Accounts.refresh(); await Accounts.refresh();
@@ -63,7 +60,7 @@ class Request {
try { try {
final html = await Request().get(Api.dynamicSpmPrefix, final html = await Request().get(Api.dynamicSpmPrefix,
options: Options(extra: {'account': account})); options: Options(extra: {'account': account}));
final String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!; final String spmPrefix = _spmPrefixExp.firstMatch(html.data)!.group(1)!;
final String randPngEnd = base64.encode( final String randPngEnd = base64.encode(
List<int>.generate(32, (_) => _rand.nextInt(256)) + List<int>.generate(32, (_) => _rand.nextInt(256)) +
List<int>.filled(4, 0) + List<int>.filled(4, 0) +
@@ -112,12 +109,10 @@ class Request {
responseDecoder: responseDecoder, // Http2Adapter没有自动解压 responseDecoder: responseDecoder, // Http2Adapter没有自动解压
persistentConnection: true); persistentConnection: true);
enableSystemProxy = GStorage.setting final bool enableSystemProxy = GStorage.setting
.get(SettingBoxKey.enableSystemProxy, defaultValue: false) as bool; .get(SettingBoxKey.enableSystemProxy, defaultValue: false);
systemProxyHost = final String systemProxyHost = GStorage.defaultSystemProxyHost;
GStorage.setting.get(SettingBoxKey.systemProxyHost, defaultValue: ''); final String systemProxyPort = GStorage.defaultSystemProxyPort;
systemProxyPort =
GStorage.setting.get(SettingBoxKey.systemProxyPort, defaultValue: '');
final http11Adapter = IOHttpClientAdapter(createHttpClient: () { final http11Adapter = IOHttpClientAdapter(createHttpClient: () {
final client = HttpClient() final client = HttpClient()
@@ -286,10 +281,10 @@ class Request {
ResponseBody responseBody) { ResponseBody responseBody) {
switch (responseBody.headers['content-encoding']?.firstOrNull) { switch (responseBody.headers['content-encoding']?.firstOrNull) {
case 'gzip': case 'gzip':
return utf8.decode(gzipDecoder.decodeBytes(responseBytes), return utf8.decode(_gzipDecoder.decodeBytes(responseBytes),
allowMalformed: true); allowMalformed: true);
case 'br': case 'br':
return utf8.decode(brotilDecoder.convert(responseBytes), return utf8.decode(_brotilDecoder.convert(responseBytes),
allowMalformed: true); allowMalformed: true);
default: default:
return utf8.decode(responseBytes, allowMalformed: true); return utf8.decode(responseBytes, allowMalformed: true);

View File

@@ -148,8 +148,7 @@ class SearchHttp {
} else if (bvid != null) { } else if (bvid != null) {
data['bvid'] = bvid; data['bvid'] = bvid;
} }
final dynamic res = await Request() final dynamic res = await Request().get(Api.ab2c, queryParameters: data);
.get(Api.ab2c, queryParameters: <String, dynamic>{...data});
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return part != null return part != null
? ((res.data['data'] as List).getOrNull(part - 1)?['cid'] ?? ? ((res.data['data'] as List).getOrNull(part - 1)?['cid'] ??
@@ -201,8 +200,8 @@ class SearchHttp {
} else if (epId != null) { } else if (epId != null) {
data['ep_id'] = epId; data['ep_id'] = epId;
} }
final dynamic res = await Request() final dynamic res =
.get(Api.bangumiInfo, queryParameters: <String, dynamic>{...data}); await Request().get(Api.bangumiInfo, queryParameters: data);
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return { return {

View File

@@ -349,13 +349,11 @@ class VideoHttp {
var res = var res =
await Request().get(Api.relatedList, queryParameters: {'bvid': bvid}); await Request().get(Api.relatedList, queryParameters: {'bvid': bvid});
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
List<HotVideoItemModel> list = []; final items =
for (var i in res.data['data']) { (res.data['data'] as List).map((i) => HotVideoItemModel.fromJson(i));
HotVideoItemModel videoItem = HotVideoItemModel.fromJson(i); final list = RecommendFilter.applyFilterToRelatedVideos
if (!RecommendFilter.filter(videoItem, relatedVideos: true)) { ? items.where((i) => !RecommendFilter.filterAll(i)).toList()
list.add(videoItem); : items.toList();
}
}
return LoadingState.success(list); return LoadingState.success(list);
} else { } else {
return LoadingState.error(res.data['message']); return LoadingState.error(res.data['message']);
@@ -390,7 +388,6 @@ class VideoHttp {
static Future hasCoinVideo({required String bvid}) async { static Future hasCoinVideo({required String bvid}) async {
var res = var res =
await Request().get(Api.hasCoinVideo, queryParameters: {'bvid': bvid}); await Request().get(Api.hasCoinVideo, queryParameters: {'bvid': bvid});
debugPrint('res: $res');
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {'status': true, 'data': res.data['data']};
} else { } else {

View File

@@ -8,7 +8,7 @@ class BangumiListDataModel {
}); });
int? hasNext; int? hasNext;
List? list; List<BangumiListItemModel>? list;
int? num; int? num;
int? size; int? size;
int? total; int? total;

View File

@@ -1,60 +1,20 @@
import 'package:PiliPlus/models/model_rec_video_item.dart';
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
class RecVideoItemAppModel { class RecVideoItemAppModel extends BaseRecVideoItemModel {
RecVideoItemAppModel({
this.id,
this.aid,
this.bvid,
this.cid,
this.pic,
this.stat,
this.duration,
this.title,
this.isFollowed,
this.owner,
this.rcmdReason,
this.goto,
this.param,
this.uri,
this.talkBack,
this.bangumiView,
this.bangumiFollow,
this.bangumiBadge,
this.cardType,
this.adInfo,
this.threePoint,
this.desc,
});
int? id; int? id;
int? aid;
String? bvid;
int? cid;
String? pic;
RcmdStat? stat;
int? duration;
String? title;
int? isFollowed;
RcmdOwner? owner;
String? rcmdReason;
String? goto;
int? param;
String? uri;
String? talkBack; String? talkBack;
// 番剧
String? bangumiView;
String? bangumiFollow;
String? bangumiBadge;
String? cardType; String? cardType;
Map? adInfo; Map? adInfo;
ThreePoint? threePoint; ThreePoint? threePoint;
String? desc;
RecVideoItemAppModel.fromJson(Map<String, dynamic> json) { RecVideoItemAppModel.fromJson(Map<String, dynamic> json) {
id = json['player_args'] != null id = json['player_args'] != null
? json['player_args']['aid'] ? json['player_args']['aid']
: int.parse(json['param'] ?? '-1'); : int.tryParse(json['param'] ?? '-1');
aid = id; aid = id;
bvid = json['bvid'] ?? bvid = json['bvid'] ??
(json['player_args'] != null (json['player_args'] != null
@@ -70,21 +30,22 @@ class RecVideoItemAppModel {
title = json['title']; title = json['title'];
owner = RcmdOwner.fromJson(json); owner = RcmdOwner.fromJson(json);
rcmdReason = json['bottom_rcmd_reason'] ?? json['top_rcmd_reason']; rcmdReason = json['bottom_rcmd_reason'] ?? json['top_rcmd_reason'];
if (rcmdReason != null && rcmdReason!.contains('')) {
// 有时能在推荐原因里获得点赞数
(stat as RcmdStat).like = Utils.parseNum(rcmdReason!);
}
// 由于app端api并不会直接返回与owner的关注状态 // 由于app端api并不会直接返回与owner的关注状态
// 所以借用推荐原因是否为“已关注”、“新关注”判别关注状态从而与web端接口等效 // 所以借用推荐原因是否为“已关注”、“新关注”判别关注状态从而与web端接口等效
isFollowed = (rcmdReason == '关注') || (rcmdReason == '新关注') ? 1 : 0; isFollowed = const {'已关注', '关注'}.contains(rcmdReason);
// 如果是就无需再显示推荐原因交由view统一处理即可 // 如果是就无需再显示推荐原因交由view统一处理即可
if (isFollowed == 1) { if (isFollowed) rcmdReason = null;
rcmdReason = null;
}
goto = json['goto']; goto = json['goto'];
param = int.parse(json['param']); param = int.parse(json['param']);
uri = json['uri']; uri = json['uri'];
talkBack = json['talk_back']; talkBack = json['talk_back'];
if (json['goto'] == 'bangumi') { if (json['goto'] == 'bangumi') {
bangumiView = json['cover_left_text_1'];
bangumiFollow = json['cover_left_text_2'];
bangumiBadge = json['cover_right_text']; bangumiBadge = json['cover_right_text'];
} }
@@ -95,30 +56,37 @@ class RecVideoItemAppModel {
: null; : null;
desc = json['desc']; desc = json['desc'];
} }
// @override
// int? get pubdate => null;
} }
class RcmdStat { class RcmdStat implements BaseStat {
RcmdStat({ @override
this.view, int? like;
this.like,
this.danmu, @override
}); int? get view => Utils.parseNum(viewStr);
String? view; @override
String? like; int? get danmu => Utils.parseNum(danmuStr);
String? danmu;
@override
late String viewStr;
@override
late String danmuStr;
RcmdStat.fromJson(Map<String, dynamic> json) { RcmdStat.fromJson(Map<String, dynamic> json) {
view = json["cover_left_text_1"]; viewStr = json["cover_left_text_1"];
danmu = json['cover_left_text_2'] ?? '-'; danmuStr = json['cover_left_text_2'];
} }
@override
set danmu(_) {}
@override
set view(_) {}
} }
class RcmdOwner { class RcmdOwner extends BaseOwner {
RcmdOwner({this.name, this.mid});
String? name;
int? mid;
RcmdOwner.fromJson(Map<String, dynamic> json) { RcmdOwner.fromJson(Map<String, dynamic> json) {
name = json['goto'] == 'av' name = json['goto'] == 'av'
? json['args']['up_name'] ? json['args']['up_name']
@@ -130,63 +98,26 @@ class RcmdOwner {
} }
class ThreePoint { class ThreePoint {
ThreePoint({ List<Reason>? dislikeReasons;
this.dislikeReasons, List<Reason>? feedbacks;
this.feedbacks,
this.watchLater,
});
List<DislikeReason>? dislikeReasons;
List<FeedbackReason>? feedbacks;
int? watchLater; int? watchLater;
ThreePoint.fromJson(Map<String, dynamic> json) { ThreePoint.fromJson(Map<String, dynamic> json) {
if (json['dislike_reasons'] != null) { dislikeReasons = (json['dislike_reasons'] as List?)
dislikeReasons = []; ?.map((v) => Reason.fromJson(v))
json['dislike_reasons'].forEach((v) { .toList();
dislikeReasons!.add(DislikeReason.fromJson(v)); feedbacks =
}); (json['feedbacks'] as List?)?.map((v) => Reason.fromJson(v)).toList();
}
if (json['feedbacks'] != null) {
feedbacks = [];
json['feedbacks'].forEach((v) {
feedbacks!.add(FeedbackReason.fromJson(v));
});
}
watchLater = json['watch_later']; watchLater = json['watch_later'];
} }
} }
class DislikeReason { class Reason {
DislikeReason({
this.id,
this.name,
this.toast,
});
int? id; int? id;
String? name; String? name;
String? toast; String? toast;
DislikeReason.fromJson(Map<String, dynamic> json) { Reason.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
toast = json['toast'];
}
}
class FeedbackReason {
FeedbackReason({
this.id,
this.name,
this.toast,
});
int? id;
String? name;
String? toast;
FeedbackReason.fromJson(Map<String, dynamic> json) {
id = json['id']; id = json['id'];
name = json['name']; name = json['name'];
toast = json['toast']; toast = json['toast'];

View File

@@ -1,3 +1,7 @@
import 'package:PiliPlus/utils/utils.dart';
import '../model_video.dart';
class MemberArchiveDataModel { class MemberArchiveDataModel {
MemberArchiveDataModel({ MemberArchiveDataModel({
this.list, this.list,
@@ -51,114 +55,60 @@ class TListItemModel {
} }
} }
class VListItemModel { class VListItemModel extends BaseVideoItemModel {
VListItemModel({
this.comment,
this.typeid,
this.play,
this.pic,
this.subtitle,
this.description,
this.copyright,
this.title,
this.review,
this.author,
this.mid,
this.created,
this.pubdate,
this.length,
this.duration,
this.videoReview,
this.aid,
this.bvid,
this.cid,
this.hideClick,
this.isChargingSrc,
this.rcmdReason,
this.owner,
});
int? comment; int? comment;
int? typeid; int? typeid;
int? play;
String? pic;
String? subtitle; String? subtitle;
String? description;
String? copyright; String? copyright;
String? title;
int? review; int? review;
String? author;
int? mid;
int? created;
int? pubdate;
String? length;
String? duration;
int? videoReview;
int? aid;
String? bvid;
int? cid;
bool? hideClick; bool? hideClick;
bool? isChargingSrc; bool? isChargingSrc;
Stat? stat;
String? rcmdReason;
Owner? owner;
VListItemModel.fromJson(Map<String, dynamic> json) { VListItemModel.fromJson(Map<String, dynamic> json) {
comment = json['comment']; comment = json['comment'];
typeid = json['typeid']; typeid = json['typeid'];
play = json['play'];
pic = json['pic']; pic = json['pic'];
subtitle = json['subtitle']; subtitle = json['subtitle'];
description = json['description']; desc = json['description'];
copyright = json['copyright']; copyright = json['copyright'];
title = json['title']; title = json['title'];
review = json['review']; review = json['review'];
author = json['author'];
mid = json['mid'];
created = json['created'];
pubdate = json['created']; pubdate = json['created'];
length = json['length']; if (json['length'] != null) duration = Utils.duration(json['length']);
duration = json['length'];
videoReview = json['video_review'];
aid = json['aid']; aid = json['aid'];
bvid = json['bvid']; bvid = json['bvid'];
cid = null;
hideClick = json['hide_click']; hideClick = json['hide_click'];
isChargingSrc = json['is_charging_arc']; isChargingSrc = json['is_charging_arc'];
stat = Stat.fromJson(json); stat = VListStat.fromJson(json);
rcmdReason = null; owner = VListOwner.fromJson(json);
owner = Owner.fromJson(json); }
// @override
// int? cid = null;
// @override
// String? rcmdReason = null;
// @override
// String? goto;
// @override
// bool isFollowed;
// @override
// String? uri;
}
class VListOwner extends BaseOwner {
VListOwner.fromJson(Map<String, dynamic> json) {
mid = json["mid"];
name = json["author"];
} }
} }
class Stat { class VListStat extends BaseStat {
Stat({ VListStat.fromJson(Map<String, dynamic> json) {
this.view,
this.danmu,
});
int? view;
int? danmu;
Stat.fromJson(Map<String, dynamic> json) {
view = json["play"]; view = json["play"];
danmu = json['video_review']; danmu = json['video_review'];
} }
} }
class Owner {
Owner({
this.mid,
this.name,
this.face,
});
int? mid;
String? name;
String? face;
Owner.fromJson(Map<String, dynamic> json) {
mid = json["mid"];
name = json["author"];
face = '';
}
}

View File

@@ -1,89 +1,26 @@
class MemberCoinsDataModel { import '../model_hot_video_item.dart';
MemberCoinsDataModel({
this.aid,
this.bvid,
this.cid,
this.coins,
this.copyright,
this.ctime,
this.desc,
this.duration,
this.owner,
this.pic,
this.pubLocation,
this.pubdate,
this.resourceType,
this.state,
this.subtitle,
this.time,
this.title,
this.tname,
this.videos,
this.view,
this.danmaku,
});
int? aid; class MemberCoinsDataModel extends HotVideoItemModel {
String? bvid;
int? cid;
int? coins;
int? copyright;
int? ctime;
String? desc;
int? duration;
Owner? owner;
String? pic;
String? pubLocation;
int? pubdate;
String? resourceType;
int? state;
String? subtitle; String? subtitle;
int? coins;
int? time; int? time;
String? title; // int? get view => stat.view;
String? tname; // int? get danmaku => stat.danmu;
int? videos;
int? view;
int? danmaku;
MemberCoinsDataModel.fromJson(Map<String, dynamic> json) { MemberCoinsDataModel.fromJson(Map<String, dynamic> json)
aid = json['aid']; : super.fromJson(json) {
bvid = json['bvid'];
cid = json['cid'];
coins = json['coins']; coins = json['coins'];
copyright = json['copyright'];
ctime = json['ctime'];
desc = json['desc'];
duration = json['duration'];
owner = Owner.fromJson(json['owner']);
pic = json['pic'];
pubLocation = json['pub_location'];
pubdate = json['pubdate'];
resourceType = json['resource_type'];
state = json['state'];
subtitle = json['subtitle']; subtitle = json['subtitle'];
time = json['time']; time = json['time'];
title = json['title']; // view = json['stat']['view'];
tname = json['tname']; // danmaku = json['stat']['danmaku'];
videos = json['videos'];
view = json['stat']['view'];
danmaku = json['stat']['danmaku'];
}
}
class Owner {
Owner({
this.mid,
this.name,
this.face,
});
int? mid;
String? name;
String? face;
Owner.fromJson(Map<String, dynamic> json) {
mid = json['mid'];
name = json['name'];
face = json['face'];
} }
// @override
// String? goto;
// @override
// bool isFollowed;
// @override
// String? rcmdReason;
// @override
// String? uri;
} }

View File

@@ -1,66 +1,23 @@
import './model_owner.dart'; import 'model_owner.dart';
import 'model_rec_video_item.dart';
import 'model_video.dart';
class HotVideoItemModel { // 稍后再看, 排行榜等网页返回也使用该类
HotVideoItemModel({ class HotVideoItemModel extends BaseRecVideoItemModel {
this.aid,
this.cid,
this.bvid,
this.videos,
this.tid,
this.tname,
this.copyright,
this.pic,
this.title,
this.pubdate,
this.ctime,
this.desc,
this.state,
this.duration,
this.middionId,
this.owner,
this.stat,
this.vDynamic,
this.dimension,
this.shortLinkV2,
this.firstFrame,
this.pubLocation,
this.seasontype,
this.isOgv,
this.rcmdReason,
this.checked,
this.pgcLabel,
this.redirectUrl,
});
int? aid;
int? cid;
String? bvid;
int? videos; int? videos;
int? tid; int? tid;
String? tname; String? tname;
int? copyright; int? copyright;
String? pic;
String? title;
int? pubdate;
int? ctime; int? ctime;
String? desc;
int? state; int? state;
int? duration;
int? middionId;
Owner? owner;
Stat? stat;
String? vDynamic;
Dimension? dimension; Dimension? dimension;
String? shortLinkV2;
String? firstFrame; String? firstFrame;
String? pubLocation; String? pubLocation;
int? seasontype;
bool? isOgv;
RcmdReason? rcmdReason;
bool? checked;
String? pgcLabel; String? pgcLabel;
String? redirectUrl; String? redirectUrl;
bool? checked; // 手动设置的
HotVideoItemModel.fromJson(Map<String, dynamic> json) { HotVideoItemModel.fromJson(Map<String, dynamic> json) {
aid = json["aid"]; aid = json["aid"];
cid = json["cid"]; cid = json["cid"];
@@ -76,97 +33,62 @@ class HotVideoItemModel {
desc = json["desc"]; desc = json["desc"];
state = json["state"]; state = json["state"];
duration = json["duration"]; duration = json["duration"];
middionId = json["middion_id"];
owner = Owner.fromJson(json["owner"]); owner = Owner.fromJson(json["owner"]);
stat = Stat.fromJson(json['stat']); stat = HotStat.fromJson(json['stat']);
vDynamic = json["vDynamic"]; dimension = Dimension.fromJson(json['dimension']);
dimension = Dimension.fromMap(json['dimension']);
shortLinkV2 = json["short_link_v2"];
firstFrame = json["first_frame"]; firstFrame = json["first_frame"];
pubLocation = json["pub_location"]; pubLocation = json["pub_location"];
seasontype = json["seasontype"]; dynamic rcmd = json['rcmd_reason'];
isOgv = json["isOgv"]; rcmdReason = rcmd is Map ? rcmd['content'] : rcmd; // 相关视频里rcmd为String,
rcmdReason = json['rcmd_reason'] != '' && json['rcmd_reason'] != null if (rcmdReason?.isEmpty == true) rcmdReason = null;
? RcmdReason.fromJson(json['rcmd_reason'])
: null;
pgcLabel = json['pgc_label']; pgcLabel = json['pgc_label'];
redirectUrl = json['redirect_url']; redirectUrl = json['redirect_url'];
// uri = json['uri']; // 仅在稍后再看存在
} }
// @override
// get isFollowed => false;
// @override
// get goto => 'av';
// @override
// get uri => 'bilibili://video/$aid';
} }
class Stat { class HotStat extends Stat {
Stat({
this.aid,
this.view,
this.danmu,
this.reply,
this.favorite,
this.coin,
this.share,
this.nowRank,
this.hisRank,
this.like,
this.dislike,
this.vt,
this.vv,
});
int? aid;
int? view;
int? danmu;
int? reply; int? reply;
int? favorite; int? favorite;
int? coin; int? coin;
int? share; int? share;
int? nowRank; int? nowRank;
int? hisRank; int? hisRank;
int? like;
int? dislike; int? dislike;
int? vt; int? vt;
int? vv; int? vv;
Stat.fromJson(Map<String, dynamic> json) { HotStat.fromJson(Map<String, dynamic> json) : super.fromJson(json) {
aid = json["aid"];
view = json["view"];
danmu = json['danmaku'];
reply = json["reply"]; reply = json["reply"];
favorite = json["favorite"]; favorite = json["favorite"];
coin = json['coin']; coin = json['coin'];
share = json["share"]; share = json["share"];
nowRank = json["now_rank"]; nowRank = json["now_rank"];
hisRank = json['his_rank']; hisRank = json['his_rank'];
like = json["like"];
dislike = json["dislike"]; dislike = json["dislike"];
vt = json['vt']; vt = json['vt'];
vv = json["vv"]; vv = json["vv"];
} }
} }
class Dimension { // class RcmdReason {
Dimension({this.width, this.height, this.rotate}); // RcmdReason({
// this.rcornerMark,
// this.content,
// });
int? width; // int? rcornerMark;
int? height; // String? content = '';
int? rotate;
Dimension.fromMap(Map<String, dynamic> json) { // RcmdReason.fromJson(Map<String, dynamic> json) {
width = json["width"]; // rcornerMark = json["corner_mark"];
height = json["height"]; // content = json["content"] ?? '';
rotate = json["rotate"]; // }
} // }
}
class RcmdReason {
RcmdReason({
this.rcornerMark,
this.content,
});
int? rcornerMark;
String? content = '';
RcmdReason.fromJson(Map<String, dynamic> json) {
rcornerMark = json["corner_mark"];
content = json["content"] ?? '';
}
}

View File

@@ -1,17 +1,21 @@
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'model_video.dart';
part 'model_owner.g.dart'; part 'model_owner.g.dart';
@HiveType(typeId: 3) @HiveType(typeId: 3)
class Owner { class Owner implements BaseOwner {
Owner({ Owner({
this.mid, this.mid,
this.name, this.name,
this.face, this.face,
}); });
@HiveField(0) @HiveField(0)
@override
int? mid; int? mid;
@HiveField(1) @HiveField(1)
@override
String? name; String? name;
@HiveField(2) @HiveField(2)
String? face; String? face;

View File

@@ -1,55 +1,19 @@
import './model_owner.dart'; import './model_owner.dart';
import 'package:hive/hive.dart'; import 'model_video.dart';
part 'model_rec_video_item.g.dart'; abstract class BaseRecVideoItemModel extends BaseVideoItemModel {
String? goto;
@HiveType(typeId: 0) String? uri;
class RecVideoItemModel {
RecVideoItemModel({
this.id,
this.bvid,
this.cid,
this.goto,
this.uri,
this.pic,
this.title,
this.duration,
this.pubdate,
this.owner,
this.stat,
this.isFollowed,
this.rcmdReason,
});
@HiveField(0)
int? id = -1;
@HiveField(1)
String? bvid = '';
@HiveField(2)
int? cid = -1;
@HiveField(3)
String? goto = '';
@HiveField(4)
String? uri = '';
@HiveField(5)
String? pic = '';
@HiveField(6)
String? title = '';
@HiveField(7)
int? duration = -1;
@HiveField(8)
int? pubdate = -1;
@HiveField(9)
Owner? owner;
@HiveField(10)
Stat? stat;
@HiveField(11)
int? isFollowed;
@HiveField(12)
String? rcmdReason; String? rcmdReason;
// app推荐专属
int? param;
String? bangumiBadge;
}
class RecVideoItemModel extends BaseRecVideoItemModel {
RecVideoItemModel.fromJson(Map<String, dynamic> json) { RecVideoItemModel.fromJson(Map<String, dynamic> json) {
id = json["id"]; aid = json["id"];
bvid = json["bvid"]; bvid = json["bvid"];
cid = json["cid"]; cid = json["cid"];
goto = json["goto"]; goto = json["goto"];
@@ -60,34 +24,15 @@ class RecVideoItemModel {
pubdate = json["pubdate"]; pubdate = json["pubdate"];
owner = Owner.fromJson(json["owner"]); owner = Owner.fromJson(json["owner"]);
stat = Stat.fromJson(json["stat"]); stat = Stat.fromJson(json["stat"]);
isFollowed = json["is_followed"] ?? 0; isFollowed = json["is_followed"] == 1;
// rcmdReason = json["rcmd_reason"] != null // rcmdReason = json["rcmd_reason"] != null
// ? RcmdReason.fromJson(json["rcmd_reason"]) // ? RcmdReason.fromJson(json["rcmd_reason"])
// : RcmdReason(content: ''); // : RcmdReason(content: '');
rcmdReason = json["rcmd_reason"]?['content']; rcmdReason = json["rcmd_reason"]?['content'];
} }
}
@HiveType(typeId: 1) // @override
class Stat { // String? get desc => null;
Stat({
this.view,
this.like,
this.danmu,
});
@HiveField(0)
int? view;
@HiveField(1)
int? like;
@HiveField(2)
int? danmu;
Stat.fromJson(Map<String, dynamic> json) {
// 无需在model中转换以保留原始数据在view层处理即可
view = json["view"];
like = json["like"];
danmu = json['danmaku'];
}
} }
// @HiveType(typeId: 2) // @HiveType(typeId: 2)
@@ -96,10 +41,8 @@ class Stat {
// this.reasonType, // this.reasonType,
// this.content, // this.content,
// }); // });
// @HiveField(0) // // int? reasonType;
// int? reasonType; // // String? content;
// @HiveField(1)
// String? content = '';
// //
// RcmdReason.fromJson(Map<String, dynamic> json) { // RcmdReason.fromJson(Map<String, dynamic> json) {
// reasonType = json["reason_type"]; // reasonType = json["reason_type"];

View File

@@ -1,154 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'model_rec_video_item.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class RecVideoItemModelAdapter extends TypeAdapter<RecVideoItemModel> {
@override
final int typeId = 0;
@override
RecVideoItemModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return RecVideoItemModel(
id: fields[0] as int?,
bvid: fields[1] as String?,
cid: fields[2] as int?,
goto: fields[3] as String?,
uri: fields[4] as String?,
pic: fields[5] as String?,
title: fields[6] as String?,
duration: fields[7] as int?,
pubdate: fields[8] as int?,
owner: fields[9] as Owner?,
stat: fields[10] as Stat?,
isFollowed: fields[11] as int?,
rcmdReason: fields[12] as String?,
);
}
@override
void write(BinaryWriter writer, RecVideoItemModel obj) {
writer
..writeByte(13)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.bvid)
..writeByte(2)
..write(obj.cid)
..writeByte(3)
..write(obj.goto)
..writeByte(4)
..write(obj.uri)
..writeByte(5)
..write(obj.pic)
..writeByte(6)
..write(obj.title)
..writeByte(7)
..write(obj.duration)
..writeByte(8)
..write(obj.pubdate)
..writeByte(9)
..write(obj.owner)
..writeByte(10)
..write(obj.stat)
..writeByte(11)
..write(obj.isFollowed)
..writeByte(12)
..write(obj.rcmdReason);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RecVideoItemModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class StatAdapter extends TypeAdapter<Stat> {
@override
final int typeId = 1;
@override
Stat read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Stat(
view: fields[0] as int?,
like: fields[1] as int?,
danmu: fields[2] as int?,
);
}
@override
void write(BinaryWriter writer, Stat obj) {
writer
..writeByte(3)
..writeByte(0)
..write(obj.view)
..writeByte(1)
..write(obj.like)
..writeByte(2)
..write(obj.danmu);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is StatAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
//
// class RcmdReasonAdapter extends TypeAdapter<RcmdReason> {
// @override
// final int typeId = 2;
//
// @override
// RcmdReason read(BinaryReader reader) {
// final numOfFields = reader.readByte();
// final fields = <int, dynamic>{
// for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
// };
// return RcmdReason(
// reasonType: fields[0] as int?,
// content: fields[1] as String?,
// );
// }
//
// @override
// void write(BinaryWriter writer, RcmdReason obj) {
// writer
// ..writeByte(2)
// ..writeByte(0)
// ..write(obj.reasonType)
// ..writeByte(1)
// ..write(obj.content);
// }
//
// @override
// int get hashCode => typeId.hashCode;
//
// @override
// bool operator ==(Object other) =>
// identical(this, other) ||
// other is RcmdReasonAdapter &&
// runtimeType == other.runtimeType &&
// typeId == other.typeId;
// }

View File

@@ -0,0 +1,59 @@
import 'package:PiliPlus/utils/utils.dart';
abstract class BaseSimpleVideoItemModel {
late String title;
String? bvid;
int? cid;
String? pic;
int duration = -1;
late BaseOwner owner;
late BaseStat stat;
}
abstract class BaseVideoItemModel extends BaseSimpleVideoItemModel {
int? aid;
String? desc;
int? pubdate;
bool isFollowed = false;
}
abstract class BaseOwner {
int? mid;
String? name;
}
abstract class BaseStat {
int? view;
int? like;
int? danmu;
String get viewStr => Utils.numFormat(view);
String get danmuStr => Utils.numFormat(danmu);
}
class Stat extends BaseStat {
Stat.fromJson(Map<String, dynamic> json) {
view = json["view"];
like = json["like"];
danmu = json['danmaku'];
}
}
class PlayStat extends BaseStat {
PlayStat.fromJson(Map<String, dynamic> json) {
view = json['play'];
danmu = json['danmaku'];
}
}
class Dimension {
int? width;
int? height;
int? rotate;
Dimension.fromJson(Map<String, dynamic> json) {
width = json["width"];
height = json["height"];
rotate = json["rotate"];
}
}

View File

@@ -1,12 +1,10 @@
import 'package:PiliPlus/utils/em.dart'; import 'package:PiliPlus/utils/em.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
class SearchVideoModel { import '../model_owner.dart';
SearchVideoModel({ import '../model_video.dart';
this.numResults,
this.list,
});
class SearchVideoModel {
int? numResults; int? numResults;
List<SearchVideoItemModel>? list; List<SearchVideoItemModel>? list;
@@ -19,68 +17,25 @@ class SearchVideoModel {
} }
} }
class SearchVideoItemModel { class SearchVideoItemModel extends BaseVideoItemModel {
SearchVideoItemModel({
this.type,
this.id,
this.cid,
// this.author,
this.mid,
// this.typeid,
// this.typename,
this.arcurl,
this.aid,
this.bvid,
this.title,
this.description,
this.pic,
// this.play,
this.videoReview,
// this.favorites,
this.tag,
// this.review,
this.pubdate,
this.senddate,
this.duration,
// this.viewType,
// this.like,
// this.upic,
// this.danmaku,
this.owner,
this.stat,
this.rcmdReason,
});
String? type; String? type;
int? id; int? id;
int? cid;
// String? author; // String? author;
int? mid;
// String? typeid; // String? typeid;
// String? typename; // String? typename;
String? arcurl; String? arcurl;
int? aid;
String? bvid;
List? title;
// List? titleList;
String? description;
String? pic;
// String? play; // String? play;
int? videoReview; // int? videoReview;
// String? favorites; // String? favorites;
String? tag; String? tag;
// String? review; // String? review;
int? pubdate; int? ctime;
int? senddate;
int? duration;
// String? duration; // String? duration;
// String? viewType; // String? viewType;
// String? like; // String? like;
// String? upic; // String? upic;
// String? danmaku; // String? danmaku;
Owner? owner; List<Map<String, String>>? titleList;
Stat? stat;
String? rcmdReason;
SearchVideoItemModel.fromJson(Map<String, dynamic> json) { SearchVideoItemModel.fromJson(Map<String, dynamic> json) {
type = json['type']; type = json['type'];
@@ -88,43 +43,35 @@ class SearchVideoItemModel {
arcurl = json['arcurl']; arcurl = json['arcurl'];
aid = json['aid']; aid = json['aid'];
bvid = json['bvid']; bvid = json['bvid'];
mid = json['mid'];
// title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); // title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
title = Em.regTitle(json['title']); titleList = Em.regTitle(json['title']);
description = json['description']; title = titleList!.map((i) => i['text']!).join();
desc = json['description'];
pic = json['pic'] != null && json['pic'].startsWith('//') pic = json['pic'] != null && json['pic'].startsWith('//')
? 'https:${json['pic']}' ? 'https:${json['pic']}'
: json['pic'] ?? ''; : json['pic'] ?? '';
videoReview = json['video_review'];
pubdate = json['pubdate']; pubdate = json['pubdate'];
senddate = json['senddate']; ctime = json['senddate'];
duration = Utils.duration(json['duration']); duration = Utils.duration(json['duration']);
owner = Owner.fromJson(json); owner = SearchOwner.fromJson(json);
stat = Stat.fromJson(json); stat = SearchStat.fromJson(json);
} }
// @override
// String? goto;
// @override
// bool isFollowed;
// @override
// String? uri;
} }
class Stat { class SearchStat extends BaseStat {
Stat({
this.view,
this.danmu,
this.favorite,
this.reply,
this.like,
});
// 播放量
int? view;
// 弹幕数
int? danmu;
// 收藏数 // 收藏数
int? favorite; int? favorite;
// 评论数 // 评论数
int? reply; int? reply;
// 喜欢
int? like;
Stat.fromJson(Map<String, dynamic> json) { SearchStat.fromJson(Map<String, dynamic> json) {
view = json['play']; view = json['play'];
danmu = json['danmaku']; danmu = json['danmaku'];
favorite = json['favorite']; favorite = json['favorite'];
@@ -133,17 +80,8 @@ class Stat {
} }
} }
class Owner { class SearchOwner extends Owner {
Owner({ SearchOwner.fromJson(Map<String, dynamic> json) {
this.mid,
this.name,
this.face,
});
int? mid;
String? name;
String? face;
Owner.fromJson(Map<String, dynamic> json) {
mid = json["mid"]; mid = json["mid"];
name = json["author"]; name = json["author"];
face = json['upic']; face = json['upic'];
@@ -301,7 +239,7 @@ class SearchLiveItemModel {
rankScore = json['rank_score']; rankScore = json['rank_score'];
roomid = json['roomid']; roomid = json['roomid'];
attentions = json['attentions']; attentions = json['attentions'];
cateName = Em.regCate(json['cate_name']) ?? ''; cateName = Em.regCate(json['cate_name']);
} }
} }

View File

@@ -1,107 +1,90 @@
import 'package:json_annotation/json_annotation.dart'; import '../model_owner.dart';
import '../model_video.dart';
import 'badge.dart'; import 'badge.dart';
import 'cursor_attr.dart'; import 'cursor_attr.dart';
import 'three_point.dart'; import 'three_point.dart';
part 'item.g.dart'; class Item extends BaseSimpleVideoItemModel {
@JsonSerializable()
class Item {
String? title;
String? subtitle; String? subtitle;
String? tname; String? tname;
String? cover; String? get cover => pic; // 不知道哪里使用了cover
@JsonKey(name: 'cover_icon')
String? coverIcon; String? coverIcon;
String? uri; String? uri;
String? param; String? param;
String? goto; String? goto;
String? length; String? length;
int? duration;
@JsonKey(name: 'is_popular')
bool? isPopular; bool? isPopular;
@JsonKey(name: 'is_steins')
bool? isSteins; bool? isSteins;
@JsonKey(name: 'is_ugcpay')
bool? isUgcpay; bool? isUgcpay;
@JsonKey(name: 'is_cooperation')
bool? isCooperation; bool? isCooperation;
@JsonKey(name: 'is_pgc')
bool? isPgc; bool? isPgc;
@JsonKey(name: 'is_live_playback')
bool? isLivePlayback; bool? isLivePlayback;
@JsonKey(name: 'is_pugv')
bool? isPugv; bool? isPugv;
@JsonKey(name: 'is_fold')
bool? isFold; bool? isFold;
@JsonKey(name: 'is_oneself')
bool? isOneself; bool? isOneself;
int? play;
int? danmaku;
int? ctime; int? ctime;
@JsonKey(name: 'ugc_pay')
int? ugcPay; int? ugcPay;
String? author;
bool? state; bool? state;
String? bvid;
int? videos; int? videos;
@JsonKey(name: 'three_point')
List<ThreePoint>? threePoint; List<ThreePoint>? threePoint;
@JsonKey(name: 'first_cid')
int? firstCid;
@JsonKey(name: 'cursor_attr')
CursorAttr? cursorAttr; CursorAttr? cursorAttr;
@JsonKey(name: 'view_content')
String? viewContent;
@JsonKey(name: 'icon_type')
int? iconType; int? iconType;
@JsonKey(name: 'publish_time_text')
String? publishTimeText; String? publishTimeText;
List<Badge>? badges; List<Badge>? badges;
Map? season; Map? season;
Map? history; Map? history;
Item({ Item.fromJson(Map<String, dynamic> json) {
this.title, title = json['title'];
this.subtitle, subtitle = json['subtitle'];
this.tname, tname = json['tname'];
this.cover, pic = json['cover'];
this.coverIcon, coverIcon = json['cover_icon'];
this.uri, uri = json['uri'];
this.param, param = json['param'];
this.goto, goto = json['goto'];
this.length, length = json['length'];
this.duration, duration = json['duration'] ?? -1;
this.isPopular, isPopular = json['is_popular'];
this.isSteins, isSteins = json['is_steins'];
this.isUgcpay, isUgcpay = json['is_ugcpay'];
this.isCooperation, isCooperation = json['is_cooperation'];
this.isPgc, isPgc = json['is_pgc'];
this.isLivePlayback, isLivePlayback = json['is_live_playback'];
this.isPugv, isPugv = json['is_pugv'];
this.isFold, isFold = json['is_fold'];
this.isOneself, isOneself = json['is_oneself'];
this.play, ctime = json['ctime'];
this.danmaku, ugcPay = json['ugc_pay'];
this.ctime, state = json['state'];
this.ugcPay, bvid = json['bvid'];
this.author, videos = json['videos'];
this.state, threePoint = (json['three_point'] as List<dynamic>?)
this.bvid, ?.map((e) => ThreePoint.fromJson(e as Map<String, dynamic>))
this.videos, .toList();
this.threePoint, cid = json['first_cid'];
this.firstCid, cursorAttr = json['cursor_attr'] == null
this.cursorAttr, ? null
this.viewContent, : CursorAttr.fromJson(json['cursor_attr'] as Map<String, dynamic>);
this.iconType, iconType = json['icon_type'];
this.publishTimeText, publishTimeText = json['publish_time_text'];
this.badges, badges = (json['badges'] as List<dynamic>?)
this.season, ?.map((e) => Badge.fromJson(e as Map<String, dynamic>))
this.history, .toList();
}); season = json['season'];
history = json['history'];
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json); stat = PlayStat.fromJson(json);
owner = Owner(mid: 0, name: json['author']);
Map<String, dynamic> toJson() => _$ItemToJson(this); }
}
class UserStat extends PlayStat {
String? _viewStr;
@override
String get viewStr => _viewStr ?? super.viewStr;
UserStat.fromJson(Map<String, dynamic> json) : super.fromJson(json) {
_viewStr = json['view_content'];
}
} }

View File

@@ -1,90 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'item.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Item _$ItemFromJson(Map<String, dynamic> json) => Item(
title: json['title'] as String?,
subtitle: json['subtitle'] as String?,
tname: json['tname'] as String?,
cover: json['cover'] as String?,
coverIcon: json['cover_icon'] as String?,
uri: json['uri'] as String?,
param: json['param'] as String?,
goto: json['goto'] as String?,
length: json['length'] as String?,
duration: (json['duration'] as num?)?.toInt(),
isPopular: json['is_popular'] as bool?,
isSteins: json['is_steins'] as bool?,
isUgcpay: json['is_ugcpay'] as bool?,
isCooperation: json['is_cooperation'] as bool?,
isPgc: json['is_pgc'] as bool?,
isLivePlayback: json['is_live_playback'] as bool?,
isPugv: json['is_pugv'] as bool?,
isFold: json['is_fold'] as bool?,
isOneself: json['is_oneself'] as bool?,
play: (json['play'] as num?)?.toInt(),
danmaku: (json['danmaku'] as num?)?.toInt(),
ctime: (json['ctime'] as num?)?.toInt(),
ugcPay: (json['ugc_pay'] as num?)?.toInt(),
author: json['author'] as String?,
state: json['state'] as bool?,
bvid: json['bvid'] as String?,
videos: (json['videos'] as num?)?.toInt(),
threePoint: (json['three_point'] as List<dynamic>?)
?.map((e) => ThreePoint.fromJson(e as Map<String, dynamic>))
.toList(),
firstCid: (json['first_cid'] as num?)?.toInt(),
cursorAttr: json['cursor_attr'] == null
? null
: CursorAttr.fromJson(json['cursor_attr'] as Map<String, dynamic>),
viewContent: json['view_content'] as String?,
iconType: (json['icon_type'] as num?)?.toInt(),
publishTimeText: json['publish_time_text'] as String?,
badges: (json['badges'] as List<dynamic>?)
?.map((e) => Badge.fromJson(e as Map<String, dynamic>))
.toList(),
season: json['season'],
history: json['history'],
);
Map<String, dynamic> _$ItemToJson(Item instance) => <String, dynamic>{
'title': instance.title,
'subtitle': instance.subtitle,
'tname': instance.tname,
'cover': instance.cover,
'cover_icon': instance.coverIcon,
'uri': instance.uri,
'param': instance.param,
'goto': instance.goto,
'length': instance.length,
'duration': instance.duration,
'is_popular': instance.isPopular,
'is_steins': instance.isSteins,
'is_ugcpay': instance.isUgcpay,
'is_cooperation': instance.isCooperation,
'is_pgc': instance.isPgc,
'is_live_playback': instance.isLivePlayback,
'is_pugv': instance.isPugv,
'is_fold': instance.isFold,
'is_oneself': instance.isOneself,
'play': instance.play,
'danmaku': instance.danmaku,
'ctime': instance.ctime,
'ugc_pay': instance.ugcPay,
'author': instance.author,
'state': instance.state,
'bvid': instance.bvid,
'videos': instance.videos,
'three_point': instance.threePoint,
'first_cid': instance.firstCid,
'cursor_attr': instance.cursorAttr,
'view_content': instance.viewContent,
'icon_type': instance.iconType,
'publish_time_text': instance.publishTimeText,
'badges': instance.badges,
'season': instance.season,
};

View File

@@ -1,83 +1,45 @@
import 'package:PiliPlus/models/model_owner.dart'; import '../model_owner.dart';
import 'package:PiliPlus/models/user/fav_folder.dart'; import '../model_video.dart';
import 'fav_folder.dart';
class FavDetailData { class FavDetailData {
FavDetailData({
this.info,
this.medias,
this.hasMore,
});
FavFolderItemData? info; FavFolderItemData? info;
List<FavDetailItemData>? medias; List<FavDetailItemData>? list;
List<FavDetailItemData>? get medias => list;
bool? hasMore; bool? hasMore;
FavDetailData.fromJson(Map<String, dynamic> json) { FavDetailData.fromJson(Map<String, dynamic> json) {
info = info =
json['info'] == null ? null : FavFolderItemData.fromJson(json['info']); json['info'] == null ? null : FavFolderItemData.fromJson(json['info']);
medias = (json['medias'] as List?) list = (json['medias'] as List?)
?.map<FavDetailItemData>((e) => FavDetailItemData.fromJson(e)) ?.map<FavDetailItemData>((e) => FavDetailItemData.fromJson(e))
.toList(); .toList();
hasMore = json['has_more']; hasMore = json['has_more'];
} }
} }
class FavDetailItemData { class FavDetailItemData extends BaseVideoItemModel {
FavDetailItemData({
this.id,
this.type,
this.title,
this.pic,
this.intro,
this.page,
this.duration,
this.owner,
this.attr,
this.cntInfo,
this.link,
this.ctime,
this.pubdate,
this.favTime,
this.bvId,
this.bvid,
// this.season,
this.ogv,
this.stat,
this.cid,
this.epId,
this.checked,
});
int? id; int? id;
int? type; int? type;
String? title;
String? pic;
String? intro;
int? page; int? page;
int? duration;
Owner? owner;
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/fav/list.md // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/fav/list.md
// | attr | num | 失效 | 0: 正常9: up自己删除1: 其他原因删除 | // | attr | num | 失效 | 0: 正常9: up自己删除1: 其他原因删除 |
int? attr; int? attr;
Map? cntInfo; Map? cntInfo;
String? link; String? link;
int? ctime; int? ctime;
int? pubdate;
int? favTime; int? favTime;
String? bvId;
String? bvid;
Map? ogv; Map? ogv;
Stat? stat;
int? cid;
String? epId; String? epId;
bool? checked; bool? checked;
FavDetailItemData.fromJson(Map<String, dynamic> json) { FavDetailItemData.fromJson(Map<String, dynamic> json) {
id = json['id']; id = json['id'];
aid = id;
type = json['type']; type = json['type'];
title = json['title']; title = json['title'];
pic = json['cover']; pic = json['cover'];
intro = json['intro']; desc = json['intro'];
page = json['page']; page = json['page'];
duration = json['duration']; duration = json['duration'];
owner = Owner.fromJson(json['upper']); owner = Owner.fromJson(json['upper']);
@@ -87,38 +49,18 @@ class FavDetailItemData {
ctime = json['ctime']; ctime = json['ctime'];
pubdate = json['pubtime']; pubdate = json['pubtime'];
favTime = json['fav_time']; favTime = json['fav_time'];
bvId = json['bv_id'];
bvid = json['bvid']; bvid = json['bvid'];
ogv = json['ogv']; ogv = json['ogv'];
stat = Stat.fromJson(json['cnt_info']); stat = PlayStat.fromJson(json['cnt_info']);
cid = json['ugc'] != null ? json['ugc']['first_cid'] : null; cid = json['ugc']?['first_cid'];
if (json['link'] != null && json['link'].contains('/bangumi')) { if (json['link'] != null && json['link'].contains('/bangumi')) {
epId = resolveEpId(json['link']); epId = resolveEpId(json['link']);
} }
} }
String resolveEpId(url) { static final _digitRegExp = RegExp(r'\d+');
RegExp regex = RegExp(r'\d+'); String resolveEpId(String url) => _digitRegExp.firstMatch(url)!.group(0)!;
Iterable<Match> matches = regex.allMatches(url);
List<String> numbers = []; // @override
for (Match match in matches) { // bool isFollowed;
numbers.add(match.group(0)!);
}
return numbers[0];
}
}
class Stat {
Stat({
this.view,
this.danmu,
});
int? view;
int? danmu;
Stat.fromJson(Map<String, dynamic> json) {
view = json['play'];
danmu = json['danmaku'];
}
} }

View File

@@ -60,47 +60,19 @@ class HisTabItem {
} }
class HisListItem { class HisListItem {
HisListItem({ late String title;
this.title,
this.longTitle,
this.cover,
this.pic,
this.covers,
this.uri,
this.history,
this.videos,
this.authorName,
this.authorFace,
this.authorMid,
this.viewAt,
this.progress,
this.badge,
this.showTitle,
this.duration,
this.current,
this.total,
this.newDesc,
this.isFinish,
this.isFav,
this.kid,
this.tagName,
this.liveStatus,
this.checked,
});
String? title;
String? longTitle; String? longTitle;
String? cover; String? cover;
String? pic; String? pic;
List? covers; List? covers;
String? uri; String? uri;
History? history; late History history;
int? videos; int? videos;
String? authorName; String? authorName;
String? authorFace; String? authorFace;
int? authorMid; int? authorMid;
int? viewAt; int? viewAt;
int? progress; int progress = 0;
String? badge; String? badge;
String? showTitle; String? showTitle;
int? duration; int? duration;
@@ -113,7 +85,7 @@ class HisListItem {
String? tagName; String? tagName;
int? liveStatus; int? liveStatus;
bool? checked; bool? checked;
void isFullScreen; dynamic isFullScreen;
HisListItem.fromJson(Map<String, dynamic> json) { HisListItem.fromJson(Map<String, dynamic> json) {
title = json['title']; title = json['title'];

View File

@@ -1,17 +1,16 @@
class SubDetailModelData { class SubDetailModelData {
DetailInfo? info; DetailInfo? info;
List<SubDetailMediaItem>? medias; List<SubDetailMediaItem>? list;
SubDetailModelData({this.info, this.medias}); List<SubDetailMediaItem>? get medias => list; // 不知道哪里使用了这个
SubDetailModelData({this.info, this.list});
SubDetailModelData.fromJson(Map<String, dynamic> json) { SubDetailModelData.fromJson(Map<String, dynamic> json) {
info = DetailInfo.fromJson(json['info']); info = DetailInfo.fromJson(json['info']);
if (json['medias'] != null) { list = (json['medias'] as List?)
medias = <SubDetailMediaItem>[]; ?.map((i) => SubDetailMediaItem.fromJson(i))
json['medias'].forEach((v) { .toList();
medias!.add(SubDetailMediaItem.fromJson(v));
});
}
} }
} }
@@ -23,8 +22,8 @@ class SubDetailMediaItem {
int? duration; int? duration;
int? pubtime; int? pubtime;
String? bvid; String? bvid;
Map? upper; Map<String, dynamic>? upper;
Map? cntInfo; Map<String, dynamic>? cntInfo;
int? enableVt; int? enableVt;
String? vtDisplay; String? vtDisplay;

View File

@@ -1,3 +1,6 @@
import '../model_owner.dart';
import '../model_video.dart';
class MediaVideoItemModel { class MediaVideoItemModel {
MediaVideoItemModel({ MediaVideoItemModel({
this.id, this.id,
@@ -43,7 +46,7 @@ class MediaVideoItemModel {
int? attr; int? attr;
int? tid; int? tid;
int? copyRight; int? copyRight;
Map? cntInfo; Map<String, dynamic>? cntInfo;
String? cover; String? cover;
int? duration; int? duration;
int? pubtime; int? pubtime;
@@ -152,24 +155,6 @@ class Page {
); );
} }
class Dimension {
Dimension({
this.width,
this.height,
this.rotate,
});
int? width;
int? height;
int? rotate;
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
width: json["width"],
height: json["height"],
rotate: json["rotate"],
);
}
class Meta { class Meta {
Meta({ Meta({
this.quality, this.quality,
@@ -224,26 +209,7 @@ class Rights {
); );
} }
class Upper { class Upper extends Owner {
Upper({
this.mid,
this.name,
this.face,
this.followed,
this.fans,
this.vipType,
this.vipStatue,
this.vipDueDate,
this.vipPayType,
this.officialRole,
this.officialTitle,
this.officialDesc,
this.displayName,
});
int? mid;
String? name;
String? face;
int? followed; int? followed;
int? fans; int? fans;
int? vipType; int? vipType;
@@ -255,19 +221,16 @@ class Upper {
String? officialDesc; String? officialDesc;
String? displayName; String? displayName;
factory Upper.fromJson(Map<String, dynamic> json) => Upper( Upper.fromJson(Map<String, dynamic> json) : super.fromJson(json) {
mid: json["mid"], followed = json["followed"];
name: json["name"], fans = json["fans"];
face: json["face"], vipType = json["vip_type"];
followed: json["followed"], vipStatue = json["vip_statue"];
fans: json["fans"], vipDueDate = json["vip_due_date"];
vipType: json["vip_type"], vipPayType = json["vip_pay_type"];
vipStatue: json["vip_statue"], officialRole = json["official_role"];
vipDueDate: json["vip_due_date"], officialTitle = json["official_title"];
vipPayType: json["vip_pay_type"], officialDesc = json["official_desc"];
officialRole: json["official_role"], displayName = json["display_name"];
officialTitle: json["official_title"], }
officialDesc: json["official_desc"],
displayName: json["display_name"],
);
} }

View File

@@ -324,19 +324,19 @@ class _BangumiInfoState extends State<BangumiInfo>
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: !widget.isLoading value: Utils.numFormat(!widget.isLoading
? widget.bangumiDetail!.stat!['views'] ? widget.bangumiDetail!.stat!['views']
: bangumiItem!.stat!['views'], : bangumiItem!.stat!['views']),
size: 'medium', size: 'medium',
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: !widget.isLoading value: Utils.numFormat(!widget.isLoading
? widget ? widget
.bangumiDetail!.stat!['danmakus'] .bangumiDetail!.stat!['danmakus']
: bangumiItem!.stat!['danmakus'], : bangumiItem!.stat!['danmakus']),
size: 'medium', size: 'medium',
), ),
if (isLandscape) ...[ if (isLandscape) ...[

View File

@@ -1,18 +1,19 @@
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/bangumi/info.dart';
import 'package:PiliPlus/pages/common/common_collapse_slide_page.dart'; import 'package:PiliPlus/pages/common/common_collapse_slide_page.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../../utils/utils.dart'; import '../../../../utils/utils.dart';
class IntroDetail extends CommonCollapseSlidePage { class IntroDetail extends CommonCollapseSlidePage {
final dynamic bangumiDetail; final BangumiInfoModel bangumiDetail;
final dynamic videoTags; final dynamic videoTags;
const IntroDetail({ const IntroDetail({
super.key, super.key,
this.bangumiDetail, required this.bangumiDetail,
this.videoTags, this.videoTags,
}); });
@@ -70,7 +71,7 @@ class _IntroDetailState extends CommonCollapseSlidePageState<IntroDetail> {
), ),
children: [ children: [
SelectableText( SelectableText(
widget.bangumiDetail!.title, widget.bangumiDetail.title!,
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
), ),
@@ -81,14 +82,14 @@ class _IntroDetailState extends CommonCollapseSlidePageState<IntroDetail> {
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: widget.bangumiDetail!.stat!['views'], value: Utils.numFormat(widget.bangumiDetail.stat!['views']),
size: 'medium', size: 'medium',
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: widget.bangumiDetail!.stat!['danmakus'], value: Utils.numFormat(widget.bangumiDetail.stat!['danmakus']),
size: 'medium', size: 'medium',
), ),
], ],
@@ -97,17 +98,17 @@ class _IntroDetailState extends CommonCollapseSlidePageState<IntroDetail> {
Row( Row(
children: [ children: [
Text( Text(
widget.bangumiDetail!.areas!.first['name'], widget.bangumiDetail.areas!.first['name'],
style: smallTitle, style: smallTitle,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
widget.bangumiDetail!.publish!['pub_time_show'], widget.bangumiDetail.publish!['pub_time_show'],
style: smallTitle, style: smallTitle,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
widget.bangumiDetail!.newEp!['desc'], widget.bangumiDetail.newEp!['desc'],
style: smallTitle, style: smallTitle,
), ),
], ],
@@ -119,7 +120,7 @@ class _IntroDetailState extends CommonCollapseSlidePageState<IntroDetail> {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
SelectableText( SelectableText(
'${widget.bangumiDetail!.evaluate!}', widget.bangumiDetail.evaluate!,
style: smallTitle.copyWith(fontSize: 14), style: smallTitle.copyWith(fontSize: 14),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@@ -129,7 +130,7 @@ class _IntroDetailState extends CommonCollapseSlidePageState<IntroDetail> {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
SelectableText( SelectableText(
widget.bangumiDetail.actors, widget.bangumiDetail.actors!,
style: smallTitle.copyWith(fontSize: 14), style: smallTitle.copyWith(fontSize: 14),
), ),
if (widget.videoTags is List && widget.videoTags.isNotEmpty) ...[ if (widget.videoTags is List && widget.videoTags.isNotEmpty) ...[

View File

@@ -98,7 +98,7 @@ Widget bangumiContent(Item bangumiItem) {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
bangumiItem.title ?? '', bangumiItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
letterSpacing: 0.3, letterSpacing: 0.3,

View File

@@ -41,21 +41,20 @@ class FavDetailController extends MultiSelectController {
item.value = data.info ?? FavFolderItemData(); item.value = data.info ?? FavFolderItemData();
isOwner.value = data.info?.mid == mid; isOwner.value = data.info?.mid == mid;
} }
if (data.medias.isNullOrEmpty) { if (data.list.isNullOrEmpty) {
isEnd = true; isEnd = true;
} }
if (currentPage != 1 && loadingState.value is Success) { if (currentPage != 1 && loadingState.value is Success) {
data.medias ??= <FavDetailItemData>[]; data.list ??= <FavDetailItemData>[];
data.medias!.insertAll( data.list!.insertAll(
0, 0,
List<FavDetailItemData>.from((loadingState.value as Success).response), (loadingState.value as Success).response,
); );
} }
if (isEnd.not && if (isEnd.not && (data.list?.length ?? 0) >= (data.info?.mediaCount ?? 0)) {
(data.medias?.length ?? 0) >= (data.info?.mediaCount ?? 0)) {
isEnd = true; isEnd = true;
} }
loadingState.value = LoadingState.success(data.medias); loadingState.value = LoadingState.success(data.list);
return true; return true;
} }
@@ -138,8 +137,7 @@ class FavDetailController extends MultiSelectController {
void toViewPlayAll() { void toViewPlayAll() {
if (loadingState.value is Success) { if (loadingState.value is Success) {
List<FavDetailItemData> list = List<FavDetailItemData>.from( List<FavDetailItemData> list = (loadingState.value as Success).response;
(loadingState.value as Success).response);
for (FavDetailItemData element in list) { for (FavDetailItemData element in list) {
if (element.cid == null) { if (element.cid == null) {
continue; continue;

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/dialog.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/models/user/fav_detail.dart';
import 'package:PiliPlus/models/user/fav_folder.dart'; import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType; import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType;
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
@@ -429,15 +430,15 @@ class _FavDetailPageState extends State<FavDetailPage> {
), ),
); );
} }
final element = loadingState.response[index]; FavDetailItemData element = loadingState.response[index];
return Stack( return Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: FavVideoCardH( child: FavVideoCardH(
videoItem: element, videoItem: element,
callFn: () => _favDetailController.onCancelFav( callFn: () => _favDetailController.onCancelFav(
element.id, element.id!,
element.type, element.type!,
), ),
onViewFav: () { onViewFav: () {
Utils.toViewPage( Utils.toViewPage(

View File

@@ -1,12 +1,12 @@
import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/common/widgets/image_save.dart'; import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/user/fav_detail.dart'; import 'package:PiliPlus/models/user/fav_detail.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart';
@@ -16,7 +16,7 @@ import '../../../common/widgets/badge.dart';
// 收藏视频卡片 - 水平布局 // 收藏视频卡片 - 水平布局
class FavVideoCardH extends StatelessWidget { class FavVideoCardH extends StatelessWidget {
final dynamic videoItem; final FavDetailItemData videoItem;
final Function? callFn; final Function? callFn;
final int? searchType; final int? searchType;
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
@@ -37,7 +37,7 @@ class FavVideoCardH extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
int id = videoItem.id; int id = videoItem.id!;
String bvid = videoItem.bvid ?? IdUtils.av2bv(id); String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
return InkWell( return InkWell(
onTap: () async { onTap: () async {
@@ -48,11 +48,11 @@ class FavVideoCardH extends StatelessWidget {
String? epId; String? epId;
if (videoItem.type == 24) { if (videoItem.type == 24) {
videoItem.cid = await SearchHttp.ab2c(bvid: bvid); videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
dynamic seasonId = videoItem.ogv['season_id']; dynamic seasonId = videoItem.ogv!['season_id'];
epId = videoItem.epId; epId = videoItem.epId;
Utils.viewBangumi(seasonId: seasonId, epId: epId); Utils.viewBangumi(seasonId: seasonId, epId: epId);
return; return;
} else if (videoItem.page == 0 || videoItem.page > 1) { } else if (videoItem.page == 0 || videoItem.page! > 1) {
var result = await VideoHttp.videoIntro(bvid: bvid); var result = await VideoHttp.videoIntro(bvid: bvid);
if (result['status']) { if (result['status']) {
epId = result['data'].epId; epId = result['data'].epId;
@@ -61,9 +61,8 @@ class FavVideoCardH extends StatelessWidget {
} }
} }
if (videoItem is FavDetailItemData && if ([0, 16].contains(videoItem.attr).not) {
[0, 16].contains(videoItem.attr).not) { Get.toNamed('/member?mid=${videoItem.owner.mid}');
Get.toNamed('/member?mid=${videoItem.owner?.mid}');
return; return;
} }
onViewFav(); onViewFav();
@@ -117,20 +116,19 @@ class FavVideoCardH extends StatelessWidget {
height: maxHeight, height: maxHeight,
), ),
PBadge( PBadge(
text: Utils.timeFormat(videoItem.duration!), text: Utils.timeFormat(videoItem.duration),
right: 6.0, right: 6.0,
bottom: 6.0, bottom: 6.0,
type: 'gray', type: 'gray',
), ),
if (videoItem.ogv != null) ...[ if (videoItem.ogv != null)
PBadge( PBadge(
text: videoItem.ogv['type_name'], text: videoItem.ogv!['type_name'],
top: 6.0, top: 6.0,
right: 6.0, right: 6.0,
bottom: null, bottom: null,
left: null, left: null,
), ),
],
], ],
); );
}, },
@@ -163,30 +161,28 @@ class FavVideoCardH extends StatelessWidget {
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
if (videoItem.ogv != null) ...[ if (videoItem.ogv != null)
Text( Text(
videoItem.intro, videoItem.desc!,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
],
const Spacer(), const Spacer(),
Text( Text(
Utils.dateFormat(videoItem.favTime), Utils.dateFormat(videoItem.favTime),
style: TextStyle( style: TextStyle(
fontSize: 11, color: Theme.of(context).colorScheme.outline), fontSize: 11, color: Theme.of(context).colorScheme.outline),
), ),
if (videoItem.owner.name != '') ...[ if (!videoItem.owner.name.isNullOrEmpty)
Text( Text(
videoItem.owner.name, videoItem.owner.name!,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
],
Padding( Padding(
padding: const EdgeInsets.only(top: 2), padding: const EdgeInsets.only(top: 2),
child: Row( child: Row(
@@ -194,13 +190,13 @@ class FavVideoCardH extends StatelessWidget {
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: videoItem.cntInfo['play'], value: videoItem.stat.viewStr,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: videoItem.cntInfo['danmaku'], value: videoItem.stat.danmuStr,
), ),
const Spacer(), const Spacer(),
], ],

View File

@@ -49,17 +49,11 @@ class FavSearchController extends CommonController {
late List currentList = loadingState.value is Success late List currentList = loadingState.value is Success
? (loadingState.value as Success).response ? (loadingState.value as Success).response
: []; : [];
List? dataList = searchType == SearchType.fav List? dataList = currentPage == 1
? (currentPage == 1 ? response.response.list
? response.response.medias : response.response.list != null
: response.response.medias != null ? currentList + response.response.list
? currentList + response.response.medias : currentList;
: currentList)
: (currentPage == 1
? response.response.list
: response.response.list != null
? currentList + response.response.list
: currentList);
isEnd = searchType == SearchType.fav isEnd = searchType == SearchType.fav
? response.response.hasMore == false ? response.response.hasMore == false
: response.response.list == null || response.response.list.isEmpty; : response.response.list == null || response.response.list.isEmpty;

View File

@@ -65,8 +65,8 @@ class _FavSearchPageState extends State<FavSearchPage> {
return switch (loadingState) { return switch (loadingState) {
Loading() => errorWidget(), Loading() => errorWidget(),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => (loadingState.response as List?)?.isNotEmpty == true
? _favSearchCtr.searchType == SearchType.fav ? switch (_favSearchCtr.searchType) {
? CustomScrollView( SearchType.fav => CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
controller: _favSearchCtr.scrollController, controller: _favSearchCtr.scrollController,
slivers: [ slivers: [
@@ -120,54 +120,54 @@ class _FavSearchPageState extends State<FavSearchPage> {
), ),
), ),
], ],
) ),
: _favSearchCtr.searchType == SearchType.follow SearchType.follow => ListView.builder(
? ListView.builder( padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
controller: _favSearchCtr.scrollController,
itemCount: loadingState.response.length,
itemBuilder: ((context, index) {
if (index == loadingState.response.length - 1) {
_favSearchCtr.onLoadMore();
}
return FollowItem(
item: loadingState.response[index],
);
}),
),
SearchType.history => CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _favSearchCtr.scrollController,
slivers: [
SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80, bottom: MediaQuery.of(context).padding.bottom + 80,
), ),
controller: _favSearchCtr.scrollController, sliver: SliverGrid(
itemCount: loadingState.response.length, gridDelegate: SliverGridDelegateWithExtentAndRatio(
itemBuilder: ((context, index) { mainAxisSpacing: 2,
if (index == loadingState.response.length - 1) { maxCrossAxisExtent: Grid.mediumCardWidth * 2,
_favSearchCtr.onLoadMore(); childAspectRatio: StyleString.aspectRatio * 2.2,
}
return FollowItem(
item: loadingState.response[index],
);
}),
)
: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _favSearchCtr.scrollController,
slivers: [
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
_favSearchCtr.onLoadMore();
}
return HistoryItem(
videoItem: loadingState.response[index],
ctr: _favSearchCtr,
onChoose: null,
);
},
childCount: loadingState.response.length,
),
),
), ),
], delegate: SliverChildBuilderDelegate(
) (context, index) {
if (index == loadingState.response.length - 1) {
_favSearchCtr.onLoadMore();
}
return HistoryItem(
videoItem: loadingState.response[index],
ctr: _favSearchCtr,
onChoose: null,
);
},
childCount: loadingState.response.length,
),
),
),
],
),
}
: errorWidget( : errorWidget(
callback: _favSearchCtr.onReload, callback: _favSearchCtr.onReload,
), ),

View File

@@ -67,7 +67,7 @@ class HistoryController extends MultiSelectController
bool customHandleResponse(Success response) { bool customHandleResponse(Success response) {
HistoryData data = response.response; HistoryData data = response.response;
isEnd = data.list.isNullOrEmpty || data.list!.length < 20; isEnd = data.list.isNullOrEmpty || data.list!.length < 20;
max = data.list?.lastOrNull?.history?.oid; max = data.list?.lastOrNull?.history.oid;
viewAt = data.list?.lastOrNull?.viewAt; viewAt = data.list?.lastOrNull?.viewAt;
if (currentPage == 1) { if (currentPage == 1) {
if (type == null && tabs.isEmpty && data.tab?.isNotEmpty == true) { if (type == null && tabs.isEmpty && data.tab?.isNotEmpty == true) {

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/models/user/history.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart'; import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/pages/fav_search/controller.dart'; import 'package:PiliPlus/pages/fav_search/controller.dart';
import 'package:PiliPlus/pages/history/base_controller.dart'; import 'package:PiliPlus/pages/history/base_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -20,7 +21,7 @@ import 'package:PiliPlus/utils/utils.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
class HistoryItem extends StatelessWidget { class HistoryItem extends StatelessWidget {
final dynamic videoItem; final HisListItem videoItem;
final dynamic ctr; final dynamic ctr;
final Function? onChoose; final Function? onChoose;
final Function? onDelete; final Function? onDelete;
@@ -35,7 +36,7 @@ class HistoryItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
int aid = videoItem.history.oid; int aid = videoItem.history.oid!;
String bvid = videoItem.history.bvid ?? IdUtils.av2bv(aid); String bvid = videoItem.history.bvid ?? IdUtils.av2bv(aid);
return InkWell( return InkWell(
onTap: () async { onTap: () async {
@@ -45,7 +46,7 @@ class HistoryItem extends StatelessWidget {
onChoose?.call(); onChoose?.call();
return; return;
} }
if (videoItem.history.business.contains('article')) { if (videoItem.history.business?.contains('article') == true) {
// int cid = videoItem.history.cid ?? // int cid = videoItem.history.cid ??
// // videoItem.history.oid ?? // // videoItem.history.oid ??
// await SearchHttp.ab2c(aid: aid, bvid: bvid); // await SearchHttp.ab2c(aid: aid, bvid: bvid);
@@ -80,8 +81,8 @@ class HistoryItem extends StatelessWidget {
} else { } else {
SmartDialog.showToast('直播未开播'); SmartDialog.showToast('直播未开播');
} }
} else if (videoItem.history?.business == 'pgc' || } else if (videoItem.history.business == 'pgc' ||
videoItem.tagName.contains('动画')) { videoItem.tagName?.contains('动画') == true) {
/// hack /// hack
var bvid = videoItem.history.bvid; var bvid = videoItem.history.bvid;
if (bvid != null && bvid != '') { if (bvid != null && bvid != '') {
@@ -106,7 +107,7 @@ class HistoryItem extends StatelessWidget {
SmartDialog.showToast(result['msg']); SmartDialog.showToast(result['msg']);
} }
} else { } else {
if (videoItem.history.epid != '') { if (videoItem.history.epid != null && videoItem.history.epid != 0) {
Utils.viewBangumi(epId: videoItem.history.epid); Utils.viewBangumi(epId: videoItem.history.epid);
} }
} }
@@ -164,9 +165,9 @@ class HistoryItem extends StatelessWidget {
return Stack( return Stack(
children: [ children: [
NetworkImgLayer( NetworkImgLayer(
src: (videoItem.cover != '' src: (videoItem.cover.isNullOrEmpty
? videoItem.cover ? videoItem.covers?.first ?? ''
: videoItem.covers.first), : videoItem.cover),
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
), ),
@@ -176,7 +177,7 @@ class HistoryItem extends StatelessWidget {
PBadge( PBadge(
text: videoItem.progress == -1 text: videoItem.progress == -1
? '已看完' ? '已看完'
: '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}', : '${Utils.timeFormat(videoItem.progress)}/${Utils.timeFormat(videoItem.duration!)}',
right: 6.0, right: 6.0,
bottom: 8.0, bottom: 8.0,
type: 'gray', type: 'gray',
@@ -254,7 +255,7 @@ class HistoryItem extends StatelessWidget {
child: videoProgressIndicator( child: videoProgressIndicator(
videoItem.progress == -1 videoItem.progress == -1
? 1 ? 1
: videoItem.progress / videoItem.duration, : videoItem.progress / videoItem.duration!,
), ),
), ),
], ],
@@ -281,7 +282,7 @@ class HistoryItem extends StatelessWidget {
style: const TextStyle( style: const TextStyle(
letterSpacing: 0.3, letterSpacing: 0.3,
), ),
maxLines: videoItem.videos > 1 ? 1 : 2, maxLines: videoItem.videos! > 1 ? 1 : 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
if (videoItem.isFullScreen != null) ...[ if (videoItem.isFullScreen != null) ...[
@@ -301,7 +302,7 @@ class HistoryItem extends StatelessWidget {
Row( Row(
children: [ children: [
Text( Text(
videoItem.authorName, videoItem.authorName!,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
@@ -318,82 +319,79 @@ class HistoryItem extends StatelessWidget {
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline), color: Theme.of(context).colorScheme.outline),
), ),
if (videoItem is HisListItem) // if (videoItem is HisListItem)
SizedBox( SizedBox(
width: 29, width: 29,
height: 29, height: 29,
child: PopupMenuButton<String>( child: PopupMenuButton<String>(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
tooltip: '功能菜单', tooltip: '功能菜单',
icon: Icon( icon: Icon(
Icons.more_vert_outlined, Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
size: 18, size: 18,
), ),
position: PopupMenuPosition.under, position: PopupMenuPosition.under,
itemBuilder: (BuildContext context) => itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[ <PopupMenuEntry<String>>[
if (videoItem.authorMid != null && if (videoItem.authorMid != null &&
videoItem.authorName?.isNotEmpty == true) videoItem.authorName?.isNotEmpty == true)
PopupMenuItem<String>(
onTap: () {
Get.toNamed(
'/member?mid=${videoItem.authorMid}',
arguments: {
'heroTag': '${videoItem.authorMid}',
},
);
},
height: 35,
child: Row(
children: [
Icon(MdiIcons.accountCircleOutline, size: 16),
SizedBox(width: 6),
Text(
'访问:${videoItem.authorName}',
style: TextStyle(fontSize: 13),
)
],
),
),
if (videoItem.history?.business != 'pgc' &&
videoItem.badge != '番剧' &&
!videoItem.tagName.contains('动画') &&
videoItem.history.business != 'live' &&
!videoItem.history.business.contains('article'))
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
bvid: videoItem.history.bvid);
SmartDialog.showToast(res['msg']);
},
height: 35,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
PopupMenuItem<String>( PopupMenuItem<String>(
onTap: () => ctr is HistoryBaseController onTap: () {
? onDelete?.call( Get.toNamed(
videoItem.kid, videoItem.history.business) '/member?mid=${videoItem.authorMid}',
: ctr!.delHistory( arguments: {
videoItem.kid, videoItem.history.business), 'heroTag': '${videoItem.authorMid}',
},
);
},
height: 35, height: 35,
child: const Row( child: Row(
children: [ children: [
Icon(Icons.close_outlined, size: 16), Icon(MdiIcons.accountCircleOutline, size: 16),
SizedBox(width: 6), SizedBox(width: 6),
Text('删除记录', style: TextStyle(fontSize: 13)) Text(
'访问:${videoItem.authorName}',
style: TextStyle(fontSize: 13),
)
], ],
), ),
), ),
], if (videoItem.history.business != 'pgc' &&
), videoItem.badge != '番剧' &&
videoItem.tagName?.contains('动画') != true &&
videoItem.history.business != 'live' &&
videoItem.history.business?.contains('article') != true)
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
bvid: videoItem.history.bvid);
SmartDialog.showToast(res['msg']);
},
height: 35,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
PopupMenuItem<String>(
onTap: () => ctr!.delHistory(
videoItem.kid, videoItem.history.business),
height: 35,
child: const Row(
children: [
Icon(Icons.close_outlined, size: 16),
SizedBox(width: 6),
Text('删除记录', style: TextStyle(fontSize: 13))
],
),
),
],
), ),
),
], ],
), ),
], ],

View File

@@ -5,7 +5,6 @@ import 'package:get/get.dart';
import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/member/archive.dart';
import 'package:PiliPlus/models/member/coin.dart'; import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/models/member/info.dart'; import 'package:PiliPlus/models/member/info.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
@@ -22,7 +21,6 @@ class MemberController extends GetxController {
late int ownerMid; late int ownerMid;
bool specialFollowed = false; bool specialFollowed = false;
// 投稿列表 // 投稿列表
RxList<VListItemModel>? archiveList = <VListItemModel>[].obs;
dynamic userInfo; dynamic userInfo;
RxInt attribute = (-1).obs; RxInt attribute = (-1).obs;
RxString attributeText = '关注'.obs; RxString attributeText = '关注'.obs;
@@ -43,7 +41,12 @@ class MemberController extends GetxController {
} }
// 获取用户信息 // 获取用户信息
Future<Map<String, dynamic>> getInfo() async { Future<Map<String, dynamic>> getInfo() {
return Future.wait([getMemberInfo(), getMemberStat(), getMemberView()])
.then((res) => res[0]);
}
Future<Map<String, dynamic>> getMemberInfo() async {
wwebid = await Utils.getWwebid(mid); wwebid = await Utils.getWwebid(mid);
await getMemberStat(); await getMemberStat();
await getMemberView(); await getMemberView();

View File

@@ -136,7 +136,7 @@ class MemberVideoCtr extends CommonController {
} }
for (Item element in list) { for (Item element in list) {
if (element.firstCid == null) { if (element.cid == null) {
continue; continue;
} else { } else {
if (element.bvid != list.first.bvid) { if (element.bvid != list.first.bvid) {
@@ -150,7 +150,7 @@ class MemberVideoCtr extends CommonController {
? desc.not ? desc.not
: desc; : desc;
Utils.toViewPage( Utils.toViewPage(
'bvid=${element.bvid}&cid=${element.firstCid}', 'bvid=${element.bvid}&cid=${element.cid}',
arguments: { arguments: {
'videoItem': element, 'videoItem': element,
'heroTag': Utils.makeHeroTag(element.bvid), 'heroTag': Utils.makeHeroTag(element.bvid),

View File

@@ -10,7 +10,7 @@ class MemberArchiveController extends GetxController {
int pn = 1; int pn = 1;
int count = 0; int count = 0;
RxMap<String, String> currentOrder = <String, String>{}.obs; RxMap<String, String> currentOrder = <String, String>{}.obs;
List<Map<String, String>> orderList = [ static const List<Map<String, String>> orderList = [
{'type': 'pubdate', 'label': '最新发布'}, {'type': 'pubdate', 'label': '最新发布'},
{'type': 'click', 'label': '最多播放'}, {'type': 'click', 'label': '最多播放'},
{'type': 'stow', 'label': '最多收藏'}, {'type': 'stow', 'label': '最多收藏'},
@@ -37,8 +37,7 @@ class MemberArchiveController extends GetxController {
if (res['status']) { if (res['status']) {
if (type == 'init') { if (type == 'init') {
archivesList.value = res['data'].list.vlist; archivesList.value = res['data'].list.vlist;
} } else if (type == 'onLoad') {
if (type == 'onLoad') {
archivesList.addAll(res['data'].list.vlist); archivesList.addAll(res['data'].list.vlist);
} }
count = res['data'].page['count']; count = res['data'].page['count'];
@@ -49,7 +48,7 @@ class MemberArchiveController extends GetxController {
return res; return res;
} }
toggleSort() async { toggleSort() {
List<String> typeList = orderList.map((e) => e['type']!).toList(); List<String> typeList = orderList.map((e) => e['type']!).toList();
int index = typeList.indexOf(currentOrder['type']!); int index = typeList.indexOf(currentOrder['type']!);
if (index == orderList.length - 1) { if (index == orderList.length - 1) {
@@ -61,9 +60,7 @@ class MemberArchiveController extends GetxController {
} }
// 上拉加载 // 上拉加载
Future onLoad() async { Future onLoad() => getMemberArchive('onLoad');
getMemberArchive('onLoad');
}
@override @override
void onClose() { void onClose() {

View File

@@ -86,7 +86,7 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
); );
} }
Map data = snapshot.data as Map; Map data = snapshot.data as Map;
List list = _memberArchivesController.archivesList; final list = _memberArchivesController.archivesList;
if (data['status']) { if (data['status']) {
return Obx( return Obx(
() => list.isNotEmpty () => list.isNotEmpty

View File

@@ -1,9 +1,9 @@
import 'package:PiliPlus/common/widgets/image_save.dart'; import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/member/coin.dart'; import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
@@ -53,7 +53,7 @@ class MemberCoinsItem extends StatelessWidget {
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
), ),
if (coinItem.duration != null) if (coinItem.duration > 0)
PBadge( PBadge(
bottom: 6, bottom: 6,
right: 6, right: 6,
@@ -80,9 +80,15 @@ class MemberCoinsItem extends StatelessWidget {
children: [ children: [
StatView( StatView(
context: context, context: context,
value: coinItem.view!, value: coinItem.stat.viewStr,
theme: 'gray', theme: 'gray',
), ),
const SizedBox(width: 8),
StatDanMu(
context: context,
theme: 'gray',
value: coinItem.stat.danmuStr,
),
const Spacer(), const Spacer(),
Text( Text(
Utils.customStampStr( Utils.customStampStr(

View File

@@ -1,13 +1,14 @@
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/member/seasons.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
class MemberSeasonsItem extends StatelessWidget { class MemberSeasonsItem extends StatelessWidget {
final dynamic seasonItem; final MemberArchiveItem seasonItem;
const MemberSeasonsItem({ const MemberSeasonsItem({
super.key, super.key,
@@ -65,7 +66,7 @@ class MemberSeasonsItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
seasonItem.title, seasonItem.title!,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -74,7 +75,7 @@ class MemberSeasonsItem extends StatelessWidget {
children: [ children: [
StatView( StatView(
context: context, context: context,
value: seasonItem.view, value: Utils.numFormat(seasonItem.view!),
theme: 'gray', theme: 'gray',
), ),
const Spacer(), const Spacer(),

View File

@@ -132,8 +132,6 @@ class _SearchPanelState extends State<SearchPanel>
_searchPanelController, _searchPanelController,
loadingState, loadingState,
); );
default:
return const SizedBox.shrink();
} }
} }
} }

View File

@@ -50,15 +50,16 @@ class SubDetailController extends GetxController {
); );
} }
if (res['status']) { if (res['status']) {
subInfo.value = res['data'].info; SubDetailModelData data = res['data'];
subInfo.value = data.info!;
if (currentPage == 1 && type == 'init') { if (currentPage == 1 && type == 'init') {
subList.value = res['data'].medias; subList.value = data.list!;
mediaCount = res['data'].info.mediaCount; mediaCount = data.info!.mediaCount!;
if (item.type == 11) { if (item.type == 11) {
playCount.value = res['data'].info.cntInfo!['play']; playCount.value = data.info!.cntInfo!['play'];
} }
} else if (type == 'onLoad') { } else if (type == 'onLoad') {
subList.addAll(res['data'].medias); subList.addAll(data.list!);
} }
if (subList.length >= mediaCount) { if (subList.length >= mediaCount) {
loadingText.value = '没有更多了'; loadingText.value = '没有更多了';

View File

@@ -129,13 +129,13 @@ class SubVideoCardH extends StatelessWidget {
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: videoItem.cntInfo?['play'], value: Utils.numFormat(videoItem.cntInfo?['play']),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: videoItem.cntInfo?['danmaku'], value: Utils.numFormat(videoItem.cntInfo?['danmaku']),
), ),
const Spacer(), const Spacer(),
], ],

View File

@@ -616,9 +616,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: !widget.loadingStatus value: Utils.numFormat(!widget.loadingStatus
? videoDetail.stat?.view ?? '-' ? videoDetail.stat?.view ?? '-'
: videoItem['stat']?.view ?? '-', : videoItem['stat']?.view ?? '-'),
size: 'medium', size: 'medium',
textColor: t.colorScheme.outline, textColor: t.colorScheme.outline,
), ),
@@ -626,9 +626,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: !widget.loadingStatus value: Utils.numFormat(!widget.loadingStatus
? videoDetail.stat?.danmu ?? '-' ? videoDetail.stat?.danmu ?? '-'
: videoItem['stat']?.danmu ?? '-', : videoItem['stat']?.danmu ?? '-'),
size: 'medium', size: 'medium',
textColor: t.colorScheme.outline, textColor: t.colorScheme.outline,
), ),

View File

@@ -57,14 +57,14 @@ class IntroDetail extends StatelessWidget {
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: videoDetail!.stat!.view, value: Utils.numFormat(videoDetail!.stat!.view),
size: 'medium', size: 'medium',
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: videoDetail!.stat!.danmu, value: Utils.numFormat(videoDetail!.stat!.danmu),
size: 'medium', size: 'medium',
), ),
const SizedBox(width: 10), const SizedBox(width: 10),

View File

@@ -187,7 +187,7 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
widget.videoIntroController.changeSeasonOrbangu( widget.videoIntroController.changeSeasonOrbangu(
null, null,
videoItem.bvid, videoItem.bvid,
videoItem.firstCid, videoItem.cid,
IdUtils.bv2av(videoItem.bvid!), IdUtils.bv2av(videoItem.bvid!),
videoItem.cover, videoItem.cover,
); );

View File

@@ -184,7 +184,7 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiDetail> {
children: [ children: [
TextSpan( TextSpan(
text: text:
Utils.tampToSeektime(item.timestamp!), Utils.formatDuration(item.timestamp!),
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
@@ -198,15 +198,8 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiDetail> {
tag: Get tag: Get
.arguments['heroTag']) .arguments['heroTag'])
.plPlayerController .plPlayerController
.seekTo( .seekTo(Duration(
Duration( seconds: item.timestamp!));
seconds: Utils.duration(
Utils.tampToSeektime(
item.timestamp!)
.toString(),
),
),
);
} catch (_) {} } catch (_) {}
}, },
), ),

View File

@@ -234,14 +234,15 @@ class _MediaListPanelState extends CommonSlidePageState<MediaListPanel> {
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: item.cntInfo!['play'] as int, value: Utils.numFormat(
item.cntInfo!['play']!),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: value: Utils.numFormat(
item.cntInfo!['danmaku'] as int, item.cntInfo!['danmaku']!),
), ),
], ],
), ),

View File

@@ -1,31 +1,27 @@
import 'package:html/parser.dart' show parse; import 'package:html/parser.dart' show parse;
class Em { class Em {
static regCate(String origin) { static final _exp = RegExp('<[^>]*>([^<]*)</[^>]*>');
String str = origin;
RegExp exp = RegExp('<[^>]*>([^<]*)</[^>]*>'); static String regCate(String origin) {
Iterable<Match> matches = exp.allMatches(origin); Iterable<Match> matches = _exp.allMatches(origin);
for (Match match in matches) { return matches.lastOrNull?.group(1) ?? origin;
str = match.group(1)!;
}
return str;
} }
static regTitle(String origin) { static List<Map<String, String>> regTitle(String origin) {
RegExp exp = RegExp('<[^>]*>([^<]*)</[^>]*>'); List<Map<String, String>> res = [];
List res = [];
origin.splitMapJoin( origin.splitMapJoin(
exp, _exp,
onMatch: (Match match) { onMatch: (Match match) {
String matchStr = match[0]!; String matchStr = match[0]!;
Map map = {'type': 'em', 'text': regCate(matchStr)}; var map = {'type': 'em', 'text': regCate(matchStr)};
res.add(map); res.add(map);
return regCate(matchStr); return matchStr;
}, },
onNonMatch: (String str) { onNonMatch: (String str) {
if (str != '') { if (str != '') {
str = parse(str).body?.text ?? str; str = parse(str).body?.text ?? str;
Map map = {'type': 'text', 'text': str}; var map = {'type': 'text', 'text': str};
res.add(map); res.add(map);
} }
return str; return str;

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/models/model_video.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'storage.dart'; import 'storage.dart';
@@ -30,46 +31,30 @@ class RecommendFilter {
.get(SettingBoxKey.applyFilterToRelatedVideos, defaultValue: true); .get(SettingBoxKey.applyFilterToRelatedVideos, defaultValue: true);
} }
static bool filter(dynamic videoItem, {bool relatedVideos = false}) { static bool filter(BaseVideoItemModel videoItem) {
if (relatedVideos && !applyFilterToRelatedVideos) {
return false;
}
//由于相关视频中没有已关注标签,只能视为非关注视频 //由于相关视频中没有已关注标签,只能视为非关注视频
if (!relatedVideos && if (videoItem.isFollowed && exemptFilterForFollowed) {
videoItem.isFollowed == 1 &&
exemptFilterForFollowed) {
return false; return false;
} }
if (videoItem.duration > 0 && videoItem.duration < minDurationForRcmd) { return filterAll(videoItem);
return true;
}
if (filterLikeRatio(videoItem.stat.like, videoItem.stat.view)) {
return true;
}
if (filterTitle(videoItem.title)) {
return true;
}
return false;
} }
static bool filterLikeRatio(like, view) { static bool filterLikeRatio(int? like, int? view) {
if (view is int && return (view != null &&
view > -1 && view > -1 &&
like is int && like != null &&
like > -1 && like > -1 &&
like * 100 < minLikeRatioForRecommend * view) { like * 100 < minLikeRatioForRecommend * view);
return true;
}
return false;
} }
static bool filterTitle(String title, {bool? isFollowed}) { static bool filterTitle(String title) {
if (exemptFilterForFollowed && isFollowed == true) { return (rcmdRegExp.pattern.isNotEmpty && rcmdRegExp.hasMatch(title));
return false; }
}
if (rcmdRegExp.pattern.isNotEmpty && rcmdRegExp.hasMatch(title)) { static bool filterAll(BaseVideoItemModel videoItem) {
return true; return (videoItem.duration > 0 &&
} videoItem.duration < minDurationForRcmd) ||
return false; filterLikeRatio(videoItem.stat.like, videoItem.stat.view) ||
filterTitle(videoItem.title);
} }
} }

View File

@@ -47,11 +47,17 @@ import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as html_parser; import 'package:html/parser.dart' as html_parser;
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../models/home/rcmd/result.dart';
import '../models/model_rec_video_item.dart';
import '../models/model_video.dart';
class Utils { class Utils {
static final Random random = Random(); static final Random random = Random();
static const channel = MethodChannel("PiliPlus"); static const channel = MethodChannel("PiliPlus");
static final _numRegExp = RegExp(r'([\d\.]+)([千万亿])?');
static ThemeData getThemeData({ static ThemeData getThemeData({
required ColorScheme colorScheme, required ColorScheme colorScheme,
required bool isDynamic, required bool isDynamic,
@@ -1149,6 +1155,32 @@ class Utils {
// return tempPath; // return tempPath;
// } // }
static int getUnit(String? unit) {
switch (unit) {
case '':
return 1000;
case '':
return 10000;
case '亿':
return 100000000;
default:
return 1;
}
}
static int parseNum(String numberStr) {
if (numberStr == '-') return 0;
try {
final match = _numRegExp.firstMatch(numberStr)!;
var number = double.parse(match.group(1)!);
number *= getUnit(match.group(2));
return number.toInt();
} catch (e) {
debugPrint('parse failed: "$numberStr" : $e');
return 0;
}
}
static String numFormat(dynamic number) { static String numFormat(dynamic number) {
if (number == null) { if (number == null) {
return '00:00'; return '00:00';
@@ -1197,63 +1229,49 @@ class Utils {
return '${int.parse(durationParts[0])}'; return '${int.parse(durationParts[0])}';
} }
static String videoItemSemantics(dynamic videoItem) { static String videoItemSemantics(BaseVideoItemModel videoItem) {
String semanticsLabel = ""; StringBuffer semanticsLabel = StringBuffer();
bool emptyStatCheck(dynamic stat) { bool emptyStatCheck(int? stat) {
return stat == null || return stat == null || stat <= 0;
stat == '' ||
stat == 0 ||
stat == '0' ||
stat == '-';
} }
if (videoItem.runtimeType.toString() == "RecVideoItemAppModel") { if (videoItem is RecVideoItemAppModel) {
if (videoItem.goto == 'picture') { if (videoItem.goto == 'picture') {
semanticsLabel += '动态,'; semanticsLabel.write('动态,');
} else if (videoItem.goto == 'bangumi') { } else if (videoItem.goto == 'bangumi') {
semanticsLabel += '番剧,'; semanticsLabel.write('番剧,');
} }
} }
if (videoItem.title is String) { semanticsLabel.write(videoItem.title);
semanticsLabel += videoItem.title;
} else {
semanticsLabel +=
videoItem.title.map((e) => e['text'] as String).join('');
}
if (!emptyStatCheck(videoItem.stat.view)) { if (!emptyStatCheck(videoItem.stat.view)) {
semanticsLabel += ',${Utils.numFormat(videoItem.stat.view)}'; semanticsLabel.write(',${Utils.numFormat(videoItem.stat.view)}');
semanticsLabel += semanticsLabel.write(
(videoItem.runtimeType.toString() == "RecVideoItemAppModel" && (videoItem is RecVideoItemAppModel && videoItem.goto == 'picture')
videoItem.goto == 'picture')
? '浏览' ? '浏览'
: '播放'; : '播放');
} }
if (!emptyStatCheck(videoItem.stat.danmu)) { if (!emptyStatCheck(videoItem.stat.danmu)) {
semanticsLabel += ',${Utils.numFormat(videoItem.stat.danmu)}弹幕'; semanticsLabel.write(',${Utils.numFormat(videoItem.stat.danmu)}弹幕');
} }
if (videoItem.rcmdReason != null) { if ((videoItem is BaseRecVideoItemModel) && videoItem.rcmdReason != null) {
semanticsLabel += ',${videoItem.rcmdReason}'; semanticsLabel.write(',${videoItem.rcmdReason}');
} }
if (!emptyStatCheck(videoItem.duration) && if (!emptyStatCheck(videoItem.duration) && videoItem.duration > 0) {
(videoItem.duration is! int || videoItem.duration > 0)) { semanticsLabel.write(
semanticsLabel += ',时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}');
',时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}';
} }
if (videoItem.runtimeType.toString() != "RecVideoItemAppModel" && if (videoItem.pubdate != null) {
videoItem.pubdate != null) { semanticsLabel
semanticsLabel += .write(',${Utils.dateFormat(videoItem.pubdate!, formatType: 'day')}');
',${Utils.dateFormat(videoItem.pubdate!, formatType: 'day')}';
} }
if (videoItem.owner.name != '') { if (videoItem.owner.name != '') {
semanticsLabel += ',Up主${videoItem.owner.name}'; semanticsLabel.write(',Up主${videoItem.owner.name}');
} }
if ((videoItem.runtimeType.toString() == "RecVideoItemAppModel" || if (videoItem is BaseRecVideoItemModel && videoItem.isFollowed) {
videoItem.runtimeType.toString() == "RecVideoItemModel") && semanticsLabel.write(',已关注');
videoItem.isFollowed == 1) {
semanticsLabel += ',已关注';
} }
return semanticsLabel; return semanticsLabel.toString();
} }
static String timeFormat(dynamic time) { static String timeFormat(dynamic time) {
@@ -1263,14 +1281,7 @@ class Utils {
if (time == null || time == 0) { if (time == null || time == 0) {
return '00:00'; return '00:00';
} }
int hour = time ~/ 3600; return formatDuration(time);
int minute = (time % 3600) ~/ 60;
int second = time % 60;
String paddingStr(int number) {
return number.toString().padLeft(2, '0');
}
return '${hour > 0 ? "${paddingStr(hour)}:" : ""}${paddingStr(minute)}:${paddingStr(second)}';
} }
static String shortenChineseDateString(String date) { static String shortenChineseDateString(String date) {
@@ -1570,17 +1581,6 @@ class Utils {
} }
} }
// 时间戳转时间
static tampToSeektime(number) {
int hours = number ~/ 60;
int minutes = number % 60;
String formattedHours = hours.toString().padLeft(2, '0');
String formattedMinutes = minutes.toString().padLeft(2, '0');
return '$formattedHours:$formattedMinutes';
}
static double getSheetHeight(BuildContext context) { static double getSheetHeight(BuildContext context) {
double height = context.height.abs(); double height = context.height.abs();
double width = context.width.abs(); double width = context.width.abs();