Compare commits
268 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc8907b3ef | ||
|
|
14f8ec37c5 | ||
|
|
2b567e7cb3 | ||
|
|
b58a3ec044 | ||
|
|
2d0d578bb4 | ||
|
|
54ba05c4aa | ||
|
|
27b251b06e | ||
|
|
5643ebfe48 | ||
|
|
d9c2f6bf91 | ||
|
|
3eb404a9e2 | ||
|
|
bc9c20c509 | ||
|
|
7cc0c83df1 | ||
|
|
41daefa6c4 | ||
|
|
38fa8a10b7 | ||
|
|
07d37a1209 | ||
|
|
509f0d1266 | ||
|
|
7966bab62d | ||
|
|
a136c150ad | ||
|
|
a89fe6b026 | ||
|
|
56460c937d | ||
|
|
f2080bfb7b | ||
|
|
012d55452e | ||
|
|
6ac482ed5e | ||
|
|
68df173558 | ||
|
|
d9c6c31a4d | ||
|
|
d3d2715418 | ||
|
|
a93fbd4444 | ||
|
|
9fee9a4cf1 | ||
|
|
4bbc008788 | ||
|
|
671b6e1ef7 | ||
|
|
634bae915a | ||
|
|
a7bbfc983e | ||
|
|
17548e935e | ||
|
|
15f84712cd | ||
|
|
2f34ae7d45 | ||
|
|
16cbe7e43c | ||
|
|
8d633377ae | ||
|
|
0b867c254f | ||
|
|
08a47e6c1d | ||
|
|
6c9cd8b120 | ||
|
|
71e7219084 | ||
|
|
c13063b230 | ||
|
|
26ca69cb83 | ||
|
|
afc8c5f873 | ||
|
|
4d3f739a0c | ||
|
|
1781fdb7ca | ||
|
|
32aa37505c | ||
|
|
9f9ed7dd4b | ||
|
|
03e3b897cf | ||
|
|
3bc20ce1d4 | ||
|
|
9ce9940306 | ||
|
|
da35cf471e | ||
|
|
c517df2c09 | ||
|
|
02dee71670 | ||
|
|
1eadcd41f6 | ||
|
|
e8185535b0 | ||
|
|
b68bebfa2e | ||
|
|
3801bdf9d7 | ||
|
|
9a6ba82467 | ||
|
|
3a52c1199c | ||
|
|
ea5c0584cc | ||
|
|
01b30d942b | ||
|
|
5aa5308a50 | ||
|
|
de029b7043 | ||
|
|
a45da453ce | ||
|
|
e1b73f4766 | ||
|
|
99b19e7b03 | ||
|
|
37bd849a86 | ||
|
|
4eb6f78a38 | ||
|
|
68f03f2311 | ||
|
|
2a60a9b393 | ||
|
|
1d4b08672b | ||
|
|
b0d9a1dada | ||
|
|
796494e53f | ||
|
|
cef7bfd534 | ||
|
|
36ff4a0ed3 | ||
|
|
6a6894030b | ||
|
|
497d31ddf7 | ||
|
|
783218429c | ||
|
|
0ccd15047b | ||
|
|
fe2a6ec006 | ||
|
|
a3ecf59fae | ||
|
|
4f4f89a1d7 | ||
|
|
ece3bdd2e8 | ||
|
|
f403ed1a21 | ||
|
|
17e3a0206a | ||
|
|
5da86d85de | ||
|
|
d3cbc95235 | ||
|
|
a7eebcc209 | ||
|
|
fca22eb592 | ||
|
|
1202e5ec0f | ||
|
|
03830533eb | ||
|
|
850e5a199e | ||
|
|
2d11158ecd | ||
|
|
a34c18b262 | ||
|
|
560b1e40cc | ||
|
|
3cd512857c | ||
|
|
356adbef5c | ||
|
|
42d7445d83 | ||
|
|
3a0f32fce7 | ||
|
|
6bc128cfda | ||
|
|
6f2d697748 | ||
|
|
4de180c23a | ||
|
|
af289c533f | ||
|
|
82d615fbbf | ||
|
|
457f2ea6c7 | ||
|
|
41ad5c45ed | ||
|
|
e9da2e8d6b | ||
|
|
a8cfbb12fd | ||
|
|
6d89b7769e | ||
|
|
2d86daec83 | ||
|
|
a5e8594611 | ||
|
|
99810ef512 | ||
|
|
2317b831db | ||
|
|
e073086cf4 | ||
|
|
b14844f459 | ||
|
|
8719c8f639 | ||
|
|
d3cec0ec72 | ||
|
|
a8daf02610 | ||
|
|
f9b844fb1a | ||
|
|
6d1d6b575a | ||
|
|
0a5a094e54 | ||
|
|
754da4777a | ||
|
|
216e3e606e | ||
|
|
bb013a8fe6 | ||
|
|
6b6449f023 | ||
|
|
fcf3348371 | ||
|
|
f90f759667 | ||
|
|
b02e6c04b9 | ||
|
|
08dc04f874 | ||
|
|
4776b84c7c | ||
|
|
78d13b586a | ||
|
|
f522ecd42d | ||
|
|
44fa2a8c3e | ||
|
|
ff30c8c2bf | ||
|
|
4aaaffbcea | ||
|
|
21da122902 | ||
|
|
849904ad45 | ||
|
|
1c0bae600f | ||
|
|
f1433c6e9b | ||
|
|
2dc106adcb | ||
|
|
df6738f607 | ||
|
|
ee64f1e7f1 | ||
|
|
d921f6176b | ||
|
|
7009c3400a | ||
|
|
7bd481b090 | ||
|
|
7fafa88eb7 | ||
|
|
cb3e57feec | ||
|
|
9a7d73cb6b | ||
|
|
f5c2bd47d5 | ||
|
|
c154d25f7a | ||
|
|
8c259205f5 | ||
|
|
849329b66b | ||
|
|
f542565dc5 | ||
|
|
08aedbf0b0 | ||
|
|
09c8a41c52 | ||
|
|
6a7d14a3f8 | ||
|
|
5b171ec044 | ||
|
|
978d634cb3 | ||
|
|
7437d8c592 | ||
|
|
e190ca5868 | ||
|
|
64fc995f6b | ||
|
|
2d0e801a1a | ||
|
|
d409424871 | ||
|
|
b855ef9865 | ||
|
|
86abf006d0 | ||
|
|
d1a6798f2e | ||
|
|
f64d543ec7 | ||
|
|
2abf01362c | ||
|
|
9bbd934f8e | ||
|
|
6ab72b65aa | ||
|
|
c39de1e245 | ||
|
|
d112843a8a | ||
|
|
89df091542 | ||
|
|
d870c36a96 | ||
|
|
fc55bf33d0 | ||
|
|
f99740ef2d | ||
|
|
f9f30a5f13 | ||
|
|
f70cf05870 | ||
|
|
22866012ca | ||
|
|
cdb2718aeb | ||
|
|
e4f3203351 | ||
|
|
d6b388ad5c | ||
|
|
61819d9f27 | ||
|
|
3ccc7ef69d | ||
|
|
e834311664 | ||
|
|
fd8dff327c | ||
|
|
a9df8cd883 | ||
|
|
909394965e | ||
|
|
2d5991e0c5 | ||
|
|
e7ae66a3dc | ||
|
|
5929150047 | ||
|
|
acb6bc569e | ||
|
|
678db34c81 | ||
|
|
134bfd43ff | ||
|
|
8712248ef2 | ||
|
|
c97227e807 | ||
|
|
40429021be | ||
|
|
e89bd2fedf | ||
|
|
9da3a538fb | ||
|
|
a904414f3d | ||
|
|
f003e8bf35 | ||
|
|
7399915357 | ||
|
|
7aa0289c1f | ||
|
|
99d0b1c468 | ||
|
|
84a342a0e0 | ||
|
|
db1c836a3e | ||
|
|
6539457f83 | ||
|
|
82f9f48a8e | ||
|
|
2ddfea5cf3 | ||
|
|
79aee2fdd9 | ||
|
|
5dc8b8e54f | ||
|
|
72fa9c51f0 | ||
|
|
6ea8ffea7a | ||
|
|
eea5257da2 | ||
|
|
385cffefb8 | ||
|
|
347420c531 | ||
|
|
cc774015f9 | ||
|
|
da3f64feab | ||
|
|
76d031e8d1 | ||
|
|
25995b0ed6 | ||
|
|
3cdd40a710 | ||
|
|
f36f8d69fc | ||
|
|
5655e6ccdf | ||
|
|
49fff821b1 | ||
|
|
3b34cecdcd | ||
|
|
3693d6c350 | ||
|
|
54cb1a6fc0 | ||
|
|
ab7b1524b6 | ||
|
|
2b4a27076c | ||
|
|
63a7fa95f5 | ||
|
|
a15b932a69 | ||
|
|
3a6b6614a4 | ||
|
|
6ff83e34f3 | ||
|
|
e4cadc5a40 | ||
|
|
907d37dd4c | ||
|
|
00d30313af | ||
|
|
950dd82e3c | ||
|
|
c53c3a387c | ||
|
|
5d0b2dc8e3 | ||
|
|
f5e9375917 | ||
|
|
cdfab7a7db | ||
|
|
f3e6a59e4f | ||
|
|
239c9ca2a7 | ||
|
|
becb566ca8 | ||
|
|
aa378d924b | ||
|
|
812f351ddd | ||
|
|
b9adf26ee0 | ||
|
|
018cd058ca | ||
|
|
cd3385be63 | ||
|
|
d4e4813c78 | ||
|
|
8030912087 | ||
|
|
4879701008 | ||
|
|
6ab8e5925e | ||
|
|
52dda9964c | ||
|
|
1825329236 | ||
|
|
60a650f798 | ||
|
|
0f78669faf | ||
|
|
015309b3dc | ||
|
|
a74edd22c1 | ||
|
|
7a6085e923 | ||
|
|
bf464994df | ||
|
|
a611a88f69 | ||
|
|
025b5c8e6d | ||
|
|
d37685f7cf | ||
|
|
10a22b5186 | ||
|
|
d9a74c43dc | ||
|
|
90c8aeb05d |
6
.github/ISSUE_TEMPLATE/bug-反馈.yml
vendored
@@ -9,7 +9,11 @@ body:
|
||||
attributes:
|
||||
label: 检查清单
|
||||
options:
|
||||
- label: 之前没有人提交过类似或相同的 bug report。
|
||||
- label: 之前没有人提交过类似或相同的 bug report。1
|
||||
required: true
|
||||
- label: 之前没有人提交过类似或相同的 bug report。2
|
||||
required: true
|
||||
- label: 之前没有人提交过类似或相同的 bug report。3
|
||||
required: true
|
||||
- label: 正在使用最新版本。
|
||||
required: true
|
||||
|
||||
13
README.md
@@ -47,13 +47,22 @@
|
||||
|
||||
## feat
|
||||
|
||||
- [x] 直播弹幕发送表情
|
||||
- [x] 收藏夹排序
|
||||
- [x] 稍后再看`未看`/`未看完`/`已看完`分类
|
||||
- [x] WebDAV 备份/恢复设置
|
||||
- [x] 保存评论/动态
|
||||
- [x] 高级弹幕 by [@My-Responsitories](https://github.com/My-Responsitories)
|
||||
- [x] 取消/置顶评论
|
||||
- [x] 记笔记
|
||||
- [x] 多账号支持 by [@My-Responsitories](https://github.com/My-Responsitories)
|
||||
- [x] 屏蔽带货动态/评论
|
||||
- [x] 互动视频
|
||||
- [x] 发评/动态反诈
|
||||
- [x] 高能进度条
|
||||
- [x] 滑动跳转预览视频缩略图
|
||||
- [x] Live Photo
|
||||
- [x] 复制/移动收藏夹/稍后再看视频
|
||||
- [x] 复制/移动/排序收藏夹/稍后再看视频
|
||||
- [x] 超分辨率
|
||||
- [x] 合并弹幕
|
||||
- [x] 会员彩色弹幕
|
||||
@@ -71,7 +80,6 @@
|
||||
- [x] 评论楼中楼定位点击查看的评论
|
||||
- [x] 评论楼中楼按热度/时间排序
|
||||
- [x] 评论点踩
|
||||
- [x] 显示ops专栏
|
||||
- [x] 私信发图
|
||||
- [x] 投币动画
|
||||
- [x] 取消/追番,更新追番状态
|
||||
@@ -156,7 +164,6 @@
|
||||
- [x] 音质选择(视视频而定)
|
||||
- [x] 解码格式选择(视视频而定)
|
||||
- [x] 弹幕
|
||||
- [ ] 直播弹幕
|
||||
- [x] 字幕
|
||||
- [x] 记忆播放
|
||||
- [x] 视频比例:高度/宽度适应、填充、包含等
|
||||
|
||||
@@ -7,8 +7,10 @@ import com.ryanheise.audioservice.AudioServiceActivity
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.WindowManager.LayoutParams
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@@ -55,6 +57,22 @@ class MainActivity : AudioServiceActivity() {
|
||||
}
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {}
|
||||
} else if (call.method == "linkVerifySettings") {
|
||||
try {
|
||||
val intent = Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
|
||||
Uri.parse("package:" + context.packageName))
|
||||
context.startActivity(intent)
|
||||
} catch (t: Throwable) {
|
||||
try {
|
||||
val intent = Intent("android.intent.action.MAIN", Uri.parse("package:" + context.packageName))
|
||||
intent.setClassName("com.android.settings", "com.android.settings.applications.InstalledAppOpenByDefaultActivity")
|
||||
context.startActivity(intent)
|
||||
} catch (t2: Throwable) {
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||
Uri.parse("package:" + context.packageName))
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
|
||||
@@ -18,19 +18,33 @@ subprojects {
|
||||
afterEvaluate { project ->
|
||||
if (project.extensions.findByName("android") != null) {
|
||||
Integer pluginCompileSdk = project.android.compileSdk
|
||||
if (pluginCompileSdk != null && pluginCompileSdk < 31) {
|
||||
project.logger.error(
|
||||
"Warning: Overriding compileSdk version in Flutter plugin: "
|
||||
+ project.name
|
||||
+ " from "
|
||||
+ pluginCompileSdk
|
||||
+ " to 31 (to work around https://issuetracker.google.com/issues/199180389)."
|
||||
+ "\nIf there is not a new version of " + project.name + ", consider filing an issue against "
|
||||
+ project.name
|
||||
+ " to increase their compileSdk to the latest (otherwise try updating to the latest version)."
|
||||
)
|
||||
project.android {
|
||||
compileSdk 31
|
||||
if (pluginCompileSdk != null) {
|
||||
if (pluginCompileSdk < 31) {
|
||||
project.logger.error(
|
||||
"Warning: Overriding compileSdk version in Flutter plugin: "
|
||||
+ project.name
|
||||
+ " from "
|
||||
+ pluginCompileSdk
|
||||
+ " to 31 (to work around https://issuetracker.google.com/issues/199180389)."
|
||||
+ "\nIf there is not a new version of " + project.name + ", consider filing an issue against "
|
||||
+ project.name
|
||||
+ " to increase their compileSdk to the latest (otherwise try updating to the latest version)."
|
||||
)
|
||||
project.android {
|
||||
compileSdk 31
|
||||
}
|
||||
}
|
||||
if (pluginCompileSdk > 34) {
|
||||
project.logger.error(
|
||||
"Warning: Overriding compileSdk version in Flutter plugin: "
|
||||
+ project.name
|
||||
+ " from "
|
||||
+ pluginCompileSdk
|
||||
+ " to 34"
|
||||
)
|
||||
project.android {
|
||||
compileSdk 34
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
assets/images/live/live_@28h.gif
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 514 B After Width: | Height: | Size: 915 B |
|
Before Width: | Height: | Size: 524 B After Width: | Height: | Size: 876 B |
|
Before Width: | Height: | Size: 518 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 541 B After Width: | Height: | Size: 991 B |
|
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 912 B |
|
Before Width: | Height: | Size: 539 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 517 B After Width: | Height: | Size: 1.1 KiB |
BIN
assets/images/lv/lv6_s.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
@@ -87,6 +87,7 @@ class DynamicCardSkeleton extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PBadge extends StatelessWidget {
|
||||
@@ -13,10 +14,11 @@ class PBadge extends StatelessWidget {
|
||||
final String? semanticsLabel;
|
||||
final bool bold;
|
||||
final double? textScaleFactor;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
const PBadge({
|
||||
super.key,
|
||||
this.text,
|
||||
required this.text,
|
||||
this.top,
|
||||
this.right,
|
||||
this.bottom,
|
||||
@@ -28,10 +30,15 @@ class PBadge extends StatelessWidget {
|
||||
this.semanticsLabel,
|
||||
this.bold = true,
|
||||
this.textScaleFactor,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (text.isNullOrEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
ColorScheme t = Theme.of(context).colorScheme;
|
||||
// 背景色
|
||||
Color bgColor = t.primary;
|
||||
@@ -40,20 +47,21 @@ class PBadge extends StatelessWidget {
|
||||
// 边框色
|
||||
Color borderColor = Colors.transparent;
|
||||
if (type == 'gray') {
|
||||
bgColor = Colors.black54.withOpacity(0.4);
|
||||
bgColor = Colors.black45;
|
||||
color = Colors.white;
|
||||
}
|
||||
if (type == 'color') {
|
||||
} else if (type == 'color') {
|
||||
bgColor = t.secondaryContainer.withOpacity(0.5);
|
||||
color = t.onSecondaryContainer;
|
||||
}
|
||||
if (type == 'line') {
|
||||
} else if (type == 'line') {
|
||||
bgColor = Colors.transparent;
|
||||
color = t.primary;
|
||||
borderColor = t.primary;
|
||||
} else if (type == 'error') {
|
||||
bgColor = t.error;
|
||||
color = t.onError;
|
||||
}
|
||||
|
||||
EdgeInsets paddingStyle =
|
||||
late EdgeInsets paddingStyle =
|
||||
const EdgeInsets.symmetric(vertical: 2, horizontal: 3);
|
||||
double fontSize = 11;
|
||||
BorderRadius br = BorderRadius.circular(4);
|
||||
@@ -65,14 +73,14 @@ class PBadge extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget content = Container(
|
||||
padding: paddingStyle,
|
||||
padding: padding ?? paddingStyle,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: br,
|
||||
color: bgColor,
|
||||
border: Border.all(color: borderColor),
|
||||
),
|
||||
child: Text(
|
||||
text ?? "",
|
||||
text!,
|
||||
textScaler: textScaleFactor != null
|
||||
? TextScaler.linear(textScaleFactor!)
|
||||
: null,
|
||||
|
||||
@@ -33,3 +33,70 @@ void showConfirmDialog({
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void showPgcFollowDialog({
|
||||
required BuildContext context,
|
||||
required String type,
|
||||
required int followStatus,
|
||||
required ValueChanged<int> onUpdateStatus,
|
||||
}) {
|
||||
Widget statusItem({
|
||||
required bool enabled,
|
||||
required String text,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return ListTile(
|
||||
dense: true,
|
||||
enabled: enabled,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
'标记为 $text',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
trailing: !enabled ? const Icon(size: 22, Icons.check) : null,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
...[
|
||||
{'followStatus': 3, 'title': '看过'},
|
||||
{'followStatus': 2, 'title': '在看'},
|
||||
{'followStatus': 1, 'title': '想看'},
|
||||
].map(
|
||||
(Map item) => statusItem(
|
||||
enabled: followStatus != item['followStatus'],
|
||||
text: item['title'],
|
||||
onTap: () {
|
||||
Get.back();
|
||||
onUpdateStatus(item['followStatus']);
|
||||
},
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
'取消$type',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
onUpdateStatus(-1);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class DynamicSliverAppBar extends StatefulWidget {
|
||||
this.stretchTriggerOffset = 100.0,
|
||||
this.onStretchTrigger,
|
||||
this.shape,
|
||||
this.toolbarHeight = kToolbarHeight + 20,
|
||||
this.toolbarHeight = kToolbarHeight,
|
||||
this.leadingWidth,
|
||||
this.toolbarTextStyle,
|
||||
this.titleTextStyle,
|
||||
@@ -43,8 +43,10 @@ class DynamicSliverAppBar extends StatefulWidget {
|
||||
this.forceMaterialTransparency = false,
|
||||
this.clipBehavior,
|
||||
this.appBarClipper,
|
||||
this.hasTabBar = false,
|
||||
});
|
||||
|
||||
final bool hasTabBar;
|
||||
final Widget? flexibleSpace;
|
||||
final Widget? leading;
|
||||
final bool automaticallyImplyLeading;
|
||||
@@ -95,7 +97,6 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
|
||||
// As long as the height is 0 instead of the sliver app bar a sliver to box adapter will be used
|
||||
// to calculate dynamically the size for the sliver app bar
|
||||
double _height = 0;
|
||||
Orientation? _orientation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -103,13 +104,6 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
|
||||
_updateHeight();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant DynamicSliverAppBar oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
_updateHeight();
|
||||
}
|
||||
|
||||
void _updateHeight() {
|
||||
// Gets the new height and updates the sliver app bar. Needs to be called after the last frame has been rebuild
|
||||
// otherwise this will throw an error
|
||||
@@ -123,23 +117,27 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_height = 0;
|
||||
_updateHeight();
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//Needed to lay out the flexibleSpace the first time, so we can calculate its intrinsic height
|
||||
Orientation orientation = MediaQuery.orientationOf(context);
|
||||
if (_orientation != orientation) {
|
||||
_orientation = orientation;
|
||||
_height = 0;
|
||||
}
|
||||
if (_height == 0) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Container(
|
||||
child: SizedBox(
|
||||
key: _childKey,
|
||||
child: widget.flexibleSpace ?? SizedBox(height: kToolbarHeight),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
MediaQuery.orientationOf(context);
|
||||
|
||||
return SliverAppBar(
|
||||
leading: widget.leading,
|
||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||
@@ -168,7 +166,7 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
|
||||
onStretchTrigger: widget.onStretchTrigger,
|
||||
shape: widget.shape,
|
||||
toolbarHeight: widget.toolbarHeight,
|
||||
expandedHeight: _height,
|
||||
expandedHeight: _height + (widget.hasTabBar ? 48 : 0),
|
||||
leadingWidth: widget.leadingWidth,
|
||||
toolbarTextStyle: widget.toolbarTextStyle,
|
||||
titleTextStyle: widget.titleTextStyle,
|
||||
|
||||
655
lib/common/widgets/episode_panel.dart
Normal file
@@ -0,0 +1,655 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/video.dart';
|
||||
import 'package:PiliPlus/models/bangumi/info.dart' as bangumi;
|
||||
import 'package:PiliPlus/models/video_detail_res.dart' as video;
|
||||
import 'package:PiliPlus/pages/common/common_slide_page.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/controller.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/controller.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/page.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
enum EpisodeType { part, season, bangumi }
|
||||
|
||||
extension EpisodeTypeExt on EpisodeType {
|
||||
String get title => ['分P', '合集', '番剧'][index];
|
||||
}
|
||||
|
||||
class EpisodePanel extends CommonSlidePage {
|
||||
const EpisodePanel({
|
||||
super.key,
|
||||
super.enableSlide,
|
||||
required this.videoIntroController,
|
||||
required this.heroTag,
|
||||
required this.type,
|
||||
// required this.count,
|
||||
// required this.name,
|
||||
required this.aid,
|
||||
required this.bvid,
|
||||
required this.cid,
|
||||
required this.cover,
|
||||
this.showTitle,
|
||||
required this.list,
|
||||
this.seasonId,
|
||||
this.initialTabIndex = 0,
|
||||
this.isSupportReverse,
|
||||
this.isReversed,
|
||||
this.onReverse,
|
||||
required this.changeFucCall,
|
||||
this.onClose,
|
||||
});
|
||||
|
||||
final VideoIntroController videoIntroController;
|
||||
final String heroTag;
|
||||
final EpisodeType type;
|
||||
// final int count;
|
||||
// final String name;
|
||||
final int? aid;
|
||||
final String bvid;
|
||||
final int cid;
|
||||
final String? cover;
|
||||
final bool? showTitle;
|
||||
final List list;
|
||||
final int? seasonId;
|
||||
final int initialTabIndex;
|
||||
final bool? isSupportReverse;
|
||||
final bool? isReversed;
|
||||
final Function changeFucCall;
|
||||
final VoidCallback? onReverse;
|
||||
final VoidCallback? onClose;
|
||||
|
||||
@override
|
||||
State<EpisodePanel> createState() => _EpisodePanelState();
|
||||
}
|
||||
|
||||
class _EpisodePanelState extends CommonSlidePageState<EpisodePanel>
|
||||
with SingleTickerProviderStateMixin {
|
||||
// tab
|
||||
late final TabController _tabController = TabController(
|
||||
initialIndex: widget.initialTabIndex,
|
||||
length: widget.list.length,
|
||||
vsync: this,
|
||||
)..addListener(listener);
|
||||
late final RxInt _currentTabIndex = _tabController.index.obs;
|
||||
|
||||
List get _getCurrEpisodes => widget.type == EpisodeType.season
|
||||
? widget.list[_currentTabIndex.value].episodes
|
||||
: widget.list[_currentTabIndex.value];
|
||||
|
||||
// item
|
||||
late RxInt _currentItemIndex;
|
||||
int get _findCurrentItemIndex => max(
|
||||
0,
|
||||
_getCurrEpisodes.indexWhere((item) => item.cid == widget.cid),
|
||||
);
|
||||
|
||||
late final List<bool> _isReversed;
|
||||
late final List<ItemScrollController> _itemScrollController;
|
||||
|
||||
// fav
|
||||
Rx<LoadingState>? _favState;
|
||||
|
||||
late bool _isInit = true;
|
||||
|
||||
void listener() {
|
||||
_currentTabIndex.value = _tabController.index;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(EpisodePanel oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.showTitle != false) {
|
||||
return;
|
||||
}
|
||||
|
||||
void jumpToCurrent() {
|
||||
int newItemIndex = _findCurrentItemIndex;
|
||||
if (_currentItemIndex.value != _findCurrentItemIndex) {
|
||||
_currentItemIndex.value = newItemIndex;
|
||||
try {
|
||||
_itemScrollController[_currentTabIndex.value].jumpTo(
|
||||
index: newItemIndex,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
// jump to current
|
||||
if (_currentTabIndex.value != widget.initialTabIndex) {
|
||||
_tabController.animateTo(
|
||||
widget.initialTabIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
Future.delayed(const Duration(milliseconds: 300)).then((_) {
|
||||
jumpToCurrent();
|
||||
});
|
||||
} else {
|
||||
jumpToCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_itemScrollController =
|
||||
List.generate(widget.list.length, (_) => ItemScrollController());
|
||||
_isReversed = List.generate(widget.list.length, (_) => false);
|
||||
|
||||
if (widget.type == EpisodeType.season && Accounts.main.isLogin) {
|
||||
_favState = LoadingState.loading().obs;
|
||||
VideoHttp.videoRelation(bvid: widget.bvid).then((result) {
|
||||
if (result['status']) {
|
||||
if (result['data']?['season_fav'] is bool) {
|
||||
_favState!.value =
|
||||
LoadingState.success(result['data']['season_fav']);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_currentItemIndex = _findCurrentItemIndex.obs;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isInit = false;
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
try {
|
||||
_itemScrollController[widget.initialTabIndex]
|
||||
.jumpTo(index: _currentItemIndex.value);
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.removeListener(listener);
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isInit) {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
);
|
||||
}
|
||||
|
||||
return super.build(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget get buildPage => Material(
|
||||
color: widget.showTitle == false
|
||||
? Colors.transparent
|
||||
: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildToolbar,
|
||||
if (widget.type == EpisodeType.season &&
|
||||
widget.list.length > 1) ...[
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
padding: const EdgeInsets.only(right: 60),
|
||||
isScrollable: true,
|
||||
tabs: widget.list.map((item) => Tab(text: item.title)).toList(),
|
||||
dividerHeight: 1,
|
||||
dividerColor: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
Expanded(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: tabBarView(
|
||||
controller: _tabController,
|
||||
children: List.generate(
|
||||
widget.list.length,
|
||||
(index) => _buildBody(
|
||||
index,
|
||||
widget.list[index].episodes,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
] else
|
||||
Expanded(
|
||||
child: enableSlide ? slideList() : buildList,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget get buildList => Material(
|
||||
color: Colors.transparent,
|
||||
child: _buildBody(0, _getCurrEpisodes),
|
||||
);
|
||||
|
||||
Widget _buildBody(int index, episodes) {
|
||||
return KeepAliveWrapper(
|
||||
builder: (context) => ScrollablePositionedList.separated(
|
||||
padding: EdgeInsets.only(
|
||||
top: 7,
|
||||
bottom: MediaQuery.of(context).padding.bottom + 80,
|
||||
),
|
||||
reverse: _isReversed[index],
|
||||
itemCount: episodes.length,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final episode = episodes[index];
|
||||
return widget.type == EpisodeType.season &&
|
||||
widget.showTitle != false &&
|
||||
episode.pages.length > 1
|
||||
? Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Obx(
|
||||
() => _buildEpisodeItem(
|
||||
episode: episode,
|
||||
index: index,
|
||||
length: episodes.length,
|
||||
isCurrentIndex:
|
||||
_currentTabIndex.value == widget.initialTabIndex
|
||||
? _currentItemIndex.value == index
|
||||
: false,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 5),
|
||||
child: PagesPanel(
|
||||
list:
|
||||
widget.initialTabIndex == _currentTabIndex.value &&
|
||||
index == _currentItemIndex.value
|
||||
? null
|
||||
: episode.pages,
|
||||
cover: episode.arc?.pic,
|
||||
heroTag: widget.heroTag,
|
||||
videoIntroController: widget.videoIntroController,
|
||||
bvid: IdUtils.av2bv(episode.aid),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Obx(
|
||||
() => _buildEpisodeItem(
|
||||
episode: episode,
|
||||
index: index,
|
||||
length: episodes.length,
|
||||
isCurrentIndex:
|
||||
_currentTabIndex.value == widget.initialTabIndex
|
||||
? _currentItemIndex.value == index
|
||||
: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
itemScrollController: _itemScrollController[index],
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEpisodeItem({
|
||||
required dynamic episode,
|
||||
required int index,
|
||||
required int length,
|
||||
required bool isCurrentIndex,
|
||||
}) {
|
||||
late String title;
|
||||
String? cover;
|
||||
num? duration;
|
||||
int? pubdate;
|
||||
int? view;
|
||||
int? danmaku;
|
||||
|
||||
switch (episode) {
|
||||
case video.Part():
|
||||
cover = episode.firstFrame ?? widget.cover;
|
||||
title = episode.pagePart!;
|
||||
duration = episode.duration;
|
||||
pubdate = episode.ctime;
|
||||
break;
|
||||
case video.EpisodeItem():
|
||||
title = episode.title!;
|
||||
cover = episode.arc?.pic;
|
||||
duration = episode.arc?.duration;
|
||||
pubdate = episode.arc?.pubdate;
|
||||
view = episode.arc?.stat?.view;
|
||||
danmaku = episode.arc?.stat?.danmaku;
|
||||
break;
|
||||
case bangumi.EpisodeItem():
|
||||
if (episode.longTitle != null && episode.longTitle != "") {
|
||||
dynamic leading = episode.title ?? index + 1;
|
||||
title =
|
||||
"${Utils.isStringNumeric(leading) ? '第$leading话' : leading} ${episode.longTitle!}";
|
||||
} else {
|
||||
title = episode.title!;
|
||||
}
|
||||
|
||||
cover = episode.cover;
|
||||
duration = episode.duration == null ? null : episode.duration! ~/ 1000;
|
||||
pubdate = episode.pubTime;
|
||||
break;
|
||||
}
|
||||
late final Color primary = Theme.of(context).colorScheme.primary;
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: SizedBox(
|
||||
height: 98,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (episode.badge != null && episode.badge == "会员") {
|
||||
dynamic userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
int vipStatus = 0;
|
||||
if (userInfo != null) {
|
||||
vipStatus = userInfo.vipStatus;
|
||||
}
|
||||
if (vipStatus != 1) {
|
||||
SmartDialog.showToast('需要大会员');
|
||||
// return;
|
||||
}
|
||||
}
|
||||
SmartDialog.showToast('切换到:$title');
|
||||
widget.onClose?.call();
|
||||
if (widget.showTitle == false) {
|
||||
_currentItemIndex.value = index;
|
||||
}
|
||||
widget.changeFucCall(
|
||||
episode is bangumi.EpisodeItem ? episode.epId : null,
|
||||
episode.runtimeType.toString() == "EpisodeItem"
|
||||
? episode.bvid
|
||||
: widget.bvid,
|
||||
episode.cid,
|
||||
episode.runtimeType.toString() == "EpisodeItem"
|
||||
? episode.aid
|
||||
: widget.aid,
|
||||
cover,
|
||||
);
|
||||
if (widget.type == EpisodeType.season) {
|
||||
try {
|
||||
Get.find<VideoDetailController>(
|
||||
tag: widget.videoIntroController.heroTag)
|
||||
.seasonCid = episode.cid;
|
||||
} catch (_) {}
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (cover?.isNotEmpty == true) {
|
||||
imageSaveDialog(context: context, title: title, cover: cover);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
if (cover?.isNotEmpty == true)
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: cover,
|
||||
width: boxConstraints.maxWidth,
|
||||
height: boxConstraints.maxHeight,
|
||||
),
|
||||
if (duration != null && duration > 0)
|
||||
PBadge(
|
||||
text: Utils.timeFormat(duration),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
else if (isCurrentIndex)
|
||||
Image.asset(
|
||||
'assets/images/live.png',
|
||||
color: primary,
|
||||
height: 12,
|
||||
semanticLabel: "正在播放:",
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.fontSize,
|
||||
height: 1.42,
|
||||
letterSpacing: 0.3,
|
||||
fontWeight: isCurrentIndex ? FontWeight.bold : null,
|
||||
color: isCurrentIndex ? primary : null,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (pubdate != null)
|
||||
Text(
|
||||
Utils.dateFormat(pubdate),
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.fontSize,
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
),
|
||||
if (view != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
value: view,
|
||||
),
|
||||
if (danmaku != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
StatDanMu(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
value: danmaku,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (episode.badge != null) ...[
|
||||
if (episode.badge == '会员')
|
||||
Image.asset(
|
||||
'assets/images/big-vip.png',
|
||||
height: 20,
|
||||
semanticLabel: "大会员",
|
||||
)
|
||||
else
|
||||
Text(episode.badge),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFavBtn(LoadingState loadingState) {
|
||||
return switch (loadingState) {
|
||||
Success() => mediumButton(
|
||||
tooltip: loadingState.response ? '取消订阅' : '订阅',
|
||||
icon: loadingState.response
|
||||
? Icons.notifications_off_outlined
|
||||
: Icons.notifications_active_outlined,
|
||||
onPressed: () async {
|
||||
dynamic result = await VideoHttp.seasonFav(
|
||||
isFav: loadingState.response,
|
||||
seasonId: widget.seasonId,
|
||||
);
|
||||
if (result['status']) {
|
||||
SmartDialog.showToast('${loadingState.response ? '取消' : ''}订阅成功');
|
||||
_favState!.value = LoadingState.success(!loadingState.response);
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
},
|
||||
),
|
||||
_ => const SizedBox.shrink(),
|
||||
};
|
||||
}
|
||||
|
||||
Widget get _buildReverseBtn => mediumButton(
|
||||
tooltip: widget.isReversed == true ? '正序播放' : '倒序播放',
|
||||
icon: widget.isReversed == true
|
||||
? MdiIcons.sortDescending
|
||||
: MdiIcons.sortAscending,
|
||||
onPressed: () {
|
||||
widget.onReverse?.call();
|
||||
},
|
||||
);
|
||||
|
||||
Widget get _buildToolbar => Container(
|
||||
height: 45,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: widget.showTitle != false ? 14 : 6),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.showTitle != false)
|
||||
Text(
|
||||
widget.type.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
if (_favState != null) Obx(() => _buildFavBtn(_favState!.value)),
|
||||
mediumButton(
|
||||
tooltip: '跳至顶部',
|
||||
icon: Icons.vertical_align_top,
|
||||
onPressed: () {
|
||||
try {
|
||||
_itemScrollController[_currentTabIndex.value].scrollTo(
|
||||
index: !_isReversed[_currentTabIndex.value]
|
||||
? 0
|
||||
: _getCurrEpisodes.length - 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('to top: $e');
|
||||
}
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至底部',
|
||||
icon: Icons.vertical_align_bottom,
|
||||
onPressed: () {
|
||||
try {
|
||||
_itemScrollController[_currentTabIndex.value].scrollTo(
|
||||
index: !_isReversed[_currentTabIndex.value]
|
||||
? _getCurrEpisodes.length - 1
|
||||
: 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('to bottom: $e');
|
||||
}
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至当前',
|
||||
icon: Icons.my_location,
|
||||
onPressed: () async {
|
||||
try {
|
||||
if (_currentTabIndex.value != widget.initialTabIndex) {
|
||||
_tabController.animateTo(widget.initialTabIndex);
|
||||
await Future.delayed(const Duration(milliseconds: 225));
|
||||
}
|
||||
_itemScrollController[_currentTabIndex.value].scrollTo(
|
||||
index: _currentItemIndex.value,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
if (widget.isSupportReverse == true)
|
||||
Obx(
|
||||
() {
|
||||
return _currentTabIndex.value == widget.initialTabIndex
|
||||
? _buildReverseBtn
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
Obx(
|
||||
() => mediumButton(
|
||||
tooltip: _isReversed[_currentTabIndex.value] ? '顺序' : '倒序',
|
||||
icon: !_isReversed[_currentTabIndex.value]
|
||||
? MdiIcons.sortNumericAscending
|
||||
: MdiIcons.sortNumericDescending,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isReversed[_currentTabIndex.value] =
|
||||
!_isReversed[_currentTabIndex.value];
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.onClose != null)
|
||||
mediumButton(
|
||||
tooltip: '关闭',
|
||||
icon: Icons.close,
|
||||
onPressed: widget.onClose,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
|
||||
show SourceModel;
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'network_img_layer.dart';
|
||||
@@ -32,7 +34,7 @@ Widget htmlRender({
|
||||
final bool isEmote = imgUrl.contains('/emote/');
|
||||
final bool isMall = imgUrl.contains('/mall/');
|
||||
if (isMall) {
|
||||
return const SizedBox();
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
// bool inTable =
|
||||
// extensionContext.element!.previousElementSibling == null ||
|
||||
@@ -43,6 +45,18 @@ Widget htmlRender({
|
||||
// width: isEmote ? 22 : null,
|
||||
// height: isEmote ? 22 : null,
|
||||
// );
|
||||
String? clazz = attributes['class'];
|
||||
String? height = RegExp(r'max-height:(\d+)px')
|
||||
.firstMatch('${attributes['style']}')
|
||||
?.group(1);
|
||||
if (clazz?.contains('cut-off') == true || height != null) {
|
||||
return CachedNetworkImage(
|
||||
width: constrainedWidth,
|
||||
height: height != null ? double.parse(height) : null,
|
||||
imageUrl: Utils.thumbnailImgUrl(imgUrl),
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
}
|
||||
return Hero(
|
||||
tag: imgUrl,
|
||||
child: GestureDetector(
|
||||
|
||||
@@ -22,7 +22,7 @@ Widget iconButton({
|
||||
color: iconColor ?? Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
style: IconButton.styleFrom(
|
||||
padding: EdgeInsets.all(0),
|
||||
padding: EdgeInsets.zero,
|
||||
backgroundColor:
|
||||
bgColor ?? Theme.of(context).colorScheme.secondaryContainer,
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/utils/download.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -71,25 +72,36 @@ void imageSaveDialog({
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
IconButton(
|
||||
tooltip: '保存封面图',
|
||||
onPressed: () async {
|
||||
bool saveStatus = await DownloadUtils.downloadImg(
|
||||
context,
|
||||
[cover ?? ''],
|
||||
);
|
||||
// 保存成功,自动关闭弹窗
|
||||
if (saveStatus) {
|
||||
if (cover?.isNotEmpty == true) ...[
|
||||
const SizedBox(width: 4),
|
||||
iconButton(
|
||||
context: context,
|
||||
tooltip: '分享',
|
||||
onPressed: () {
|
||||
SmartDialog.dismiss();
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.download,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
DownloadUtils.onShareImg(cover!);
|
||||
},
|
||||
iconSize: 20,
|
||||
icon: Icons.share,
|
||||
bgColor: Colors.transparent,
|
||||
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
iconButton(
|
||||
context: context,
|
||||
tooltip: '保存封面图',
|
||||
onPressed: () async {
|
||||
bool saveStatus =
|
||||
await DownloadUtils.downloadImg(context, [cover!]);
|
||||
if (saveStatus) {
|
||||
SmartDialog.dismiss();
|
||||
}
|
||||
},
|
||||
iconSize: 20,
|
||||
icon: Icons.download,
|
||||
bgColor: Colors.transparent,
|
||||
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -31,7 +31,7 @@ class ImageModel {
|
||||
bool get isLivePhoto => _isLivePhoto ??= liveUrl?.isNotEmpty == true;
|
||||
}
|
||||
|
||||
Widget imageview(
|
||||
Widget imageView(
|
||||
double maxWidth,
|
||||
List<ImageModel> picArr, {
|
||||
VoidCallback? onViewImage,
|
||||
@@ -1,21 +1,16 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/utils/download.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'interactive_viewer_boundary.dart';
|
||||
import 'interactive_viewer.dart' as custom;
|
||||
|
||||
@@ -380,7 +375,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
onTap: () => onShareImg(
|
||||
onTap: () => DownloadUtils.onShareImg(
|
||||
widget.sources[currentIndex.value].url),
|
||||
child: const Text("分享图片"),
|
||||
),
|
||||
@@ -394,7 +389,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
this.context,
|
||||
[widget.sources[currentIndex.value].url],
|
||||
);
|
||||
},
|
||||
@@ -404,7 +399,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
this.context,
|
||||
widget.sources
|
||||
.map((item) => item.url)
|
||||
.toList(),
|
||||
@@ -417,7 +412,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
DownloadUtils.downloadLivePhoto(
|
||||
context: context,
|
||||
context: this.context,
|
||||
url: widget.sources[currentIndex.value].url,
|
||||
liveUrl: widget
|
||||
.sources[currentIndex.value].liveUrl!,
|
||||
@@ -442,34 +437,6 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
);
|
||||
}
|
||||
|
||||
// 图片分享
|
||||
void onShareImg(String imgUrl) async {
|
||||
try {
|
||||
SmartDialog.showLoading();
|
||||
var response = await Request()
|
||||
.get(imgUrl, options: Options(responseType: ResponseType.bytes));
|
||||
final temp = await getTemporaryDirectory();
|
||||
SmartDialog.dismiss();
|
||||
String imgName =
|
||||
"plpl_pic_${DateTime.now().toString().split('-').join()}.jpg";
|
||||
var path = '${temp.path}/$imgName';
|
||||
File(path).writeAsBytesSync(response.data);
|
||||
|
||||
Rect? sharePositionOrigin;
|
||||
if (Platform.isIOS && (await Utils.isIpad())) {
|
||||
sharePositionOrigin = Rect.fromLTWH(0, 0, Get.width, Get.height / 2);
|
||||
}
|
||||
|
||||
Share.shareXFiles(
|
||||
[XFile(path)],
|
||||
subject: imgUrl,
|
||||
sharePositionOrigin: sharePositionOrigin,
|
||||
);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Widget _itemBuilder(index) {
|
||||
return Center(
|
||||
child: Hero(
|
||||
@@ -480,19 +447,27 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
image: FileImage(File(widget.sources[index].url)),
|
||||
),
|
||||
SourceType.networkImage => CachedNetworkImage(
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
fadeOutDuration: const Duration(milliseconds: 0),
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
imageUrl: _getActualUrl(index),
|
||||
// fit: BoxFit.contain,
|
||||
progressIndicatorBuilder: (context, url, progress) {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 150.0,
|
||||
child:
|
||||
LinearProgressIndicator(value: progress.progress ?? 0),
|
||||
),
|
||||
placeholderFadeInDuration: Duration.zero,
|
||||
placeholder: (context, url) {
|
||||
return CachedNetworkImage(
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
imageUrl: Utils.thumbnailImgUrl(widget.sources[index].url),
|
||||
);
|
||||
},
|
||||
// fit: BoxFit.contain,
|
||||
// progressIndicatorBuilder: (context, url, progress) {
|
||||
// return Center(
|
||||
// child: SizedBox(
|
||||
// width: 150.0,
|
||||
// child:
|
||||
// LinearProgressIndicator(value: progress.progress ?? 0),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// errorListener: (value) {
|
||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// setState(() {
|
||||
@@ -573,7 +548,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
children: [
|
||||
ListTile(
|
||||
onTap: () {
|
||||
onShareImg(widget.sources[currentIndex.value].url);
|
||||
DownloadUtils.onShareImg(
|
||||
widget.sources[currentIndex.value].url);
|
||||
Get.back();
|
||||
},
|
||||
dense: true,
|
||||
@@ -591,7 +567,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
onTap: () {
|
||||
Get.back();
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
this.context,
|
||||
[widget.sources[currentIndex.value].url],
|
||||
);
|
||||
},
|
||||
@@ -603,7 +579,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
onTap: () {
|
||||
Get.back();
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
this.context,
|
||||
widget.sources.map((item) => item.url).toList(),
|
||||
);
|
||||
},
|
||||
@@ -616,7 +592,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
onTap: () {
|
||||
Get.back();
|
||||
DownloadUtils.downloadLivePhoto(
|
||||
context: context,
|
||||
context: this.context,
|
||||
url: widget.sources[currentIndex.value].url,
|
||||
liveUrl: widget.sources[currentIndex.value].liveUrl!,
|
||||
width: widget.sources[currentIndex.value].width!,
|
||||
|
||||
27
lib/common/widgets/keep_alive_wrapper.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class KeepAliveWrapper extends StatefulWidget {
|
||||
const KeepAliveWrapper({
|
||||
super.key,
|
||||
required this.builder,
|
||||
this.wantKeepAlive = true,
|
||||
});
|
||||
|
||||
final WidgetBuilder builder;
|
||||
final bool wantKeepAlive;
|
||||
|
||||
@override
|
||||
State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
|
||||
}
|
||||
|
||||
class _KeepAliveWrapperState extends State<KeepAliveWrapper>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return widget.builder(context);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => widget.wantKeepAlive;
|
||||
}
|
||||
@@ -1,537 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/http/video.dart';
|
||||
import 'package:PiliPlus/models/bangumi/info.dart' as bangumi;
|
||||
import 'package:PiliPlus/models/video_detail_res.dart' as video;
|
||||
import 'package:PiliPlus/pages/common/common_slide_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
import '../../utils/storage.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import 'package:PiliPlus/common/widgets/spring_physics.dart';
|
||||
|
||||
class ListSheetContent extends CommonSlidePage {
|
||||
const ListSheetContent({
|
||||
super.key,
|
||||
this.index, // tab index
|
||||
this.season,
|
||||
this.episodes,
|
||||
this.bvid,
|
||||
this.aid,
|
||||
required this.currentCid,
|
||||
required this.changeFucCall,
|
||||
this.onClose,
|
||||
this.onReverse,
|
||||
this.showTitle,
|
||||
this.isSupportReverse,
|
||||
this.isReversed,
|
||||
super.enableSlide,
|
||||
});
|
||||
|
||||
final dynamic index;
|
||||
final dynamic season;
|
||||
final dynamic episodes;
|
||||
final String? bvid;
|
||||
final int? aid;
|
||||
final int currentCid;
|
||||
final Function changeFucCall;
|
||||
final VoidCallback? onClose;
|
||||
final VoidCallback? onReverse;
|
||||
final bool? showTitle;
|
||||
final bool? isSupportReverse;
|
||||
final bool? isReversed;
|
||||
|
||||
@override
|
||||
State<ListSheetContent> createState() => _ListSheetContentState();
|
||||
}
|
||||
|
||||
class _ListSheetContentState extends CommonSlidePageState<ListSheetContent>
|
||||
with TickerProviderStateMixin {
|
||||
late List<ItemScrollController> itemScrollController = [];
|
||||
late int currentIndex = _currentIndex;
|
||||
late List<bool> reverse;
|
||||
|
||||
int get _index => widget.index ?? 0;
|
||||
late final bool _isList = widget.season != null &&
|
||||
widget.season?.sections is List &&
|
||||
widget.season.sections.length > 1;
|
||||
dynamic get episodes =>
|
||||
widget.episodes ?? widget.season?.sections[_index].episodes;
|
||||
TabController? _ctr;
|
||||
StreamController? _indexStream;
|
||||
int? _seasonFav;
|
||||
StreamController? _favStream;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ListSheetContent oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.showTitle != false) {
|
||||
return;
|
||||
}
|
||||
|
||||
int currentIndex = _currentIndex;
|
||||
|
||||
void jumpToCurrent() {
|
||||
if (this.currentIndex != currentIndex) {
|
||||
this.currentIndex = currentIndex;
|
||||
try {
|
||||
itemScrollController[_index].jumpTo(
|
||||
index: currentIndex,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
// jump to current
|
||||
if (_ctr != null && widget.index != _ctr?.index) {
|
||||
_ctr?.animateTo(_index, duration: const Duration(milliseconds: 200));
|
||||
Future.delayed(const Duration(milliseconds: 300)).then((_) {
|
||||
jumpToCurrent();
|
||||
});
|
||||
} else {
|
||||
jumpToCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
int get _currentIndex => max(
|
||||
0,
|
||||
_isList
|
||||
? widget.season.sections[_index].episodes
|
||||
.indexWhere((e) => e.cid == widget.currentCid)
|
||||
: episodes.indexWhere((e) => e.cid == widget.currentCid));
|
||||
|
||||
void listener() {
|
||||
_indexStream?.add(_ctr?.index);
|
||||
}
|
||||
|
||||
late bool _isInit = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (_isList) {
|
||||
_indexStream ??= StreamController<int>.broadcast();
|
||||
_ctr = TabController(
|
||||
vsync: this,
|
||||
length: widget.season.sections.length,
|
||||
initialIndex: _index,
|
||||
)..addListener(listener);
|
||||
}
|
||||
itemScrollController = _isList
|
||||
? List.generate(
|
||||
widget.season.sections.length, (_) => ItemScrollController())
|
||||
: [ItemScrollController()];
|
||||
reverse = _isList
|
||||
? List.generate(widget.season.sections.length, (_) => false)
|
||||
: [false];
|
||||
if (Accounts.main.isLogin && widget.bvid != null && widget.season != null) {
|
||||
_favStream ??= StreamController<int>();
|
||||
() async {
|
||||
dynamic result = await VideoHttp.videoRelation(bvid: widget.bvid);
|
||||
if (result['status']) {
|
||||
_seasonFav = result['data']['season_fav'] ? 1 : 0;
|
||||
_favStream?.add(_seasonFav);
|
||||
}
|
||||
}();
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (GStorage.collapsibleVideoPage) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isInit = false;
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
try {
|
||||
itemScrollController[_index].jumpTo(index: currentIndex);
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
itemScrollController[_index].jumpTo(index: currentIndex);
|
||||
} catch (_) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_favStream?.close();
|
||||
_favStream = null;
|
||||
_indexStream?.close();
|
||||
_indexStream = null;
|
||||
_ctr?.removeListener(listener);
|
||||
_ctr?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget buildEpisodeListItem(
|
||||
dynamic episode,
|
||||
int index,
|
||||
int length,
|
||||
bool isCurrentIndex,
|
||||
) {
|
||||
Color primary = Theme.of(context).colorScheme.primary;
|
||||
late String title;
|
||||
if (episode.runtimeType.toString() == "EpisodeItem") {
|
||||
if (episode.longTitle != null && episode.longTitle != "") {
|
||||
dynamic leading = episode.title ?? index + 1;
|
||||
title =
|
||||
"${Utils.isStringNumeric(leading) ? '第$leading话' : leading} ${episode.longTitle!}";
|
||||
} else {
|
||||
title = episode.title!;
|
||||
}
|
||||
} else if (episode.runtimeType.toString() == "PageItem") {
|
||||
title = episode.pagePart!;
|
||||
} else if (episode.runtimeType.toString() == "Part") {
|
||||
title = episode.pagePart!;
|
||||
// debugPrint("未知类型:${episode.runtimeType}");
|
||||
}
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
if (episode.badge != null && episode.badge == "会员") {
|
||||
dynamic userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
int vipStatus = 0;
|
||||
if (userInfo != null) {
|
||||
vipStatus = userInfo.vipStatus;
|
||||
}
|
||||
if (vipStatus != 1) {
|
||||
SmartDialog.showToast('需要大会员');
|
||||
// return;
|
||||
}
|
||||
}
|
||||
SmartDialog.showToast('切换到:$title');
|
||||
widget.onClose?.call();
|
||||
currentIndex = index;
|
||||
widget.changeFucCall(
|
||||
episode is bangumi.EpisodeItem ? episode.epId : null,
|
||||
episode.runtimeType.toString() == "EpisodeItem"
|
||||
? episode.bvid
|
||||
: widget.bvid,
|
||||
episode.cid,
|
||||
episode.runtimeType.toString() == "EpisodeItem"
|
||||
? episode.aid
|
||||
: widget.aid,
|
||||
episode is video.EpisodeItem
|
||||
? episode.arc?.pic
|
||||
: episode is bangumi.EpisodeItem
|
||||
? episode.cover
|
||||
: null,
|
||||
);
|
||||
},
|
||||
dense: false,
|
||||
leading: (episode is video.EpisodeItem && episode.arc?.pic != null) ||
|
||||
(episode is video.Part && episode.firstFrame != null) ||
|
||||
(episode is bangumi.EpisodeItem && episode.cover != null)
|
||||
? Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: isCurrentIndex
|
||||
? BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
width: 1.8,
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => NetworkImgLayer(
|
||||
radius: 6,
|
||||
src: episode is video.EpisodeItem
|
||||
? episode.arc?.pic
|
||||
: episode is bangumi.EpisodeItem
|
||||
? episode.cover
|
||||
: episode.firstFrame,
|
||||
width: constraints.maxHeight * StyleString.aspectRatio,
|
||||
height: constraints.maxHeight,
|
||||
),
|
||||
),
|
||||
)
|
||||
: isCurrentIndex
|
||||
? Image.asset(
|
||||
'assets/images/live.png',
|
||||
color: primary,
|
||||
height: 12,
|
||||
semanticLabel: "正在播放:",
|
||||
)
|
||||
: null,
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: isCurrentIndex ? FontWeight.bold : null,
|
||||
color: isCurrentIndex
|
||||
? primary
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (episode.badge != null) ...[
|
||||
if (episode.badge == '会员')
|
||||
Image.asset(
|
||||
'assets/images/big-vip.png',
|
||||
height: 20,
|
||||
semanticLabel: "大会员",
|
||||
)
|
||||
else
|
||||
Text(episode.badge),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
if (episode is! bangumi.EpisodeItem) Text('${index + 1}/$length'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (GStorage.collapsibleVideoPage && _isInit) {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
);
|
||||
}
|
||||
|
||||
return enableSlide
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(top: padding),
|
||||
child: buildPage,
|
||||
)
|
||||
: buildPage;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget get buildPage => Material(
|
||||
color: widget.showTitle == false
|
||||
? Colors.transparent
|
||||
: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: widget.showTitle != false ? 14 : 6),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.showTitle != false)
|
||||
Text(
|
||||
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: _favStream?.stream,
|
||||
builder: (context, snapshot) => snapshot.hasData
|
||||
? mediumButton(
|
||||
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
|
||||
icon: _seasonFav == 1
|
||||
? Icons.notifications_off_outlined
|
||||
: Icons.notifications_active_outlined,
|
||||
onPressed: () async {
|
||||
dynamic result = await VideoHttp.seasonFav(
|
||||
isFav: _seasonFav == 1,
|
||||
seasonId: widget.season.id,
|
||||
);
|
||||
if (result['status']) {
|
||||
SmartDialog.showToast(
|
||||
'${_seasonFav == 1 ? '取消' : ''}订阅成功');
|
||||
_seasonFav = _seasonFav == 1 ? 0 : 1;
|
||||
_favStream?.add(_seasonFav);
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至顶部',
|
||||
icon: Icons.vertical_align_top,
|
||||
onPressed: () {
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? 0
|
||||
: _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至底部',
|
||||
icon: Icons.vertical_align_bottom,
|
||||
onPressed: () {
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1
|
||||
: 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至当前',
|
||||
icon: Icons.my_location,
|
||||
onPressed: () async {
|
||||
if (_ctr != null && _ctr?.index != (_index)) {
|
||||
_ctr?.animateTo(_index);
|
||||
await Future.delayed(const Duration(milliseconds: 225));
|
||||
}
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
if (widget.isSupportReverse == true)
|
||||
if (!_isList)
|
||||
_reverseButton
|
||||
else
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.data == _index
|
||||
? _reverseButton
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) => mediumButton(
|
||||
tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
|
||||
icon: !reverse[snapshot.data]
|
||||
? MdiIcons.sortNumericAscending
|
||||
: MdiIcons.sortNumericDescending,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
reverse[_ctr?.index ?? 0] =
|
||||
!reverse[_ctr?.index ?? 0];
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.onClose != null)
|
||||
mediumButton(
|
||||
tooltip: '关闭',
|
||||
icon: Icons.close,
|
||||
onPressed: widget.onClose,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
if (_isList)
|
||||
TabBar(
|
||||
controller: _ctr,
|
||||
padding: const EdgeInsets.only(right: 60),
|
||||
isScrollable: true,
|
||||
tabs: (widget.season.sections as List)
|
||||
.map((item) => Tab(text: item.title))
|
||||
.toList(),
|
||||
dividerHeight: 1,
|
||||
dividerColor: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
Expanded(
|
||||
child: _isList
|
||||
? Material(
|
||||
color: Colors.transparent,
|
||||
child: tabBarView(
|
||||
controller: _ctr,
|
||||
children: List.generate(
|
||||
widget.season.sections.length,
|
||||
(index) => _buildBody(
|
||||
index, widget.season.sections[index].episodes),
|
||||
),
|
||||
),
|
||||
)
|
||||
: enableSlide
|
||||
? slideList()
|
||||
: buildList,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget get buildList => Material(
|
||||
color: Colors.transparent,
|
||||
child: _buildBody(null, episodes),
|
||||
);
|
||||
|
||||
Widget get _reverseButton => mediumButton(
|
||||
tooltip: widget.isReversed == true ? '正序播放' : '倒序播放',
|
||||
icon: widget.isReversed == true
|
||||
? MdiIcons.sortDescending
|
||||
: MdiIcons.sortAscending,
|
||||
onPressed: () async {
|
||||
if (widget.showTitle == false) {
|
||||
// jump to current
|
||||
if (_ctr != null && _ctr?.index != (_index)) {
|
||||
_ctr?.animateTo(_index);
|
||||
await Future.delayed(const Duration(milliseconds: 225));
|
||||
}
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
widget.onReverse?.call();
|
||||
},
|
||||
);
|
||||
|
||||
Widget _buildBody(i, episodes) => ScrollablePositionedList.separated(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 80,
|
||||
),
|
||||
reverse: reverse[i ?? 0],
|
||||
itemCount: episodes.length,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildEpisodeListItem(
|
||||
episodes[index],
|
||||
index,
|
||||
episodes.length,
|
||||
i != null
|
||||
? i == (_index)
|
||||
? currentIndex == index
|
||||
: false
|
||||
: currentIndex == index,
|
||||
);
|
||||
},
|
||||
itemScrollController: itemScrollController[i ?? 0],
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget get loadingWidget => Center(child: CircularProgressIndicator());
|
||||
@@ -11,7 +10,6 @@ Widget errorWidget({errMsg, callback}) => HttpError(
|
||||
);
|
||||
|
||||
Widget scrollErrorWidget({errMsg, callback}) => CustomScrollView(
|
||||
controller: ScrollController(),
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: errMsg,
|
||||
@@ -19,27 +17,3 @@ Widget scrollErrorWidget({errMsg, callback}) => CustomScrollView(
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
Widget replyErrorWidget(context, isSliver, errMsg, onReload) => HttpError(
|
||||
isSliver: isSliver,
|
||||
errMsg:
|
||||
'${errMsg.startsWith('gRPC Error') ? '如无法加载评论:\n关闭代理\n或设置中关闭使用gRPC加载评论\n\n' : ''}$errMsg',
|
||||
callback: onReload,
|
||||
extraWidget: errMsg.startsWith('gRPC Error') && GlobalData().grpcReply
|
||||
? FilledButton.tonal(
|
||||
onPressed: () {
|
||||
GlobalData().grpcReply = false;
|
||||
onReload();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||
return Theme.of(context).colorScheme.primary.withAlpha(20);
|
||||
}),
|
||||
),
|
||||
child: Text(
|
||||
'暂时关闭gRPC加载评论',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
||||
@@ -22,6 +22,7 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
this.isLongPic,
|
||||
this.callback,
|
||||
this.getPlaceHolder,
|
||||
this.boxFit,
|
||||
});
|
||||
|
||||
final String? src;
|
||||
@@ -38,6 +39,7 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
final Function? isLongPic;
|
||||
final Function? callback;
|
||||
final Function? getPlaceHolder;
|
||||
final BoxFit? boxFit;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -57,7 +59,7 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
|
||||
Widget _buildImage(context) {
|
||||
int? memCacheWidth, memCacheHeight;
|
||||
if (callback?.call() == true || width <= height) {
|
||||
if (ignoreHeight == true || callback?.call() == true || width <= height) {
|
||||
memCacheWidth = width.cacheSize(context);
|
||||
} else {
|
||||
memCacheHeight = height.cacheSize(context);
|
||||
@@ -68,7 +70,7 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
height: ignoreHeight == null || ignoreHeight == false ? height : null,
|
||||
memCacheWidth: memCacheWidth,
|
||||
memCacheHeight: memCacheHeight,
|
||||
fit: BoxFit.cover,
|
||||
fit: boxFit ?? BoxFit.cover,
|
||||
alignment:
|
||||
isLongPic?.call() == true ? Alignment.topCenter : Alignment.center,
|
||||
fadeOutDuration: fadeOutDuration ?? const Duration(milliseconds: 120),
|
||||
@@ -81,7 +83,6 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget placeholder(BuildContext context) {
|
||||
int cacheWidth = width.cacheSize(context);
|
||||
return Container(
|
||||
width: width,
|
||||
height: height,
|
||||
@@ -104,7 +105,7 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
: 'assets/images/loading.png',
|
||||
width: width,
|
||||
height: height,
|
||||
cacheWidth: cacheWidth == 0 ? null : cacheWidth,
|
||||
cacheWidth: width.cacheSize(context),
|
||||
// cacheHeight: height.cacheSize(context),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,3 +6,14 @@ class Pair<T, R> {
|
||||
T first;
|
||||
R second;
|
||||
}
|
||||
|
||||
class Triple<T, R, S> {
|
||||
Triple({
|
||||
required this.first,
|
||||
required this.second,
|
||||
required this.third,
|
||||
});
|
||||
T first;
|
||||
R second;
|
||||
S third;
|
||||
}
|
||||
|
||||
559
lib/common/widgets/save_panel.dart
Normal file
@@ -0,0 +1,559 @@
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/bangumi/introduction/controller.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/controller.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart';
|
||||
import 'package:PiliPlus/utils/download.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pretty_qr_code/pretty_qr_code.dart';
|
||||
import 'package:saver_gallery/saver_gallery.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class SavePanel extends StatefulWidget {
|
||||
const SavePanel({
|
||||
required this.item,
|
||||
// reply
|
||||
this.upMid,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final dynamic upMid;
|
||||
final dynamic item;
|
||||
|
||||
@override
|
||||
State<SavePanel> createState() => _SavePanelState();
|
||||
|
||||
static void toSavePanel({upMid, item}) {
|
||||
Get.generalDialog(
|
||||
barrierLabel: '',
|
||||
barrierDismissible: true,
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return SavePanel(upMid: upMid, item: item);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 255),
|
||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||
var tween = Tween<double>(begin: 0, end: 1)
|
||||
.chain(CurveTween(curve: Curves.easeInOut));
|
||||
return FadeTransition(
|
||||
opacity: animation.drive(tween),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
routeSettings: RouteSettings(arguments: Get.arguments),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SavePanelState extends State<SavePanel> {
|
||||
final boundaryKey = GlobalKey();
|
||||
|
||||
bool showBottom = true;
|
||||
|
||||
// item
|
||||
dynamic get _item => widget.item;
|
||||
late String viewType = '查看';
|
||||
late String itemType = '内容';
|
||||
|
||||
//reply
|
||||
String? cover;
|
||||
String? title;
|
||||
int? pubdate;
|
||||
String? uname;
|
||||
|
||||
String uri = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (_item is ReplyInfo) {
|
||||
itemType = '评论';
|
||||
final currentRoute = Get.currentRoute;
|
||||
late final hasRoot = _item.hasRoot();
|
||||
|
||||
if (currentRoute.startsWith('/video')) {
|
||||
try {
|
||||
final heroTag = Get.arguments?['heroTag'];
|
||||
late final ctr = Get.find<VideoIntroController>(tag: heroTag);
|
||||
cover = ctr.videoDetail.value.pic;
|
||||
title = ctr.videoDetail.value.title;
|
||||
pubdate = ctr.videoDetail.value.pubdate;
|
||||
uname = ctr.videoDetail.value.owner?.name;
|
||||
} catch (_) {}
|
||||
uri =
|
||||
'bilibili://video/${_item.oid}?comment_root_id=${hasRoot ? _item.root : _item.id}${hasRoot ? '&comment_secondary_id=${_item.id}' : ''}';
|
||||
|
||||
try {
|
||||
final heroTag = Get.arguments?['heroTag'];
|
||||
late final ctr = Get.find<BangumiIntroController>(tag: heroTag);
|
||||
final type = _item.type.toInt();
|
||||
late final oid = _item.oid;
|
||||
late final rootId = hasRoot ? _item.root : _item.id;
|
||||
late final anchor = hasRoot ? 'anchor=${_item.id}&' : '';
|
||||
uri =
|
||||
'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=bilibili://pgc/season/ep/${ctr.epId}';
|
||||
} catch (_) {}
|
||||
} else if (currentRoute.startsWith('/dynamicDetail')) {
|
||||
try {
|
||||
DynamicItemModel dynItem = Get.arguments['item'];
|
||||
uname = dynItem.modules?.moduleAuthor?.name;
|
||||
final type = _item.type.toInt();
|
||||
late final oid = dynItem.idStr;
|
||||
late final rootId = hasRoot ? _item.root : _item.id;
|
||||
late final anchor = hasRoot ? 'anchor=${_item.id}&' : '';
|
||||
late final enterUri = parseDyn(dynItem);
|
||||
viewType = '查看';
|
||||
itemType = '评论';
|
||||
uri = switch (type) {
|
||||
1 ||
|
||||
11 ||
|
||||
12 =>
|
||||
'bilibili://comment/detail/$type/${dynItem.basic!['rid_str']}/$rootId/?${anchor}enterUri=$enterUri',
|
||||
_ =>
|
||||
'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=$enterUri',
|
||||
};
|
||||
} catch (_) {}
|
||||
} else if (currentRoute.startsWith('/Scaffold')) {
|
||||
try {
|
||||
final type = _item.type.toInt();
|
||||
late final oid = Get.arguments['oid'];
|
||||
late final rootId = hasRoot ? _item.root : _item.id;
|
||||
late final anchor = hasRoot ? 'anchor=${_item.id}&' : '';
|
||||
late final enterUri = 'bilibili://following/detail/$oid';
|
||||
uri = switch (type) {
|
||||
1 ||
|
||||
11 ||
|
||||
12 =>
|
||||
'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=${Get.arguments['enterUri']}',
|
||||
_ =>
|
||||
'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=$enterUri',
|
||||
};
|
||||
} catch (_) {}
|
||||
} else if (currentRoute.startsWith('/htmlRender')) {
|
||||
try {
|
||||
final type = _item.type.toInt();
|
||||
late final oid = _item.oid;
|
||||
late final rootId = hasRoot ? _item.root : _item.id;
|
||||
late final anchor = hasRoot ? 'anchor=${_item.id}&' : '';
|
||||
late final enterUri =
|
||||
'bilibili://following/detail/${Get.parameters['id'] ?? Get.arguments?['id']}';
|
||||
uri =
|
||||
'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=$enterUri';
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
debugPrint(uri);
|
||||
} else if (_item is DynamicItemModel) {
|
||||
uri = parseDyn(_item);
|
||||
|
||||
debugPrint(uri);
|
||||
}
|
||||
}
|
||||
|
||||
String parseDyn(item) {
|
||||
String uri = '';
|
||||
try {
|
||||
switch (item.type) {
|
||||
case 'DYNAMIC_TYPE_AV':
|
||||
viewType = '观看';
|
||||
itemType = '视频';
|
||||
uri = 'bilibili://video/${item.basic!['comment_id_str']}';
|
||||
break;
|
||||
|
||||
case 'DYNAMIC_TYPE_ARTICLE':
|
||||
itemType = '专栏';
|
||||
uri = 'bilibili://following/detail/${item.idStr}';
|
||||
break;
|
||||
|
||||
case 'DYNAMIC_TYPE_LIVE_RCMD':
|
||||
viewType = '观看';
|
||||
itemType = '直播';
|
||||
final roomId = item.modules.moduleDynamic.major.liveRcmd.roomId;
|
||||
uri = 'bilibili://live/$roomId';
|
||||
break;
|
||||
|
||||
case 'DYNAMIC_TYPE_UGC_SEASON':
|
||||
viewType = '观看';
|
||||
itemType = '合集';
|
||||
int aid = item.modules.moduleDynamic.major.ugcSeason.aid;
|
||||
uri = 'bilibili://video/$aid';
|
||||
break;
|
||||
|
||||
case 'DYNAMIC_TYPE_PGC':
|
||||
case 'DYNAMIC_TYPE_PGC_UNION':
|
||||
viewType = '观看';
|
||||
itemType =
|
||||
item?.modules?.moduleDynamic?.major?.pgc?.badge?['text'] ?? '番剧';
|
||||
final epid = item.modules.moduleDynamic.major.pgc.epid;
|
||||
uri = 'bilibili://pgc/season/ep/$epid';
|
||||
break;
|
||||
|
||||
// https://www.bilibili.com/medialist/detail/ml12345678
|
||||
case 'DYNAMIC_TYPE_MEDIALIST':
|
||||
itemType = '收藏夹';
|
||||
final mediaId = item.modules.moduleDynamic.major.medialist!['id'];
|
||||
uri = 'bilibili://medialist/detail/$mediaId';
|
||||
break;
|
||||
|
||||
// 纯文字动态查看
|
||||
// case 'DYNAMIC_TYPE_WORD':
|
||||
// # 装扮/剧集点评/普通分享
|
||||
// case 'DYNAMIC_TYPE_COMMON_SQUARE':
|
||||
// 转发的动态
|
||||
// case 'DYNAMIC_TYPE_FORWARD':
|
||||
// 图文动态查看
|
||||
// case 'DYNAMIC_TYPE_DRAW':
|
||||
default:
|
||||
itemType = '动态';
|
||||
uri = 'bilibili://following/detail/${item.idStr}';
|
||||
break;
|
||||
}
|
||||
} catch (_) {}
|
||||
return uri;
|
||||
}
|
||||
|
||||
void _onSaveOrSharePic([bool isShare = false]) async {
|
||||
if (!isShare) {
|
||||
if (mounted &&
|
||||
!await DownloadUtils.checkPermissionDependOnSdkInt(context)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
SmartDialog.showLoading();
|
||||
try {
|
||||
RenderRepaintBoundary boundary = boundaryKey.currentContext!
|
||||
.findRenderObject() as RenderRepaintBoundary;
|
||||
var image = await boundary.toImage(pixelRatio: 3);
|
||||
ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);
|
||||
Uint8List pngBytes = byteData!.buffer.asUint8List();
|
||||
String picName =
|
||||
"plpl_reply_${DateTime.now().toString().substring(0, 19).replaceAll(RegExp(r'[- :]'), '')}";
|
||||
if (isShare) {
|
||||
Get.back();
|
||||
SmartDialog.dismiss();
|
||||
Share.shareXFiles(
|
||||
[
|
||||
XFile.fromData(
|
||||
pngBytes,
|
||||
name: picName,
|
||||
mimeType: 'image/png',
|
||||
)
|
||||
],
|
||||
sharePositionOrigin: await Utils.isIpad()
|
||||
? Rect.fromLTWH(0, 0, Get.width, Get.height / 2)
|
||||
: null,
|
||||
);
|
||||
} else {
|
||||
final result = await SaverGallery.saveImage(
|
||||
pngBytes,
|
||||
fileName: '$picName.png',
|
||||
androidRelativePath: "Pictures/PiliPlus",
|
||||
skipIfExists: false,
|
||||
);
|
||||
SmartDialog.dismiss();
|
||||
if (result.isSuccess) {
|
||||
Get.back();
|
||||
SmartDialog.showToast('保存成功');
|
||||
} else if (result.errorMessage?.isNotEmpty == true) {
|
||||
SmartDialog.showToast(result.errorMessage!);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('on save/share reply: $e');
|
||||
SmartDialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: Get.back,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(top: 12, bottom: 80),
|
||||
child: SafeArea(
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
width: min(Get.width, Get.height),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: RepaintBoundary(
|
||||
key: boundaryKey,
|
||||
child: Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: AnimatedSize(
|
||||
curve: Curves.easeInOut,
|
||||
alignment: Alignment.topCenter,
|
||||
duration: const Duration(milliseconds: 255),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_item is ReplyInfo)
|
||||
IgnorePointer(
|
||||
child: ReplyItemGrpc(
|
||||
replyItem: _item,
|
||||
replyLevel: '',
|
||||
needDivider: false,
|
||||
upMid: widget.upMid,
|
||||
),
|
||||
)
|
||||
else if (_item is DynamicItemModel)
|
||||
IgnorePointer(
|
||||
child: DynamicPanel(
|
||||
item: _item,
|
||||
source: 'detail',
|
||||
isSave: true,
|
||||
),
|
||||
),
|
||||
if (cover?.isNotEmpty == true &&
|
||||
title?.isNotEmpty == true)
|
||||
Container(
|
||||
height: 81,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin:
|
||||
const EdgeInsets.symmetric(horizontal: 12),
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
src: cover!,
|
||||
height: MediaQuery.textScalerOf(context)
|
||||
.scale(65),
|
||||
width: MediaQuery.textScalerOf(context)
|
||||
.scale(65) *
|
||||
16 /
|
||||
9,
|
||||
quality: 100,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'$title\n',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (pubdate != null) ...[
|
||||
const Spacer(),
|
||||
Text(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
pubdate! * 1000)
|
||||
.toString()
|
||||
.substring(0, 19),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
showBottom
|
||||
? Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
if (uri.isNotEmpty)
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (uname?.isNotEmpty ==
|
||||
true) ...[
|
||||
Text(
|
||||
'@$uname',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow
|
||||
.ellipsis,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
Text(
|
||||
'识别二维码,$viewType$itemType',
|
||||
textAlign: TextAlign.end,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
DateTime.now()
|
||||
.toString()
|
||||
.split('.')
|
||||
.first,
|
||||
textAlign: TextAlign.end,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
padding:
|
||||
const EdgeInsets.all(12),
|
||||
child: Container(
|
||||
color: Get.isDarkMode
|
||||
? Colors.white
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.surface,
|
||||
padding:
|
||||
const EdgeInsets.all(3),
|
||||
child: PrettyQrView.data(
|
||||
data: uri,
|
||||
decoration:
|
||||
const PrettyQrDecoration(
|
||||
shape:
|
||||
PrettyQrRoundedSymbol(
|
||||
borderRadius:
|
||||
BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Image.asset(
|
||||
'assets/images/logo/logo_2.png',
|
||||
width: 100,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.black54,
|
||||
],
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.only(bottom: 25, top: 10),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
iconButton(
|
||||
size: 42,
|
||||
tooltip: '关闭',
|
||||
context: context,
|
||||
icon: Icons.clear,
|
||||
onPressed: Get.back,
|
||||
bgColor: Theme.of(context).colorScheme.onInverseSurface,
|
||||
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 40),
|
||||
iconButton(
|
||||
size: 42,
|
||||
tooltip: showBottom ? '隐藏' : '显示',
|
||||
context: context,
|
||||
icon: showBottom
|
||||
? Icons.visibility_off
|
||||
: Icons.visibility,
|
||||
onPressed: () => setState(() {
|
||||
showBottom = !showBottom;
|
||||
})),
|
||||
const SizedBox(width: 40),
|
||||
iconButton(
|
||||
size: 42,
|
||||
tooltip: '分享',
|
||||
context: context,
|
||||
icon: Icons.share,
|
||||
onPressed: () => _onSaveOrSharePic(true),
|
||||
),
|
||||
const SizedBox(width: 40),
|
||||
iconButton(
|
||||
size: 42,
|
||||
tooltip: '保存',
|
||||
context: context,
|
||||
icon: Icons.save_alt,
|
||||
onPressed: _onSaveOrSharePic,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
97
lib/common/widgets/scroll_physics.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget videoTabBarView({
|
||||
required List<Widget> children,
|
||||
TabController? controller,
|
||||
}) =>
|
||||
TabBarView(
|
||||
physics: const CustomTabBarViewClampingScrollPhysics(),
|
||||
controller: controller,
|
||||
children: children,
|
||||
);
|
||||
|
||||
Widget tabBarView({
|
||||
required List<Widget> children,
|
||||
TabController? controller,
|
||||
}) =>
|
||||
TabBarView(
|
||||
physics: const CustomTabBarViewScrollPhysics(),
|
||||
controller: controller,
|
||||
children: children,
|
||||
);
|
||||
|
||||
class CustomTabBarViewScrollPhysics extends ScrollPhysics {
|
||||
const CustomTabBarViewScrollPhysics({super.parent});
|
||||
|
||||
@override
|
||||
CustomTabBarViewScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
return CustomTabBarViewScrollPhysics(parent: buildParent(ancestor));
|
||||
}
|
||||
|
||||
@override
|
||||
SpringDescription get spring => CustomSpringDescription();
|
||||
}
|
||||
|
||||
class CustomTabBarViewClampingScrollPhysics extends ClampingScrollPhysics {
|
||||
const CustomTabBarViewClampingScrollPhysics({super.parent});
|
||||
|
||||
@override
|
||||
CustomTabBarViewClampingScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
return CustomTabBarViewClampingScrollPhysics(parent: buildParent(ancestor));
|
||||
}
|
||||
|
||||
@override
|
||||
SpringDescription get spring => CustomSpringDescription();
|
||||
}
|
||||
|
||||
class PositionRetainedScrollPhysics extends AlwaysScrollableScrollPhysics {
|
||||
const PositionRetainedScrollPhysics({super.parent, this.shouldRetain = true});
|
||||
|
||||
final bool shouldRetain;
|
||||
|
||||
@override
|
||||
PositionRetainedScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
return PositionRetainedScrollPhysics(parent: buildParent(ancestor));
|
||||
}
|
||||
|
||||
@override
|
||||
double adjustPositionForNewDimensions({
|
||||
required ScrollMetrics oldPosition,
|
||||
required ScrollMetrics newPosition,
|
||||
required bool isScrolling,
|
||||
required double velocity,
|
||||
}) {
|
||||
final position = super.adjustPositionForNewDimensions(
|
||||
oldPosition: oldPosition,
|
||||
newPosition: newPosition,
|
||||
isScrolling: isScrolling,
|
||||
velocity: velocity,
|
||||
);
|
||||
|
||||
late final diff = newPosition.maxScrollExtent - oldPosition.maxScrollExtent;
|
||||
|
||||
if (shouldRetain && oldPosition.pixels == 0 && diff > 0) {
|
||||
return position + diff;
|
||||
} else {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CustomSpringDescription implements SpringDescription {
|
||||
@override
|
||||
final mass = GStorage.springDescription[0];
|
||||
|
||||
@override
|
||||
final stiffness = GStorage.springDescription[1];
|
||||
|
||||
@override
|
||||
final damping = GStorage.springDescription[2];
|
||||
|
||||
CustomSpringDescription._();
|
||||
|
||||
static final _instance = CustomSpringDescription._();
|
||||
|
||||
factory CustomSpringDescription() => _instance;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget videoTabBarView({
|
||||
required List<Widget> children,
|
||||
TabController? controller,
|
||||
}) =>
|
||||
TabBarView(
|
||||
physics: const CustomTabBarViewClampingScrollPhysics(),
|
||||
controller: controller,
|
||||
children: children,
|
||||
);
|
||||
|
||||
Widget tabBarView({
|
||||
required List<Widget> children,
|
||||
TabController? controller,
|
||||
}) =>
|
||||
TabBarView(
|
||||
physics: const CustomTabBarViewScrollPhysics(),
|
||||
controller: controller,
|
||||
children: children,
|
||||
);
|
||||
|
||||
class CustomTabBarViewScrollPhysics extends ScrollPhysics {
|
||||
const CustomTabBarViewScrollPhysics({super.parent});
|
||||
|
||||
@override
|
||||
CustomTabBarViewScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
return CustomTabBarViewScrollPhysics(parent: buildParent(ancestor));
|
||||
}
|
||||
|
||||
@override
|
||||
SpringDescription get spring => SpringDescription(
|
||||
mass: GStorage.springDescription[0],
|
||||
stiffness: GStorage.springDescription[1],
|
||||
damping: GStorage.springDescription[2],
|
||||
);
|
||||
}
|
||||
|
||||
class CustomTabBarViewClampingScrollPhysics extends ClampingScrollPhysics {
|
||||
const CustomTabBarViewClampingScrollPhysics({super.parent});
|
||||
|
||||
@override
|
||||
CustomTabBarViewClampingScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
return CustomTabBarViewClampingScrollPhysics(parent: buildParent(ancestor));
|
||||
}
|
||||
|
||||
@override
|
||||
SpringDescription get spring => SpringDescription(
|
||||
mass: GStorage.springDescription[0],
|
||||
stiffness: GStorage.springDescription[1],
|
||||
damping: GStorage.springDescription[2],
|
||||
);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
Widget statDanMu({
|
||||
required BuildContext context,
|
||||
String? theme,
|
||||
dynamic danmu,
|
||||
String? size,
|
||||
Color? textColor,
|
||||
}) {
|
||||
Map<String, Color> colorObject = {
|
||||
'white': Colors.white,
|
||||
'gray': Theme.of(context).colorScheme.outline.withOpacity(0.8),
|
||||
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||
};
|
||||
Color color = textColor ?? colorObject[theme]!;
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.subtitles_outlined,
|
||||
size: 14,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
Utils.numFormat(danmu!),
|
||||
style: TextStyle(
|
||||
fontSize: size == 'medium' ? 12 : 11,
|
||||
color: color,
|
||||
),
|
||||
overflow: TextOverflow.clip,
|
||||
semanticsLabel: '${Utils.numFormat(danmu!)}条弹幕',
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
93
lib/common/widgets/stat/stat.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class _StatItemBase extends StatelessWidget {
|
||||
final BuildContext context;
|
||||
final Object value;
|
||||
final String? theme;
|
||||
final String? size;
|
||||
final Color? textColor;
|
||||
final double iconSize;
|
||||
|
||||
const _StatItemBase({
|
||||
required this.context,
|
||||
required this.value,
|
||||
this.theme,
|
||||
this.size,
|
||||
this.textColor,
|
||||
this.iconSize = 13,
|
||||
});
|
||||
|
||||
IconData get iconData;
|
||||
String get semanticsLabel;
|
||||
|
||||
Color get color {
|
||||
return textColor ??
|
||||
switch (theme) {
|
||||
'gray' => Theme.of(context).colorScheme.outline.withOpacity(0.8),
|
||||
'black' => Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||
_ => Colors.white,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
iconData,
|
||||
size: iconSize,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
Utils.numFormat(value),
|
||||
style: TextStyle(fontSize: size == 'medium' ? 12 : 11, color: color),
|
||||
overflow: TextOverflow.clip,
|
||||
semanticsLabel: semanticsLabel,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StatView extends _StatItemBase {
|
||||
final String? goto;
|
||||
|
||||
const StatView({
|
||||
required super.context,
|
||||
required super.value,
|
||||
this.goto,
|
||||
super.theme,
|
||||
super.size,
|
||||
super.textColor,
|
||||
}) : super(iconSize: 13);
|
||||
|
||||
@override
|
||||
IconData get iconData => switch (goto) {
|
||||
'picture' => Icons.remove_red_eye_outlined,
|
||||
'like' => Icons.thumb_up_outlined,
|
||||
'reply' => Icons.comment_outlined,
|
||||
_ => Icons.play_circle_outlined,
|
||||
};
|
||||
|
||||
@override
|
||||
String get semanticsLabel =>
|
||||
'${Utils.numFormat(value)}次${goto == "picture" ? "浏览" : "播放"}';
|
||||
}
|
||||
|
||||
class StatDanMu extends _StatItemBase {
|
||||
const StatDanMu({
|
||||
required super.context,
|
||||
required super.value,
|
||||
super.theme,
|
||||
super.size,
|
||||
super.textColor,
|
||||
}) : super(iconSize: 14);
|
||||
|
||||
@override
|
||||
IconData get iconData => Icons.subtitles_outlined;
|
||||
|
||||
@override
|
||||
String get semanticsLabel => '${Utils.numFormat(value)}条弹幕';
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
Widget statView({
|
||||
required BuildContext context,
|
||||
String? theme,
|
||||
dynamic view,
|
||||
String? size,
|
||||
String? goto,
|
||||
Color? textColor,
|
||||
}) {
|
||||
Map<String, Color> colorObject = {
|
||||
'white': Colors.white,
|
||||
'gray': Theme.of(context).colorScheme.outline.withOpacity(0.8),
|
||||
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||
};
|
||||
Color color = textColor ?? colorObject[theme]!;
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
goto == 'picture'
|
||||
? Icons.remove_red_eye_outlined
|
||||
: Icons.play_circle_outlined,
|
||||
size: 13,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
Utils.numFormat(view!),
|
||||
style: TextStyle(
|
||||
fontSize: size == 'medium' ? 12 : 11,
|
||||
color: color,
|
||||
),
|
||||
overflow: TextOverflow.clip,
|
||||
semanticsLabel:
|
||||
'${Utils.numFormat(view!)}次${goto == "picture" ? "浏览" : "播放"}',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_progress_indicator.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';
|
||||
@@ -7,8 +10,7 @@ import '../../utils/utils.dart';
|
||||
import '../constants.dart';
|
||||
import 'badge.dart';
|
||||
import 'network_img_layer.dart';
|
||||
import 'stat/danmu.dart';
|
||||
import 'stat/view.dart';
|
||||
import 'stat/stat.dart';
|
||||
import 'video_popup_menu.dart';
|
||||
|
||||
// 视频卡片 - 水平布局
|
||||
@@ -25,7 +27,7 @@ class VideoCardH extends StatelessWidget {
|
||||
this.onLongPress,
|
||||
this.onViewLater,
|
||||
});
|
||||
final dynamic videoItem;
|
||||
final BaseVideoItemModel videoItem;
|
||||
final String source;
|
||||
final bool showOwner;
|
||||
final bool showView;
|
||||
@@ -37,15 +39,22 @@ 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(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Semantics(
|
||||
label: Utils.videoItemSemantics(videoItem),
|
||||
@@ -62,13 +71,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,
|
||||
);
|
||||
}
|
||||
@@ -82,9 +85,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;
|
||||
}
|
||||
}
|
||||
@@ -122,23 +127,49 @@ class VideoCardH extends StatelessWidget {
|
||||
BoxConstraints boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
num? progress;
|
||||
if (videoItem is HotVideoItemModel) {
|
||||
progress =
|
||||
(videoItem as HotVideoItemModel).progress;
|
||||
}
|
||||
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: videoItem.pic as String,
|
||||
src: videoItem.pic,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if (videoItem is HotVideoItemModel &&
|
||||
videoItem.pgcLabel?.isNotEmpty == true)
|
||||
if (videoItem is HotVideoItemModel)
|
||||
PBadge(
|
||||
text: videoItem.pgcLabel,
|
||||
text:
|
||||
(videoItem as HotVideoItemModel).pgcLabel,
|
||||
top: 6.0,
|
||||
right: 6.0,
|
||||
),
|
||||
if (videoItem.duration != 0)
|
||||
if (progress != null && progress != 0) ...[
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration!),
|
||||
text: progress == -1
|
||||
? '已看完'
|
||||
: '${Utils.timeFormat(progress)}/${Utils.timeFormat(videoItem.duration)}',
|
||||
right: 6,
|
||||
bottom: 8,
|
||||
type: 'gray',
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: videoProgressIndicator(
|
||||
progress == -1
|
||||
? 1
|
||||
: progress / videoItem.duration,
|
||||
),
|
||||
)
|
||||
] else if (videoItem.duration > 0)
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
@@ -181,7 +212,7 @@ class VideoCardH extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget videoContent(context) {
|
||||
Widget videoContent(BuildContext context) {
|
||||
String pubdate = showPubdate
|
||||
? Utils.dateFormat(videoItem.pubdate!, formatType: 'day')
|
||||
: '';
|
||||
@@ -190,7 +221,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,
|
||||
@@ -203,31 +260,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 &&
|
||||
@@ -265,18 +297,18 @@ class VideoCardH extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
if (showView) ...[
|
||||
statView(
|
||||
StatView(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
view: videoItem.stat.view as int,
|
||||
value: videoItem.stat.viewStr,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
if (showDanmaku)
|
||||
statDanMu(
|
||||
StatDanMu(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
danmu: videoItem.stat.danmu as int,
|
||||
value: videoItem.stat.danmuStr,
|
||||
),
|
||||
const Spacer(),
|
||||
if (source == 'normal') const SizedBox(width: 24),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/danmu.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/view.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_popup_menu.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_progress_indicator.dart';
|
||||
import 'package:PiliPlus/models/space_archive/item.dart';
|
||||
@@ -18,10 +17,12 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
required this.videoItem,
|
||||
this.onTap,
|
||||
this.bvid,
|
||||
this.fromViewAid,
|
||||
});
|
||||
final Item videoItem;
|
||||
final VoidCallback? onTap;
|
||||
final dynamic bvid;
|
||||
final String? fromViewAid;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -43,12 +44,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),
|
||||
},
|
||||
@@ -83,6 +84,25 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if (fromViewAid == videoItem.param)
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.black54,
|
||||
),
|
||||
child: Center(
|
||||
child: const Text(
|
||||
'上次观看',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 15,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (videoItem.badges?.isNotEmpty == true)
|
||||
PBadge(
|
||||
text: videoItem.badges!
|
||||
@@ -90,15 +110,11 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
.join('|'),
|
||||
right: 6.0,
|
||||
top: 6.0,
|
||||
type: videoItem.badges!.first.text == '充电专属'
|
||||
? 'error'
|
||||
: 'primary',
|
||||
),
|
||||
if (videoItem.duration != null)
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
if (videoItem.history != null)
|
||||
if (videoItem.history != null) ...[
|
||||
Builder(builder: (context) {
|
||||
try {
|
||||
return Positioned(
|
||||
@@ -114,6 +130,34 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Builder(builder: (context) {
|
||||
try {
|
||||
return PBadge(
|
||||
text: videoItem.history!['progress'] ==
|
||||
videoItem.history!['duration']
|
||||
? '已看完'
|
||||
: '${Utils.timeFormat(videoItem.history!['progress'])}/${Utils.timeFormat(videoItem.history!['duration'])}',
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
);
|
||||
} catch (_) {
|
||||
return PBadge(
|
||||
text:
|
||||
Utils.timeFormat(videoItem.duration),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
);
|
||||
}
|
||||
}),
|
||||
] else if (videoItem.duration > 0)
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
@@ -148,7 +192,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
|
||||
@@ -180,19 +224,19 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
const SizedBox(height: 3),
|
||||
Row(
|
||||
children: [
|
||||
statView(
|
||||
StatView(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
// view: videoItem.season?['view_content'] ??
|
||||
// videoItem.viewContent,
|
||||
view: videoItem.viewContent,
|
||||
value: videoItem.stat.viewStr,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
statDanMu(
|
||||
StatDanMu(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
// danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku,
|
||||
danmu: videoItem.danmaku,
|
||||
value: videoItem.stat.danmuStr,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -5,9 +5,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../models/home/rcmd/result.dart';
|
||||
import '../../models/model_rec_video_item.dart';
|
||||
import 'stat/danmu.dart';
|
||||
import 'stat/view.dart';
|
||||
import '../../http/dynamics.dart';
|
||||
import 'stat/stat.dart';
|
||||
import '../../utils/id_utils.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../constants.dart';
|
||||
@@ -17,7 +15,7 @@ import 'video_popup_menu.dart';
|
||||
|
||||
// 视频卡片 - 垂直布局
|
||||
class VideoCardV extends StatelessWidget {
|
||||
final dynamic videoItem;
|
||||
final BaseRecVideoItemModel videoItem;
|
||||
final VoidCallback? onRemove;
|
||||
|
||||
const VideoCardV({
|
||||
@@ -32,14 +30,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);
|
||||
}
|
||||
@@ -56,32 +54,21 @@ 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}';
|
||||
}
|
||||
if (uri.startsWith('http')) {
|
||||
String path = Uri.parse(uri).path;
|
||||
if (isStringNumeric(path.split('/')[1])) {
|
||||
// 请求接口
|
||||
var res =
|
||||
await DynamicsHttp.dynamicDetail(id: path.split('/')[1]);
|
||||
if (res['status']) {
|
||||
Get.toNamed('/dynamicDetail', arguments: {
|
||||
'item': res['data'],
|
||||
'floor': 1,
|
||||
'action': 'detail'
|
||||
});
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
String id = Uri.parse(uri).path.split('/')[1];
|
||||
if (isStringNumeric(id)) {
|
||||
Utils.pushDynFromId(id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -96,8 +83,8 @@ class VideoCardV extends StatelessWidget {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
SmartDialog.showToast(videoItem.goto);
|
||||
Utils.handleWebview(videoItem.uri);
|
||||
SmartDialog.showToast(goto);
|
||||
Utils.handleWebview(videoItem.uri!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +102,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,
|
||||
@@ -180,7 +167,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,
|
||||
@@ -224,7 +211,7 @@ class VideoCardV extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
],
|
||||
if (videoItem.isFollowed == 1) ...[
|
||||
if (videoItem.isFollowed) ...[
|
||||
const PBadge(
|
||||
text: '已关注',
|
||||
stack: 'normal',
|
||||
@@ -260,18 +247,18 @@ class VideoCardV extends StatelessWidget {
|
||||
Widget videoStat(context) {
|
||||
return Row(
|
||||
children: [
|
||||
statView(
|
||||
StatView(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
view: videoItem.stat.view,
|
||||
value: videoItem.stat.viewStr,
|
||||
goto: videoItem.goto,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
if (videoItem.goto != 'picture')
|
||||
statDanMu(
|
||||
StatDanMu(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
danmu: videoItem.stat.danmu,
|
||||
value: videoItem.stat.danmuStr,
|
||||
),
|
||||
if (videoItem is RecVideoItemModel) ...<Widget>[
|
||||
const Spacer(),
|
||||
@@ -295,7 +282,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,
|
||||
@@ -311,7 +298,7 @@ class VideoCardV extends StatelessWidget {
|
||||
.withOpacity(0.8),
|
||||
),
|
||||
text: Utils.shortenChineseDateString(
|
||||
videoItem.desc.split(' · ').last)),
|
||||
videoItem.desc!.split(' · ').last)),
|
||||
)),
|
||||
const SizedBox(width: 2),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/http/search.dart';
|
||||
import 'package:PiliPlus/models/space/item.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import '../../utils/utils.dart';
|
||||
@@ -28,9 +30,15 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
}
|
||||
String bvid = videoItem.bvid ?? '';
|
||||
String? aid = videoItem.param;
|
||||
String? bvid = videoItem.bvid;
|
||||
if (aid == null && bvid == null) {
|
||||
return;
|
||||
}
|
||||
int? cid = videoItem.firstCid;
|
||||
cid ??= await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
Utils.toViewPage(
|
||||
'bvid=$bvid&cid=${videoItem.firstCid}',
|
||||
'bvid=${bvid ?? IdUtils.av2bv(int.parse(aid!))}&cid=$cid',
|
||||
arguments: {
|
||||
// 'videoItem': videoItem,
|
||||
'pic': videoItem.cover,
|
||||
|
||||
@@ -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'] ?? '成功');
|
||||
},
|
||||
|
||||
@@ -49,7 +49,7 @@ class Api {
|
||||
/// aid num 稿件avid 必要(可选) avid与bvid任选一个
|
||||
/// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
|
||||
// https://api.bilibili.com/x/web-interface/archive/has/like
|
||||
static const String hasLikeVideo = '/x/web-interface/archive/has/like';
|
||||
// static const String hasLikeVideo = '/x/web-interface/archive/has/like';
|
||||
|
||||
static const String bangumiLikeCoinFav = '/pgc/season/episode/community';
|
||||
|
||||
@@ -79,7 +79,19 @@ class Api {
|
||||
/// aid num 稿件avid 必要(可选) avid与bvid任选一个
|
||||
/// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
|
||||
/// https://api.bilibili.com/x/web-interface/archive/coins
|
||||
static const String hasCoinVideo = '/x/web-interface/archive/coins';
|
||||
// static const String hasCoinVideo = '/x/web-interface/archive/coins';
|
||||
|
||||
/// 收藏夹 详情
|
||||
/// media_id 当前收藏夹id 搜索全部时为默认收藏夹id
|
||||
/// pn int 当前页
|
||||
/// ps int pageSize
|
||||
/// keyword String 搜索词
|
||||
/// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿
|
||||
/// tid int 分区id
|
||||
/// platform web
|
||||
/// type 0 当前收藏夹 1 全部收藏夹
|
||||
// https://api.bilibili.com/x/v3/fav/resource/list?media_id=76614671&pn=1&ps=20&keyword=&order=mtime&type=0&tid=0
|
||||
static const String favResourceList = '/x/v3/fav/resource/list';
|
||||
|
||||
// 收藏视频(双端)POST
|
||||
// access_key str APP登录Token APP方式必要
|
||||
@@ -96,10 +108,20 @@ class Api {
|
||||
|
||||
static const String delFav = '/x/v3/fav/resource/batch-del';
|
||||
|
||||
static const String copyFav = '/x/v3/fav/resource/copy';
|
||||
|
||||
static const String moveFav = '/x/v3/fav/resource/move';
|
||||
|
||||
static const String cleanFav = '/x/v3/fav/resource/clean';
|
||||
|
||||
static const String sortFav = '/x/v3/fav/resource/sort';
|
||||
|
||||
static const String sortFavFolder = '/x/v3/fav/folder/sort';
|
||||
|
||||
// 判断视频是否被收藏(双端)GET
|
||||
/// aid
|
||||
// https://api.bilibili.com/x/v2/fav/video/favoured
|
||||
static const String hasFavVideo = '/x/v2/fav/video/favoured';
|
||||
// static const String hasFavVideo = '/x/v2/fav/video/favoured';
|
||||
|
||||
// 分享视频 (Web端) POST
|
||||
// https://api.bilibili.com/x/web-interface/share/add
|
||||
@@ -123,10 +145,6 @@ class Api {
|
||||
// rid num 目标 视频稿件avid
|
||||
static const String favFolder = '/x/v3/fav/folder/created/list-all';
|
||||
|
||||
static const String copyFav = '/x/v3/fav/resource/copy';
|
||||
|
||||
static const String moveFav = '/x/v3/fav/resource/move';
|
||||
|
||||
static const String copyToview = '/x/v2/history/toview/copy';
|
||||
|
||||
static const String moveToview = '/x/v2/history/toview/move';
|
||||
@@ -135,7 +153,9 @@ class Api {
|
||||
static const String relatedList = '/x/web-interface/archive/related';
|
||||
|
||||
// 查询用户与自己关系_仅查关注
|
||||
static const String hasFollow = '/x/relation';
|
||||
static const String relation = '/x/relation';
|
||||
|
||||
static const String relations = '/x/relation/relations';
|
||||
|
||||
// 操作用户关系
|
||||
static const String relationMod = '/x/relation/modify';
|
||||
@@ -190,20 +210,6 @@ class Api {
|
||||
|
||||
static const String deleteFolder = '/x/v3/fav/folder/del';
|
||||
|
||||
static const String cleanFav = '/x/v3/fav/resource/clean';
|
||||
|
||||
/// 收藏夹 详情
|
||||
/// media_id 当前收藏夹id 搜索全部时为默认收藏夹id
|
||||
/// pn int 当前页
|
||||
/// ps int pageSize
|
||||
/// keyword String 搜索词
|
||||
/// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿
|
||||
/// tid int 分区id
|
||||
/// platform web
|
||||
/// type 0 当前收藏夹 1 全部收藏夹
|
||||
// https://api.bilibili.com/x/v3/fav/resource/list?media_id=76614671&pn=1&ps=20&keyword=&order=mtime&type=0&tid=0
|
||||
static const String userFavFolderDetail = '/x/v3/fav/resource/list';
|
||||
|
||||
// 正在直播的up & 关注的up
|
||||
// https://api.bilibili.com/x/polymer/web-dynamic/v1/portal
|
||||
static const String followUp = '/x/polymer/web-dynamic/v1/portal';
|
||||
@@ -219,7 +225,7 @@ class Api {
|
||||
'${HttpString.tUrl}/dynamic_like/v1/dynamic_like/thumb';
|
||||
|
||||
// 获取稍后再看
|
||||
static const String seeYouLater = '/x/v2/history/toview';
|
||||
static const String seeYouLater = '/x/v2/history/toview/web';
|
||||
|
||||
// 获取历史记录
|
||||
static const String historyList = '/x/web-interface/history/cursor';
|
||||
@@ -257,6 +263,11 @@ class Api {
|
||||
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/report.md
|
||||
static const String heartBeat = '/x/click-interface/web/heartbeat';
|
||||
|
||||
static const String historyReport = '/x/v2/history/report';
|
||||
|
||||
static const String roomEntryAction =
|
||||
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction';
|
||||
|
||||
static const String mediaListHistory = '/x/v1/medialist/history';
|
||||
|
||||
// 查询视频分P列表 (avid/bvid转cid)
|
||||
@@ -277,10 +288,6 @@ class Api {
|
||||
// https://api.bilibili.com/x/relation/tag?mid=17340771&tagid=-10&pn=1&ps=20
|
||||
static const String tagFollowings = '/x/relation/tag';
|
||||
|
||||
// 关注分类
|
||||
// https://api.bilibili.com/x/relation/tags
|
||||
static const String followingsClass = '/x/relation/tags';
|
||||
|
||||
// 搜索follow
|
||||
static const followSearch = '/x/relation/followings/search';
|
||||
|
||||
@@ -324,6 +331,9 @@ class Api {
|
||||
static const String spaceArchive =
|
||||
'${HttpString.appBaseUrl}/x/v2/space/archive/cursor';
|
||||
|
||||
static const String spaceStory =
|
||||
'${HttpString.appBaseUrl}/x/v2/feed/index/space/story/cursor';
|
||||
|
||||
static const String spaceChargingArchive =
|
||||
'${HttpString.appBaseUrl}/x/v2/space/archive/charging';
|
||||
|
||||
@@ -362,7 +372,9 @@ class Api {
|
||||
static const String memberArchive = '/x/space/wbi/arc/search';
|
||||
|
||||
// 用户动态搜索
|
||||
static const String memberDynamicSearch = '/x/space/dynamic/search';
|
||||
// static const String memberDynamicSearch = '/x/space/dynamic/search';
|
||||
static const String memberDynamicSearch =
|
||||
'/x/polymer/web-dynamic/v1/feed/space/search';
|
||||
|
||||
// 用户动态
|
||||
static const String memberDynamic = '/x/polymer/web-dynamic/v1/feed/space';
|
||||
@@ -371,7 +383,7 @@ class Api {
|
||||
static const String toViewLater = '/x/v2/history/toview/add';
|
||||
|
||||
// 移除已观看
|
||||
static const String toViewDel = '/x/v2/history/toview/del';
|
||||
static const String toViewDel = '/x/v2/history/toview/v2/dels';
|
||||
|
||||
// 清空稍后再看
|
||||
static const String toViewClear = '/x/v2/history/toview/clear';
|
||||
@@ -406,7 +418,7 @@ class Api {
|
||||
'/pgc/season/index/result?st=1&order=3&season_version=-1&spoken_language_type=-1&area=-1&is_finish=-1©right=-1&season_status=-1&season_month=-1&year=-1&style_id=-1&sort=0&season_type=1&pagesize=20&type=1';
|
||||
|
||||
// 我的追番/追剧 ?type=1&pn=1&ps=15
|
||||
static const String bangumiFollow = '/x/space/bangumi/follow/list';
|
||||
static const String bangumiFollowList = '/x/space/bangumi/follow/list';
|
||||
|
||||
// 黑名单
|
||||
static const String blackLst = '/x/relation/blacks';
|
||||
@@ -638,9 +650,6 @@ class Api {
|
||||
/// 最近点赞的视频
|
||||
static const getRecentLikeVideoApi = '/x/space/like/video';
|
||||
|
||||
/// 最近追番
|
||||
static const getRecentBangumiApi = '/x/space/bangumi/follow/list';
|
||||
|
||||
/// 用户专栏
|
||||
static const getMemberSeasonsApi = '/x/polymer/web-space/home/seasons_series';
|
||||
|
||||
@@ -672,9 +681,6 @@ class Api {
|
||||
/// 我的订阅-合集详情
|
||||
static const favSeasonList = '/x/space/fav/season/list';
|
||||
|
||||
/// 我的订阅-播单详情
|
||||
static const favResourceList = '/x/v3/fav/resource/list';
|
||||
|
||||
/// 发送私信
|
||||
static const String sendMsg = '${HttpString.tUrl}/web_im/v1/web_im/send_msg';
|
||||
|
||||
@@ -693,7 +699,9 @@ class Api {
|
||||
|
||||
static const String removeMsg = '/session_svr/v1/session_svr/remove_session';
|
||||
|
||||
static const String removeSysMsg = '/x/sys-msg/del_notify_list';
|
||||
static const String delSysMsg = '/x/sys-msg/del_notify_list';
|
||||
|
||||
static const String delMsgfeed = '/x/msgfeed/del';
|
||||
|
||||
static const String setTop = '/session_svr/v1/session_svr/set_top';
|
||||
|
||||
@@ -707,6 +715,7 @@ class Api {
|
||||
|
||||
static const String uploadImage = '/x/upload/web/image';
|
||||
|
||||
// 点赞投币收藏关注
|
||||
static const String videoRelation = '/x/web-interface/archive/relation';
|
||||
|
||||
static const String seasonFav = '/x/v3/fav/season/'; // + fav unfav
|
||||
@@ -722,5 +731,37 @@ class Api {
|
||||
|
||||
static const String pgcIndexResult = '/pgc/season/index/result';
|
||||
|
||||
static const String noteList = '/x/note/publish/list/archive';
|
||||
static const String archiveNoteList = '/x/note/publish/list/archive';
|
||||
|
||||
static const String noteList = '/x/note/list';
|
||||
|
||||
static const String userNoteList = '/x/note/publish/list/user';
|
||||
|
||||
static const String addNote = '/x/note/add';
|
||||
|
||||
static const String delNote = '/x/note/del';
|
||||
|
||||
static const String delPublishNote = '/x/note/publish/del';
|
||||
|
||||
static const String archiveNote = '/x/note/list/archive';
|
||||
|
||||
static const String favArticle = '/x/polymer/web-dynamic/v1/opus/feed/fav';
|
||||
|
||||
static const String communityAction =
|
||||
'/x/community/cosmo/interface/simple_action';
|
||||
|
||||
static const String delFavArticle = '/x/article/favorites/del';
|
||||
|
||||
static const String addFavArticle = '/x/article/favorites/add';
|
||||
|
||||
static const String replyTop = '/x/v2/reply/top';
|
||||
|
||||
static const String getCoin = '${HttpString.accountBaseUrl}/site/getCoin';
|
||||
|
||||
static const String getLiveEmoticons =
|
||||
'${HttpString.liveBaseUrl}/xlive/web-ucenter/v2/emoticon/GetEmoticons';
|
||||
|
||||
static const String pgcTimeline = '/pgc/web/timeline';
|
||||
|
||||
static const String searchTrending = '/x/v2/search/trending/ranking';
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/bangumi/pgc_timeline/pgc_timeline.dart';
|
||||
import 'package:PiliPlus/models/bangumi/pgc_timeline/result.dart';
|
||||
|
||||
import '../models/bangumi/list.dart';
|
||||
import '../models/bangumi/pgc_index/condition.dart';
|
||||
@@ -50,7 +52,7 @@ class BangumiHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> bangumiList({
|
||||
static Future<LoadingState<List<BangumiListItemModel>?>> bangumiList({
|
||||
int? page,
|
||||
int? indexType,
|
||||
}) async {
|
||||
@@ -67,20 +69,41 @@ class BangumiHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> bangumiFollow({
|
||||
dynamic mid,
|
||||
static Future<LoadingState<BangumiListDataModel>> bangumiFollowList({
|
||||
required dynamic mid,
|
||||
required int type,
|
||||
required int pn,
|
||||
int? followStatus,
|
||||
}) async {
|
||||
var res = await Request().get(Api.bangumiFollow, queryParameters: {
|
||||
var res = await Request().get(Api.bangumiFollowList, queryParameters: {
|
||||
'vmid': mid,
|
||||
'type': type,
|
||||
if (followStatus != null) 'follow_status': followStatus,
|
||||
'pn': pn,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
BangumiListDataModel data =
|
||||
BangumiListDataModel.fromJson(res.data['data']);
|
||||
return LoadingState.success(data);
|
||||
return LoadingState.success(
|
||||
BangumiListDataModel.fromJson(res.data['data']));
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<List<Result>?>> pgcTimeline({
|
||||
int types = 1, // 1:`番剧`<br />3:`电影`<br />4:`国创` |
|
||||
required int before,
|
||||
required int after,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.pgcTimeline,
|
||||
queryParameters: {
|
||||
'types': types,
|
||||
'before': before,
|
||||
'after': after,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(PgcTimeline.fromJson(res.data).result);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import '../models/user/black.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class BlackHttp {
|
||||
static Future<LoadingState> blackList({required int pn, int? ps}) async {
|
||||
static Future<LoadingState<BlackListDataModel>> blackList(
|
||||
{required int pn, int? ps}) async {
|
||||
var res = await Request().get(Api.blackLst, queryParameters: {
|
||||
'pn': pn,
|
||||
'ps': ps ?? 50,
|
||||
|
||||
@@ -8,5 +8,7 @@ class HttpString {
|
||||
static const String messageBaseUrl = 'https://message.bilibili.com';
|
||||
static const String dynamicShareBaseUrl = 'https://t.bilibili.com';
|
||||
static const String spaceBaseUrl = 'https://space.bilibili.com';
|
||||
static const String accountBaseUrl = 'https://account.bilibili.com';
|
||||
|
||||
static const String sponsorBlockBaseUrl = 'https://www.bsbsb.top';
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class DanmakuHttp {
|
||||
await GrpcRepo.dmSegMobile(cid: cid, segmentIndex: segmentIndex);
|
||||
if (!response['status']) {
|
||||
if (queryCount >= 3) {
|
||||
return DmSegMobileReply();
|
||||
return {'status': false};
|
||||
} else {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return await queryDanmaku(
|
||||
@@ -43,7 +43,7 @@ class DanmakuHttp {
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
return {'status': true, 'data': data};
|
||||
}
|
||||
|
||||
static Future shootDanmaku({
|
||||
@@ -60,7 +60,7 @@ class DanmakuHttp {
|
||||
int? fontsize, // 弹幕字号(默认25)
|
||||
int? pool, // 弹幕池选择(0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕)默认普通池,0)
|
||||
//int? rnd,// 当前时间戳*1000000(若无此项,则发送弹幕冷却时间限制为90s;若有此项,则发送弹幕冷却时间限制为5s)
|
||||
int? colorful, //60001:专属渐变彩色(需要会员)
|
||||
bool? colorful, //60001:专属渐变彩色(需要会员)
|
||||
int? checkbox_type, //是否带 UP 身份标识(0:普通;4:带有标识)
|
||||
// String? csrf,//CSRF Token(位于 Cookie) Cookie 方式必要
|
||||
// String? access_key,// APP 登录 Token APP 方式必要
|
||||
@@ -77,11 +77,11 @@ class DanmakuHttp {
|
||||
//'aid': aid,
|
||||
'bvid': bvid,
|
||||
'progress': progress,
|
||||
'color': color,
|
||||
'color': colorful == true ? null : color,
|
||||
'fontsize': fontsize,
|
||||
'pool': pool,
|
||||
'rnd': DateTime.now().microsecondsSinceEpoch,
|
||||
'colorful': colorful,
|
||||
'colorful': colorful == true ? 60001 : null,
|
||||
'checkbox_type': checkbox_type,
|
||||
'csrf': await Request.getCsrf(),
|
||||
// 'access_key': access_key,
|
||||
@@ -97,7 +97,6 @@ class DanmakuHttp {
|
||||
if (response.statusCode != 200) {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '弹幕发送失败,状态码:${response.statusCode}',
|
||||
};
|
||||
}
|
||||
@@ -109,7 +108,6 @@ class DanmakuHttp {
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': "${response.data['code']}: ${response.data['message']}",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ class DanmakuFilterHttp {
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import '../models/dynamics/up.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class DynamicsHttp {
|
||||
static Future<LoadingState> followDynamic({
|
||||
static Future<LoadingState<DynamicsDataModel>> followDynamic({
|
||||
String? type,
|
||||
String? offset,
|
||||
int? mid,
|
||||
@@ -53,11 +53,7 @@ class DynamicsHttp {
|
||||
'data': FollowUpModel.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,11 +76,7 @@ class DynamicsHttp {
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +94,7 @@ class DynamicsHttp {
|
||||
if (id != null) 'id': id,
|
||||
if (rid != null) 'rid': rid,
|
||||
if (type != null) 'type': type,
|
||||
'features': 'itemOpusStyle',
|
||||
'features': 'itemOpusStyle,listOnlyfans',
|
||||
},
|
||||
options:
|
||||
clearCookie ? Options(extra: {'account': AnonymousAccount()}) : null,
|
||||
@@ -116,14 +108,12 @@ class DynamicsHttp {
|
||||
} catch (err) {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': err.toString(),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import '../models/fans/result.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class FanHttp {
|
||||
static Future<LoadingState> fans(
|
||||
static Future<LoadingState<FansDataModel>> fans(
|
||||
{int? vmid, int? pn, int? ps, String? orderType}) async {
|
||||
var res = await Request().get(Api.fans, queryParameters: {
|
||||
'vmid': vmid,
|
||||
|
||||
@@ -17,11 +17,7 @@ class FollowHttp {
|
||||
'data': FollowDataModel.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,19 +64,32 @@ class HtmlHttp {
|
||||
.innerHtml;
|
||||
} catch (_) {}
|
||||
|
||||
String commentId = opusDetail
|
||||
List comment = opusDetail
|
||||
.querySelector('.bili-comment-container')!
|
||||
.className
|
||||
.split(' ')[1]
|
||||
.split('-')[2];
|
||||
.split('-');
|
||||
// List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
|
||||
|
||||
dynamic mid;
|
||||
try {
|
||||
final regex = RegExp(r'window\.__INITIAL_STATE__\s*=\s*(\{.*?\});');
|
||||
final match = regex.firstMatch(response.data);
|
||||
if (match != null) {
|
||||
final json = jsonDecode(match.group(1)!);
|
||||
mid = json['detail']['basic']['uid'];
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
return {
|
||||
'status': true,
|
||||
'mid': mid,
|
||||
'avatar': avatar,
|
||||
'uname': uname,
|
||||
'updateTime': updateTime,
|
||||
'content': (test ?? '') + opusContent,
|
||||
'commentId': int.parse(commentId)
|
||||
'commentType': int.parse(comment[1]),
|
||||
'commentId': int.parse(comment[2]),
|
||||
};
|
||||
} catch (err) {
|
||||
debugPrint('err: $err');
|
||||
@@ -114,12 +127,11 @@ class HtmlHttp {
|
||||
// String avatar =
|
||||
// authorHeader.querySelector('.bili-avatar-img')!.attributes['data-src']!;
|
||||
// 正则寻找形如"author":{"mid":\d+,"name":".*","face":"xxxx"的匹配项
|
||||
String avatar =
|
||||
RegExp(r'"author":\{"mid":\d+?,"name":".+?","face":"(.+?)"')
|
||||
.firstMatch(response.data)!
|
||||
.group(1)!
|
||||
.replaceAll(r'\u002F', '/')
|
||||
.split('@')[0];
|
||||
final match =
|
||||
RegExp(r'"author":\{"mid":(\d+)?,"name":".+?","face":"(.+?)"')
|
||||
.firstMatch(response.data)!;
|
||||
String mid = match.group(1)!;
|
||||
String avatar = match.group(2)!.replaceAll(r'\u002F', '/').split('@')[0];
|
||||
// debugPrint(avatar);
|
||||
String uname = authorHeader.querySelector('.up-name')!.text.trim();
|
||||
// 动态详情
|
||||
@@ -159,11 +171,10 @@ class HtmlHttp {
|
||||
}
|
||||
}
|
||||
|
||||
RegExp digitRegExp = RegExp(r'\d+');
|
||||
Iterable<Match> matches = digitRegExp.allMatches(id);
|
||||
String number = matches.first.group(0)!;
|
||||
String number = RegExp(r'\d+').firstMatch(id)!.group(0)!;
|
||||
return {
|
||||
'status': true,
|
||||
'mid': mid,
|
||||
'avatar': avatar,
|
||||
'uname': uname,
|
||||
'updateTime': '',
|
||||
|
||||
@@ -1,40 +1,34 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math' show Random;
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/http/retry_interceptor.dart';
|
||||
import 'package:PiliPlus/http/user.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account_manager/account_mgr.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:brotli/brotli.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../utils/storage.dart';
|
||||
import 'api.dart';
|
||||
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 =
|
||||
RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
|
||||
// static final _rand = Random();
|
||||
// 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();
|
||||
@@ -49,6 +43,22 @@ class Request {
|
||||
isSecure: item.secure,
|
||||
isHttpOnly: item.httpOnly,
|
||||
)));
|
||||
|
||||
if (Accounts.main.isLogin) {
|
||||
final coin = GStorage.userInfo.get('userInfoCache')?.money;
|
||||
if (coin == null) {
|
||||
setCoin();
|
||||
} else {
|
||||
GlobalData().coins = coin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> setCoin() async {
|
||||
final res = await UserHttp.getCoin();
|
||||
if (res['status']) {
|
||||
GlobalData().coins = res['data'];
|
||||
}
|
||||
}
|
||||
|
||||
// 从cookie中获取 csrf token
|
||||
@@ -56,36 +66,36 @@ class Request {
|
||||
return Accounts.main.csrf;
|
||||
}
|
||||
|
||||
static Future<void> buvidActive(Account account) async {
|
||||
// 这样线程不安全, 但仍按预期进行
|
||||
if (account.activited) return;
|
||||
account.activited = true;
|
||||
try {
|
||||
final html = await Request().get(Api.dynamicSpmPrefix,
|
||||
options: Options(extra: {'account': account}));
|
||||
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) +
|
||||
[73, 69, 78, 68] +
|
||||
List<int>.generate(4, (_) => _rand.nextInt(256)));
|
||||
// static Future<void> buvidActive(Account account) async {
|
||||
// // 这样线程不安全, 但仍按预期进行
|
||||
// if (account.activited) return;
|
||||
// account.activited = true;
|
||||
// try {
|
||||
// final html = await Request().get(Api.dynamicSpmPrefix,
|
||||
// options: Options(extra: {'account': account}));
|
||||
// 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) +
|
||||
// [73, 69, 78, 68] +
|
||||
// List<int>.generate(4, (_) => _rand.nextInt(256)));
|
||||
|
||||
String jsonData = json.encode({
|
||||
'3064': 1,
|
||||
'39c8': '$spmPrefix.fp.risk',
|
||||
'3c43': {
|
||||
'adca': 'Linux',
|
||||
'bfe9': randPngEnd.substring(randPngEnd.length - 50),
|
||||
},
|
||||
});
|
||||
// String jsonData = json.encode({
|
||||
// '3064': 1,
|
||||
// '39c8': '$spmPrefix.fp.risk',
|
||||
// '3c43': {
|
||||
// 'adca': 'Linux',
|
||||
// 'bfe9': randPngEnd.substring(randPngEnd.length - 50),
|
||||
// },
|
||||
// });
|
||||
|
||||
await Request().post(Api.activateBuvidApi,
|
||||
data: {'payload': jsonData},
|
||||
options: Options(contentType: Headers.jsonContentType));
|
||||
} catch (e) {
|
||||
log("setCookie, $e");
|
||||
}
|
||||
}
|
||||
// await Request().post(Api.activateBuvidApi,
|
||||
// data: {'payload': jsonData},
|
||||
// options: Options(contentType: Headers.jsonContentType));
|
||||
// } catch (e) {
|
||||
// log("setCookie, $e");
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
* config it and create
|
||||
@@ -96,9 +106,9 @@ class Request {
|
||||
//请求基地址,可以包含子路径
|
||||
baseUrl: HttpString.apiBaseUrl,
|
||||
//连接服务器超时时间,单位是毫秒.
|
||||
connectTimeout: const Duration(milliseconds: 4000),
|
||||
connectTimeout: const Duration(milliseconds: 10000),
|
||||
//响应流上前后两次接受到数据的间隔,单位为毫秒。
|
||||
receiveTimeout: const Duration(milliseconds: 4000),
|
||||
receiveTimeout: const Duration(milliseconds: 10000),
|
||||
//Http请求头.
|
||||
headers: {
|
||||
'connection': 'keep-alive',
|
||||
@@ -112,12 +122,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()
|
||||
@@ -247,20 +255,28 @@ class Request {
|
||||
/*
|
||||
* 下载文件
|
||||
*/
|
||||
downloadFile(urlPath, savePath) async {
|
||||
Response response;
|
||||
Future<Response> downloadFile(urlPath, savePath, {cancelToken}) async {
|
||||
try {
|
||||
response = await dio.download(urlPath, savePath,
|
||||
onReceiveProgress: (int count, int total) {
|
||||
Response response = await dio.download(
|
||||
urlPath,
|
||||
savePath,
|
||||
cancelToken: cancelToken,
|
||||
// onReceiveProgress: (int count, int total) {
|
||||
//进度
|
||||
// debugPrint("$count $total");
|
||||
});
|
||||
debugPrint('downloadFile success: ${response.data}');
|
||||
|
||||
return response.data;
|
||||
// },
|
||||
);
|
||||
// debugPrint('downloadFile success: ${response.data}');
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
debugPrint('downloadFile error: $e');
|
||||
return Future.error(AccountManager.dioError(e));
|
||||
// debugPrint('downloadFile error: $e');
|
||||
return Response(
|
||||
data: {
|
||||
'message': await AccountManager.dioError(e),
|
||||
},
|
||||
statusCode: -1,
|
||||
requestOptions: RequestOptions(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,21 +291,22 @@ class Request {
|
||||
}
|
||||
|
||||
static String headerUa({type = 'mob'}) {
|
||||
return type == 'mob'
|
||||
? Platform.isIOS
|
||||
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1'
|
||||
: 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36'
|
||||
: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15';
|
||||
return switch (type) {
|
||||
'mob' =>
|
||||
'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36',
|
||||
_ =>
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15'
|
||||
};
|
||||
}
|
||||
|
||||
static String responseDecoder(List<int> responseBytes, RequestOptions options,
|
||||
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);
|
||||
|
||||
@@ -2,6 +2,9 @@ import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/live/danmu_info.dart';
|
||||
import 'package:PiliPlus/models/live/follow.dart';
|
||||
import 'package:PiliPlus/models/live/live_emoticons/data.dart';
|
||||
import 'package:PiliPlus/models/live/live_emoticons/datum.dart';
|
||||
import 'package:PiliPlus/utils/wbi_sign.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import '../models/live/item.dart';
|
||||
import '../models/live/room_info.dart';
|
||||
@@ -10,13 +13,27 @@ import 'api.dart';
|
||||
import 'init.dart';
|
||||
|
||||
class LiveHttp {
|
||||
static Future<LoadingState> liveList(
|
||||
static Future<LoadingState<List<LiveItemModel>?>> liveList(
|
||||
{int? vmid, int? pn, int? ps, String? orderType}) async {
|
||||
var res = await Request().get(Api.liveList,
|
||||
queryParameters: {'page': pn, 'page_size': 30, 'platform': 'web'});
|
||||
var res = await Request().get(
|
||||
Api.liveList,
|
||||
queryParameters: await WbiSign.makSign({
|
||||
'page': pn,
|
||||
'page_size': 30,
|
||||
'platform': 'web',
|
||||
'web_location': 0.0,
|
||||
}),
|
||||
options: Options(
|
||||
headers: {
|
||||
'origin': 'https://live.bilibili.com',
|
||||
'referer': 'https://live.bilibili.com/',
|
||||
'user-agent': Request.headerUa(type: 'pc'),
|
||||
},
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<LiveItemModel> list = res.data['data']['list']
|
||||
.map<LiveItemModel>((e) => LiveItemModel.fromJson(e))
|
||||
List<LiveItemModel>? list = (res.data['data']?['list'] as List?)
|
||||
?.map<LiveItemModel>((e) => LiveItemModel.fromJson(e))
|
||||
.toList();
|
||||
return LoadingState.success(list);
|
||||
} else {
|
||||
@@ -24,35 +41,34 @@ class LiveHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future sendLiveMsg({
|
||||
roomId,
|
||||
msg,
|
||||
}) async {
|
||||
static Future sendLiveMsg({roomId, msg, dmType, emoticonOptions}) async {
|
||||
dynamic csrf = await Request.getCsrf();
|
||||
var res = await Request().post(
|
||||
Api.sendLiveMsg,
|
||||
data: {
|
||||
data: FormData.fromMap({
|
||||
'bubble': 0,
|
||||
'msg': msg,
|
||||
'color': 16777215,
|
||||
'mode': 1,
|
||||
'room_type': 0,
|
||||
'jumpfrom': 71000,
|
||||
'reply_mid': 0,
|
||||
'reply_attr': 0,
|
||||
'replay_dmid': '',
|
||||
'statistics': Constants.statistics,
|
||||
'reply_type': 0,
|
||||
'reply_uname': '',
|
||||
if (dmType != null) 'dm_type': dmType,
|
||||
if (emoticonOptions != null)
|
||||
'emoticonOptions': emoticonOptions
|
||||
else ...{
|
||||
'room_type': 0,
|
||||
'jumpfrom': 0,
|
||||
'reply_mid': 0,
|
||||
'reply_attr': 0,
|
||||
'replay_dmid': '',
|
||||
'statistics': Constants.statistics,
|
||||
'reply_type': 0,
|
||||
'reply_uname': '',
|
||||
},
|
||||
'fontsize': 25,
|
||||
'rnd': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
'roomid': roomId,
|
||||
'csrf': csrf,
|
||||
'csrf_token': csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
}),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
@@ -82,11 +98,7 @@ class LiveHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': RoomInfoModel.fromJson(res.data['data'])};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +112,7 @@ class LiveHttp {
|
||||
'data': RoomInfoH5Model.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,11 +123,7 @@ class LiveHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']['room']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,11 +134,7 @@ class LiveHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': LiveDanmakuInfo.fromJson(res.data)};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,4 +162,21 @@ class LiveHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<List<LiveEmoteDatum>?>> getLiveEmoticons(
|
||||
{required int roomId}) async {
|
||||
var res = await Request().get(
|
||||
Api.getLiveEmoticons,
|
||||
queryParameters: {
|
||||
'platform': 'pc',
|
||||
'room_id': roomId,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(
|
||||
LiveEmoteData.fromJson(res.data['data']).data);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,25 +2,52 @@ abstract class LoadingState<T> {
|
||||
const LoadingState();
|
||||
|
||||
factory LoadingState.loading() = Loading;
|
||||
// factory LoadingState.empty() = Empty;
|
||||
factory LoadingState.success(T response) = Success<T>;
|
||||
factory LoadingState.error(String errMsg) = Error;
|
||||
}
|
||||
|
||||
class Loading extends LoadingState<Never> {
|
||||
const Loading();
|
||||
}
|
||||
Loading._internal();
|
||||
|
||||
// class Empty extends LoadingState<Never> {
|
||||
// const Empty();
|
||||
// }
|
||||
static final Loading _instance = Loading._internal();
|
||||
|
||||
factory Loading() => _instance;
|
||||
}
|
||||
|
||||
class Success<T> extends LoadingState<T> {
|
||||
final T response;
|
||||
const Success(this.response);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (other is Success) {
|
||||
return response == other.response;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => response.hashCode;
|
||||
}
|
||||
|
||||
class Error extends LoadingState<Never> {
|
||||
final String errMsg;
|
||||
const Error(this.errMsg);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (other is Error) {
|
||||
return errMsg == other.errMsg;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => errMsg.hashCode;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/space/data.dart';
|
||||
import 'package:PiliPlus/models/space_archive/data.dart' as space_archive;
|
||||
import 'package:PiliPlus/models/space_article/data.dart' as space_article;
|
||||
import 'package:PiliPlus/models/space/data.dart' as space_;
|
||||
import 'package:PiliPlus/models/space_fav/space_fav.dart';
|
||||
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
|
||||
show ContributeType;
|
||||
@@ -61,7 +65,7 @@ class MemberHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> spaceArticle({
|
||||
static Future<LoadingState<space_article.Data>> spaceArticle({
|
||||
required int mid,
|
||||
required int page,
|
||||
}) async {
|
||||
@@ -137,13 +141,13 @@ class MemberHttp {
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(res.data['data']['items_lists']);
|
||||
return LoadingState.success(res.data['data']?['items_lists']);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> spaceArchive({
|
||||
static Future<LoadingState<space_archive.Data>> spaceArchive({
|
||||
required ContributeType type,
|
||||
required int? mid,
|
||||
String? aid,
|
||||
@@ -153,6 +157,7 @@ class MemberHttp {
|
||||
int? next,
|
||||
int? seasonId,
|
||||
int? seriesId,
|
||||
includeCursor,
|
||||
}) async {
|
||||
Map<String, String> data = {
|
||||
if (aid != null) 'aid': aid.toString(),
|
||||
@@ -170,6 +175,7 @@ class MemberHttp {
|
||||
'qn': type == ContributeType.video ? '80' : '32',
|
||||
if (order != null) 'order': order,
|
||||
if (sort != null) 'sort': sort,
|
||||
if (includeCursor != null) 'include_cursor': includeCursor.toString(),
|
||||
'statistics': Constants.statistics,
|
||||
'vmid': mid.toString(),
|
||||
};
|
||||
@@ -196,8 +202,51 @@ class MemberHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> space({
|
||||
static Future<LoadingState> spaceStory({
|
||||
required mid,
|
||||
required aid,
|
||||
required beforeSize,
|
||||
required afterSize,
|
||||
required cid,
|
||||
required contain,
|
||||
required index,
|
||||
}) async {
|
||||
Map<String, String> data = {
|
||||
'aid': aid.toString(),
|
||||
'before_size': beforeSize.toString(),
|
||||
'after_size': afterSize.toString(),
|
||||
'cid': cid.toString(),
|
||||
'contain': contain.toString(),
|
||||
'index': index.toString(),
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
'mobi_app': 'android_hd',
|
||||
'platform': 'android',
|
||||
's_locale': 'zh_CN',
|
||||
'statistics': Constants.statistics,
|
||||
'vmid': mid.toString(),
|
||||
};
|
||||
dynamic res = await Request().get(
|
||||
Api.spaceStory,
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
'bili-http-engine': 'cronet',
|
||||
'user-agent': Constants.userAgent,
|
||||
},
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(res.data['data']);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<space_.Data>> space({
|
||||
int? mid,
|
||||
dynamic fromViewAid,
|
||||
}) async {
|
||||
Map<String, String> data = {
|
||||
'build': '1462100',
|
||||
@@ -206,6 +255,7 @@ class MemberHttp {
|
||||
'mobi_app': 'android_hd',
|
||||
'platform': 'android',
|
||||
's_locale': 'zh_CN',
|
||||
if (fromViewAid != null) 'from_view_aid': fromViewAid,
|
||||
'statistics': Constants.statistics,
|
||||
'vmid': mid.toString(),
|
||||
};
|
||||
@@ -250,11 +300,7 @@ class MemberHttp {
|
||||
'data': MemberInfoModel.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,11 +309,7 @@ class MemberHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,23 +324,19 @@ class MemberHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future memberArchive({
|
||||
int? mid,
|
||||
int ps = 40,
|
||||
required int mid,
|
||||
int ps = 25,
|
||||
int tid = 0,
|
||||
int? pn,
|
||||
String? keyword,
|
||||
String order = 'pubdate',
|
||||
bool orderAvoided = true,
|
||||
dynamic wwebid,
|
||||
String? wwebid,
|
||||
}) async {
|
||||
String dmImgStr = Utils.base64EncodeRandomString(16, 64);
|
||||
String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
|
||||
@@ -310,7 +348,7 @@ class MemberHttp {
|
||||
'keyword': keyword ?? '',
|
||||
'order': order,
|
||||
'platform': 'web',
|
||||
'web_location': 1550101,
|
||||
'web_location': '333.1387',
|
||||
'order_avoided': orderAvoided,
|
||||
'dm_img_list': '[]',
|
||||
'dm_img_str': dmImgStr,
|
||||
@@ -321,7 +359,11 @@ class MemberHttp {
|
||||
var res = await Request().get(
|
||||
Api.memberArchive,
|
||||
queryParameters: params,
|
||||
extra: {'ua': 'Mozilla/5.0'},
|
||||
options: Options(headers: {
|
||||
HttpHeaders.userAgentHeader: Request.headerUa(type: 'pc'),
|
||||
HttpHeaders.refererHeader: HttpString.spaceBaseUrl,
|
||||
'origin': HttpString.spaceBaseUrl,
|
||||
}),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
@@ -340,7 +382,7 @@ class MemberHttp {
|
||||
}
|
||||
|
||||
// 用户动态
|
||||
static Future<LoadingState> memberDynamic({
|
||||
static Future<LoadingState<DynamicsDataModel>> memberDynamic({
|
||||
String? offset,
|
||||
int? mid,
|
||||
}) async {
|
||||
@@ -382,23 +424,26 @@ class MemberHttp {
|
||||
|
||||
// 搜索用户动态
|
||||
static Future memberDynamicSearch({
|
||||
int? pn,
|
||||
int? ps,
|
||||
int? mid,
|
||||
required int pn,
|
||||
required dynamic mid,
|
||||
required dynamic offset,
|
||||
required String keyword,
|
||||
}) async {
|
||||
var res = await Request().get(Api.memberDynamicSearch, queryParameters: {
|
||||
'keyword': keyword,
|
||||
'mid': mid,
|
||||
'pn': pn,
|
||||
'ps': ps,
|
||||
'platform': 'web'
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.memberDynamicSearch,
|
||||
queryParameters: {
|
||||
'host_mid': mid,
|
||||
'page': pn,
|
||||
'offset': offset,
|
||||
'keyword': keyword,
|
||||
'features': 'itemOpusStyle,listOnlyfans',
|
||||
'web_location': 333.1387,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data']['cards'],
|
||||
'count': res.data['data']['total']
|
||||
'data': DynamicsDataModel.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -419,11 +464,7 @@ class MemberHttp {
|
||||
.toList()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,13 +507,9 @@ class MemberHttp {
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': [], 'msg': '操作成功'};
|
||||
return {'status': true, 'msg': '操作成功'};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,11 +535,7 @@ class MemberHttp {
|
||||
.toList()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,11 +550,7 @@ class MemberHttp {
|
||||
.toList()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,16 +567,13 @@ class MemberHttp {
|
||||
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 最近投币
|
||||
static Future<LoadingState> getRecentCoinVideo({required int mid}) async {
|
||||
static Future<LoadingState<List<MemberCoinsDataModel>?>> getRecentCoinVideo(
|
||||
{required int mid}) async {
|
||||
Map params = await WbiSign.makSign({
|
||||
'mid': mid,
|
||||
'gaia_source': 'main_web',
|
||||
@@ -564,16 +590,18 @@ class MemberHttp {
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(res.data['data']
|
||||
.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
|
||||
.toList());
|
||||
List<MemberCoinsDataModel>? list = (res.data['data'] as List?)
|
||||
?.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
|
||||
.toList();
|
||||
return LoadingState.success(list);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
// 最近点赞
|
||||
static Future<LoadingState> getRecentLikeVideo({required int mid}) async {
|
||||
static Future<LoadingState<List<MemberCoinsDataModel>?>> getRecentLikeVideo(
|
||||
{required int mid}) async {
|
||||
Map params = await WbiSign.makSign({
|
||||
'mid': mid,
|
||||
'gaia_source': 'main_web',
|
||||
@@ -590,9 +618,10 @@ class MemberHttp {
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(res.data['data']['list']
|
||||
.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
|
||||
.toList());
|
||||
List<MemberCoinsDataModel>? list = (res.data['data']?['list'] as List?)
|
||||
?.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
|
||||
.toList();
|
||||
return LoadingState.success(list);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
@@ -626,11 +655,7 @@ class MemberHttp {
|
||||
debugPrint(err.toString());
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,11 +666,7 @@ class MemberHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import 'api.dart';
|
||||
import 'init.dart';
|
||||
|
||||
class MsgHttp {
|
||||
static Future<LoadingState> msgFeedReplyMe(
|
||||
static Future<LoadingState<MsgFeedReplyMe>> msgFeedReplyMe(
|
||||
{int cursor = -1, int cursorTime = -1}) async {
|
||||
var res = await Request().get(Api.msgFeedReply, queryParameters: {
|
||||
'id': cursor == -1 ? null : cursor,
|
||||
@@ -34,7 +34,7 @@ class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> msgFeedAtMe(
|
||||
static Future<LoadingState<MsgFeedAtMe>> msgFeedAtMe(
|
||||
{int cursor = -1, int cursorTime = -1}) async {
|
||||
var res = await Request().get(Api.msgFeedAt, queryParameters: {
|
||||
'id': cursor == -1 ? null : cursor,
|
||||
@@ -51,7 +51,7 @@ class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> msgFeedLikeMe(
|
||||
static Future<LoadingState<MsgFeedLikeMe>> msgFeedLikeMe(
|
||||
{int cursor = -1, int cursorTime = -1}) async {
|
||||
var res = await Request().get(Api.msgFeedLike, queryParameters: {
|
||||
'id': cursor == -1 ? null : cursor,
|
||||
@@ -68,7 +68,7 @@ class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> msgFeedNotify(
|
||||
static Future<LoadingState<List<SystemNotifyList>?>> msgFeedNotify(
|
||||
{int cursor = -1, int pageSize = 20}) async {
|
||||
var res = await Request().get(Api.msgSysNotify, queryParameters: {
|
||||
'cursor': cursor == -1 ? null : cursor,
|
||||
@@ -110,11 +110,7 @@ class MsgHttp {
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,22 +227,24 @@ class MsgHttp {
|
||||
dynamic path,
|
||||
String? category,
|
||||
String? biz,
|
||||
CancelToken? cancelToken,
|
||||
}) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
Map<String, dynamic> data = await WbiSign.makSign({
|
||||
'file_up': await MultipartFile.fromFile(path),
|
||||
final file = await MultipartFile.fromFile(path);
|
||||
Map<String, dynamic> data = {
|
||||
'file_up': file,
|
||||
if (category != null) 'category': category,
|
||||
if (biz != null) 'biz': biz,
|
||||
'csrf': csrf,
|
||||
});
|
||||
'csrf': await Request.getCsrf(),
|
||||
};
|
||||
var res = await Request().post(
|
||||
Api.uploadBfs,
|
||||
data: FormData.fromMap(data),
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
'data': res.data['data']..['img_size'] = file.length,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -329,12 +327,41 @@ class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future removeSysMsg(
|
||||
static Future delMsgfeed(
|
||||
int tp,
|
||||
dynamic id,
|
||||
) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
var res = await Request().post(
|
||||
HttpString.messageBaseUrl + Api.removeSysMsg,
|
||||
Api.delMsgfeed,
|
||||
data: {
|
||||
'tp': tp,
|
||||
'id': id,
|
||||
'build': 0,
|
||||
'mobi_app': 'web',
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future delSysMsg(
|
||||
dynamic id,
|
||||
) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
var res = await Request().post(
|
||||
HttpString.messageBaseUrl + Api.delSysMsg,
|
||||
queryParameters: {
|
||||
'mobi_app': 'android',
|
||||
'csrf': csrf,
|
||||
@@ -403,21 +430,13 @@ class MsgHttp {
|
||||
try {
|
||||
return {
|
||||
'status': true,
|
||||
'data': SessionDataModel.fromJson(res.data['data']),
|
||||
'data': SessionDataModel.fromJson(res.data['data']).sessionList,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': err.toString(),
|
||||
};
|
||||
return {'status': false, 'msg': err.toString()};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,11 +458,7 @@ class MsgHttp {
|
||||
debugPrint('err🔟: $err');
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,11 +484,7 @@ class MsgHttp {
|
||||
debugPrint(err.toString());
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,7 +512,6 @@ class MsgHttp {
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': "message: ${res.data['message']},"
|
||||
" msg: ${res.data['msg']},"
|
||||
" code: ${res.data['code']}",
|
||||
@@ -550,11 +560,7 @@ class MsgHttp {
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'] ?? res.data['msg'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class ReplyHttp {
|
||||
static RegExp replyRegExp =
|
||||
RegExp(GStorage.banWordForReply, caseSensitive: false);
|
||||
|
||||
@Deprecated('Use replyListGrpc instead')
|
||||
static Future<LoadingState> replyList({
|
||||
required bool isLogin,
|
||||
required int oid,
|
||||
@@ -123,7 +124,7 @@ class ReplyHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> replyListGrpc({
|
||||
static Future<LoadingState<MainListReply>> replyListGrpc({
|
||||
int type = 1,
|
||||
required int oid,
|
||||
required CursorReq cursor,
|
||||
@@ -214,6 +215,7 @@ class ReplyHttp {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Deprecated('Use replyReplyListGrpc instead')
|
||||
static Future<LoadingState> replyReplyList({
|
||||
required bool isLogin,
|
||||
required int oid,
|
||||
@@ -349,11 +351,7 @@ class ReplyHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,15 +375,12 @@ class ReplyHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> getEmoteList({String? business}) async {
|
||||
static Future<LoadingState<List<Packages>?>> getEmoteList(
|
||||
{String? business}) async {
|
||||
var res = await Request().get(Api.myEmote, queryParameters: {
|
||||
'business': business ?? 'reply',
|
||||
'web_location': '333.1245',
|
||||
@@ -397,4 +392,30 @@ class ReplyHttp {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future replyTop({
|
||||
required oid,
|
||||
required type,
|
||||
required rpid,
|
||||
required bool isUpTop,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.replyTop,
|
||||
data: {
|
||||
'oid': oid,
|
||||
'type': type,
|
||||
'rpid': rpid,
|
||||
'action': isUpTop ? 0 : 1,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class RetryInterceptor extends Interceptor {
|
||||
case DioExceptionType.connectionError:
|
||||
case DioExceptionType.connectionTimeout:
|
||||
case DioExceptionType.sendTimeout:
|
||||
case DioExceptionType.unknown:
|
||||
if ((err.requestOptions.extra['_rt'] ??= 0) < _count) {
|
||||
Future.delayed(
|
||||
Duration(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:PiliPlus/models/search/search_trending/trending_data.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -29,11 +30,7 @@ class SearchHttp {
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '请求错误',
|
||||
};
|
||||
return {'status': false, 'msg': '请求错误'};
|
||||
}
|
||||
|
||||
// 获取搜索建议
|
||||
@@ -50,19 +47,17 @@ class SearchHttp {
|
||||
'status': true,
|
||||
'data': resultMap['result'] is Map
|
||||
? SearchSuggestModel.fromJson(resultMap['result'])
|
||||
: [],
|
||||
: null,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '请求错误 🙅',
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '请求错误 🙅',
|
||||
};
|
||||
}
|
||||
@@ -148,8 +143,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'] ??
|
||||
@@ -161,7 +155,8 @@ class SearchHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> bangumiInfoNew({int? seasonId, int? epId}) async {
|
||||
static Future<LoadingState<BangumiInfoModel>> bangumiInfoNew(
|
||||
{int? seasonId, int? epId}) async {
|
||||
final dynamic res = await Request().get(
|
||||
Api.bangumiInfo,
|
||||
queryParameters: {
|
||||
@@ -201,8 +196,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 {
|
||||
@@ -210,11 +205,22 @@ class SearchHttp {
|
||||
'data': BangumiInfoModel.fromJson(res.data['result']),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<TrendingData>> searchTrending(
|
||||
{int limit = 30}) async {
|
||||
final dynamic res = await Request().get(
|
||||
Api.searchTrending,
|
||||
queryParameters: {
|
||||
'limit': limit,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(TrendingData.fromJson(res.data['data']));
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/video/later.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:PiliPlus/utils/wbi_sign.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import '../common/constants.dart';
|
||||
@@ -28,6 +31,7 @@ class UserHttp {
|
||||
var res = await Request().get(Api.userInfo);
|
||||
if (res.data['code'] == 0) {
|
||||
UserInfoData data = UserInfoData.fromJson(res.data['data']);
|
||||
GlobalData().coins = data.money;
|
||||
return {'status': true, 'data': data};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
@@ -40,12 +44,12 @@ class UserHttp {
|
||||
UserStat data = UserStat.fromJson(res.data['data']);
|
||||
return {'status': true, 'data': data};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 收藏夹
|
||||
static Future<LoadingState> userfavFolder({
|
||||
static Future<LoadingState<FavFolderData>> userfavFolder({
|
||||
required int pn,
|
||||
required int ps,
|
||||
required dynamic mid,
|
||||
@@ -62,6 +66,52 @@ class UserHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future sortFavFolder({
|
||||
required List<int?> sort,
|
||||
}) async {
|
||||
Map<String, dynamic> data = {
|
||||
'sort': sort.join(','),
|
||||
'csrf': await Request.getCsrf(),
|
||||
};
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.sortFavFolder,
|
||||
data: data,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future sortFav({
|
||||
required dynamic mediaId,
|
||||
required List<String> sort,
|
||||
}) async {
|
||||
Map<String, dynamic> data = {
|
||||
'media_id': mediaId,
|
||||
'sort': sort.join(','),
|
||||
'csrf': await Request.getCsrf(),
|
||||
};
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.sortFav,
|
||||
data: data,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future cleanFav({
|
||||
required dynamic mediaId,
|
||||
}) async {
|
||||
@@ -145,14 +195,14 @@ class UserHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> userFavFolderDetail(
|
||||
static Future<LoadingState<FavDetailData>> userFavFolderDetail(
|
||||
{required int mediaId,
|
||||
required int pn,
|
||||
required int ps,
|
||||
String keyword = '',
|
||||
String order = 'mtime',
|
||||
int type = 0}) async {
|
||||
var res = await Request().get(Api.userFavFolderDetail, queryParameters: {
|
||||
var res = await Request().get(Api.favResourceList, queryParameters: {
|
||||
'media_id': mediaId,
|
||||
'pn': pn,
|
||||
'ps': ps,
|
||||
@@ -170,16 +220,29 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 稍后再看
|
||||
static Future<LoadingState> seeYouLater() async {
|
||||
var res = await Request().get(Api.seeYouLater);
|
||||
static Future<LoadingState<Map>> seeYouLater({
|
||||
required int page,
|
||||
int viewed = 0,
|
||||
String keyword = '',
|
||||
bool asc = false,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.seeYouLater,
|
||||
queryParameters: await WbiSign.makSign({
|
||||
'pn': page,
|
||||
'ps': 20,
|
||||
'viewed': viewed,
|
||||
'key': keyword,
|
||||
'asc': asc,
|
||||
'need_split': true,
|
||||
'web_location': 333.881,
|
||||
}),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
if (res.data['data']['count'] == 0) {
|
||||
return LoadingState.success({
|
||||
'list': [],
|
||||
'count': 0,
|
||||
});
|
||||
return LoadingState.success({'count': 0});
|
||||
}
|
||||
List<HotVideoItemModel> list = [];
|
||||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||
if (res.data['data']?['list'] != null) {
|
||||
for (var i in res.data['data']['list']) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
@@ -195,12 +258,13 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 观看历史
|
||||
static Future<LoadingState> historyList({
|
||||
static Future<LoadingState<HistoryData>> historyList({
|
||||
required String type,
|
||||
int? max,
|
||||
int? viewAt,
|
||||
}) async {
|
||||
var res = await Request().get(Api.historyList, queryParameters: {
|
||||
'type': 'all',
|
||||
'type': type,
|
||||
'ps': 20,
|
||||
'max': max ?? 0,
|
||||
'view_at': viewAt ?? 0,
|
||||
@@ -232,7 +296,7 @@ class UserHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,13 +332,10 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 移除已观看
|
||||
static Future toViewDel({
|
||||
List? aids,
|
||||
}) async {
|
||||
static Future toViewDel({required List<int?> aids}) async {
|
||||
final Map<String, dynamic> params = {
|
||||
'jsonp': 'jsonp',
|
||||
'csrf': await Request.getCsrf(),
|
||||
if (aids != null) 'aid': aids.join(',') else 'viewed': true
|
||||
'resources': aids.join(',')
|
||||
};
|
||||
dynamic res = await Request().post(
|
||||
Api.toViewDel,
|
||||
@@ -307,12 +368,12 @@ class UserHttp {
|
||||
}
|
||||
}
|
||||
|
||||
// 清空稍后再看
|
||||
static Future toViewClear() async {
|
||||
// 清空稍后再看 // clean_type: null->all, 1->invalid, 2->viewed
|
||||
static Future toViewClear([int? cleanType]) async {
|
||||
var res = await Request().post(
|
||||
Api.toViewClear,
|
||||
queryParameters: {
|
||||
'jsonp': 'jsonp',
|
||||
if (cleanType != null) 'clean_type': cleanType,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
@@ -324,7 +385,7 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 删除历史记录
|
||||
static Future delHistory(List kidList) async {
|
||||
static Future delHistory(List<String> kidList) async {
|
||||
var res = await Request().post(
|
||||
Api.delHistory,
|
||||
data: {
|
||||
@@ -345,7 +406,7 @@ class UserHttp {
|
||||
|
||||
static Future hasFollow(int mid) async {
|
||||
var res = await Request().get(
|
||||
Api.hasFollow,
|
||||
Api.relation,
|
||||
queryParameters: {
|
||||
'fid': mid,
|
||||
},
|
||||
@@ -400,7 +461,7 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 我的订阅
|
||||
static Future<LoadingState> userSubFolder({
|
||||
static Future<LoadingState<List<SubFolderItemData>?>> userSubFolder({
|
||||
required int mid,
|
||||
required int pn,
|
||||
required int ps,
|
||||
@@ -442,6 +503,84 @@ class UserHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> favArticle({
|
||||
required int page,
|
||||
}) async {
|
||||
var res = await Request().get(Api.favArticle, queryParameters: {
|
||||
'page_size': 20,
|
||||
'page': page,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(res.data['data']?['items']);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future addFavArticle({
|
||||
required int id,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.addFavArticle,
|
||||
data: {
|
||||
'id': id,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future delFavArticle({
|
||||
required int id,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.delFavArticle,
|
||||
data: {
|
||||
'id': id,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future communityAction({
|
||||
required dynamic opusId,
|
||||
required dynamic action,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.communityAction,
|
||||
queryParameters: {
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
data: {
|
||||
"entity": {
|
||||
"object_id_str": opusId,
|
||||
"type": {"biz": 2}
|
||||
},
|
||||
"action": action, // 3 fav, 4 unfav
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future favResourceList({
|
||||
required int id,
|
||||
required int pn,
|
||||
@@ -500,41 +639,6 @@ class UserHttp {
|
||||
}
|
||||
}
|
||||
|
||||
// 稍后再看播放全部
|
||||
// static Future toViewPlayAll({required int oid, required String bvid}) async {
|
||||
// var res = await Request().get(
|
||||
// Api.watchLaterHtml,
|
||||
// data: {
|
||||
// 'oid': oid,
|
||||
// 'bvid': bvid,
|
||||
// },
|
||||
// );
|
||||
// String scriptContent =
|
||||
// extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||
// int startIndex = scriptContent.indexOf('{');
|
||||
// int endIndex = scriptContent.lastIndexOf('};');
|
||||
// String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||
// // 解析JSON字符串为Map
|
||||
// Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||
// // 输出解析后的数据
|
||||
// return {
|
||||
// 'status': true,
|
||||
// 'data': jsonData['resourceList']
|
||||
// .map((e) => MediaVideoItemModel.fromJson(e))
|
||||
// .toList()
|
||||
// };
|
||||
// }
|
||||
static List<String> extractScriptContents(String htmlContent) {
|
||||
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
|
||||
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);
|
||||
List<String> scriptContents = [];
|
||||
for (Match match in matches) {
|
||||
String scriptContent = match.group(1)!;
|
||||
scriptContents.add(scriptContent);
|
||||
}
|
||||
return scriptContents;
|
||||
}
|
||||
|
||||
// 稍后再看列表
|
||||
static Future getMediaList({
|
||||
required dynamic type,
|
||||
@@ -571,38 +675,19 @@ class UserHttp {
|
||||
.map<MediaVideoItemModel>(
|
||||
(e) => MediaVideoItemModel.fromJson(e))
|
||||
.toList()
|
||||
: []
|
||||
: <MediaVideoItemModel>[]
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 解析收藏夹视频
|
||||
// static Future parseFavVideo({
|
||||
// required int mediaId,
|
||||
// required int oid,
|
||||
// required String bvid,
|
||||
// }) async {
|
||||
// var res = await Request().get(
|
||||
// 'https://www.bilibili.com/list/ml$mediaId',
|
||||
// queryParameters: {
|
||||
// 'oid': mediaId,
|
||||
// 'bvid': bvid,
|
||||
// },
|
||||
// );
|
||||
// String scriptContent =
|
||||
// extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||
// int startIndex = scriptContent.indexOf('{');
|
||||
// int endIndex = scriptContent.lastIndexOf('};');
|
||||
// String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||
// // 解析JSON字符串为Map
|
||||
// Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||
// return {
|
||||
// 'status': true,
|
||||
// 'data': jsonData['resourceList']
|
||||
// .map<MediaVideoItemModel>((e) => MediaVideoItemModel.fromJson(e))
|
||||
// .toList()
|
||||
// };
|
||||
// }
|
||||
static Future getCoin() async {
|
||||
final res = await Request().get(Api.getCoin);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']?['money']};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:developer';
|
||||
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
|
||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/member/article.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -49,7 +50,7 @@ class VideoHttp {
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemModel> list = [];
|
||||
List<RecVideoItemModel> list = <RecVideoItemModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['item']) {
|
||||
//过滤掉live与ad,以及拉黑用户
|
||||
@@ -118,7 +119,7 @@ class VideoHttp {
|
||||
}),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemAppModel> list = [];
|
||||
List<RecVideoItemAppModel> list = <RecVideoItemAppModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['items']) {
|
||||
// 屏蔽推广和拉黑用户
|
||||
@@ -145,14 +146,14 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 最热视频
|
||||
static Future<LoadingState> hotVideoList(
|
||||
static Future<LoadingState<List<HotVideoItemModel>>> hotVideoList(
|
||||
{required int pn, required int ps}) async {
|
||||
var res = await Request().get(
|
||||
Api.hotList,
|
||||
queryParameters: {'pn': pn, 'ps': ps},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMids.contains(i['owner']['mid']) &&
|
||||
@@ -176,7 +177,7 @@ class VideoHttp {
|
||||
static Future<LoadingState> hotVideoListGrpc({required int idx}) async {
|
||||
dynamic res = await GrpcRepo.popular(idx);
|
||||
if (res['status']) {
|
||||
List<card.Card> list = [];
|
||||
List<card.Card> list = <card.Card>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (card.Card item in res['data']) {
|
||||
if (!blackMids.contains(item.smallCoverV5.up.id.toInt())) {
|
||||
@@ -258,13 +259,12 @@ class VideoHttp {
|
||||
}
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'code': res.data['code'],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
return {'status': false, 'data': [], 'msg': err};
|
||||
return {'status': false, 'msg': err};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,17 +345,16 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 相关视频
|
||||
static Future<LoadingState> relatedVideoList({required String bvid}) async {
|
||||
static Future<LoadingState<List<HotVideoItemModel>?>> relatedVideoList(
|
||||
{required String bvid}) async {
|
||||
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']);
|
||||
@@ -376,27 +375,26 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 获取点赞状态
|
||||
static Future hasLikeVideo({required String bvid}) async {
|
||||
var res =
|
||||
await Request().get(Api.hasLikeVideo, queryParameters: {'bvid': bvid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
// static Future hasLikeVideo({required String bvid}) async {
|
||||
// var res =
|
||||
// await Request().get(Api.hasLikeVideo, queryParameters: {'bvid': bvid});
|
||||
// if (res.data['code'] == 0) {
|
||||
// return {'status': true, 'data': res.data['data']};
|
||||
// } else {
|
||||
// return {'status': false, 'msg': res.data['message']};
|
||||
// }
|
||||
// }
|
||||
|
||||
// 获取投币状态
|
||||
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 {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
// static Future hasCoinVideo({required String bvid}) async {
|
||||
// var res =
|
||||
// await Request().get(Api.hasCoinVideo, queryParameters: {'bvid': bvid});
|
||||
// if (res.data['code'] == 0) {
|
||||
// return {'status': true, 'data': res.data['data']};
|
||||
// } else {
|
||||
// return {'status': false, 'msg': res.data['message']};
|
||||
// }
|
||||
// }
|
||||
|
||||
// 投币
|
||||
static Future coinVideo({
|
||||
@@ -418,20 +416,20 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取收藏状态
|
||||
static Future hasFavVideo({required int aid}) async {
|
||||
var res =
|
||||
await Request().get(Api.hasFavVideo, queryParameters: {'aid': aid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
// static Future hasFavVideo({required int aid}) async {
|
||||
// var res =
|
||||
// await Request().get(Api.hasFavVideo, queryParameters: {'aid': aid});
|
||||
// if (res.data['code'] == 0) {
|
||||
// return {'status': true, 'data': res.data['data']};
|
||||
// } else {
|
||||
// return {'status': false, 'msg': res.data['message']};
|
||||
// }
|
||||
// }
|
||||
|
||||
// 一键三连 bangumi
|
||||
static Future triple({dynamic epId, required dynamic seasonId}) async {
|
||||
@@ -453,7 +451,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,7 +479,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,7 +501,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,7 +718,7 @@ class VideoHttp {
|
||||
FavFolderData data = FavFolderData.fromJson(res.data['data']);
|
||||
return {'status': true, 'data': data};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,7 +740,7 @@ class VideoHttp {
|
||||
bool? syncToDynamic,
|
||||
}) async {
|
||||
if (message == '') {
|
||||
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
||||
return {'status': false, 'msg': '请输入评论内容'};
|
||||
}
|
||||
Map<String, dynamic> data = {
|
||||
'type': type.index,
|
||||
@@ -762,7 +760,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,16 +783,6 @@ class VideoHttp {
|
||||
}
|
||||
}
|
||||
|
||||
// 查询是否关注up
|
||||
static Future hasFollow({required int mid}) async {
|
||||
var res = await Request().get(Api.hasFollow, queryParameters: {'fid': mid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
// 操作用户关系
|
||||
static Future relationMod(
|
||||
{required int mid, required int act, required int reSrc}) async {
|
||||
@@ -829,6 +817,32 @@ class VideoHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future roomEntryAction({
|
||||
roomId,
|
||||
}) async {
|
||||
await Request().post(
|
||||
Api.roomEntryAction,
|
||||
queryParameters: {
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
data: {
|
||||
'room_id': roomId,
|
||||
'platform': 'pc',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future historyReport({
|
||||
aid,
|
||||
type,
|
||||
}) async {
|
||||
await Request().post(Api.historyReport, queryParameters: {
|
||||
if (aid != null) 'aid': aid,
|
||||
if (type != null) 'type': type,
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
}
|
||||
|
||||
// 视频播放进度
|
||||
static Future heartBeat({
|
||||
bvid,
|
||||
@@ -907,13 +921,13 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
static Future bangumiUpdate({
|
||||
dynamic seasonId,
|
||||
dynamic status,
|
||||
required List seasonId,
|
||||
required dynamic status,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.bangumiUpdate,
|
||||
data: {
|
||||
'season_id': seasonId,
|
||||
'season_id': seasonId.join(','),
|
||||
'status': status,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
@@ -958,11 +972,11 @@ class VideoHttp {
|
||||
'data': AiConclusionModel.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future subtitlesJson(
|
||||
static Future<Map<String, dynamic>> subtitlesJson(
|
||||
{String? aid, String? bvid, required int cid}) async {
|
||||
assert(aid != null || bvid != null);
|
||||
var res = await Request().get(
|
||||
@@ -992,98 +1006,53 @@ class VideoHttp {
|
||||
*/
|
||||
return {
|
||||
'status': true,
|
||||
'data': data['subtitle']['subtitles'],
|
||||
'subtitles': data['subtitle']['subtitles'],
|
||||
'view_points': data['view_points'],
|
||||
// 'last_play_time': data['last_play_time'],
|
||||
'last_play_cid': data['last_play_cid'],
|
||||
'interaction': data['interaction'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future vttSubtitles(List subtitlesJson) async {
|
||||
if (subtitlesJson.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
List<Map<String, String>> subtitlesVtt = [];
|
||||
|
||||
static Future vttSubtitles(Map<String, dynamic> subtile) async {
|
||||
String subtitleTimecode(num seconds) {
|
||||
int h = (seconds / 3600).floor();
|
||||
int m = ((seconds % 3600) / 60).floor();
|
||||
int s = (seconds % 60).floor();
|
||||
int ms = ((seconds * 1000) % 1000).floor();
|
||||
if (h == 0) {
|
||||
return "${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}.${ms.toString().padLeft(3, '0')}";
|
||||
}
|
||||
return "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}.${ms.toString().padLeft(3, '0')}";
|
||||
int h = seconds ~/ 3600;
|
||||
seconds %= 3600;
|
||||
int m = seconds ~/ 60;
|
||||
seconds %= 60;
|
||||
String sms = seconds.toStringAsFixed(3).padLeft(6, '0');
|
||||
return h == 0
|
||||
? "${m.toString().padLeft(2, '0')}:$sms"
|
||||
: "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:$sms";
|
||||
}
|
||||
|
||||
String processList(List list) {
|
||||
return list.fold('WEBVTT\n\n', (previous, item) {
|
||||
return '$previous${item?['sid'] ?? 0}\n${subtitleTimecode(item['from'])} --> ${subtitleTimecode(item['to'])}\n${item['content'].trim()}\n\n';
|
||||
});
|
||||
final sb = StringBuffer('WEBVTT\n\n');
|
||||
sb.writeAll(
|
||||
list.map((item) =>
|
||||
'${item?['sid'] ?? 0}\n${subtitleTimecode(item['from'])} --> ${subtitleTimecode(item['to'])}\n${item['content'].trim()}'),
|
||||
'\n\n');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
for (var i in subtitlesJson) {
|
||||
var res =
|
||||
await Request().get("https://${i['subtitle_url'].split('//')[1]}");
|
||||
/*
|
||||
{
|
||||
"font_size": 0.4,
|
||||
"font_color": "#FFFFFF",
|
||||
"background_alpha": 0.5,
|
||||
"background_color": "#9C27B0",
|
||||
"Stroke": "none",
|
||||
"type": "AIsubtitle",
|
||||
"lang": "zh",
|
||||
"version": "v1.6.0.4",
|
||||
"body": [
|
||||
{
|
||||
"from": 0.5,
|
||||
"to": 1.58,
|
||||
"sid": 1,
|
||||
"location": 2,
|
||||
"content": "很多人可能不知道",
|
||||
"music": 0.0
|
||||
},
|
||||
……,
|
||||
{
|
||||
"from": 558.629,
|
||||
"to": 560.22,
|
||||
"sid": 280,
|
||||
"location": 2,
|
||||
"content": "我们下期再见",
|
||||
"music": 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
if (res.data != null && res.data?['body'] is List) {
|
||||
String vttData = await compute(processList, res.data['body'] as List);
|
||||
subtitlesVtt.add({
|
||||
'language': i['lan'],
|
||||
'title': i['lan_doc'],
|
||||
'text': vttData,
|
||||
});
|
||||
} else {
|
||||
// SmartDialog.showToast("字幕${i['lan_doc']}加载失败, ${res.data['message']}");
|
||||
debugPrint('字幕${i['lan_doc']}加载失败, ${res.data['message']}');
|
||||
}
|
||||
var res = await Request().get("https:${subtile['subtitle_url']}");
|
||||
|
||||
if (res.data?['body'] is List) {
|
||||
return await compute(processList, res.data['body'] as List);
|
||||
}
|
||||
if (subtitlesVtt.isNotEmpty) {
|
||||
subtitlesVtt.insert(0, {'language': '', 'title': '关闭字幕', 'text': ""});
|
||||
}
|
||||
return subtitlesVtt;
|
||||
return null;
|
||||
}
|
||||
|
||||
// 视频排行
|
||||
static Future<LoadingState> getRankVideoList(int rid) async {
|
||||
static Future<LoadingState<List<HotVideoItemModel>>> getRankVideoList(
|
||||
int rid) async {
|
||||
var rankApi = "${Api.getRankApi}?rid=$rid&type=all";
|
||||
var res = await Request().get(rankApi);
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMids.contains(i['owner']['mid']) &&
|
||||
@@ -1110,7 +1079,7 @@ class VideoHttp {
|
||||
required int page,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.noteList,
|
||||
Api.archiveNoteList,
|
||||
queryParameters: {
|
||||
'csrf': Accounts.main.csrf,
|
||||
'oid': oid,
|
||||
@@ -1126,4 +1095,115 @@ class VideoHttp {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<List<FavArticleModel>?>> noteList({
|
||||
required int page,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.noteList,
|
||||
queryParameters: {
|
||||
'pn': page,
|
||||
'ps': 10,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<FavArticleModel>? list = (res.data['data']?['list'] as List?)
|
||||
?.map((e) => FavArticleModel.fromJson(e))
|
||||
.toList();
|
||||
return LoadingState.success(list);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<List<FavArticleModel>?>> userNoteList({
|
||||
required int page,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.userNoteList,
|
||||
queryParameters: {
|
||||
'pn': page,
|
||||
'ps': 10,
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<FavArticleModel>? list = (res.data['data']?['list'] as List?)
|
||||
?.map((e) => FavArticleModel.fromJson(e))
|
||||
.toList();
|
||||
return LoadingState.success(list);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> addNote({
|
||||
required oid,
|
||||
required String title,
|
||||
required String summary,
|
||||
}) async {
|
||||
String noteId = '';
|
||||
try {
|
||||
final res = await Request().get(Api.archiveNote, queryParameters: {
|
||||
'oid': oid,
|
||||
'oid_type': 0,
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
if (res.data['data']['noteIds'] != null) {
|
||||
noteId = res.data['data']['noteIds'].first;
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
final res = await Request().post(
|
||||
Api.addNote,
|
||||
data: {
|
||||
'cont_len': summary.length,
|
||||
'note_id': noteId,
|
||||
'oid': oid,
|
||||
'oid_type': 0,
|
||||
'platform': 'web',
|
||||
'title': title,
|
||||
'summary': summary,
|
||||
'content': jsonEncode([
|
||||
{"insert": summary},
|
||||
]),
|
||||
'from': 'close',
|
||||
'hash': DateTime.now().millisecondsSinceEpoch,
|
||||
'tags': '',
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(res.data['data']?['list']);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future delNote({
|
||||
required bool isPublish,
|
||||
required List noteIds,
|
||||
}) async {
|
||||
final res = await Request().post(
|
||||
isPublish ? Api.delPublishNote : Api.delNote,
|
||||
data: {
|
||||
isPublish ? 'cvids' : 'note_ids': noteIds.join(','),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import 'package:hive/hive.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_toast.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/models/common/color_type.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/index.dart';
|
||||
import 'package:PiliPlus/router/app_pages.dart';
|
||||
import 'package:PiliPlus/pages/main/view.dart';
|
||||
import 'package:PiliPlus/services/service_locator.dart';
|
||||
@@ -32,7 +31,7 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
MediaKit.ensureInitialized();
|
||||
await GStorage.init();
|
||||
if (GStorage.setting.get(SettingBoxKey.autoClearCache, defaultValue: false)) {
|
||||
if (GStorage.setting.get(SettingBoxKey.autoClearCache, defaultValue: true)) {
|
||||
await CacheManage.clearLibraryCache();
|
||||
}
|
||||
if (GStorage.setting
|
||||
@@ -218,7 +217,6 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
navigatorObservers: [
|
||||
FlutterSmartDialog.observer,
|
||||
VideoDetailPage.routeObserver,
|
||||
VideoDetailPageV.routeObserver,
|
||||
MainApp.routeObserver,
|
||||
],
|
||||
@@ -235,7 +233,7 @@ class _CustomHttpOverrides extends HttpOverrides {
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
final client = super.createHttpClient(context)
|
||||
..maxConnectionsPerHost = 32
|
||||
// ..maxConnectionsPerHost = 32
|
||||
..idleTimeout = const Duration(seconds: 15);
|
||||
if (badCertificateCallback) {
|
||||
client.badCertificateCallback =
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
|
||||
show MultiSelectData;
|
||||
|
||||
class BangumiListDataModel {
|
||||
BangumiListDataModel({
|
||||
this.hasNext,
|
||||
@@ -8,7 +11,7 @@ class BangumiListDataModel {
|
||||
});
|
||||
|
||||
int? hasNext;
|
||||
List? list;
|
||||
List<BangumiListItemModel>? list;
|
||||
int? num;
|
||||
int? size;
|
||||
int? total;
|
||||
@@ -24,7 +27,7 @@ class BangumiListDataModel {
|
||||
}
|
||||
}
|
||||
|
||||
class BangumiListItemModel {
|
||||
class BangumiListItemModel with MultiSelectData {
|
||||
BangumiListItemModel({
|
||||
this.badge,
|
||||
this.badgeType,
|
||||
@@ -44,6 +47,7 @@ class BangumiListItemModel {
|
||||
this.title,
|
||||
this.titleIcon,
|
||||
this.progress,
|
||||
this.newEp,
|
||||
});
|
||||
|
||||
String? badge;
|
||||
@@ -62,10 +66,12 @@ class BangumiListItemModel {
|
||||
String? subTitle;
|
||||
String? title;
|
||||
String? titleIcon;
|
||||
|
||||
Map? newEp;
|
||||
String? progress;
|
||||
String? renewalTime;
|
||||
|
||||
BangumiListItemModel.fromJson(Map<String, dynamic> json) {
|
||||
renewalTime = json['renewal_time'];
|
||||
badge = json['badge'] == '' ? null : json['badge'];
|
||||
badgeType = json['badge_type'];
|
||||
cover = json['cover'];
|
||||
@@ -82,7 +88,7 @@ class BangumiListItemModel {
|
||||
subTitle = json['sub_title'];
|
||||
title = json['title'];
|
||||
titleIcon = json['title_icon'];
|
||||
|
||||
newEp = json['new_ep'];
|
||||
progress = json['progress'];
|
||||
}
|
||||
}
|
||||
|
||||
91
lib/models/bangumi/pgc_timeline/episode.dart
Normal file
@@ -0,0 +1,91 @@
|
||||
import 'icon_font.dart';
|
||||
|
||||
class Episode {
|
||||
String? cover;
|
||||
int? delay;
|
||||
int? delayId;
|
||||
String? delayIndex;
|
||||
String? delayReason;
|
||||
bool? enableVt;
|
||||
String? epCover;
|
||||
int? episodeId;
|
||||
int? follow;
|
||||
String? follows;
|
||||
IconFont? iconFont;
|
||||
String? plays;
|
||||
String? pubIndex;
|
||||
String? pubTime;
|
||||
int? pubTs;
|
||||
int? published;
|
||||
int? seasonId;
|
||||
String? squareCover;
|
||||
String? title;
|
||||
|
||||
Episode({
|
||||
this.cover,
|
||||
this.delay,
|
||||
this.delayId,
|
||||
this.delayIndex,
|
||||
this.delayReason,
|
||||
this.enableVt,
|
||||
this.epCover,
|
||||
this.episodeId,
|
||||
this.follow,
|
||||
this.follows,
|
||||
this.iconFont,
|
||||
this.plays,
|
||||
this.pubIndex,
|
||||
this.pubTime,
|
||||
this.pubTs,
|
||||
this.published,
|
||||
this.seasonId,
|
||||
this.squareCover,
|
||||
this.title,
|
||||
});
|
||||
|
||||
factory Episode.fromJson(Map<String, dynamic> json) => Episode(
|
||||
cover: json['cover'] as String?,
|
||||
delay: json['delay'] as int?,
|
||||
delayId: json['delay_id'] as int?,
|
||||
delayIndex: json['delay_index'] as String?,
|
||||
delayReason: json['delay_reason'] as String?,
|
||||
enableVt: json['enable_vt'] as bool?,
|
||||
epCover: json['ep_cover'] as String?,
|
||||
episodeId: json['episode_id'] as int?,
|
||||
follow: json['follow'] as int?,
|
||||
follows: json['follows'] as String?,
|
||||
iconFont: json['icon_font'] == null
|
||||
? null
|
||||
: IconFont.fromJson(json['icon_font'] as Map<String, dynamic>),
|
||||
plays: json['plays'] as String?,
|
||||
pubIndex: json['pub_index'] as String?,
|
||||
pubTime: json['pub_time'] as String?,
|
||||
pubTs: json['pub_ts'] as int?,
|
||||
published: json['published'] as int?,
|
||||
seasonId: json['season_id'] as int?,
|
||||
squareCover: json['square_cover'] as String?,
|
||||
title: json['title'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'cover': cover,
|
||||
'delay': delay,
|
||||
'delay_id': delayId,
|
||||
'delay_index': delayIndex,
|
||||
'delay_reason': delayReason,
|
||||
'enable_vt': enableVt,
|
||||
'ep_cover': epCover,
|
||||
'episode_id': episodeId,
|
||||
'follow': follow,
|
||||
'follows': follows,
|
||||
'icon_font': iconFont?.toJson(),
|
||||
'plays': plays,
|
||||
'pub_index': pubIndex,
|
||||
'pub_time': pubTime,
|
||||
'pub_ts': pubTs,
|
||||
'published': published,
|
||||
'season_id': seasonId,
|
||||
'square_cover': squareCover,
|
||||
'title': title,
|
||||
};
|
||||
}
|
||||
16
lib/models/bangumi/pgc_timeline/icon_font.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
class IconFont {
|
||||
String? name;
|
||||
String? text;
|
||||
|
||||
IconFont({this.name, this.text});
|
||||
|
||||
factory IconFont.fromJson(Map<String, dynamic> json) => IconFont(
|
||||
name: json['name'] as String?,
|
||||
text: json['text'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'text': text,
|
||||
};
|
||||
}
|
||||
23
lib/models/bangumi/pgc_timeline/pgc_timeline.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'result.dart';
|
||||
|
||||
class PgcTimeline {
|
||||
int? code;
|
||||
String? message;
|
||||
List<Result>? result;
|
||||
|
||||
PgcTimeline({this.code, this.message, this.result});
|
||||
|
||||
factory PgcTimeline.fromJson(Map<String, dynamic> json) => PgcTimeline(
|
||||
code: json['code'] as int?,
|
||||
message: json['message'] as String?,
|
||||
result: (json['result'] as List<dynamic>?)
|
||||
?.map((e) => Result.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'code': code,
|
||||
'message': message,
|
||||
'result': result?.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
35
lib/models/bangumi/pgc_timeline/result.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'episode.dart';
|
||||
|
||||
class Result {
|
||||
String? date;
|
||||
int? dateTs;
|
||||
int? dayOfWeek;
|
||||
List<Episode>? episodes;
|
||||
int? isToday;
|
||||
|
||||
Result({
|
||||
this.date,
|
||||
this.dateTs,
|
||||
this.dayOfWeek,
|
||||
this.episodes,
|
||||
this.isToday,
|
||||
});
|
||||
|
||||
factory Result.fromJson(Map<String, dynamic> json) => Result(
|
||||
date: json['date'] as String?,
|
||||
dateTs: json['date_ts'] as int?,
|
||||
dayOfWeek: json['day_of_week'] as int?,
|
||||
episodes: (json['episodes'] as List<dynamic>?)
|
||||
?.map((e) => Episode.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
isToday: json['is_today'] as int?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'date': date,
|
||||
'date_ts': dateTs,
|
||||
'day_of_week': dayOfWeek,
|
||||
'episodes': episodes?.map((e) => e.toJson()).toList(),
|
||||
'is_today': isToday,
|
||||
};
|
||||
}
|
||||
@@ -8,7 +8,7 @@ class PostSegmentModel {
|
||||
required this.category,
|
||||
required this.actionType,
|
||||
});
|
||||
Pair<int, int> segment;
|
||||
Pair<double, double> segment;
|
||||
SegmentType category;
|
||||
ActionType actionType;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ class DynamicsDataModel {
|
||||
this.hasMore,
|
||||
this.items,
|
||||
this.offset,
|
||||
this.total,
|
||||
});
|
||||
bool? hasMore;
|
||||
List<DynamicItemModel>? items;
|
||||
String? offset;
|
||||
int? total;
|
||||
|
||||
DynamicsDataModel.fromJson(Map<String, dynamic> json) {
|
||||
hasMore = json['has_more'];
|
||||
@@ -18,6 +20,7 @@ class DynamicsDataModel {
|
||||
?.map<DynamicItemModel>((e) => DynamicItemModel.fromJson(e))
|
||||
.toList();
|
||||
offset = json['offset'];
|
||||
total = json['total'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ class FollowDataModel {
|
||||
FollowDataModel.fromJson(Map<String, dynamic> json) {
|
||||
total = json['total'] ?? 0;
|
||||
list = (json['list'] as List?)
|
||||
?.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||
.toList();
|
||||
?.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||
.toList() ??
|
||||
[];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -22,9 +22,8 @@ class LiveFollowingModel {
|
||||
LiveFollowingModel.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
list = (json['list'] as List?)
|
||||
?.map((item) => LiveFollowingItemModel.fromJson(item))
|
||||
.toList() ??
|
||||
<LiveFollowingItemModel>[];
|
||||
?.map((item) => LiveFollowingItemModel.fromJson(item))
|
||||
.toList();
|
||||
liveCount = json['live_count'];
|
||||
neverLivedCount = json['never_lived_count'];
|
||||
neverLivedFaces = json['never_lived_faces'];
|
||||
|
||||
23
lib/models/live/live_emoticons/data.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'datum.dart';
|
||||
|
||||
class LiveEmoteData {
|
||||
int? fansBrand;
|
||||
List<LiveEmoteDatum>? data;
|
||||
dynamic purchaseUrl;
|
||||
|
||||
LiveEmoteData({this.fansBrand, this.data, this.purchaseUrl});
|
||||
|
||||
factory LiveEmoteData.fromJson(Map<String, dynamic> json) => LiveEmoteData(
|
||||
fansBrand: json['fans_brand'] as int?,
|
||||
data: (json['data'] as List<dynamic>?)
|
||||
?.map((e) => LiveEmoteDatum.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
purchaseUrl: json['purchase_url'] as dynamic,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'fans_brand': fansBrand,
|
||||
'data': data?.map((e) => e.toJson()).toList(),
|
||||
'purchase_url': purchaseUrl,
|
||||
};
|
||||
}
|
||||
71
lib/models/live/live_emoticons/datum.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'emoticon.dart';
|
||||
import 'top_show.dart';
|
||||
import 'top_show_recent.dart';
|
||||
|
||||
class LiveEmoteDatum {
|
||||
List<LiveEmoticon>? emoticons;
|
||||
int? pkgId;
|
||||
String? pkgName;
|
||||
int? pkgType;
|
||||
String? pkgDescript;
|
||||
int? pkgPerm;
|
||||
int? unlockIdentity;
|
||||
int? unlockNeedGift;
|
||||
String? currentCover;
|
||||
List<dynamic>? recentlyUsedEmoticons;
|
||||
TopShow? topShow;
|
||||
TopShowRecent? topShowRecent;
|
||||
|
||||
LiveEmoteDatum({
|
||||
this.emoticons,
|
||||
this.pkgId,
|
||||
this.pkgName,
|
||||
this.pkgType,
|
||||
this.pkgDescript,
|
||||
this.pkgPerm,
|
||||
this.unlockIdentity,
|
||||
this.unlockNeedGift,
|
||||
this.currentCover,
|
||||
this.recentlyUsedEmoticons,
|
||||
this.topShow,
|
||||
this.topShowRecent,
|
||||
});
|
||||
|
||||
factory LiveEmoteDatum.fromJson(Map<String, dynamic> json) => LiveEmoteDatum(
|
||||
emoticons: (json['emoticons'] as List<dynamic>?)
|
||||
?.map((e) => LiveEmoticon.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
pkgId: json['pkg_id'] as int?,
|
||||
pkgName: json['pkg_name'] as String?,
|
||||
pkgType: json['pkg_type'] as int?,
|
||||
pkgDescript: json['pkg_descript'] as String?,
|
||||
pkgPerm: json['pkg_perm'] as int?,
|
||||
unlockIdentity: json['unlock_identity'] as int?,
|
||||
unlockNeedGift: json['unlock_need_gift'] as int?,
|
||||
currentCover: json['current_cover'] as String?,
|
||||
recentlyUsedEmoticons:
|
||||
json['recently_used_emoticons'] as List<dynamic>?,
|
||||
topShow: json['top_show'] == null
|
||||
? null
|
||||
: TopShow.fromJson(json['top_show'] as Map<String, dynamic>),
|
||||
topShowRecent: json['top_show_recent'] == null
|
||||
? null
|
||||
: TopShowRecent.fromJson(
|
||||
json['top_show_recent'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'emoticons': emoticons?.map((e) => e.toJson()).toList(),
|
||||
'pkg_id': pkgId,
|
||||
'pkg_name': pkgName,
|
||||
'pkg_type': pkgType,
|
||||
'pkg_descript': pkgDescript,
|
||||
'pkg_perm': pkgPerm,
|
||||
'unlock_identity': unlockIdentity,
|
||||
'unlock_need_gift': unlockNeedGift,
|
||||
'current_cover': currentCover,
|
||||
'recently_used_emoticons': recentlyUsedEmoticons,
|
||||
'top_show': topShow?.toJson(),
|
||||
'top_show_recent': topShowRecent?.toJson(),
|
||||
};
|
||||
}
|
||||
83
lib/models/live/live_emoticons/emoticon.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
class LiveEmoticon {
|
||||
String? emoji;
|
||||
String? descript;
|
||||
String? url;
|
||||
int? isDynamic;
|
||||
int? inPlayerArea;
|
||||
int? width;
|
||||
int? height;
|
||||
int? identity;
|
||||
int? unlockNeedGift;
|
||||
int? perm;
|
||||
int? unlockNeedLevel;
|
||||
int? emoticonValueType;
|
||||
int? bulgeDisplay;
|
||||
String? unlockShowText;
|
||||
String? unlockShowColor;
|
||||
String? emoticonUnique;
|
||||
String? unlockShowImage;
|
||||
int? emoticonId;
|
||||
|
||||
LiveEmoticon({
|
||||
this.emoji,
|
||||
this.descript,
|
||||
this.url,
|
||||
this.isDynamic,
|
||||
this.inPlayerArea,
|
||||
this.width,
|
||||
this.height,
|
||||
this.identity,
|
||||
this.unlockNeedGift,
|
||||
this.perm,
|
||||
this.unlockNeedLevel,
|
||||
this.emoticonValueType,
|
||||
this.bulgeDisplay,
|
||||
this.unlockShowText,
|
||||
this.unlockShowColor,
|
||||
this.emoticonUnique,
|
||||
this.unlockShowImage,
|
||||
this.emoticonId,
|
||||
});
|
||||
|
||||
factory LiveEmoticon.fromJson(Map<String, dynamic> json) => LiveEmoticon(
|
||||
emoji: json['emoji'] as String?,
|
||||
descript: json['descript'] as String?,
|
||||
url: json['url'] as String?,
|
||||
isDynamic: json['is_dynamic'] as int?,
|
||||
inPlayerArea: json['in_player_area'] as int?,
|
||||
width: json['width'] as int? ?? 0,
|
||||
height: json['height'] as int? ?? 0,
|
||||
identity: json['identity'] as int?,
|
||||
unlockNeedGift: json['unlock_need_gift'] as int?,
|
||||
perm: json['perm'] as int?,
|
||||
unlockNeedLevel: json['unlock_need_level'] as int?,
|
||||
emoticonValueType: json['emoticon_value_type'] as int?,
|
||||
bulgeDisplay: json['bulge_display'] as int?,
|
||||
unlockShowText: json['unlock_show_text'] as String?,
|
||||
unlockShowColor: json['unlock_show_color'] as String?,
|
||||
emoticonUnique: json['emoticon_unique'] as String?,
|
||||
unlockShowImage: json['unlock_show_image'] as String?,
|
||||
emoticonId: json['emoticon_id'] as int?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'emoji': emoji,
|
||||
'descript': descript,
|
||||
'url': url,
|
||||
'is_dynamic': isDynamic,
|
||||
'in_player_area': inPlayerArea,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'identity': identity,
|
||||
'unlock_need_gift': unlockNeedGift,
|
||||
'perm': perm,
|
||||
'unlock_need_level': unlockNeedLevel,
|
||||
'emoticon_value_type': emoticonValueType,
|
||||
'bulge_display': bulgeDisplay,
|
||||
'unlock_show_text': unlockShowText,
|
||||
'unlock_show_color': unlockShowColor,
|
||||
'emoticon_unique': emoticonUnique,
|
||||
'unlock_show_image': unlockShowImage,
|
||||
'emoticon_id': emoticonId,
|
||||
};
|
||||
}
|
||||
26
lib/models/live/live_emoticons/live_emoticons.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'data.dart';
|
||||
|
||||
class LiveEmoticons {
|
||||
int? code;
|
||||
String? message;
|
||||
int? ttl;
|
||||
LiveEmoteData? data;
|
||||
|
||||
LiveEmoticons({this.code, this.message, this.ttl, this.data});
|
||||
|
||||
factory LiveEmoticons.fromJson(Map<String, dynamic> json) => LiveEmoticons(
|
||||
code: json['code'] as int?,
|
||||
message: json['message'] as String?,
|
||||
ttl: json['ttl'] as int?,
|
||||
data: json['data'] == null
|
||||
? null
|
||||
: LiveEmoteData.fromJson(json['data'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'code': code,
|
||||
'message': message,
|
||||
'ttl': ttl,
|
||||
'data': data?.toJson(),
|
||||
};
|
||||
}
|
||||
16
lib/models/live/live_emoticons/top_left.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
class TopLeft {
|
||||
String? image;
|
||||
String? text;
|
||||
|
||||
TopLeft({this.image, this.text});
|
||||
|
||||
factory TopLeft.fromJson(Map<String, dynamic> json) => TopLeft(
|
||||
image: json['image'] as String?,
|
||||
text: json['text'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'image': image,
|
||||
'text': text,
|
||||
};
|
||||
}
|
||||
16
lib/models/live/live_emoticons/top_right.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
class TopRight {
|
||||
String? image;
|
||||
String? text;
|
||||
|
||||
TopRight({this.image, this.text});
|
||||
|
||||
factory TopRight.fromJson(Map<String, dynamic> json) => TopRight(
|
||||
image: json['image'] as String?,
|
||||
text: json['text'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'image': image,
|
||||
'text': text,
|
||||
};
|
||||
}
|
||||
23
lib/models/live/live_emoticons/top_show.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'top_left.dart';
|
||||
import 'top_right.dart';
|
||||
|
||||
class TopShow {
|
||||
TopLeft? topLeft;
|
||||
TopRight? topRight;
|
||||
|
||||
TopShow({this.topLeft, this.topRight});
|
||||
|
||||
factory TopShow.fromJson(Map<String, dynamic> json) => TopShow(
|
||||
topLeft: json['top_left'] == null
|
||||
? null
|
||||
: TopLeft.fromJson(json['top_left'] as Map<String, dynamic>),
|
||||
topRight: json['top_right'] == null
|
||||
? null
|
||||
: TopRight.fromJson(json['top_right'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'top_left': topLeft?.toJson(),
|
||||
'top_right': topRight?.toJson(),
|
||||
};
|
||||
}
|
||||
23
lib/models/live/live_emoticons/top_show_recent.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'top_left.dart';
|
||||
import 'top_right.dart';
|
||||
|
||||
class TopShowRecent {
|
||||
TopLeft? topLeft;
|
||||
TopRight? topRight;
|
||||
|
||||
TopShowRecent({this.topLeft, this.topRight});
|
||||
|
||||
factory TopShowRecent.fromJson(Map<String, dynamic> json) => TopShowRecent(
|
||||
topLeft: json['top_left'] == null
|
||||
? null
|
||||
: TopLeft.fromJson(json['top_left'] as Map<String, dynamic>),
|
||||
topRight: json['top_right'] == null
|
||||
? null
|
||||
: TopRight.fromJson(json['top_right'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'top_left': topLeft?.toJson(),
|
||||
'top_right': topRight?.toJson(),
|
||||
};
|
||||
}
|
||||
@@ -39,5 +39,5 @@ extension VideoQualityDesc on LiveQuality {
|
||||
'高清',
|
||||
'流畅',
|
||||
];
|
||||
get description => _descList[index];
|
||||
String get description => _descList[index];
|
||||
}
|
||||
|
||||
@@ -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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
31
lib/models/member/article.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
|
||||
|
||||
class FavArticleModel with MultiSelectData {
|
||||
FavArticleModel({
|
||||
this.webUrl,
|
||||
this.title,
|
||||
this.summary,
|
||||
this.message,
|
||||
this.pic,
|
||||
this.cvid,
|
||||
this.noteId,
|
||||
});
|
||||
|
||||
String? webUrl;
|
||||
String? title;
|
||||
String? summary;
|
||||
String? message;
|
||||
String? pic;
|
||||
dynamic cvid;
|
||||
dynamic noteId;
|
||||
|
||||
FavArticleModel.fromJson(Map json) {
|
||||
webUrl = json['web_url'];
|
||||
title = json['title'];
|
||||
summary = json['summary'];
|
||||
message = json['message'];
|
||||
pic = json['arc']?['pic'];
|
||||
cvid = json['cvid'];
|
||||
noteId = json['note_id'];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class MemberInfoModel {
|
||||
this.official,
|
||||
this.vip,
|
||||
this.liveRoom,
|
||||
this.isSeniorMember,
|
||||
});
|
||||
|
||||
int? mid;
|
||||
@@ -24,6 +25,7 @@ class MemberInfoModel {
|
||||
Map? official;
|
||||
Vip? vip;
|
||||
LiveRoom? liveRoom;
|
||||
int? isSeniorMember;
|
||||
|
||||
MemberInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
mid = json['mid'];
|
||||
@@ -38,6 +40,7 @@ class MemberInfoModel {
|
||||
vip = Vip.fromJson(json['vip']);
|
||||
liveRoom =
|
||||
json['live_room'] != null ? LiveRoom.fromJson(json['live_room']) : null;
|
||||
isSeniorMember = json['is_senior_member'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
class MemberTagItemModel {
|
||||
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
|
||||
show MultiSelectData;
|
||||
|
||||
class MemberTagItemModel with MultiSelectData {
|
||||
MemberTagItemModel({
|
||||
this.count,
|
||||
this.name,
|
||||
this.tagid,
|
||||
this.tip,
|
||||
this.checked,
|
||||
});
|
||||
|
||||
int? count;
|
||||
String? name;
|
||||
int? tagid;
|
||||
String? tip;
|
||||
bool? checked;
|
||||
|
||||
MemberTagItemModel.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
name = json['name'];
|
||||
tagid = json['tagid'];
|
||||
tip = json['tip'];
|
||||
checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,25 @@
|
||||
import './model_owner.dart';
|
||||
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
|
||||
show MultiSelectData;
|
||||
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 with MultiSelectData {
|
||||
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;
|
||||
|
||||
num? progress;
|
||||
|
||||
HotVideoItemModel.fromJson(Map<String, dynamic> json) {
|
||||
aid = json["aid"];
|
||||
cid = json["cid"];
|
||||
@@ -76,97 +35,63 @@ 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']; // 仅在稍后再看存在
|
||||
progress = json['progress'];
|
||||
}
|
||||
|
||||
// @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"] ?? '';
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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;
|
||||
// }
|
||||
59
lib/models/model_video.dart
Normal 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"];
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,7 @@ class MsgFeedReplyMe {
|
||||
MsgFeedReplyMe({this.cursor, this.items, this.lastViewAt});
|
||||
|
||||
MsgFeedReplyMe.fromJson(Map<String, dynamic> json) {
|
||||
cursor =
|
||||
json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
|
||||
cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
|
||||
if (json['items'] != null) {
|
||||
items = <ReplyMeItems>[];
|
||||
json['items'].forEach((v) {
|
||||
@@ -56,13 +55,14 @@ class ReplyMeItems {
|
||||
int? isMulti;
|
||||
int? replyTime;
|
||||
|
||||
ReplyMeItems(
|
||||
{this.id,
|
||||
this.user,
|
||||
this.item,
|
||||
this.counts,
|
||||
this.isMulti,
|
||||
this.replyTime});
|
||||
ReplyMeItems({
|
||||
this.id,
|
||||
this.user,
|
||||
this.item,
|
||||
this.counts,
|
||||
this.isMulti,
|
||||
this.replyTime,
|
||||
});
|
||||
|
||||
ReplyMeItems.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
@@ -99,11 +99,11 @@ class User {
|
||||
|
||||
User(
|
||||
{this.mid,
|
||||
this.fans,
|
||||
this.nickname,
|
||||
this.avatar,
|
||||
this.midLink,
|
||||
this.follow});
|
||||
this.fans,
|
||||
this.nickname,
|
||||
this.avatar,
|
||||
this.midLink,
|
||||
this.follow});
|
||||
|
||||
User.fromJson(Map<String, dynamic> json) {
|
||||
mid = json['mid'];
|
||||
@@ -153,28 +153,28 @@ class Item {
|
||||
|
||||
Item(
|
||||
{this.subjectId,
|
||||
this.rootId,
|
||||
this.sourceId,
|
||||
this.targetId,
|
||||
this.type,
|
||||
this.businessId,
|
||||
this.business,
|
||||
this.title,
|
||||
this.desc,
|
||||
this.image,
|
||||
this.uri,
|
||||
this.nativeUri,
|
||||
this.detailTitle,
|
||||
this.rootReplyContent,
|
||||
this.sourceContent,
|
||||
this.targetReplyContent,
|
||||
this.atDetails,
|
||||
this.topicDetails,
|
||||
this.hideReplyButton,
|
||||
this.hideLikeButton,
|
||||
this.likeState,
|
||||
this.danmu,
|
||||
this.message});
|
||||
this.rootId,
|
||||
this.sourceId,
|
||||
this.targetId,
|
||||
this.type,
|
||||
this.businessId,
|
||||
this.business,
|
||||
this.title,
|
||||
this.desc,
|
||||
this.image,
|
||||
this.uri,
|
||||
this.nativeUri,
|
||||
this.detailTitle,
|
||||
this.rootReplyContent,
|
||||
this.sourceContent,
|
||||
this.targetReplyContent,
|
||||
this.atDetails,
|
||||
this.topicDetails,
|
||||
this.hideReplyButton,
|
||||
this.hideLikeButton,
|
||||
this.likeState,
|
||||
this.danmu,
|
||||
this.message});
|
||||
|
||||
Item.fromJson(Map<String, dynamic> json) {
|
||||
subjectId = json['subject_id'];
|
||||
@@ -246,11 +246,11 @@ class AtDetails {
|
||||
|
||||
AtDetails(
|
||||
{this.mid,
|
||||
this.fans,
|
||||
this.nickname,
|
||||
this.avatar,
|
||||
this.midLink,
|
||||
this.follow});
|
||||
this.fans,
|
||||
this.nickname,
|
||||
this.avatar,
|
||||
this.midLink,
|
||||
this.follow});
|
||||
|
||||
AtDetails.fromJson(Map<String, dynamic> json) {
|
||||
mid = json['mid'];
|
||||
|
||||
@@ -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'];
|
||||
@@ -185,6 +123,7 @@ class SearchUserItemModel {
|
||||
this.isLive,
|
||||
this.roomId,
|
||||
this.officialVerify,
|
||||
this.isSeniorMember,
|
||||
});
|
||||
|
||||
String? type;
|
||||
@@ -203,6 +142,7 @@ class SearchUserItemModel {
|
||||
int? isLive;
|
||||
int? roomId;
|
||||
Map? officialVerify;
|
||||
int? isSeniorMember;
|
||||
|
||||
SearchUserItemModel.fromJson(Map<String, dynamic> json) {
|
||||
type = json['type'];
|
||||
@@ -221,6 +161,7 @@ class SearchUserItemModel {
|
||||
isLive = json['is_live'];
|
||||
roomId = json['room_id'];
|
||||
officialVerify = json['official_verify'];
|
||||
isSeniorMember = json['is_senior_member'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +242,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']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
28
lib/models/search/search_trending/search_trending.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'trending_data.dart';
|
||||
|
||||
class SearchTrending {
|
||||
int? code;
|
||||
String? message;
|
||||
int? ttl;
|
||||
TrendingData? data;
|
||||
|
||||
SearchTrending({this.code, this.message, this.ttl, this.data});
|
||||
|
||||
factory SearchTrending.fromJson(Map<String, dynamic> json) {
|
||||
return SearchTrending(
|
||||
code: json['code'] as int?,
|
||||
message: json['message'] as String?,
|
||||
ttl: json['ttl'] as int?,
|
||||
data: json['data'] == null
|
||||
? null
|
||||
: TrendingData.fromJson(json['data'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'code': code,
|
||||
'message': message,
|
||||
'ttl': ttl,
|
||||
'data': data?.toJson(),
|
||||
};
|
||||
}
|
||||
14
lib/models/search/search_trending/stat_datas.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
class StatDatas {
|
||||
StatDatas();
|
||||
|
||||
factory StatDatas.fromJson(Map<String, dynamic> json) {
|
||||
// TODO: implement fromJson
|
||||
throw UnimplementedError('StatDatas.fromJson($json) is not implemented');
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
// TODO: implement toJson
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
95
lib/models/search/search_trending/top_list.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import 'stat_datas.dart';
|
||||
|
||||
class TopList {
|
||||
int? hotId;
|
||||
String? keyword;
|
||||
String? showName;
|
||||
int? score;
|
||||
int? wordType;
|
||||
int? gotoType;
|
||||
String? gotoValue;
|
||||
String? icon;
|
||||
List<dynamic>? liveId;
|
||||
int? callReason;
|
||||
String? heatLayer;
|
||||
int? pos;
|
||||
int? id;
|
||||
String? status;
|
||||
String? nameType;
|
||||
int? resourceId;
|
||||
int? setGray;
|
||||
List<dynamic>? cardValues;
|
||||
int? heatScore;
|
||||
StatDatas? statDatas;
|
||||
|
||||
TopList({
|
||||
this.hotId,
|
||||
this.keyword,
|
||||
this.showName,
|
||||
this.score,
|
||||
this.wordType,
|
||||
this.gotoType,
|
||||
this.gotoValue,
|
||||
this.icon,
|
||||
this.liveId,
|
||||
this.callReason,
|
||||
this.heatLayer,
|
||||
this.pos,
|
||||
this.id,
|
||||
this.status,
|
||||
this.nameType,
|
||||
this.resourceId,
|
||||
this.setGray,
|
||||
this.cardValues,
|
||||
this.heatScore,
|
||||
this.statDatas,
|
||||
});
|
||||
|
||||
factory TopList.fromJson(Map<String, dynamic> json) => TopList(
|
||||
hotId: json['hot_id'] as int?,
|
||||
keyword: json['keyword'] as String?,
|
||||
showName: json['show_name'] as String?,
|
||||
score: json['score'] as int?,
|
||||
wordType: json['word_type'] as int?,
|
||||
gotoType: json['goto_type'] as int?,
|
||||
gotoValue: json['goto_value'] as String?,
|
||||
icon: json['icon'] as String?,
|
||||
liveId: json['live_id'] as List<dynamic>?,
|
||||
callReason: json['call_reason'] as int?,
|
||||
heatLayer: json['heat_layer'] as String?,
|
||||
pos: json['pos'] as int?,
|
||||
id: json['id'] as int?,
|
||||
status: json['status'] as String?,
|
||||
nameType: json['name_type'] as String?,
|
||||
resourceId: json['resource_id'] as int?,
|
||||
setGray: json['set_gray'] as int?,
|
||||
cardValues: json['card_values'] as List<dynamic>?,
|
||||
heatScore: json['heat_score'] as int?,
|
||||
statDatas: json['stat_datas'] == null
|
||||
? null
|
||||
: StatDatas.fromJson(json['stat_datas'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'hot_id': hotId,
|
||||
'keyword': keyword,
|
||||
'show_name': showName,
|
||||
'score': score,
|
||||
'word_type': wordType,
|
||||
'goto_type': gotoType,
|
||||
'goto_value': gotoValue,
|
||||
'icon': icon,
|
||||
'live_id': liveId,
|
||||
'call_reason': callReason,
|
||||
'heat_layer': heatLayer,
|
||||
'pos': pos,
|
||||
'id': id,
|
||||
'status': status,
|
||||
'name_type': nameType,
|
||||
'resource_id': resourceId,
|
||||
'set_gray': setGray,
|
||||
'card_values': cardValues,
|
||||
'heat_score': heatScore,
|
||||
'stat_datas': statDatas?.toJson(),
|
||||
};
|
||||
}
|
||||
28
lib/models/search/search_trending/trending_data.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:PiliPlus/models/search/search_trending/trending_list.dart';
|
||||
|
||||
class TrendingData {
|
||||
String? trackid;
|
||||
List<TrendingList>? list;
|
||||
List<TrendingList>? topList;
|
||||
String? hotwordEggInfo;
|
||||
|
||||
TrendingData({this.trackid, this.list, this.topList, this.hotwordEggInfo});
|
||||
|
||||
factory TrendingData.fromJson(Map<String, dynamic> json) => TrendingData(
|
||||
trackid: json['trackid'] as String?,
|
||||
list: (json['list'] as List<dynamic>?)
|
||||
?.map((e) => TrendingList.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
topList: (json['top_list'] as List<dynamic>?)
|
||||
?.map((e) => TrendingList.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
hotwordEggInfo: json['hotword_egg_info'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'trackid': trackid,
|
||||
'list': list?.map((e) => e.toJson()).toList(),
|
||||
'top_list': topList?.map((e) => e.toJson()).toList(),
|
||||
'hotword_egg_info': hotwordEggInfo,
|
||||
};
|
||||
}
|
||||
47
lib/models/search/search_trending/trending_list.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
class TrendingList {
|
||||
int? position;
|
||||
String? keyword;
|
||||
String? showName;
|
||||
int? wordType;
|
||||
String? icon;
|
||||
int? hotId;
|
||||
String? isCommercial;
|
||||
int? resourceId;
|
||||
bool? showLiveIcon;
|
||||
|
||||
TrendingList({
|
||||
this.position,
|
||||
this.keyword,
|
||||
this.showName,
|
||||
this.wordType,
|
||||
this.icon,
|
||||
this.hotId,
|
||||
this.isCommercial,
|
||||
this.resourceId,
|
||||
this.showLiveIcon,
|
||||
});
|
||||
|
||||
factory TrendingList.fromJson(Map<String, dynamic> json) => TrendingList(
|
||||
position: json['position'] as int?,
|
||||
keyword: json['keyword'] as String?,
|
||||
showName: json['show_name'] as String?,
|
||||
wordType: json['word_type'] as int?,
|
||||
icon: json['icon'] as String?,
|
||||
hotId: json['hot_id'] as int?,
|
||||
isCommercial: json['is_commercial'] as String?,
|
||||
resourceId: json['resource_id'] as int?,
|
||||
showLiveIcon: json['show_live_icon'] as bool?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'position': position,
|
||||
'keyword': keyword,
|
||||
'show_name': showName,
|
||||
'word_type': wordType,
|
||||
'icon': icon,
|
||||
'hot_id': hotId,
|
||||
'is_commercial': isCommercial,
|
||||
'resource_id': resourceId,
|
||||
'show_live_icon': showLiveIcon,
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:PiliPlus/models/space/pr_info.dart';
|
||||
import 'package:PiliPlus/models/space/space_tag_bottom.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
@@ -58,8 +59,8 @@ class Card {
|
||||
String? pendantUrl;
|
||||
@JsonKey(name: 'pendant_title')
|
||||
String? pendantTitle;
|
||||
// @JsonKey(name: 'pr_info')
|
||||
// PrInfo? prInfo;
|
||||
@JsonKey(name: 'pr_info')
|
||||
PrInfo? prInfo;
|
||||
Relation? relation;
|
||||
@JsonKey(name: 'is_deleted')
|
||||
int? isDeleted;
|
||||
@@ -120,7 +121,7 @@ class Card {
|
||||
this.achieve,
|
||||
this.pendantUrl,
|
||||
this.pendantTitle,
|
||||
// this.prInfo,
|
||||
this.prInfo,
|
||||
this.relation,
|
||||
this.isDeleted,
|
||||
this.honours,
|
||||
|
||||
@@ -58,9 +58,9 @@ Card _$CardFromJson(Map<String, dynamic> json) => Card(
|
||||
: Achieve.fromJson(json['achieve'] as Map<String, dynamic>),
|
||||
pendantUrl: json['pendant_url'] as String?,
|
||||
pendantTitle: json['pendant_title'] as String?,
|
||||
// prInfo: json['pr_info'] == null
|
||||
// ? null
|
||||
// : PrInfo.fromJson(json['pr_info'] as Map<String, dynamic>),
|
||||
prInfo: json['pr_info'] == null
|
||||
? null
|
||||
: PrInfo.fromJson(json['pr_info'] as Map<String, dynamic>),
|
||||
relation: json['relation'] == null
|
||||
? null
|
||||
: Relation.fromJson(json['relation'] as Map<String, dynamic>),
|
||||
@@ -131,7 +131,7 @@ Map<String, dynamic> _$CardToJson(Card instance) => <String, dynamic>{
|
||||
'achieve': instance.achieve,
|
||||
'pendant_url': instance.pendantUrl,
|
||||
'pendant_title': instance.pendantTitle,
|
||||
// 'pr_info': instance.prInfo,
|
||||
'pr_info': instance.prInfo,
|
||||
'relation': instance.relation,
|
||||
'is_deleted': instance.isDeleted,
|
||||
'honours': instance.honours,
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import 'package:PiliPlus/models/space/item.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'coin_archive.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class CoinArchive {
|
||||
int? count;
|
||||
List<dynamic>? item;
|
||||
int? count;
|
||||
List<Item>? item;
|
||||
|
||||
CoinArchive({this.count, this.item});
|
||||
CoinArchive({this.count, this.item});
|
||||
|
||||
factory CoinArchive.fromJson(Map<String, dynamic> json) {
|
||||
return _$CoinArchiveFromJson(json);
|
||||
}
|
||||
factory CoinArchive.fromJson(Map<String, dynamic> json) {
|
||||
return _$CoinArchiveFromJson(json);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => _$CoinArchiveToJson(this);
|
||||
Map<String, dynamic> toJson() => _$CoinArchiveToJson(this);
|
||||
}
|
||||
|
||||