From 7a6085e923eee26b8b5a007a55a469e24aa88ffa Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Tue, 25 Mar 2025 10:12:44 +0800 Subject: [PATCH] refa: video model (#523) --- lib/common/widgets/animated_dialog.dart | 3 +- lib/common/widgets/stat/stat.dart | 7 +- lib/common/widgets/video_card_h.dart | 109 ++++++------ .../widgets/video_card_h_member_video.dart | 12 +- lib/common/widgets/video_card_v.dart | 34 ++-- lib/common/widgets/video_popup_menu.dart | 15 +- lib/http/init.dart | 27 ++- lib/http/search.dart | 7 +- lib/http/video.dart | 13 +- lib/models/bangumi/list.dart | 2 +- lib/models/home/rcmd/result.dart | 159 +++++------------ lib/models/member/archive.dart | 116 ++++--------- lib/models/member/coin.dart | 97 ++--------- lib/models/model_hot_video_item.dart | 146 ++++------------ lib/models/model_owner.dart | 6 +- lib/models/model_rec_video_item.dart | 89 ++-------- lib/models/model_rec_video_item.g.dart | 154 ----------------- lib/models/model_video.dart | 59 +++++++ lib/models/search/result.dart | 112 +++--------- lib/models/space_archive/item.dart | 131 +++++++------- lib/models/space_archive/item.g.dart | 90 ---------- lib/models/user/fav_detail.dart | 90 ++-------- lib/models/user/history.dart | 36 +--- lib/models/user/sub_detail.dart | 19 +-- lib/models/video/later.dart | 71 ++------ lib/pages/bangumi/introduction/view.dart | 8 +- .../introduction/widgets/intro_detail.dart | 23 +-- .../widgets/bangumi_card_v_member_home.dart | 2 +- lib/pages/fav_detail/controller.dart | 16 +- lib/pages/fav_detail/view.dart | 7 +- .../fav_detail/widget/fav_video_card.dart | 36 ++-- lib/pages/fav_search/controller.dart | 16 +- lib/pages/fav_search/view.dart | 92 +++++----- lib/pages/history/controller.dart | 2 +- lib/pages/history/widgets/item.dart | 160 +++++++++--------- lib/pages/member/controller.dart | 9 +- .../content/video/member_video_ctr.dart | 4 +- lib/pages/member_archive/controller.dart | 11 +- lib/pages/member_archive/view.dart | 2 +- lib/pages/member_coin/widgets/item.dart | 12 +- lib/pages/member_seasons/widgets/item.dart | 9 +- lib/pages/search_panel/view.dart | 2 - lib/pages/subscription_detail/controller.dart | 11 +- .../widget/sub_video_card.dart | 4 +- lib/pages/video/detail/introduction/view.dart | 8 +- .../introduction/widgets/intro_detail.dart | 4 +- .../detail/member/horizontal_member_page.dart | 2 +- lib/pages/video/detail/widgets/ai_detail.dart | 13 +- .../detail/widgets/media_list_panel.dart | 7 +- lib/utils/em.dart | 26 ++- lib/utils/recommend_filter.dart | 49 ++---- lib/utils/utils.dart | 116 ++++++------- 52 files changed, 761 insertions(+), 1494 deletions(-) delete mode 100644 lib/models/model_rec_video_item.g.dart create mode 100644 lib/models/model_video.dart delete mode 100644 lib/models/space_archive/item.g.dart diff --git a/lib/common/widgets/animated_dialog.dart b/lib/common/widgets/animated_dialog.dart index 548a6ea4..9a8d641e 100644 --- a/lib/common/widgets/animated_dialog.dart +++ b/lib/common/widgets/animated_dialog.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/no_splash_factory.dart'; import 'package:PiliPlus/common/widgets/overlay_pop.dart'; +import 'package:PiliPlus/models/model_video.dart'; import 'package:flutter/material.dart'; class AnimatedDialog extends StatefulWidget { @@ -9,7 +10,7 @@ class AnimatedDialog extends StatefulWidget { required this.closeFn, }); - final dynamic videoItem; + final BaseVideoItemModel videoItem; final Function closeFn; @override diff --git a/lib/common/widgets/stat/stat.dart b/lib/common/widgets/stat/stat.dart index 8304d67e..24d6fa2b 100644 --- a/lib/common/widgets/stat/stat.dart +++ b/lib/common/widgets/stat/stat.dart @@ -42,13 +42,10 @@ abstract class _StatItemBase extends StatelessWidget { const SizedBox(width: 2), Text( Utils.numFormat(value), - style: TextStyle( - fontSize: size == 'medium' ? 12 : 11, - color: color, - ), + style: TextStyle(fontSize: size == 'medium' ? 12 : 11, color: color), overflow: TextOverflow.clip, semanticsLabel: semanticsLabel, - ), + ) ], ); } diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index a9f24bbb..456fac23 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -1,5 +1,7 @@ import 'package:PiliPlus/common/widgets/image_save.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_smart_dialog/flutter_smart_dialog.dart'; import '../../http/search.dart'; @@ -24,7 +26,7 @@ class VideoCardH extends StatelessWidget { this.onLongPress, this.onViewLater, }); - final dynamic videoItem; + final BaseVideoItemModel videoItem; final String source; final bool showOwner; final bool showView; @@ -36,12 +38,18 @@ class VideoCardH extends StatelessWidget { @override Widget build(BuildContext context) { - final int aid = videoItem.aid; - final String bvid = videoItem.bvid; + final int aid = videoItem.aid!; + final String bvid = videoItem.bvid!; String type = 'video'; - try { - type = videoItem.type; - } catch (_) {} + // try { + // type = videoItem.type; + // } catch (_) {} + if (videoItem is SearchVideoItemModel) { + var typeOrNull = (videoItem as SearchVideoItemModel).type; + if (typeOrNull?.isNotEmpty == true) { + type = typeOrNull!; + } + } return Material( color: Colors.transparent, child: Stack( @@ -61,13 +69,7 @@ class VideoCardH extends StatelessWidget { } else { imageSaveDialog( context: context, - title: videoItem.title is String - ? videoItem.title - : videoItem.title is List - ? (videoItem.title as List) - .map((item) => item['text']) - .join() - : '', + title: videoItem.title, cover: videoItem.pic, ); } @@ -81,9 +83,11 @@ class VideoCardH extends StatelessWidget { SmartDialog.showToast('课堂视频暂不支持播放'); return; } - if (videoItem is HotVideoItemModel && - videoItem.redirectUrl?.isNotEmpty == true) { - if (Utils.viewPgcFromUri(videoItem.redirectUrl!)) { + if ((videoItem is HotVideoItemModel) && + (videoItem as HotVideoItemModel).redirectUrl?.isNotEmpty == + true) { + if (Utils.viewPgcFromUri( + (videoItem as HotVideoItemModel).redirectUrl!)) { return; } } @@ -124,20 +128,24 @@ class VideoCardH extends StatelessWidget { return Stack( children: [ NetworkImgLayer( - src: videoItem.pic as String, + src: videoItem.pic, width: maxWidth, height: maxHeight, ), if (videoItem is HotVideoItemModel && - videoItem.pgcLabel?.isNotEmpty == true) + (videoItem as HotVideoItemModel) + .pgcLabel + ?.isNotEmpty == + true) PBadge( - text: videoItem.pgcLabel, + text: + (videoItem as HotVideoItemModel).pgcLabel, top: 6.0, right: 6.0, ), - if (videoItem.duration != 0) + if (videoItem.duration > 0) PBadge( - text: Utils.timeFormat(videoItem.duration!), + text: Utils.timeFormat(videoItem.duration), right: 6.0, bottom: 6.0, type: 'gray', @@ -180,7 +188,7 @@ class VideoCardH extends StatelessWidget { ); } - Widget videoContent(context) { + Widget videoContent(BuildContext context) { String pubdate = showPubdate ? Utils.dateFormat(videoItem.pubdate!, formatType: 'day') : ''; @@ -189,7 +197,33 @@ class VideoCardH extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, 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( child: Text( videoItem.title, @@ -202,31 +236,6 @@ class VideoCardH extends StatelessWidget { maxLines: 2, 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(), // if (videoItem.rcmdReason != null && @@ -267,7 +276,7 @@ class VideoCardH extends StatelessWidget { StatView( context: context, theme: 'gray', - value: videoItem.stat.view!, + value: videoItem.stat.viewStr, ), const SizedBox(width: 8), ], @@ -275,7 +284,7 @@ class VideoCardH extends StatelessWidget { StatDanMu( context: context, theme: 'gray', - value: videoItem.stat.danmu!, + value: videoItem.stat.danmuStr, ), const Spacer(), if (source == 'normal') const SizedBox(width: 24), diff --git a/lib/common/widgets/video_card_h_member_video.dart b/lib/common/widgets/video_card_h_member_video.dart index 61ed45cd..29ddd2fc 100644 --- a/lib/common/widgets/video_card_h_member_video.dart +++ b/lib/common/widgets/video_card_h_member_video.dart @@ -42,12 +42,12 @@ class VideoCardHMemberVideo extends StatelessWidget { return; } } - if (videoItem.bvid == null || videoItem.firstCid == null) { + if (videoItem.bvid == null || videoItem.cid == null) { return; } try { Utils.toViewPage( - 'bvid=${videoItem.bvid}&cid=${videoItem.firstCid}', + 'bvid=${videoItem.bvid}&cid=${videoItem.cid}', arguments: { 'heroTag': Utils.makeHeroTag(videoItem.bvid), }, @@ -90,7 +90,7 @@ class VideoCardHMemberVideo extends StatelessWidget { right: 6.0, top: 6.0, ), - if (videoItem.duration != null) + if (videoItem.duration > 0) PBadge( text: Utils.timeFormat(videoItem.duration), right: 6.0, @@ -147,7 +147,7 @@ class VideoCardHMemberVideo extends StatelessWidget { Expanded( child: Text( // videoItem.season?['title'] ?? videoItem.title ?? '', - videoItem.title ?? '', + videoItem.title, textAlign: TextAlign.start, style: TextStyle( fontWeight: videoItem.bvid != null && videoItem.bvid == bvid @@ -184,14 +184,14 @@ class VideoCardHMemberVideo extends StatelessWidget { theme: 'gray', // view: videoItem.season?['view_content'] ?? // videoItem.viewContent, - value: videoItem.viewContent!, + value: videoItem.stat.viewStr, ), const SizedBox(width: 8), StatDanMu( context: context, theme: 'gray', // danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku, - value: videoItem.danmaku!, + value: videoItem.stat.danmuStr, ), ], ), diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 07fed9da..4b8cca13 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -16,7 +16,7 @@ import 'video_popup_menu.dart'; // 视频卡片 - 垂直布局 class VideoCardV extends StatelessWidget { - final dynamic videoItem; + final BaseRecVideoItemModel videoItem; final VoidCallback? onRemove; const VideoCardV({ @@ -31,14 +31,14 @@ class VideoCardV extends StatelessWidget { } void onPushDetail(heroTag) async { - String goto = videoItem.goto; + String goto = videoItem.goto!; switch (goto) { case 'bangumi': - Utils.viewBangumi(epId: videoItem.param); + Utils.viewBangumi(epId: videoItem.param!); break; case 'av': - String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid); - int cid = videoItem.cid; + String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid!); + int cid = videoItem.cid!; if (cid == -1) { cid = await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid); } @@ -55,13 +55,13 @@ class VideoCardV extends StatelessWidget { case 'picture': try { String dynamicType = 'picture'; - String uri = videoItem.uri; + String uri = videoItem.uri!; String id = ''; - if (videoItem.uri.startsWith('bilibili://article/')) { + if (uri.startsWith('bilibili://article/')) { // https://www.bilibili.com/read/cv27063554 dynamicType = 'read'; RegExp regex = RegExp(r'\d+'); - Match match = regex.firstMatch(videoItem.uri)!; + Match match = regex.firstMatch(uri)!; String matchedNumber = match.group(0)!; videoItem.param = int.parse(matchedNumber); id = 'cv${videoItem.param}'; @@ -95,8 +95,8 @@ class VideoCardV extends StatelessWidget { } break; default: - SmartDialog.showToast(videoItem.goto); - Utils.handleWebview(videoItem.uri); + SmartDialog.showToast(goto); + Utils.handleWebview(videoItem.uri!); } } @@ -114,7 +114,7 @@ class VideoCardV extends StatelessWidget { clipBehavior: Clip.hardEdge, margin: EdgeInsets.zero, child: InkWell( - onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.id)), + onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.aid)), onLongPress: () => imageSaveDialog( context: context, title: videoItem.title, @@ -179,7 +179,7 @@ class VideoCardV extends StatelessWidget { Row( children: [ Expanded( - child: Text(videoItem.title + "\n", + child: Text("${videoItem.title}\n", // semanticsLabel: "${videoItem.title}", maxLines: 2, overflow: TextOverflow.ellipsis, @@ -223,7 +223,7 @@ class VideoCardV extends StatelessWidget { ), const SizedBox(width: 2), ], - if (videoItem.isFollowed == 1) ...[ + if (videoItem.isFollowed) ...[ const PBadge( text: '已关注', stack: 'normal', @@ -262,7 +262,7 @@ class VideoCardV extends StatelessWidget { StatView( context: context, theme: 'gray', - value: videoItem.stat.view!, + value: videoItem.stat.viewStr, goto: videoItem.goto, ), const SizedBox(width: 4), @@ -270,7 +270,7 @@ class VideoCardV extends StatelessWidget { StatDanMu( context: context, theme: 'gray', - value: videoItem.stat.danmu!, + value: videoItem.stat.danmuStr, ), if (videoItem is RecVideoItemModel) ...[ const Spacer(), @@ -294,7 +294,7 @@ class VideoCardV extends StatelessWidget { ], if (videoItem is RecVideoItemAppModel && videoItem.desc != null && - videoItem.desc.contains(' · ')) ...[ + videoItem.desc!.contains(' · ')) ...[ const Spacer(), Expanded( flex: 0, @@ -310,7 +310,7 @@ class VideoCardV extends StatelessWidget { .withOpacity(0.8), ), text: Utils.shortenChineseDateString( - videoItem.desc.split(' · ').last)), + videoItem.desc!.split(' · ').last)), )), const SizedBox(width: 2), ] diff --git a/lib/common/widgets/video_popup_menu.dart b/lib/common/widgets/video_popup_menu.dart index e1eddcea..2adb6a78 100644 --- a/lib/common/widgets/video_popup_menu.dart +++ b/lib/common/widgets/video_popup_menu.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; @@ -21,16 +22,16 @@ class VideoCustomAction { } class VideoCustomActions { - dynamic videoItem; + BaseSimpleVideoItemModel videoItem; BuildContext context; late List actions; VoidCallback? onRemove; VideoCustomActions(this.videoItem, this.context, [this.onRemove]) { actions = [ - if ((videoItem.bvid as String?)?.isNotEmpty == true) ...[ + if (videoItem.bvid?.isNotEmpty == true) ...[ VideoCustomAction( - videoItem.bvid, + videoItem.bvid!, 'copy', Stack( children: [ @@ -39,7 +40,7 @@ class VideoCustomActions { ], ), () { - Utils.copyText(videoItem.bvid); + Utils.copyText(videoItem.bvid!); }, ), VideoCustomAction( @@ -84,7 +85,7 @@ class VideoCustomActions { SmartDialog.showToast("未能获取dislikeReasons或feedbacks"); return; } - Widget actionButton(DislikeReason? r, FeedbackReason? f) { + Widget actionButton(Reason? r, Reason? f) { return SearchText( text: r?.name ?? f?.name ?? '未知', onTap: (_) async { @@ -258,11 +259,11 @@ class VideoCustomActions { TextButton( onPressed: () async { var res = await VideoHttp.relationMod( - mid: videoItem.owner.mid, + mid: videoItem.owner.mid!, act: 5, reSrc: 11, ); - GStorage.setBlackMid(videoItem.owner.mid); + GStorage.setBlackMid(videoItem.owner.mid!); Get.back(); SmartDialog.showToast(res['msg'] ?? '成功'); }, diff --git a/lib/http/init.dart b/lib/http/init.dart index 8015c4dd..4c7e0856 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -19,22 +19,19 @@ import 'constants.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web; class Request { - static const gzipDecoder = GZipDecoder(); - static const brotilDecoder = BrotliDecoder(); + static const _gzipDecoder = GZipDecoder(); + static const _brotilDecoder = BrotliDecoder(); static final Request _instance = Request._internal(); static late AccountManager accountManager; static late final Dio dio; factory Request() => _instance; - late bool enableSystemProxy; - late String systemProxyHost; - late String systemProxyPort; static final _rand = Random(); - static final RegExp spmPrefixExp = + static final RegExp _spmPrefixExp = RegExp(r''); /// 设置cookie - static setCookie() async { + static Future setCookie() async { accountManager = AccountManager(); dio.interceptors.add(accountManager); await Accounts.refresh(); @@ -63,7 +60,7 @@ class Request { try { final html = await Request().get(Api.dynamicSpmPrefix, 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( List.generate(32, (_) => _rand.nextInt(256)) + List.filled(4, 0) + @@ -112,12 +109,10 @@ class Request { responseDecoder: responseDecoder, // Http2Adapter没有自动解压 persistentConnection: true); - enableSystemProxy = GStorage.setting - .get(SettingBoxKey.enableSystemProxy, defaultValue: false) as bool; - systemProxyHost = - GStorage.setting.get(SettingBoxKey.systemProxyHost, defaultValue: ''); - systemProxyPort = - GStorage.setting.get(SettingBoxKey.systemProxyPort, defaultValue: ''); + final bool enableSystemProxy = GStorage.setting + .get(SettingBoxKey.enableSystemProxy, defaultValue: false); + final String systemProxyHost = GStorage.defaultSystemProxyHost; + final String systemProxyPort = GStorage.defaultSystemProxyPort; final http11Adapter = IOHttpClientAdapter(createHttpClient: () { final client = HttpClient() @@ -286,10 +281,10 @@ class Request { ResponseBody responseBody) { switch (responseBody.headers['content-encoding']?.firstOrNull) { case 'gzip': - return utf8.decode(gzipDecoder.decodeBytes(responseBytes), + return utf8.decode(_gzipDecoder.decodeBytes(responseBytes), allowMalformed: true); case 'br': - return utf8.decode(brotilDecoder.convert(responseBytes), + return utf8.decode(_brotilDecoder.convert(responseBytes), allowMalformed: true); default: return utf8.decode(responseBytes, allowMalformed: true); diff --git a/lib/http/search.dart b/lib/http/search.dart index 6ea1d9e5..5d2a4f06 100644 --- a/lib/http/search.dart +++ b/lib/http/search.dart @@ -148,8 +148,7 @@ class SearchHttp { } else if (bvid != null) { data['bvid'] = bvid; } - final dynamic res = await Request() - .get(Api.ab2c, queryParameters: {...data}); + final dynamic res = await Request().get(Api.ab2c, queryParameters: data); if (res.data['code'] == 0) { return part != null ? ((res.data['data'] as List).getOrNull(part - 1)?['cid'] ?? @@ -201,8 +200,8 @@ class SearchHttp { } else if (epId != null) { data['ep_id'] = epId; } - final dynamic res = await Request() - .get(Api.bangumiInfo, queryParameters: {...data}); + final dynamic res = + await Request().get(Api.bangumiInfo, queryParameters: data); if (res.data['code'] == 0) { return { diff --git a/lib/http/video.dart b/lib/http/video.dart index 7cb05650..74335ce2 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -349,13 +349,11 @@ class VideoHttp { var res = await Request().get(Api.relatedList, queryParameters: {'bvid': bvid}); if (res.data['code'] == 0) { - List list = []; - for (var i in res.data['data']) { - HotVideoItemModel videoItem = HotVideoItemModel.fromJson(i); - if (!RecommendFilter.filter(videoItem, relatedVideos: true)) { - list.add(videoItem); - } - } + final items = + (res.data['data'] as List).map((i) => HotVideoItemModel.fromJson(i)); + final list = RecommendFilter.applyFilterToRelatedVideos + ? items.where((i) => !RecommendFilter.filterAll(i)).toList() + : items.toList(); return LoadingState.success(list); } else { return LoadingState.error(res.data['message']); @@ -390,7 +388,6 @@ class VideoHttp { static Future hasCoinVideo({required String bvid}) async { var res = await Request().get(Api.hasCoinVideo, queryParameters: {'bvid': bvid}); - debugPrint('res: $res'); if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; } else { diff --git a/lib/models/bangumi/list.dart b/lib/models/bangumi/list.dart index 48496429..0e37a145 100644 --- a/lib/models/bangumi/list.dart +++ b/lib/models/bangumi/list.dart @@ -8,7 +8,7 @@ class BangumiListDataModel { }); int? hasNext; - List? list; + List? list; int? num; int? size; int? total; diff --git a/lib/models/home/rcmd/result.dart b/lib/models/home/rcmd/result.dart index ee04699b..e4e649a1 100644 --- a/lib/models/home/rcmd/result.dart +++ b/lib/models/home/rcmd/result.dart @@ -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/utils.dart'; -class RecVideoItemAppModel { - 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, - }); - +class RecVideoItemAppModel extends BaseRecVideoItemModel { 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? bangumiView; - String? bangumiFollow; - String? bangumiBadge; String? cardType; Map? adInfo; ThreePoint? threePoint; - String? desc; RecVideoItemAppModel.fromJson(Map json) { id = json['player_args'] != null ? json['player_args']['aid'] - : int.parse(json['param'] ?? '-1'); + : int.tryParse(json['param'] ?? '-1'); aid = id; bvid = json['bvid'] ?? (json['player_args'] != null @@ -70,21 +30,22 @@ class RecVideoItemAppModel { title = json['title']; owner = RcmdOwner.fromJson(json); rcmdReason = json['bottom_rcmd_reason'] ?? json['top_rcmd_reason']; + if (rcmdReason != null && rcmdReason!.contains('赞')) { + // 有时能在推荐原因里获得点赞数 + (stat as RcmdStat).like = Utils.parseNum(rcmdReason!); + } // 由于app端api并不会直接返回与owner的关注状态 // 所以借用推荐原因是否为“已关注”、“新关注”判别关注状态,从而与web端接口等效 - isFollowed = (rcmdReason == '已关注') || (rcmdReason == '新关注') ? 1 : 0; + isFollowed = const {'已关注', '新关注'}.contains(rcmdReason); // 如果是,就无需再显示推荐原因,交由view统一处理即可 - if (isFollowed == 1) { - rcmdReason = null; - } + if (isFollowed) rcmdReason = null; + goto = json['goto']; param = int.parse(json['param']); uri = json['uri']; talkBack = json['talk_back']; if (json['goto'] == 'bangumi') { - bangumiView = json['cover_left_text_1']; - bangumiFollow = json['cover_left_text_2']; bangumiBadge = json['cover_right_text']; } @@ -95,30 +56,37 @@ class RecVideoItemAppModel { : null; desc = json['desc']; } + + // @override + // int? get pubdate => null; } -class RcmdStat { - RcmdStat({ - this.view, - this.like, - this.danmu, - }); - String? view; - String? like; - String? danmu; +class RcmdStat implements BaseStat { + @override + int? like; + + @override + int? get view => Utils.parseNum(viewStr); + @override + int? get danmu => Utils.parseNum(danmuStr); + + @override + late String viewStr; + @override + late String danmuStr; RcmdStat.fromJson(Map json) { - view = json["cover_left_text_1"]; - danmu = json['cover_left_text_2'] ?? '-'; + viewStr = json["cover_left_text_1"]; + danmuStr = json['cover_left_text_2']; } + + @override + set danmu(_) {} + @override + set view(_) {} } -class RcmdOwner { - RcmdOwner({this.name, this.mid}); - - String? name; - int? mid; - +class RcmdOwner extends BaseOwner { RcmdOwner.fromJson(Map json) { name = json['goto'] == 'av' ? json['args']['up_name'] @@ -130,63 +98,26 @@ class RcmdOwner { } class ThreePoint { - ThreePoint({ - this.dislikeReasons, - this.feedbacks, - this.watchLater, - }); - - List? dislikeReasons; - List? feedbacks; + List? dislikeReasons; + List? feedbacks; int? watchLater; ThreePoint.fromJson(Map json) { - if (json['dislike_reasons'] != null) { - dislikeReasons = []; - json['dislike_reasons'].forEach((v) { - dislikeReasons!.add(DislikeReason.fromJson(v)); - }); - } - if (json['feedbacks'] != null) { - feedbacks = []; - json['feedbacks'].forEach((v) { - feedbacks!.add(FeedbackReason.fromJson(v)); - }); - } + dislikeReasons = (json['dislike_reasons'] as List?) + ?.map((v) => Reason.fromJson(v)) + .toList(); + feedbacks = + (json['feedbacks'] as List?)?.map((v) => Reason.fromJson(v)).toList(); watchLater = json['watch_later']; } } -class DislikeReason { - DislikeReason({ - this.id, - this.name, - this.toast, - }); - +class Reason { int? id; String? name; String? toast; - DislikeReason.fromJson(Map 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 json) { + Reason.fromJson(Map json) { id = json['id']; name = json['name']; toast = json['toast']; diff --git a/lib/models/member/archive.dart b/lib/models/member/archive.dart index aa9b62ae..54599909 100644 --- a/lib/models/member/archive.dart +++ b/lib/models/member/archive.dart @@ -1,3 +1,7 @@ +import 'package:PiliPlus/utils/utils.dart'; + +import '../model_video.dart'; + class MemberArchiveDataModel { MemberArchiveDataModel({ this.list, @@ -51,114 +55,60 @@ class TListItemModel { } } -class VListItemModel { - 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, - }); - +class VListItemModel extends BaseVideoItemModel { int? comment; int? typeid; - int? play; - String? pic; String? subtitle; - String? description; String? copyright; - String? title; 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? isChargingSrc; - Stat? stat; - String? rcmdReason; - Owner? owner; VListItemModel.fromJson(Map json) { comment = json['comment']; typeid = json['typeid']; - play = json['play']; pic = json['pic']; subtitle = json['subtitle']; - description = json['description']; + desc = json['description']; copyright = json['copyright']; title = json['title']; review = json['review']; - author = json['author']; - mid = json['mid']; - created = json['created']; pubdate = json['created']; - length = json['length']; - duration = json['length']; - videoReview = json['video_review']; + if (json['length'] != null) duration = Utils.duration(json['length']); aid = json['aid']; bvid = json['bvid']; - cid = null; hideClick = json['hide_click']; isChargingSrc = json['is_charging_arc']; - stat = Stat.fromJson(json); - rcmdReason = null; - owner = Owner.fromJson(json); + stat = VListStat.fromJson(json); + owner = VListOwner.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 json) { + mid = json["mid"]; + name = json["author"]; } } -class Stat { - Stat({ - this.view, - this.danmu, - }); - - int? view; - int? danmu; - - Stat.fromJson(Map json) { +class VListStat extends BaseStat { + VListStat.fromJson(Map json) { view = json["play"]; danmu = json['video_review']; } } - -class Owner { - Owner({ - this.mid, - this.name, - this.face, - }); - int? mid; - String? name; - String? face; - - Owner.fromJson(Map json) { - mid = json["mid"]; - name = json["author"]; - face = ''; - } -} diff --git a/lib/models/member/coin.dart b/lib/models/member/coin.dart index 701131f1..b0f6a46a 100644 --- a/lib/models/member/coin.dart +++ b/lib/models/member/coin.dart @@ -1,89 +1,26 @@ -class MemberCoinsDataModel { - 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, - }); +import '../model_hot_video_item.dart'; - int? aid; - 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; +class MemberCoinsDataModel extends HotVideoItemModel { String? subtitle; + int? coins; int? time; - String? title; - String? tname; - int? videos; - int? view; - int? danmaku; + // int? get view => stat.view; + // int? get danmaku => stat.danmu; - MemberCoinsDataModel.fromJson(Map json) { - aid = json['aid']; - bvid = json['bvid']; - cid = json['cid']; + MemberCoinsDataModel.fromJson(Map json) + : super.fromJson(json) { 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']; time = json['time']; - title = json['title']; - tname = json['tname']; - 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 json) { - mid = json['mid']; - name = json['name']; - face = json['face']; + // view = json['stat']['view']; + // danmaku = json['stat']['danmaku']; } + // @override + // String? goto; + // @override + // bool isFollowed; + // @override + // String? rcmdReason; + // @override + // String? uri; } diff --git a/lib/models/model_hot_video_item.dart b/lib/models/model_hot_video_item.dart index b57ce9e6..8cbaeb8b 100644 --- a/lib/models/model_hot_video_item.dart +++ b/lib/models/model_hot_video_item.dart @@ -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({ - 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; +// 稍后再看, 排行榜等网页返回也使用该类 +class HotVideoItemModel extends BaseRecVideoItemModel { int? videos; int? tid; String? tname; int? copyright; - String? pic; - String? title; - int? pubdate; int? ctime; - String? desc; int? state; - int? duration; - int? middionId; - Owner? owner; - Stat? stat; - String? vDynamic; Dimension? dimension; - String? shortLinkV2; String? firstFrame; String? pubLocation; - int? seasontype; - bool? isOgv; - RcmdReason? rcmdReason; - bool? checked; String? pgcLabel; String? redirectUrl; + bool? checked; // 手动设置的 + HotVideoItemModel.fromJson(Map json) { aid = json["aid"]; cid = json["cid"]; @@ -76,97 +33,62 @@ class HotVideoItemModel { desc = json["desc"]; state = json["state"]; duration = json["duration"]; - middionId = json["middion_id"]; owner = Owner.fromJson(json["owner"]); - stat = Stat.fromJson(json['stat']); - vDynamic = json["vDynamic"]; - dimension = Dimension.fromMap(json['dimension']); - shortLinkV2 = json["short_link_v2"]; + stat = HotStat.fromJson(json['stat']); + dimension = Dimension.fromJson(json['dimension']); firstFrame = json["first_frame"]; pubLocation = json["pub_location"]; - seasontype = json["seasontype"]; - isOgv = json["isOgv"]; - rcmdReason = json['rcmd_reason'] != '' && json['rcmd_reason'] != null - ? RcmdReason.fromJson(json['rcmd_reason']) - : null; + dynamic rcmd = json['rcmd_reason']; + rcmdReason = rcmd is Map ? rcmd['content'] : rcmd; // 相关视频里rcmd为String, + if (rcmdReason?.isEmpty == true) rcmdReason = null; pgcLabel = json['pgc_label']; redirectUrl = json['redirect_url']; + // uri = json['uri']; // 仅在稍后再看存在 } + + // @override + // get isFollowed => false; + // @override + // get goto => 'av'; + // @override + // get uri => 'bilibili://video/$aid'; } -class 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; +class HotStat extends Stat { int? reply; int? favorite; int? coin; int? share; int? nowRank; int? hisRank; - int? like; int? dislike; int? vt; int? vv; - Stat.fromJson(Map json) { - aid = json["aid"]; - view = json["view"]; - danmu = json['danmaku']; + HotStat.fromJson(Map json) : super.fromJson(json) { reply = json["reply"]; favorite = json["favorite"]; coin = json['coin']; share = json["share"]; nowRank = json["now_rank"]; hisRank = json['his_rank']; - like = json["like"]; dislike = json["dislike"]; vt = json['vt']; vv = json["vv"]; } } -class Dimension { - Dimension({this.width, this.height, this.rotate}); +// class RcmdReason { +// RcmdReason({ +// this.rcornerMark, +// this.content, +// }); - int? width; - int? height; - int? rotate; +// int? rcornerMark; +// String? content = ''; - Dimension.fromMap(Map json) { - width = json["width"]; - height = json["height"]; - rotate = json["rotate"]; - } -} - -class RcmdReason { - RcmdReason({ - this.rcornerMark, - this.content, - }); - - int? rcornerMark; - String? content = ''; - - RcmdReason.fromJson(Map json) { - rcornerMark = json["corner_mark"]; - content = json["content"] ?? ''; - } -} +// RcmdReason.fromJson(Map json) { +// rcornerMark = json["corner_mark"]; +// content = json["content"] ?? ''; +// } +// } diff --git a/lib/models/model_owner.dart b/lib/models/model_owner.dart index 70396cbb..ef19c39d 100644 --- a/lib/models/model_owner.dart +++ b/lib/models/model_owner.dart @@ -1,17 +1,21 @@ import 'package:hive/hive.dart'; +import 'model_video.dart'; + part 'model_owner.g.dart'; @HiveType(typeId: 3) -class Owner { +class Owner implements BaseOwner { Owner({ this.mid, this.name, this.face, }); @HiveField(0) + @override int? mid; @HiveField(1) + @override String? name; @HiveField(2) String? face; diff --git a/lib/models/model_rec_video_item.dart b/lib/models/model_rec_video_item.dart index 8c0bea44..d39f919e 100644 --- a/lib/models/model_rec_video_item.dart +++ b/lib/models/model_rec_video_item.dart @@ -1,55 +1,19 @@ import './model_owner.dart'; -import 'package:hive/hive.dart'; +import 'model_video.dart'; -part 'model_rec_video_item.g.dart'; - -@HiveType(typeId: 0) -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) +abstract class BaseRecVideoItemModel extends BaseVideoItemModel { + String? goto; + String? uri; String? rcmdReason; + // app推荐专属 + int? param; + String? bangumiBadge; +} + +class RecVideoItemModel extends BaseRecVideoItemModel { RecVideoItemModel.fromJson(Map json) { - id = json["id"]; + aid = json["id"]; bvid = json["bvid"]; cid = json["cid"]; goto = json["goto"]; @@ -60,34 +24,15 @@ class RecVideoItemModel { pubdate = json["pubdate"]; owner = Owner.fromJson(json["owner"]); stat = Stat.fromJson(json["stat"]); - isFollowed = json["is_followed"] ?? 0; + isFollowed = json["is_followed"] == 1; // rcmdReason = json["rcmd_reason"] != null // ? RcmdReason.fromJson(json["rcmd_reason"]) // : RcmdReason(content: ''); rcmdReason = json["rcmd_reason"]?['content']; } -} -@HiveType(typeId: 1) -class Stat { - Stat({ - this.view, - this.like, - this.danmu, - }); - @HiveField(0) - int? view; - @HiveField(1) - int? like; - @HiveField(2) - int? danmu; - - Stat.fromJson(Map json) { - // 无需在model中转换以保留原始数据,在view层处理即可 - view = json["view"]; - like = json["like"]; - danmu = json['danmaku']; - } + // @override + // String? get desc => null; } // @HiveType(typeId: 2) @@ -96,10 +41,8 @@ class Stat { // this.reasonType, // this.content, // }); -// @HiveField(0) -// int? reasonType; -// @HiveField(1) -// String? content = ''; +// // int? reasonType; +// // String? content; // // RcmdReason.fromJson(Map json) { // reasonType = json["reason_type"]; diff --git a/lib/models/model_rec_video_item.g.dart b/lib/models/model_rec_video_item.g.dart deleted file mode 100644 index 7c526e5f..00000000 --- a/lib/models/model_rec_video_item.g.dart +++ /dev/null @@ -1,154 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'model_rec_video_item.dart'; - -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class RecVideoItemModelAdapter extends TypeAdapter { - @override - final int typeId = 0; - - @override - RecVideoItemModel read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - 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 { - @override - final int typeId = 1; - - @override - Stat read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - 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 { -// @override -// final int typeId = 2; -// -// @override -// RcmdReason read(BinaryReader reader) { -// final numOfFields = reader.readByte(); -// final fields = { -// 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; -// } diff --git a/lib/models/model_video.dart b/lib/models/model_video.dart new file mode 100644 index 00000000..6c086171 --- /dev/null +++ b/lib/models/model_video.dart @@ -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 json) { + view = json["view"]; + like = json["like"]; + danmu = json['danmaku']; + } +} + +class PlayStat extends BaseStat { + PlayStat.fromJson(Map json) { + view = json['play']; + danmu = json['danmaku']; + } +} + +class Dimension { + int? width; + int? height; + int? rotate; + + Dimension.fromJson(Map json) { + width = json["width"]; + height = json["height"]; + rotate = json["rotate"]; + } +} diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart index b40f7874..c78912f3 100644 --- a/lib/models/search/result.dart +++ b/lib/models/search/result.dart @@ -1,12 +1,10 @@ import 'package:PiliPlus/utils/em.dart'; import 'package:PiliPlus/utils/utils.dart'; -class SearchVideoModel { - SearchVideoModel({ - this.numResults, - this.list, - }); +import '../model_owner.dart'; +import '../model_video.dart'; +class SearchVideoModel { int? numResults; List? list; @@ -19,68 +17,25 @@ class SearchVideoModel { } } -class SearchVideoItemModel { - 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, - }); - +class SearchVideoItemModel extends BaseVideoItemModel { String? type; int? id; - int? cid; // String? author; - int? mid; // String? typeid; // String? typename; String? arcurl; - int? aid; - String? bvid; - List? title; - // List? titleList; - String? description; - String? pic; // String? play; - int? videoReview; + // int? videoReview; // String? favorites; String? tag; // String? review; - int? pubdate; - int? senddate; - int? duration; + int? ctime; // String? duration; // String? viewType; // String? like; // String? upic; // String? danmaku; - Owner? owner; - Stat? stat; - String? rcmdReason; + List>? titleList; SearchVideoItemModel.fromJson(Map json) { type = json['type']; @@ -88,43 +43,35 @@ class SearchVideoItemModel { arcurl = json['arcurl']; aid = json['aid']; bvid = json['bvid']; - mid = json['mid']; // title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); - title = Em.regTitle(json['title']); - description = json['description']; + titleList = Em.regTitle(json['title']); + title = titleList!.map((i) => i['text']!).join(); + desc = json['description']; pic = json['pic'] != null && json['pic'].startsWith('//') ? 'https:${json['pic']}' : json['pic'] ?? ''; - videoReview = json['video_review']; pubdate = json['pubdate']; - senddate = json['senddate']; + ctime = json['senddate']; duration = Utils.duration(json['duration']); - owner = Owner.fromJson(json); - stat = Stat.fromJson(json); + owner = SearchOwner.fromJson(json); + stat = SearchStat.fromJson(json); } + + // @override + // String? goto; + // @override + // bool isFollowed; + // @override + // String? uri; } -class Stat { - Stat({ - this.view, - this.danmu, - this.favorite, - this.reply, - this.like, - }); - - // 播放量 - int? view; - // 弹幕数 - int? danmu; +class SearchStat extends BaseStat { // 收藏数 int? favorite; // 评论数 int? reply; - // 喜欢 - int? like; - Stat.fromJson(Map json) { + SearchStat.fromJson(Map json) { view = json['play']; danmu = json['danmaku']; favorite = json['favorite']; @@ -133,17 +80,8 @@ class Stat { } } -class Owner { - Owner({ - this.mid, - this.name, - this.face, - }); - int? mid; - String? name; - String? face; - - Owner.fromJson(Map json) { +class SearchOwner extends Owner { + SearchOwner.fromJson(Map json) { mid = json["mid"]; name = json["author"]; face = json['upic']; @@ -301,7 +239,7 @@ class SearchLiveItemModel { rankScore = json['rank_score']; roomid = json['roomid']; attentions = json['attentions']; - cateName = Em.regCate(json['cate_name']) ?? ''; + cateName = Em.regCate(json['cate_name']); } } diff --git a/lib/models/space_archive/item.dart b/lib/models/space_archive/item.dart index ac564c6b..197bb1ef 100644 --- a/lib/models/space_archive/item.dart +++ b/lib/models/space_archive/item.dart @@ -1,107 +1,90 @@ -import 'package:json_annotation/json_annotation.dart'; - +import '../model_owner.dart'; +import '../model_video.dart'; import 'badge.dart'; import 'cursor_attr.dart'; import 'three_point.dart'; -part 'item.g.dart'; - -@JsonSerializable() -class Item { - String? title; +class Item extends BaseSimpleVideoItemModel { String? subtitle; String? tname; - String? cover; - @JsonKey(name: 'cover_icon') + String? get cover => pic; // 不知道哪里使用了cover String? coverIcon; String? uri; String? param; String? goto; String? length; - int? duration; - @JsonKey(name: 'is_popular') bool? isPopular; - @JsonKey(name: 'is_steins') bool? isSteins; - @JsonKey(name: 'is_ugcpay') bool? isUgcpay; - @JsonKey(name: 'is_cooperation') bool? isCooperation; - @JsonKey(name: 'is_pgc') bool? isPgc; - @JsonKey(name: 'is_live_playback') bool? isLivePlayback; - @JsonKey(name: 'is_pugv') bool? isPugv; - @JsonKey(name: 'is_fold') bool? isFold; - @JsonKey(name: 'is_oneself') bool? isOneself; - int? play; - int? danmaku; int? ctime; - @JsonKey(name: 'ugc_pay') int? ugcPay; - String? author; bool? state; - String? bvid; int? videos; - @JsonKey(name: 'three_point') List? threePoint; - @JsonKey(name: 'first_cid') - int? firstCid; - @JsonKey(name: 'cursor_attr') CursorAttr? cursorAttr; - @JsonKey(name: 'view_content') - String? viewContent; - @JsonKey(name: 'icon_type') int? iconType; - @JsonKey(name: 'publish_time_text') String? publishTimeText; List? badges; Map? season; Map? history; - Item({ - this.title, - this.subtitle, - this.tname, - this.cover, - this.coverIcon, - this.uri, - this.param, - this.goto, - this.length, - this.duration, - this.isPopular, - this.isSteins, - this.isUgcpay, - this.isCooperation, - this.isPgc, - this.isLivePlayback, - this.isPugv, - this.isFold, - this.isOneself, - this.play, - this.danmaku, - this.ctime, - this.ugcPay, - this.author, - this.state, - this.bvid, - this.videos, - this.threePoint, - this.firstCid, - this.cursorAttr, - this.viewContent, - this.iconType, - this.publishTimeText, - this.badges, - this.season, - this.history, - }); - - factory Item.fromJson(Map json) => _$ItemFromJson(json); - - Map toJson() => _$ItemToJson(this); + Item.fromJson(Map json) { + title = json['title']; + subtitle = json['subtitle']; + tname = json['tname']; + pic = json['cover']; + coverIcon = json['cover_icon']; + uri = json['uri']; + param = json['param']; + goto = json['goto']; + length = json['length']; + duration = json['duration'] ?? -1; + isPopular = json['is_popular']; + isSteins = json['is_steins']; + isUgcpay = json['is_ugcpay']; + isCooperation = json['is_cooperation']; + isPgc = json['is_pgc']; + isLivePlayback = json['is_live_playback']; + isPugv = json['is_pugv']; + isFold = json['is_fold']; + isOneself = json['is_oneself']; + ctime = json['ctime']; + ugcPay = json['ugc_pay']; + state = json['state']; + bvid = json['bvid']; + videos = json['videos']; + threePoint = (json['three_point'] as List?) + ?.map((e) => ThreePoint.fromJson(e as Map)) + .toList(); + cid = json['first_cid']; + cursorAttr = json['cursor_attr'] == null + ? null + : CursorAttr.fromJson(json['cursor_attr'] as Map); + iconType = json['icon_type']; + publishTimeText = json['publish_time_text']; + badges = (json['badges'] as List?) + ?.map((e) => Badge.fromJson(e as Map)) + .toList(); + season = json['season']; + history = json['history']; + stat = PlayStat.fromJson(json); + owner = Owner(mid: 0, name: json['author']); + } +} + +class UserStat extends PlayStat { + String? _viewStr; + + @override + String get viewStr => _viewStr ?? super.viewStr; + + UserStat.fromJson(Map json) : super.fromJson(json) { + _viewStr = json['view_content']; + } } diff --git a/lib/models/space_archive/item.g.dart b/lib/models/space_archive/item.g.dart deleted file mode 100644 index 1efefc48..00000000 --- a/lib/models/space_archive/item.g.dart +++ /dev/null @@ -1,90 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'item.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Item _$ItemFromJson(Map 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?) - ?.map((e) => ThreePoint.fromJson(e as Map)) - .toList(), - firstCid: (json['first_cid'] as num?)?.toInt(), - cursorAttr: json['cursor_attr'] == null - ? null - : CursorAttr.fromJson(json['cursor_attr'] as Map), - 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?) - ?.map((e) => Badge.fromJson(e as Map)) - .toList(), - season: json['season'], - history: json['history'], - ); - -Map _$ItemToJson(Item instance) => { - '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, - }; diff --git a/lib/models/user/fav_detail.dart b/lib/models/user/fav_detail.dart index 7bb47764..27413b23 100644 --- a/lib/models/user/fav_detail.dart +++ b/lib/models/user/fav_detail.dart @@ -1,83 +1,45 @@ -import 'package:PiliPlus/models/model_owner.dart'; -import 'package:PiliPlus/models/user/fav_folder.dart'; +import '../model_owner.dart'; +import '../model_video.dart'; +import 'fav_folder.dart'; class FavDetailData { - FavDetailData({ - this.info, - this.medias, - this.hasMore, - }); - FavFolderItemData? info; - List? medias; + List? list; + List? get medias => list; bool? hasMore; FavDetailData.fromJson(Map json) { info = json['info'] == null ? null : FavFolderItemData.fromJson(json['info']); - medias = (json['medias'] as List?) + list = (json['medias'] as List?) ?.map((e) => FavDetailItemData.fromJson(e)) .toList(); hasMore = json['has_more']; } } -class FavDetailItemData { - 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, - }); - +class FavDetailItemData extends BaseVideoItemModel { int? id; int? type; - String? title; - String? pic; - String? intro; int? page; - int? duration; - Owner? owner; // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/fav/list.md // | attr | num | 失效 | 0: 正常;9: up自己删除;1: 其他原因删除 | int? attr; Map? cntInfo; String? link; int? ctime; - int? pubdate; int? favTime; - String? bvId; - String? bvid; Map? ogv; - Stat? stat; - int? cid; String? epId; bool? checked; FavDetailItemData.fromJson(Map json) { id = json['id']; + aid = id; type = json['type']; title = json['title']; pic = json['cover']; - intro = json['intro']; + desc = json['intro']; page = json['page']; duration = json['duration']; owner = Owner.fromJson(json['upper']); @@ -87,38 +49,18 @@ class FavDetailItemData { ctime = json['ctime']; pubdate = json['pubtime']; favTime = json['fav_time']; - bvId = json['bv_id']; bvid = json['bvid']; ogv = json['ogv']; - stat = Stat.fromJson(json['cnt_info']); - cid = json['ugc'] != null ? json['ugc']['first_cid'] : null; + stat = PlayStat.fromJson(json['cnt_info']); + cid = json['ugc']?['first_cid']; if (json['link'] != null && json['link'].contains('/bangumi')) { epId = resolveEpId(json['link']); } } - String resolveEpId(url) { - RegExp regex = RegExp(r'\d+'); - Iterable matches = regex.allMatches(url); - List numbers = []; - for (Match match in matches) { - numbers.add(match.group(0)!); - } - return numbers[0]; - } -} - -class Stat { - Stat({ - this.view, - this.danmu, - }); - - int? view; - int? danmu; - - Stat.fromJson(Map json) { - view = json['play']; - danmu = json['danmaku']; - } + static final _digitRegExp = RegExp(r'\d+'); + String resolveEpId(String url) => _digitRegExp.firstMatch(url)!.group(0)!; + + // @override + // bool isFollowed; } diff --git a/lib/models/user/history.dart b/lib/models/user/history.dart index 9222fdb3..a6e9add3 100644 --- a/lib/models/user/history.dart +++ b/lib/models/user/history.dart @@ -60,47 +60,19 @@ class HisTabItem { } class HisListItem { - HisListItem({ - 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; + late String title; String? longTitle; String? cover; String? pic; List? covers; String? uri; - History? history; + late History history; int? videos; String? authorName; String? authorFace; int? authorMid; int? viewAt; - int? progress; + int progress = 0; String? badge; String? showTitle; int? duration; @@ -113,7 +85,7 @@ class HisListItem { String? tagName; int? liveStatus; bool? checked; - void isFullScreen; + dynamic isFullScreen; HisListItem.fromJson(Map json) { title = json['title']; diff --git a/lib/models/user/sub_detail.dart b/lib/models/user/sub_detail.dart index a1e52e55..967166c4 100644 --- a/lib/models/user/sub_detail.dart +++ b/lib/models/user/sub_detail.dart @@ -1,17 +1,16 @@ class SubDetailModelData { DetailInfo? info; - List? medias; + List? list; - SubDetailModelData({this.info, this.medias}); + List? get medias => list; // 不知道哪里使用了这个 + + SubDetailModelData({this.info, this.list}); SubDetailModelData.fromJson(Map json) { info = DetailInfo.fromJson(json['info']); - if (json['medias'] != null) { - medias = []; - json['medias'].forEach((v) { - medias!.add(SubDetailMediaItem.fromJson(v)); - }); - } + list = (json['medias'] as List?) + ?.map((i) => SubDetailMediaItem.fromJson(i)) + .toList(); } } @@ -23,8 +22,8 @@ class SubDetailMediaItem { int? duration; int? pubtime; String? bvid; - Map? upper; - Map? cntInfo; + Map? upper; + Map? cntInfo; int? enableVt; String? vtDisplay; diff --git a/lib/models/video/later.dart b/lib/models/video/later.dart index 22bc1185..0ee592e0 100644 --- a/lib/models/video/later.dart +++ b/lib/models/video/later.dart @@ -1,3 +1,6 @@ +import '../model_owner.dart'; +import '../model_video.dart'; + class MediaVideoItemModel { MediaVideoItemModel({ this.id, @@ -43,7 +46,7 @@ class MediaVideoItemModel { int? attr; int? tid; int? copyRight; - Map? cntInfo; + Map? cntInfo; String? cover; int? duration; 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 json) => Dimension( - width: json["width"], - height: json["height"], - rotate: json["rotate"], - ); -} - class Meta { Meta({ this.quality, @@ -224,26 +209,7 @@ class Rights { ); } -class Upper { - 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; +class Upper extends Owner { int? followed; int? fans; int? vipType; @@ -255,19 +221,16 @@ class Upper { String? officialDesc; String? displayName; - factory Upper.fromJson(Map json) => Upper( - mid: json["mid"], - name: json["name"], - face: json["face"], - followed: json["followed"], - fans: json["fans"], - vipType: json["vip_type"], - vipStatue: json["vip_statue"], - vipDueDate: json["vip_due_date"], - vipPayType: json["vip_pay_type"], - officialRole: json["official_role"], - officialTitle: json["official_title"], - officialDesc: json["official_desc"], - displayName: json["display_name"], - ); + Upper.fromJson(Map json) : super.fromJson(json) { + followed = json["followed"]; + fans = json["fans"]; + vipType = json["vip_type"]; + vipStatue = json["vip_statue"]; + vipDueDate = json["vip_due_date"]; + vipPayType = json["vip_pay_type"]; + officialRole = json["official_role"]; + officialTitle = json["official_title"]; + officialDesc = json["official_desc"]; + displayName = json["display_name"]; + } } diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index c7317b56..2301d35c 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -324,19 +324,19 @@ class _BangumiInfoState extends State StatView( context: context, theme: 'gray', - value: !widget.isLoading + value: Utils.numFormat(!widget.isLoading ? widget.bangumiDetail!.stat!['views'] - : bangumiItem!.stat!['views'], + : bangumiItem!.stat!['views']), size: 'medium', ), const SizedBox(width: 6), StatDanMu( context: context, theme: 'gray', - value: !widget.isLoading + value: Utils.numFormat(!widget.isLoading ? widget .bangumiDetail!.stat!['danmakus'] - : bangumiItem!.stat!['danmakus'], + : bangumiItem!.stat!['danmakus']), size: 'medium', ), if (isLandscape) ...[ diff --git a/lib/pages/bangumi/introduction/widgets/intro_detail.dart b/lib/pages/bangumi/introduction/widgets/intro_detail.dart index 7a0b5ca3..bfede2d1 100644 --- a/lib/pages/bangumi/introduction/widgets/intro_detail.dart +++ b/lib/pages/bangumi/introduction/widgets/intro_detail.dart @@ -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/search/widgets/search_text.dart'; import 'package:flutter/material.dart'; -import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:get/get.dart'; import '../../../../utils/utils.dart'; class IntroDetail extends CommonCollapseSlidePage { - final dynamic bangumiDetail; + final BangumiInfoModel bangumiDetail; final dynamic videoTags; const IntroDetail({ super.key, - this.bangumiDetail, + required this.bangumiDetail, this.videoTags, }); @@ -70,7 +71,7 @@ class _IntroDetailState extends CommonCollapseSlidePageState { ), children: [ SelectableText( - widget.bangumiDetail!.title, + widget.bangumiDetail.title!, style: const TextStyle( fontSize: 16, ), @@ -81,14 +82,14 @@ class _IntroDetailState extends CommonCollapseSlidePageState { StatView( context: context, theme: 'gray', - value: widget.bangumiDetail!.stat!['views'], + value: Utils.numFormat(widget.bangumiDetail.stat!['views']), size: 'medium', ), const SizedBox(width: 6), StatDanMu( context: context, theme: 'gray', - value: widget.bangumiDetail!.stat!['danmakus'], + value: Utils.numFormat(widget.bangumiDetail.stat!['danmakus']), size: 'medium', ), ], @@ -97,17 +98,17 @@ class _IntroDetailState extends CommonCollapseSlidePageState { Row( children: [ Text( - widget.bangumiDetail!.areas!.first['name'], + widget.bangumiDetail.areas!.first['name'], style: smallTitle, ), const SizedBox(width: 6), Text( - widget.bangumiDetail!.publish!['pub_time_show'], + widget.bangumiDetail.publish!['pub_time_show'], style: smallTitle, ), const SizedBox(width: 6), Text( - widget.bangumiDetail!.newEp!['desc'], + widget.bangumiDetail.newEp!['desc'], style: smallTitle, ), ], @@ -119,7 +120,7 @@ class _IntroDetailState extends CommonCollapseSlidePageState { ), const SizedBox(height: 4), SelectableText( - '${widget.bangumiDetail!.evaluate!}', + widget.bangumiDetail.evaluate!, style: smallTitle.copyWith(fontSize: 14), ), const SizedBox(height: 20), @@ -129,7 +130,7 @@ class _IntroDetailState extends CommonCollapseSlidePageState { ), const SizedBox(height: 4), SelectableText( - widget.bangumiDetail.actors, + widget.bangumiDetail.actors!, style: smallTitle.copyWith(fontSize: 14), ), if (widget.videoTags is List && widget.videoTags.isNotEmpty) ...[ diff --git a/lib/pages/bangumi/widgets/bangumi_card_v_member_home.dart b/lib/pages/bangumi/widgets/bangumi_card_v_member_home.dart index f1930eac..cf9828c5 100644 --- a/lib/pages/bangumi/widgets/bangumi_card_v_member_home.dart +++ b/lib/pages/bangumi/widgets/bangumi_card_v_member_home.dart @@ -98,7 +98,7 @@ Widget bangumiContent(Item bangumiItem) { children: [ Expanded( child: Text( - bangumiItem.title ?? '', + bangumiItem.title, textAlign: TextAlign.start, style: const TextStyle( letterSpacing: 0.3, diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 72eb6c41..810a3ffe 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -41,21 +41,20 @@ class FavDetailController extends MultiSelectController { item.value = data.info ?? FavFolderItemData(); isOwner.value = data.info?.mid == mid; } - if (data.medias.isNullOrEmpty) { + if (data.list.isNullOrEmpty) { isEnd = true; } if (currentPage != 1 && loadingState.value is Success) { - data.medias ??= []; - data.medias!.insertAll( + data.list ??= []; + data.list!.insertAll( 0, - List.from((loadingState.value as Success).response), + (loadingState.value as Success).response, ); } - if (isEnd.not && - (data.medias?.length ?? 0) >= (data.info?.mediaCount ?? 0)) { + if (isEnd.not && (data.list?.length ?? 0) >= (data.info?.mediaCount ?? 0)) { isEnd = true; } - loadingState.value = LoadingState.success(data.medias); + loadingState.value = LoadingState.success(data.list); return true; } @@ -138,8 +137,7 @@ class FavDetailController extends MultiSelectController { void toViewPlayAll() { if (loadingState.value is Success) { - List list = List.from( - (loadingState.value as Success).response); + List list = (loadingState.value as Success).response; for (FavDetailItemData element in list) { if (element.cid == null) { continue; diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 7f157a04..0f141b08 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/dialog.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.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/pages/fav_search/view.dart' show SearchType; import 'package:PiliPlus/utils/extension.dart'; @@ -429,15 +430,15 @@ class _FavDetailPageState extends State { ), ); } - final element = loadingState.response[index]; + FavDetailItemData element = loadingState.response[index]; return Stack( children: [ Positioned.fill( child: FavVideoCardH( videoItem: element, callFn: () => _favDetailController.onCancelFav( - element.id, - element.type, + element.id!, + element.type!, ), onViewFav: () { Utils.toViewPage( diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index 2a4fd824..15059b3a 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -1,12 +1,12 @@ import 'package:PiliPlus/common/widgets/icon_button.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/utils/extension.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:flutter/material.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/video.dart'; import 'package:PiliPlus/utils/id_utils.dart'; @@ -16,7 +16,7 @@ import '../../../common/widgets/badge.dart'; // 收藏视频卡片 - 水平布局 class FavVideoCardH extends StatelessWidget { - final dynamic videoItem; + final FavDetailItemData videoItem; final Function? callFn; final int? searchType; final GestureTapCallback? onTap; @@ -37,7 +37,7 @@ class FavVideoCardH extends StatelessWidget { @override Widget build(BuildContext context) { - int id = videoItem.id; + int id = videoItem.id!; String bvid = videoItem.bvid ?? IdUtils.av2bv(id); return InkWell( onTap: () async { @@ -48,11 +48,11 @@ class FavVideoCardH extends StatelessWidget { String? epId; if (videoItem.type == 24) { videoItem.cid = await SearchHttp.ab2c(bvid: bvid); - dynamic seasonId = videoItem.ogv['season_id']; + dynamic seasonId = videoItem.ogv!['season_id']; epId = videoItem.epId; Utils.viewBangumi(seasonId: seasonId, epId: epId); return; - } else if (videoItem.page == 0 || videoItem.page > 1) { + } else if (videoItem.page == 0 || videoItem.page! > 1) { var result = await VideoHttp.videoIntro(bvid: bvid); if (result['status']) { epId = result['data'].epId; @@ -61,9 +61,8 @@ class FavVideoCardH extends StatelessWidget { } } - if (videoItem is FavDetailItemData && - [0, 16].contains(videoItem.attr).not) { - Get.toNamed('/member?mid=${videoItem.owner?.mid}'); + if ([0, 16].contains(videoItem.attr).not) { + Get.toNamed('/member?mid=${videoItem.owner.mid}'); return; } onViewFav(); @@ -117,20 +116,19 @@ class FavVideoCardH extends StatelessWidget { height: maxHeight, ), PBadge( - text: Utils.timeFormat(videoItem.duration!), + text: Utils.timeFormat(videoItem.duration), right: 6.0, bottom: 6.0, type: 'gray', ), - if (videoItem.ogv != null) ...[ + if (videoItem.ogv != null) PBadge( - text: videoItem.ogv['type_name'], + text: videoItem.ogv!['type_name'], top: 6.0, right: 6.0, bottom: null, left: null, ), - ], ], ); }, @@ -163,30 +161,28 @@ class FavVideoCardH extends StatelessWidget { maxLines: 2, overflow: TextOverflow.ellipsis, ), - if (videoItem.ogv != null) ...[ + if (videoItem.ogv != null) Text( - videoItem.intro, + videoItem.desc!, style: TextStyle( fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, color: Theme.of(context).colorScheme.outline, ), ), - ], const Spacer(), Text( Utils.dateFormat(videoItem.favTime), style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.outline), ), - if (videoItem.owner.name != '') ...[ + if (!videoItem.owner.name.isNullOrEmpty) Text( - videoItem.owner.name, + videoItem.owner.name!, style: TextStyle( fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, color: Theme.of(context).colorScheme.outline, ), ), - ], Padding( padding: const EdgeInsets.only(top: 2), child: Row( @@ -194,13 +190,13 @@ class FavVideoCardH extends StatelessWidget { StatView( context: context, theme: 'gray', - value: videoItem.cntInfo['play'], + value: videoItem.stat.viewStr, ), const SizedBox(width: 8), StatDanMu( context: context, theme: 'gray', - value: videoItem.cntInfo['danmaku'], + value: videoItem.stat.danmuStr, ), const Spacer(), ], diff --git a/lib/pages/fav_search/controller.dart b/lib/pages/fav_search/controller.dart index c3e6d785..524f972f 100644 --- a/lib/pages/fav_search/controller.dart +++ b/lib/pages/fav_search/controller.dart @@ -49,17 +49,11 @@ class FavSearchController extends CommonController { late List currentList = loadingState.value is Success ? (loadingState.value as Success).response : []; - List? dataList = searchType == SearchType.fav - ? (currentPage == 1 - ? response.response.medias - : response.response.medias != null - ? currentList + response.response.medias - : currentList) - : (currentPage == 1 - ? response.response.list - : response.response.list != null - ? currentList + response.response.list - : currentList); + List? dataList = currentPage == 1 + ? response.response.list + : response.response.list != null + ? currentList + response.response.list + : currentList; isEnd = searchType == SearchType.fav ? response.response.hasMore == false : response.response.list == null || response.response.list.isEmpty; diff --git a/lib/pages/fav_search/view.dart b/lib/pages/fav_search/view.dart index dbe2fa41..fb74e2ec 100644 --- a/lib/pages/fav_search/view.dart +++ b/lib/pages/fav_search/view.dart @@ -65,8 +65,8 @@ class _FavSearchPageState extends State { return switch (loadingState) { Loading() => errorWidget(), Success() => (loadingState.response as List?)?.isNotEmpty == true - ? _favSearchCtr.searchType == SearchType.fav - ? CustomScrollView( + ? switch (_favSearchCtr.searchType) { + SearchType.fav => CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), controller: _favSearchCtr.scrollController, slivers: [ @@ -120,54 +120,54 @@ class _FavSearchPageState extends State { ), ), ], - ) - : _favSearchCtr.searchType == SearchType.follow - ? ListView.builder( + ), + SearchType.follow => 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( 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], - ); - }), - ) - : 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, - ), - ), + 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, + ), + ), + ), + ], + ), + } : errorWidget( callback: _favSearchCtr.onReload, ), diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index b99c0c20..2d1715f6 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -67,7 +67,7 @@ class HistoryController extends MultiSelectController bool customHandleResponse(Success response) { HistoryData data = response.response; 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; if (currentPage == 1) { if (type == null && tabs.isEmpty && data.tab?.isNotEmpty == true) { diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index d7f77130..0b79d8c7 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/models/user/history.dart'; import 'package:PiliPlus/pages/common/multi_select_controller.dart'; import 'package:PiliPlus/pages/fav_search/controller.dart'; import 'package:PiliPlus/pages/history/base_controller.dart'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.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'; class HistoryItem extends StatelessWidget { - final dynamic videoItem; + final HisListItem videoItem; final dynamic ctr; final Function? onChoose; final Function? onDelete; @@ -35,7 +36,7 @@ class HistoryItem extends StatelessWidget { @override Widget build(BuildContext context) { - int aid = videoItem.history.oid; + int aid = videoItem.history.oid!; String bvid = videoItem.history.bvid ?? IdUtils.av2bv(aid); return InkWell( onTap: () async { @@ -45,7 +46,7 @@ class HistoryItem extends StatelessWidget { onChoose?.call(); return; } - if (videoItem.history.business.contains('article')) { + if (videoItem.history.business?.contains('article') == true) { // int cid = videoItem.history.cid ?? // // videoItem.history.oid ?? // await SearchHttp.ab2c(aid: aid, bvid: bvid); @@ -80,8 +81,8 @@ class HistoryItem extends StatelessWidget { } else { SmartDialog.showToast('直播未开播'); } - } else if (videoItem.history?.business == 'pgc' || - videoItem.tagName.contains('动画')) { + } else if (videoItem.history.business == 'pgc' || + videoItem.tagName?.contains('动画') == true) { /// hack var bvid = videoItem.history.bvid; if (bvid != null && bvid != '') { @@ -106,7 +107,7 @@ class HistoryItem extends StatelessWidget { SmartDialog.showToast(result['msg']); } } else { - if (videoItem.history.epid != '') { + if (videoItem.history.epid != null && videoItem.history.epid != 0) { Utils.viewBangumi(epId: videoItem.history.epid); } } @@ -164,9 +165,9 @@ class HistoryItem extends StatelessWidget { return Stack( children: [ NetworkImgLayer( - src: (videoItem.cover != '' - ? videoItem.cover - : videoItem.covers.first), + src: (videoItem.cover.isNullOrEmpty + ? videoItem.covers?.first ?? '' + : videoItem.cover), width: maxWidth, height: maxHeight, ), @@ -176,7 +177,7 @@ class HistoryItem extends StatelessWidget { PBadge( text: videoItem.progress == -1 ? '已看完' - : '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}', + : '${Utils.timeFormat(videoItem.progress)}/${Utils.timeFormat(videoItem.duration!)}', right: 6.0, bottom: 8.0, type: 'gray', @@ -254,7 +255,7 @@ class HistoryItem extends StatelessWidget { child: videoProgressIndicator( videoItem.progress == -1 ? 1 - : videoItem.progress / videoItem.duration, + : videoItem.progress / videoItem.duration!, ), ), ], @@ -281,7 +282,7 @@ class HistoryItem extends StatelessWidget { style: const TextStyle( letterSpacing: 0.3, ), - maxLines: videoItem.videos > 1 ? 1 : 2, + maxLines: videoItem.videos! > 1 ? 1 : 2, overflow: TextOverflow.ellipsis, ), if (videoItem.isFullScreen != null) ...[ @@ -301,7 +302,7 @@ class HistoryItem extends StatelessWidget { Row( children: [ Text( - videoItem.authorName, + videoItem.authorName!, style: TextStyle( fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, color: Theme.of(context).colorScheme.outline, @@ -318,82 +319,79 @@ class HistoryItem extends StatelessWidget { fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, color: Theme.of(context).colorScheme.outline), ), - if (videoItem is HisListItem) - SizedBox( - width: 29, - height: 29, - child: PopupMenuButton( - padding: EdgeInsets.zero, - tooltip: '功能菜单', - icon: Icon( - Icons.more_vert_outlined, - color: Theme.of(context).colorScheme.outline, - size: 18, - ), - position: PopupMenuPosition.under, - itemBuilder: (BuildContext context) => - >[ - if (videoItem.authorMid != null && - videoItem.authorName?.isNotEmpty == true) - PopupMenuItem( - 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( - 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)) - ], - ), - ), + // if (videoItem is HisListItem) + SizedBox( + width: 29, + height: 29, + child: PopupMenuButton( + padding: EdgeInsets.zero, + tooltip: '功能菜单', + icon: Icon( + Icons.more_vert_outlined, + color: Theme.of(context).colorScheme.outline, + size: 18, + ), + position: PopupMenuPosition.under, + itemBuilder: (BuildContext context) => + >[ + if (videoItem.authorMid != null && + videoItem.authorName?.isNotEmpty == true) PopupMenuItem( - onTap: () => ctr is HistoryBaseController - ? onDelete?.call( - videoItem.kid, videoItem.history.business) - : ctr!.delHistory( - videoItem.kid, videoItem.history.business), + onTap: () { + Get.toNamed( + '/member?mid=${videoItem.authorMid}', + arguments: { + 'heroTag': '${videoItem.authorMid}', + }, + ); + }, height: 35, - child: const Row( + child: Row( children: [ - Icon(Icons.close_outlined, size: 16), + Icon(MdiIcons.accountCircleOutline, size: 16), 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( + 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( + 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)) + ], + ), + ), + ], ), + ), ], ), ], diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 4d21927c..3cd1f738 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -5,7 +5,6 @@ import 'package:get/get.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/http/user.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/info.dart'; import 'package:PiliPlus/utils/storage.dart'; @@ -22,7 +21,6 @@ class MemberController extends GetxController { late int ownerMid; bool specialFollowed = false; // 投稿列表 - RxList? archiveList = [].obs; dynamic userInfo; RxInt attribute = (-1).obs; RxString attributeText = '关注'.obs; @@ -43,7 +41,12 @@ class MemberController extends GetxController { } // 获取用户信息 - Future> getInfo() async { + Future> getInfo() { + return Future.wait([getMemberInfo(), getMemberStat(), getMemberView()]) + .then((res) => res[0]); + } + + Future> getMemberInfo() async { wwebid = await Utils.getWwebid(mid); await getMemberStat(); await getMemberView(); diff --git a/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart b/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart index 16fff720..05d18470 100644 --- a/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart +++ b/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart @@ -136,7 +136,7 @@ class MemberVideoCtr extends CommonController { } for (Item element in list) { - if (element.firstCid == null) { + if (element.cid == null) { continue; } else { if (element.bvid != list.first.bvid) { @@ -150,7 +150,7 @@ class MemberVideoCtr extends CommonController { ? desc.not : desc; Utils.toViewPage( - 'bvid=${element.bvid}&cid=${element.firstCid}', + 'bvid=${element.bvid}&cid=${element.cid}', arguments: { 'videoItem': element, 'heroTag': Utils.makeHeroTag(element.bvid), diff --git a/lib/pages/member_archive/controller.dart b/lib/pages/member_archive/controller.dart index 16c0a47e..2e4ff5c5 100644 --- a/lib/pages/member_archive/controller.dart +++ b/lib/pages/member_archive/controller.dart @@ -10,7 +10,7 @@ class MemberArchiveController extends GetxController { int pn = 1; int count = 0; RxMap currentOrder = {}.obs; - List> orderList = [ + static const List> orderList = [ {'type': 'pubdate', 'label': '最新发布'}, {'type': 'click', 'label': '最多播放'}, {'type': 'stow', 'label': '最多收藏'}, @@ -37,8 +37,7 @@ class MemberArchiveController extends GetxController { if (res['status']) { if (type == 'init') { archivesList.value = res['data'].list.vlist; - } - if (type == 'onLoad') { + } else if (type == 'onLoad') { archivesList.addAll(res['data'].list.vlist); } count = res['data'].page['count']; @@ -49,7 +48,7 @@ class MemberArchiveController extends GetxController { return res; } - toggleSort() async { + toggleSort() { List typeList = orderList.map((e) => e['type']!).toList(); int index = typeList.indexOf(currentOrder['type']!); if (index == orderList.length - 1) { @@ -61,9 +60,7 @@ class MemberArchiveController extends GetxController { } // 上拉加载 - Future onLoad() async { - getMemberArchive('onLoad'); - } + Future onLoad() => getMemberArchive('onLoad'); @override void onClose() { diff --git a/lib/pages/member_archive/view.dart b/lib/pages/member_archive/view.dart index a1fcb881..f3f3f76a 100644 --- a/lib/pages/member_archive/view.dart +++ b/lib/pages/member_archive/view.dart @@ -86,7 +86,7 @@ class _MemberArchivePageState extends State { ); } Map data = snapshot.data as Map; - List list = _memberArchivesController.archivesList; + final list = _memberArchivesController.archivesList; if (data['status']) { return Obx( () => list.isNotEmpty diff --git a/lib/pages/member_coin/widgets/item.dart b/lib/pages/member_coin/widgets/item.dart index 03c6ccfa..5b3f19ee 100644 --- a/lib/pages/member_coin/widgets/item.dart +++ b/lib/pages/member_coin/widgets/item.dart @@ -1,9 +1,9 @@ import 'package:PiliPlus/common/widgets/image_save.dart'; +import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:flutter/material.dart'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.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/models/member/coin.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -53,7 +53,7 @@ class MemberCoinsItem extends StatelessWidget { width: maxWidth, height: maxHeight, ), - if (coinItem.duration != null) + if (coinItem.duration > 0) PBadge( bottom: 6, right: 6, @@ -80,9 +80,15 @@ class MemberCoinsItem extends StatelessWidget { children: [ StatView( context: context, - value: coinItem.view!, + value: coinItem.stat.viewStr, theme: 'gray', ), + const SizedBox(width: 8), + StatDanMu( + context: context, + theme: 'gray', + value: coinItem.stat.danmuStr, + ), const Spacer(), Text( Utils.customStampStr( diff --git a/lib/pages/member_seasons/widgets/item.dart b/lib/pages/member_seasons/widgets/item.dart index 1271424d..21cba7cc 100644 --- a/lib/pages/member_seasons/widgets/item.dart +++ b/lib/pages/member_seasons/widgets/item.dart @@ -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:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.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/utils/utils.dart'; class MemberSeasonsItem extends StatelessWidget { - final dynamic seasonItem; + final MemberArchiveItem seasonItem; const MemberSeasonsItem({ super.key, @@ -65,7 +66,7 @@ class MemberSeasonsItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - seasonItem.title, + seasonItem.title!, maxLines: 2, overflow: TextOverflow.ellipsis, ), @@ -74,7 +75,7 @@ class MemberSeasonsItem extends StatelessWidget { children: [ StatView( context: context, - value: seasonItem.view, + value: Utils.numFormat(seasonItem.view!), theme: 'gray', ), const Spacer(), diff --git a/lib/pages/search_panel/view.dart b/lib/pages/search_panel/view.dart index 7b06f220..b4419fd0 100644 --- a/lib/pages/search_panel/view.dart +++ b/lib/pages/search_panel/view.dart @@ -132,8 +132,6 @@ class _SearchPanelState extends State _searchPanelController, loadingState, ); - default: - return const SizedBox.shrink(); } } } diff --git a/lib/pages/subscription_detail/controller.dart b/lib/pages/subscription_detail/controller.dart index 00bc2e17..610678bb 100644 --- a/lib/pages/subscription_detail/controller.dart +++ b/lib/pages/subscription_detail/controller.dart @@ -50,15 +50,16 @@ class SubDetailController extends GetxController { ); } if (res['status']) { - subInfo.value = res['data'].info; + SubDetailModelData data = res['data']; + subInfo.value = data.info!; if (currentPage == 1 && type == 'init') { - subList.value = res['data'].medias; - mediaCount = res['data'].info.mediaCount; + subList.value = data.list!; + mediaCount = data.info!.mediaCount!; if (item.type == 11) { - playCount.value = res['data'].info.cntInfo!['play']; + playCount.value = data.info!.cntInfo!['play']; } } else if (type == 'onLoad') { - subList.addAll(res['data'].medias); + subList.addAll(data.list!); } if (subList.length >= mediaCount) { loadingText.value = '没有更多了'; diff --git a/lib/pages/subscription_detail/widget/sub_video_card.dart b/lib/pages/subscription_detail/widget/sub_video_card.dart index dde67b6c..8df1491e 100644 --- a/lib/pages/subscription_detail/widget/sub_video_card.dart +++ b/lib/pages/subscription_detail/widget/sub_video_card.dart @@ -129,13 +129,13 @@ class SubVideoCardH extends StatelessWidget { StatView( context: context, theme: 'gray', - value: videoItem.cntInfo?['play'], + value: Utils.numFormat(videoItem.cntInfo?['play']), ), const SizedBox(width: 8), StatDanMu( context: context, theme: 'gray', - value: videoItem.cntInfo?['danmaku'], + value: Utils.numFormat(videoItem.cntInfo?['danmaku']), ), const Spacer(), ], diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 112a2bec..6ee70199 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -616,9 +616,9 @@ class _VideoInfoState extends State with TickerProviderStateMixin { StatView( context: context, theme: 'gray', - value: !widget.loadingStatus + value: Utils.numFormat(!widget.loadingStatus ? videoDetail.stat?.view ?? '-' - : videoItem['stat']?.view ?? '-', + : videoItem['stat']?.view ?? '-'), size: 'medium', textColor: t.colorScheme.outline, ), @@ -626,9 +626,9 @@ class _VideoInfoState extends State with TickerProviderStateMixin { StatDanMu( context: context, theme: 'gray', - value: !widget.loadingStatus + value: Utils.numFormat(!widget.loadingStatus ? videoDetail.stat?.danmu ?? '-' - : videoItem['stat']?.danmu ?? '-', + : videoItem['stat']?.danmu ?? '-'), size: 'medium', textColor: t.colorScheme.outline, ), diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index 37ca2991..3cefda5f 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -57,14 +57,14 @@ class IntroDetail extends StatelessWidget { StatView( context: context, theme: 'gray', - value: videoDetail!.stat!.view, + value: Utils.numFormat(videoDetail!.stat!.view), size: 'medium', ), const SizedBox(width: 10), StatDanMu( context: context, theme: 'gray', - value: videoDetail!.stat!.danmu, + value: Utils.numFormat(videoDetail!.stat!.danmu), size: 'medium', ), const SizedBox(width: 10), diff --git a/lib/pages/video/detail/member/horizontal_member_page.dart b/lib/pages/video/detail/member/horizontal_member_page.dart index 90078383..95fb2881 100644 --- a/lib/pages/video/detail/member/horizontal_member_page.dart +++ b/lib/pages/video/detail/member/horizontal_member_page.dart @@ -187,7 +187,7 @@ class _HorizontalMemberPageState extends State { widget.videoIntroController.changeSeasonOrbangu( null, videoItem.bvid, - videoItem.firstCid, + videoItem.cid, IdUtils.bv2av(videoItem.bvid!), videoItem.cover, ); diff --git a/lib/pages/video/detail/widgets/ai_detail.dart b/lib/pages/video/detail/widgets/ai_detail.dart index db1ddae3..663bb552 100644 --- a/lib/pages/video/detail/widgets/ai_detail.dart +++ b/lib/pages/video/detail/widgets/ai_detail.dart @@ -184,7 +184,7 @@ class _AiDetailState extends CommonCollapseSlidePageState { children: [ TextSpan( text: - Utils.tampToSeektime(item.timestamp!), + Utils.formatDuration(item.timestamp!), style: TextStyle( color: Theme.of(context) .colorScheme @@ -198,15 +198,8 @@ class _AiDetailState extends CommonCollapseSlidePageState { tag: Get .arguments['heroTag']) .plPlayerController - .seekTo( - Duration( - seconds: Utils.duration( - Utils.tampToSeektime( - item.timestamp!) - .toString(), - ), - ), - ); + .seekTo(Duration( + seconds: item.timestamp!)); } catch (_) {} }, ), diff --git a/lib/pages/video/detail/widgets/media_list_panel.dart b/lib/pages/video/detail/widgets/media_list_panel.dart index ffecc33f..b273bc0d 100644 --- a/lib/pages/video/detail/widgets/media_list_panel.dart +++ b/lib/pages/video/detail/widgets/media_list_panel.dart @@ -234,14 +234,15 @@ class _MediaListPanelState extends CommonSlidePageState { StatView( context: context, theme: 'gray', - value: item.cntInfo!['play'] as int, + value: Utils.numFormat( + item.cntInfo!['play']!), ), const SizedBox(width: 8), StatDanMu( context: context, theme: 'gray', - value: - item.cntInfo!['danmaku'] as int, + value: Utils.numFormat( + item.cntInfo!['danmaku']!), ), ], ), diff --git a/lib/utils/em.dart b/lib/utils/em.dart index 3c617233..37da068e 100644 --- a/lib/utils/em.dart +++ b/lib/utils/em.dart @@ -1,31 +1,27 @@ import 'package:html/parser.dart' show parse; class Em { - static regCate(String origin) { - String str = origin; - RegExp exp = RegExp('<[^>]*>([^<]*)]*>'); - Iterable matches = exp.allMatches(origin); - for (Match match in matches) { - str = match.group(1)!; - } - return str; + static final _exp = RegExp('<[^>]*>([^<]*)]*>'); + + static String regCate(String origin) { + Iterable matches = _exp.allMatches(origin); + return matches.lastOrNull?.group(1) ?? origin; } - static regTitle(String origin) { - RegExp exp = RegExp('<[^>]*>([^<]*)]*>'); - List res = []; + static List> regTitle(String origin) { + List> res = []; origin.splitMapJoin( - exp, + _exp, onMatch: (Match match) { String matchStr = match[0]!; - Map map = {'type': 'em', 'text': regCate(matchStr)}; + var map = {'type': 'em', 'text': regCate(matchStr)}; res.add(map); - return regCate(matchStr); + return matchStr; }, onNonMatch: (String str) { if (str != '') { str = parse(str).body?.text ?? str; - Map map = {'type': 'text', 'text': str}; + var map = {'type': 'text', 'text': str}; res.add(map); } return str; diff --git a/lib/utils/recommend_filter.dart b/lib/utils/recommend_filter.dart index 109f632a..9c815308 100644 --- a/lib/utils/recommend_filter.dart +++ b/lib/utils/recommend_filter.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/models/model_video.dart'; import 'package:hive/hive.dart'; import 'storage.dart'; @@ -30,46 +31,30 @@ class RecommendFilter { .get(SettingBoxKey.applyFilterToRelatedVideos, defaultValue: true); } - static bool filter(dynamic videoItem, {bool relatedVideos = false}) { - if (relatedVideos && !applyFilterToRelatedVideos) { - return false; - } + static bool filter(BaseVideoItemModel videoItem) { //由于相关视频中没有已关注标签,只能视为非关注视频 - if (!relatedVideos && - videoItem.isFollowed == 1 && - exemptFilterForFollowed) { + if (videoItem.isFollowed && exemptFilterForFollowed) { return false; } - if (videoItem.duration > 0 && videoItem.duration < minDurationForRcmd) { - return true; - } - if (filterLikeRatio(videoItem.stat.like, videoItem.stat.view)) { - return true; - } - if (filterTitle(videoItem.title)) { - return true; - } - return false; + return filterAll(videoItem); } - static bool filterLikeRatio(like, view) { - if (view is int && + static bool filterLikeRatio(int? like, int? view) { + return (view != null && view > -1 && - like is int && + like != null && like > -1 && - like * 100 < minLikeRatioForRecommend * view) { - return true; - } - return false; + like * 100 < minLikeRatioForRecommend * view); } - static bool filterTitle(String title, {bool? isFollowed}) { - if (exemptFilterForFollowed && isFollowed == true) { - return false; - } - if (rcmdRegExp.pattern.isNotEmpty && rcmdRegExp.hasMatch(title)) { - return true; - } - return false; + static bool filterTitle(String title) { + return (rcmdRegExp.pattern.isNotEmpty && rcmdRegExp.hasMatch(title)); + } + + static bool filterAll(BaseVideoItemModel videoItem) { + return (videoItem.duration > 0 && + videoItem.duration < minDurationForRcmd) || + filterLikeRatio(videoItem.stat.like, videoItem.stat.view) || + filterTitle(videoItem.title); } } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 2e57da8b..30600e00 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -47,11 +47,17 @@ import 'package:html/dom.dart' as dom; import 'package:html/parser.dart' as html_parser; 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 { static final Random random = Random(); static const channel = MethodChannel("PiliPlus"); + static final _numRegExp = RegExp(r'([\d\.]+)([千万亿])?'); + static ThemeData getThemeData({ required ColorScheme colorScheme, required bool isDynamic, @@ -1149,6 +1155,32 @@ class Utils { // 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) { if (number == null) { return '00:00'; @@ -1197,63 +1229,49 @@ class Utils { return '${int.parse(durationParts[0])}秒'; } - static String videoItemSemantics(dynamic videoItem) { - String semanticsLabel = ""; - bool emptyStatCheck(dynamic stat) { - return stat == null || - stat == '' || - stat == 0 || - stat == '0' || - stat == '-'; + static String videoItemSemantics(BaseVideoItemModel videoItem) { + StringBuffer semanticsLabel = StringBuffer(); + bool emptyStatCheck(int? stat) { + return stat == null || stat <= 0; } - if (videoItem.runtimeType.toString() == "RecVideoItemAppModel") { + if (videoItem is RecVideoItemAppModel) { if (videoItem.goto == 'picture') { - semanticsLabel += '动态,'; + semanticsLabel.write('动态,'); } else if (videoItem.goto == 'bangumi') { - semanticsLabel += '番剧,'; + semanticsLabel.write('番剧,'); } } - if (videoItem.title is String) { - semanticsLabel += videoItem.title; - } else { - semanticsLabel += - videoItem.title.map((e) => e['text'] as String).join(''); - } + semanticsLabel.write(videoItem.title); if (!emptyStatCheck(videoItem.stat.view)) { - semanticsLabel += ',${Utils.numFormat(videoItem.stat.view)}'; - semanticsLabel += - (videoItem.runtimeType.toString() == "RecVideoItemAppModel" && - videoItem.goto == 'picture') + semanticsLabel.write(',${Utils.numFormat(videoItem.stat.view)}'); + semanticsLabel.write( + (videoItem is RecVideoItemAppModel && videoItem.goto == 'picture') ? '浏览' - : '播放'; + : '播放'); } if (!emptyStatCheck(videoItem.stat.danmu)) { - semanticsLabel += ',${Utils.numFormat(videoItem.stat.danmu)}弹幕'; + semanticsLabel.write(',${Utils.numFormat(videoItem.stat.danmu)}弹幕'); } - if (videoItem.rcmdReason != null) { - semanticsLabel += ',${videoItem.rcmdReason}'; + if ((videoItem is BaseRecVideoItemModel) && videoItem.rcmdReason != null) { + semanticsLabel.write(',${videoItem.rcmdReason}'); } - if (!emptyStatCheck(videoItem.duration) && - (videoItem.duration is! int || videoItem.duration > 0)) { - semanticsLabel += - ',时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}'; + if (!emptyStatCheck(videoItem.duration) && videoItem.duration > 0) { + semanticsLabel.write( + ',时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}'); } - if (videoItem.runtimeType.toString() != "RecVideoItemAppModel" && - videoItem.pubdate != null) { - semanticsLabel += - ',${Utils.dateFormat(videoItem.pubdate!, formatType: 'day')}'; + if (videoItem.pubdate != null) { + semanticsLabel + .write(',${Utils.dateFormat(videoItem.pubdate!, formatType: 'day')}'); } if (videoItem.owner.name != '') { - semanticsLabel += ',Up主:${videoItem.owner.name}'; + semanticsLabel.write(',Up主:${videoItem.owner.name}'); } - if ((videoItem.runtimeType.toString() == "RecVideoItemAppModel" || - videoItem.runtimeType.toString() == "RecVideoItemModel") && - videoItem.isFollowed == 1) { - semanticsLabel += ',已关注'; + if (videoItem is BaseRecVideoItemModel && videoItem.isFollowed) { + semanticsLabel.write(',已关注'); } - return semanticsLabel; + return semanticsLabel.toString(); } static String timeFormat(dynamic time) { @@ -1263,14 +1281,7 @@ class Utils { if (time == null || time == 0) { return '00:00'; } - int hour = time ~/ 3600; - 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)}'; + return formatDuration(time); } 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) { double height = context.height.abs(); double width = context.width.abs();