refa: video model (#523)

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

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/widgets/no_splash_factory.dart';
import 'package:PiliPlus/common/widgets/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

View File

@@ -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,
),
)
],
);
}

View File

@@ -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),

View File

@@ -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,
),
],
),

View File

@@ -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) ...<Widget>[
const Spacer(),
@@ -294,7 +294,7 @@ class VideoCardV extends StatelessWidget {
],
if (videoItem is RecVideoItemAppModel &&
videoItem.desc != null &&
videoItem.desc.contains(' · ')) ...<Widget>[
videoItem.desc!.contains(' · ')) ...<Widget>[
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),
]

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
@@ -21,16 +22,16 @@ class VideoCustomAction {
}
class VideoCustomActions {
dynamic videoItem;
BaseSimpleVideoItemModel videoItem;
BuildContext context;
late List<VideoCustomAction> 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'] ?? '成功');
},

View File

@@ -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'<meta name="spm_prefix" content="([^"]+?)">');
/// 设置cookie
static setCookie() async {
static Future<void> 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<int>.generate(32, (_) => _rand.nextInt(256)) +
List<int>.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);

View File

@@ -148,8 +148,7 @@ class SearchHttp {
} else if (bvid != null) {
data['bvid'] = bvid;
}
final dynamic res = await Request()
.get(Api.ab2c, queryParameters: <String, dynamic>{...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: <String, dynamic>{...data});
final dynamic res =
await Request().get(Api.bangumiInfo, queryParameters: data);
if (res.data['code'] == 0) {
return {

View File

@@ -349,13 +349,11 @@ class VideoHttp {
var res =
await Request().get(Api.relatedList, queryParameters: {'bvid': bvid});
if (res.data['code'] == 0) {
List<HotVideoItemModel> 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 {

View File

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

View File

@@ -1,60 +1,20 @@
import 'package:PiliPlus/models/model_rec_video_item.dart';
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<DislikeReason>? dislikeReasons;
List<FeedbackReason>? feedbacks;
List<Reason>? dislikeReasons;
List<Reason>? feedbacks;
int? watchLater;
ThreePoint.fromJson(Map<String, dynamic> 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<String, dynamic> json) {
id = json['id'];
name = json['name'];
toast = json['toast'];
}
}
class FeedbackReason {
FeedbackReason({
this.id,
this.name,
this.toast,
});
int? id;
String? name;
String? toast;
FeedbackReason.fromJson(Map<String, dynamic> json) {
Reason.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
toast = json['toast'];

View File

@@ -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<String, dynamic> 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<String, dynamic> json) {
mid = json["mid"];
name = json["author"];
}
}
class Stat {
Stat({
this.view,
this.danmu,
});
int? view;
int? danmu;
Stat.fromJson(Map<String, dynamic> json) {
class VListStat extends BaseStat {
VListStat.fromJson(Map<String, dynamic> 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<String, dynamic> json) {
mid = json["mid"];
name = json["author"];
face = '';
}
}

View File

@@ -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<String, dynamic> json) {
aid = json['aid'];
bvid = json['bvid'];
cid = json['cid'];
MemberCoinsDataModel.fromJson(Map<String, dynamic> 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<String, dynamic> 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;
}

View File

@@ -1,66 +1,23 @@
import './model_owner.dart';
import 'model_owner.dart';
import 'model_rec_video_item.dart';
import 'model_video.dart';
class HotVideoItemModel {
HotVideoItemModel({
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<String, dynamic> 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<String, dynamic> json) {
aid = json["aid"];
view = json["view"];
danmu = json['danmaku'];
HotStat.fromJson(Map<String, dynamic> 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<String, dynamic> json) {
width = json["width"];
height = json["height"];
rotate = json["rotate"];
}
}
class RcmdReason {
RcmdReason({
this.rcornerMark,
this.content,
});
int? rcornerMark;
String? content = '';
RcmdReason.fromJson(Map<String, dynamic> json) {
rcornerMark = json["corner_mark"];
content = json["content"] ?? '';
}
}
// RcmdReason.fromJson(Map<String, dynamic> json) {
// rcornerMark = json["corner_mark"];
// content = json["content"] ?? '';
// }
// }

View File

@@ -1,17 +1,21 @@
import 'package:hive/hive.dart';
import '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;

View File

@@ -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<String, dynamic> 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<String, dynamic> 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<String, dynamic> json) {
// reasonType = json["reason_type"];

View File

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

View File

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

View File

@@ -1,12 +1,10 @@
import 'package:PiliPlus/utils/em.dart';
import 'package:PiliPlus/utils/utils.dart';
class SearchVideoModel {
SearchVideoModel({
this.numResults,
this.list,
});
import '../model_owner.dart';
import '../model_video.dart';
class SearchVideoModel {
int? numResults;
List<SearchVideoItemModel>? 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<Map<String, String>>? titleList;
SearchVideoItemModel.fromJson(Map<String, dynamic> 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<String, dynamic> json) {
SearchStat.fromJson(Map<String, dynamic> 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<String, dynamic> json) {
class SearchOwner extends Owner {
SearchOwner.fromJson(Map<String, dynamic> 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']);
}
}

View File

@@ -1,107 +1,90 @@
import 'package:json_annotation/json_annotation.dart';
import '../model_owner.dart';
import '../model_video.dart';
import 'badge.dart';
import '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>? 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<Badge>? 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<String, dynamic> json) => _$ItemFromJson(json);
Map<String, dynamic> toJson() => _$ItemToJson(this);
Item.fromJson(Map<String, dynamic> 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<dynamic>?)
?.map((e) => ThreePoint.fromJson(e as Map<String, dynamic>))
.toList();
cid = json['first_cid'];
cursorAttr = json['cursor_attr'] == null
? null
: CursorAttr.fromJson(json['cursor_attr'] as Map<String, dynamic>);
iconType = json['icon_type'];
publishTimeText = json['publish_time_text'];
badges = (json['badges'] as List<dynamic>?)
?.map((e) => Badge.fromJson(e as Map<String, dynamic>))
.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<String, dynamic> json) : super.fromJson(json) {
_viewStr = json['view_content'];
}
}

View File

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

View File

@@ -1,83 +1,45 @@
import 'package:PiliPlus/models/model_owner.dart';
import '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<FavDetailItemData>? medias;
List<FavDetailItemData>? list;
List<FavDetailItemData>? get medias => list;
bool? hasMore;
FavDetailData.fromJson(Map<String, dynamic> json) {
info =
json['info'] == null ? null : FavFolderItemData.fromJson(json['info']);
medias = (json['medias'] as List?)
list = (json['medias'] as List?)
?.map<FavDetailItemData>((e) => FavDetailItemData.fromJson(e))
.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<String, dynamic> 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<Match> matches = regex.allMatches(url);
List<String> 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<String, dynamic> 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;
}

View File

@@ -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<String, dynamic> json) {
title = json['title'];

View File

@@ -1,17 +1,16 @@
class SubDetailModelData {
DetailInfo? info;
List<SubDetailMediaItem>? medias;
List<SubDetailMediaItem>? list;
SubDetailModelData({this.info, this.medias});
List<SubDetailMediaItem>? get medias => list; // 不知道哪里使用了这个
SubDetailModelData({this.info, this.list});
SubDetailModelData.fromJson(Map<String, dynamic> json) {
info = DetailInfo.fromJson(json['info']);
if (json['medias'] != null) {
medias = <SubDetailMediaItem>[];
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<String, dynamic>? upper;
Map<String, dynamic>? cntInfo;
int? enableVt;
String? vtDisplay;

View File

@@ -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<String, dynamic>? 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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"];
}
}

View File

@@ -324,19 +324,19 @@ class _BangumiInfoState extends State<BangumiInfo>
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) ...[

View File

@@ -1,18 +1,19 @@
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/bangumi/info.dart';
import 'package:PiliPlus/pages/common/common_collapse_slide_page.dart';
import 'package:PiliPlus/pages/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<IntroDetail> {
),
children: [
SelectableText(
widget.bangumiDetail!.title,
widget.bangumiDetail.title!,
style: const TextStyle(
fontSize: 16,
),
@@ -81,14 +82,14 @@ class _IntroDetailState extends CommonCollapseSlidePageState<IntroDetail> {
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<IntroDetail> {
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<IntroDetail> {
),
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<IntroDetail> {
),
const SizedBox(height: 4),
SelectableText(
widget.bangumiDetail.actors,
widget.bangumiDetail.actors!,
style: smallTitle.copyWith(fontSize: 14),
),
if (widget.videoTags is List && widget.videoTags.isNotEmpty) ...[

View File

@@ -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,

View File

@@ -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 ??= <FavDetailItemData>[];
data.medias!.insertAll(
data.list ??= <FavDetailItemData>[];
data.list!.insertAll(
0,
List<FavDetailItemData>.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<FavDetailItemData> list = List<FavDetailItemData>.from(
(loadingState.value as Success).response);
List<FavDetailItemData> list = (loadingState.value as Success).response;
for (FavDetailItemData element in list) {
if (element.cid == null) {
continue;

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/dialog.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/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<FavDetailPage> {
),
);
}
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(

View File

@@ -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,21 +116,20 @@ 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(),
],

View File

@@ -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
List? dataList = currentPage == 1
? response.response.list
: response.response.list != null
? currentList + response.response.list
: currentList);
: currentList;
isEnd = searchType == SearchType.fav
? response.response.hasMore == false
: response.response.list == null || response.response.list.isEmpty;

View File

@@ -65,8 +65,8 @@ class _FavSearchPageState extends State<FavSearchPage> {
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,9 +120,8 @@ class _FavSearchPageState extends State<FavSearchPage> {
),
),
],
)
: _favSearchCtr.searchType == SearchType.follow
? ListView.builder(
),
SearchType.follow => ListView.builder(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
@@ -136,8 +135,8 @@ class _FavSearchPageState extends State<FavSearchPage> {
item: loadingState.response[index],
);
}),
)
: CustomScrollView(
),
SearchType.history => CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _favSearchCtr.scrollController,
slivers: [
@@ -167,7 +166,8 @@ class _FavSearchPageState extends State<FavSearchPage> {
),
),
],
)
),
}
: errorWidget(
callback: _favSearchCtr.onReload,
),

View File

@@ -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) {

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/models/user/history.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/pages/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,7 +319,7 @@ class HistoryItem extends StatelessWidget {
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline),
),
if (videoItem is HisListItem)
// if (videoItem is HisListItem)
SizedBox(
width: 29,
height: 29,
@@ -356,11 +357,11 @@ class HistoryItem extends StatelessWidget {
],
),
),
if (videoItem.history?.business != 'pgc' &&
if (videoItem.history.business != 'pgc' &&
videoItem.badge != '番剧' &&
!videoItem.tagName.contains('动画') &&
videoItem.tagName?.contains('动画') != true &&
videoItem.history.business != 'live' &&
!videoItem.history.business.contains('article'))
videoItem.history.business?.contains('article') != true)
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
@@ -377,10 +378,7 @@ class HistoryItem extends StatelessWidget {
),
),
PopupMenuItem<String>(
onTap: () => ctr is HistoryBaseController
? onDelete?.call(
videoItem.kid, videoItem.history.business)
: ctr!.delHistory(
onTap: () => ctr!.delHistory(
videoItem.kid, videoItem.history.business),
height: 35,
child: const Row(

View File

@@ -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<VListItemModel>? archiveList = <VListItemModel>[].obs;
dynamic userInfo;
RxInt attribute = (-1).obs;
RxString attributeText = '关注'.obs;
@@ -43,7 +41,12 @@ class MemberController extends GetxController {
}
// 获取用户信息
Future<Map<String, dynamic>> getInfo() async {
Future<Map<String, dynamic>> getInfo() {
return Future.wait([getMemberInfo(), getMemberStat(), getMemberView()])
.then((res) => res[0]);
}
Future<Map<String, dynamic>> getMemberInfo() async {
wwebid = await Utils.getWwebid(mid);
await getMemberStat();
await getMemberView();

View File

@@ -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),

View File

@@ -10,7 +10,7 @@ class MemberArchiveController extends GetxController {
int pn = 1;
int count = 0;
RxMap<String, String> currentOrder = <String, String>{}.obs;
List<Map<String, String>> orderList = [
static const List<Map<String, String>> 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<String> 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() {

View File

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

View File

@@ -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(

View File

@@ -1,13 +1,14 @@
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/member/seasons.dart';
import 'package:flutter/material.dart';
import 'package: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(),

View File

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

View File

@@ -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 = '没有更多了';

View File

@@ -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(),
],

View File

@@ -616,9 +616,9 @@ class _VideoInfoState extends State<VideoInfo> 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<VideoInfo> 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,
),

View File

@@ -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),

View File

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

View File

@@ -184,7 +184,7 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiDetail> {
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<AiDetail> {
tag: Get
.arguments['heroTag'])
.plPlayerController
.seekTo(
Duration(
seconds: Utils.duration(
Utils.tampToSeektime(
item.timestamp!)
.toString(),
),
),
);
.seekTo(Duration(
seconds: item.timestamp!));
} catch (_) {}
},
),

View File

@@ -234,14 +234,15 @@ class _MediaListPanelState extends CommonSlidePageState<MediaListPanel> {
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']!),
),
],
),

View File

@@ -1,31 +1,27 @@
import 'package:html/parser.dart' show parse;
class Em {
static regCate(String origin) {
String str = origin;
RegExp exp = RegExp('<[^>]*>([^<]*)</[^>]*>');
Iterable<Match> 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<Match> matches = _exp.allMatches(origin);
return matches.lastOrNull?.group(1) ?? origin;
}
static regTitle(String origin) {
RegExp exp = RegExp('<[^>]*>([^<]*)</[^>]*>');
List res = [];
static List<Map<String, String>> regTitle(String origin) {
List<Map<String, String>> 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;

View File

@@ -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;
static bool filterTitle(String title) {
return (rcmdRegExp.pattern.isNotEmpty && rcmdRegExp.hasMatch(title));
}
if (rcmdRegExp.pattern.isNotEmpty && rcmdRegExp.hasMatch(title)) {
return true;
}
return false;
static bool filterAll(BaseVideoItemModel videoItem) {
return (videoItem.duration > 0 &&
videoItem.duration < minDurationForRcmd) ||
filterLikeRatio(videoItem.stat.like, videoItem.stat.view) ||
filterTitle(videoItem.title);
}
}

View File

@@ -47,11 +47,17 @@ import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as html_parser;
import 'package: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();