Compare commits
468 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 | ||
|
|
34f63612a4 | ||
|
|
edfa9a8dd1 | ||
|
|
95fa19f121 | ||
|
|
79d0d314f5 | ||
|
|
ddbf168c87 | ||
|
|
0eee8bbac2 | ||
|
|
92f02b5943 | ||
|
|
f110c2a55f | ||
|
|
8ddf42fff1 | ||
|
|
d2c34d64c3 | ||
|
|
2341027972 | ||
|
|
006c9301d9 | ||
|
|
1f8955d0b3 | ||
|
|
5a758ebb3a | ||
|
|
97bef56006 | ||
|
|
96ba36ed67 | ||
|
|
edf84fcc8f | ||
|
|
a8428e52d2 | ||
|
|
fd8559228e | ||
|
|
d6587cf3b6 | ||
|
|
7c3e3cb1f8 | ||
|
|
9d0ac30fad | ||
|
|
68d11d7638 | ||
|
|
3881b3dc74 | ||
|
|
99b14d0f0e | ||
|
|
066f3d4132 | ||
|
|
b15fdfa2ff | ||
|
|
94fa0652ac | ||
|
|
5c54e131ba | ||
|
|
1fc85fd618 | ||
|
|
e1c561b613 | ||
|
|
950620bf9e | ||
|
|
ae7a1e2373 | ||
|
|
bddeb72d9b | ||
|
|
b99cf4f629 | ||
|
|
b07cf62bdd | ||
|
|
57d2d3f5d9 | ||
|
|
7854c5e6b9 | ||
|
|
0b8e95477c | ||
|
|
c2e9a7deb3 | ||
|
|
361a6a4c1d | ||
|
|
8eca9a6644 | ||
|
|
0770f325ab | ||
|
|
98d52760b3 | ||
|
|
0ebe976b8a | ||
|
|
21fe0ef288 | ||
|
|
28ef1890d1 | ||
|
|
d6f238c720 | ||
|
|
44bf9dd9e1 | ||
|
|
e357da5162 | ||
|
|
c296aa036a | ||
|
|
12c46f938d | ||
|
|
b4412f5b37 | ||
|
|
5f2ac0d59b | ||
|
|
2a73725455 | ||
|
|
a8725e64ee | ||
|
|
727ae8cd2b | ||
|
|
714f288170 | ||
|
|
3da64d2641 | ||
|
|
2556290a6e | ||
|
|
66b547a904 | ||
|
|
c1ab273478 | ||
|
|
4aa3d5f273 | ||
|
|
a6a1de169b | ||
|
|
af6188be77 | ||
|
|
bd39de2109 | ||
|
|
33375aeb7d | ||
|
|
fafe6c1e91 | ||
|
|
68b072bf44 | ||
|
|
99cdec62a1 | ||
|
|
59797a2f5f | ||
|
|
5cc661e314 | ||
|
|
2ce79d21b5 | ||
|
|
b75fda3596 | ||
|
|
2efa6f4ace | ||
|
|
95e50e436b | ||
|
|
35a53bc8ac | ||
|
|
58c16ef52e | ||
|
|
847ac80d5f | ||
|
|
0408b27ca5 | ||
|
|
2949adbbfd | ||
|
|
2f616ba237 | ||
|
|
b50ead327c | ||
|
|
2fe0f43cb6 | ||
|
|
b85413be9b | ||
|
|
c88776c4a0 | ||
|
|
b7cb977f2b | ||
|
|
3048e36d2f | ||
|
|
64f37fa743 | ||
|
|
737b7d0507 | ||
|
|
973dad4176 | ||
|
|
46110adb8f | ||
|
|
b5c7ed1c34 | ||
|
|
86678ec15a | ||
|
|
893fb63a72 | ||
|
|
3b717cfc58 | ||
|
|
ed40a91a52 | ||
|
|
8b1bec6ed2 | ||
|
|
a6a3476cb2 | ||
|
|
fac3c19d3f | ||
|
|
dc1451c3af | ||
|
|
08b0a93064 | ||
|
|
72dd0b9e81 | ||
|
|
8236b93717 | ||
|
|
c4c5eee2eb | ||
|
|
5cc9c59c76 | ||
|
|
bf4ecc85dd | ||
|
|
da5c2148ad | ||
|
|
bfcea11320 | ||
|
|
ed19e13630 | ||
|
|
6497fb6cd0 | ||
|
|
9c21f03df8 | ||
|
|
7667e73d9d | ||
|
|
ff2ed0421c | ||
|
|
56c5ad360a | ||
|
|
ef644d2837 | ||
|
|
4642eda98d | ||
|
|
8ef163dd38 | ||
|
|
5986add7dd | ||
|
|
c990cf1660 | ||
|
|
76c16c035e | ||
|
|
d5a244ce7f | ||
|
|
432c5133e6 | ||
|
|
a9f9b324a9 | ||
|
|
4735297285 | ||
|
|
3abff4b9da | ||
|
|
c32b98fa7f | ||
|
|
a605c0fcfb | ||
|
|
1e83b4557f | ||
|
|
c3d729fc77 | ||
|
|
00ea891784 | ||
|
|
c98dbccbd7 | ||
|
|
4a68122c31 | ||
|
|
9c4a52de87 | ||
|
|
6c11140f43 | ||
|
|
11398ca64b | ||
|
|
312ce6e639 | ||
|
|
139b48c457 | ||
|
|
f1f478e193 | ||
|
|
1abaf3db3f | ||
|
|
f6bfbc3ed6 | ||
|
|
d2890d72e5 | ||
|
|
5c029c8f64 | ||
|
|
c37a631df2 | ||
|
|
82030b8d06 | ||
|
|
e362f75dac | ||
|
|
3fecf7c0a4 | ||
|
|
801043468d | ||
|
|
1b4f588671 | ||
|
|
7ad48570f0 | ||
|
|
5b8c68303f | ||
|
|
87d3d0ca14 | ||
|
|
b330440371 | ||
|
|
2a173ef804 | ||
|
|
bceabae06f | ||
|
|
69667c135d | ||
|
|
587870ad71 | ||
|
|
609fab345a | ||
|
|
29c47cee78 | ||
|
|
6a9795f561 | ||
|
|
72e7f0aa9f | ||
|
|
acfa384c0c | ||
|
|
c2d27ddd04 | ||
|
|
0a6950e34a | ||
|
|
1c3d77b95d | ||
|
|
fb11208bbe | ||
|
|
94f05127b6 | ||
|
|
25a3046c3c | ||
|
|
f479fc37ba | ||
|
|
3ee19a8f08 | ||
|
|
b8d2ad68dd | ||
|
|
8434c488da | ||
|
|
41f251ad50 | ||
|
|
8e99ff1173 | ||
|
|
a921b983f5 | ||
|
|
81eeda0a68 | ||
|
|
1a54f61355 | ||
|
|
382cd5b73d | ||
|
|
e236485bc7 | ||
|
|
0e69e23606 | ||
|
|
0ef85f2551 | ||
|
|
8d3990124e | ||
|
|
7f912a1781 | ||
|
|
d9ae1dd97a | ||
|
|
307db51aec | ||
|
|
347a704b54 | ||
|
|
9e242fb902 | ||
|
|
192cd60a4f | ||
|
|
a98d8511d6 | ||
|
|
811b79610c | ||
|
|
14129e8f21 | ||
|
|
16de044d3d | ||
|
|
e573a8a9c0 | ||
|
|
108648cabf | ||
|
|
8e4ce07d19 | ||
|
|
09cebd70ae | ||
|
|
6a615c408b | ||
|
|
9ebc054c8c | ||
|
|
b2c520bd91 | ||
|
|
6506afa732 |
23
.github/ISSUE_TEMPLATE/bug-反馈.md
vendored
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Bug 反馈
|
||||
about: 描述你所遇到的bug
|
||||
title: "[Bug] "
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### 问题描述
|
||||
请提供一个清晰而简明的问题描述。
|
||||
|
||||
### 复现步骤
|
||||
请提供复现该问题所需的具体步骤。
|
||||
|
||||
### 预期行为
|
||||
请描述你期望的正确行为或结果。
|
||||
|
||||
### 错误日志
|
||||
请提供设置->关于->错误日志中的内容,粘贴在下方代码框中。如果没有,请提供您的app版本号、系统版本、设备型号等相关信息。
|
||||
|
||||
### 相关信息
|
||||
请补充截图、录屏、BV号等其他有助于解决问题的信息。
|
||||
55
.github/ISSUE_TEMPLATE/bug-反馈.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Bug 反馈
|
||||
description: 描述你所遇到的bug
|
||||
labels: [ "bug" ]
|
||||
title: "[Bug] "
|
||||
body:
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: 检查清单
|
||||
options:
|
||||
- label: 之前没有人提交过类似或相同的 bug report。1
|
||||
required: true
|
||||
- label: 之前没有人提交过类似或相同的 bug report。2
|
||||
required: true
|
||||
- label: 之前没有人提交过类似或相同的 bug report。3
|
||||
required: true
|
||||
- label: 正在使用最新版本。
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: bug
|
||||
attributes:
|
||||
label: 问题描述
|
||||
description: 请提供一个清晰而简明的问题描述。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: 复现步骤
|
||||
description: 请提供复现该问题所需的具体步骤。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 预期行为
|
||||
description: 请描述你期望的正确行为或结果。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: 错误日志
|
||||
description: 请提供设置->关于->错误日志中的内容,粘贴在下方代码框中。如果没有,请提供您的app版本号、系统版本、设备型号等相关信息。
|
||||
|
||||
- type: textarea
|
||||
id: info
|
||||
attributes:
|
||||
label: 相关信息
|
||||
description: 请补充截图、录屏、BV号等其他有助于解决问题的信息。
|
||||
20
.github/ISSUE_TEMPLATE/功能请求.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: 功能请求
|
||||
about: 对于功能的一些建议
|
||||
title: "[FR] "
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### 功能描述
|
||||
请提供对所请求功能的清晰描述。
|
||||
|
||||
### 目标
|
||||
请描述你希望通过这个功能实现的目标。
|
||||
|
||||
### 解决方案
|
||||
如果你有任何关于如何实现这个功能的想法或建议,请在这里提供。
|
||||
|
||||
### 其他
|
||||
请提供已实现该功能或类似功能的应用
|
||||
43
.github/ISSUE_TEMPLATE/功能请求.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: 功能请求
|
||||
description: 对于功能的一些建议
|
||||
labels: [ "enhancement" ]
|
||||
title: "[FR] "
|
||||
body:
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: 检查清单
|
||||
options:
|
||||
- label: 之前没有人提交过类似或相同的功能请求。
|
||||
required: true
|
||||
- label: 正在使用最新版本。
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: desc
|
||||
attributes:
|
||||
label: 功能描述
|
||||
description: 请提供对所请求功能的清晰描述。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: propose
|
||||
attributes:
|
||||
label: 目标
|
||||
description: 请描述你希望通过这个功能实现的目标。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: 解决方案
|
||||
description: 如果你有任何关于如何实现这个功能的想法或建议,请在这里提供。
|
||||
|
||||
- type: textarea
|
||||
id: addition
|
||||
attributes:
|
||||
label: 其他
|
||||
description: 请提供已实现该功能或类似功能的应用
|
||||
18
README.md
@@ -47,12 +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] 滑动跳转预览视频缩略图
|
||||
- [x] Live Photo
|
||||
- [x] 复制/移动收藏夹/稍后再看视频
|
||||
- [x] 复制/移动/排序收藏夹/稍后再看视频
|
||||
- [x] 超分辨率
|
||||
- [x] 合并弹幕
|
||||
- [x] 会员彩色弹幕
|
||||
@@ -70,7 +80,6 @@
|
||||
- [x] 评论楼中楼定位点击查看的评论
|
||||
- [x] 评论楼中楼按热度/时间排序
|
||||
- [x] 评论点踩
|
||||
- [x] 显示ops专栏
|
||||
- [x] 私信发图
|
||||
- [x] 投币动画
|
||||
- [x] 取消/追番,更新追番状态
|
||||
@@ -84,7 +93,7 @@
|
||||
- [x] 筛选搜索
|
||||
- [x] 转发动态
|
||||
- [x] 合集图片
|
||||
- [x] 删除/置顶私信
|
||||
- [x] 删除/置顶/撤回私信
|
||||
- [x] 举报用户/评论/视频/动态
|
||||
- [x] 删除/发布文本/图片动态
|
||||
- [x] 其他
|
||||
@@ -155,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 |
@@ -23,251 +23,251 @@ class Constants {
|
||||
static const String userAgent =
|
||||
'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android_hd build/2001100 channel/yingyongbao innerVer/2001100 osVer/14 network/2';
|
||||
static const String statistics =
|
||||
'%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D';
|
||||
//Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
|
||||
'{"appId":5,"platform":3,"version":"1.46.2","abtest":""}';
|
||||
// 请求时会自动encodeComponent
|
||||
|
||||
static const urlPattern =
|
||||
r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]';
|
||||
|
||||
static const goodsUrlPrefix = "https://gaoneng.bilibili.com/tetris";
|
||||
static get goodsUrlPrefix => "https://gaoneng.bilibili.com/tetris";
|
||||
|
||||
// 超分辨率滤镜
|
||||
static const List<String> mpvAnime4KShaders = [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
'Anime4K_Restore_CNN_VL.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_VL.glsl',
|
||||
'Anime4K_AutoDownscalePre_x2.glsl',
|
||||
'Anime4K_AutoDownscalePre_x4.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_M.glsl'
|
||||
];
|
||||
// 超分辨率滤镜
|
||||
static List<String> get mpvAnime4KShaders => [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
'Anime4K_Restore_CNN_VL.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_VL.glsl',
|
||||
'Anime4K_AutoDownscalePre_x2.glsl',
|
||||
'Anime4K_AutoDownscalePre_x4.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_M.glsl'
|
||||
];
|
||||
|
||||
// 超分辨率滤镜 (轻量)
|
||||
static const List<String> mpvAnime4KShadersLite = [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
'Anime4K_Restore_CNN_M.glsl',
|
||||
'Anime4K_Restore_CNN_S.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_M.glsl',
|
||||
'Anime4K_AutoDownscalePre_x2.glsl',
|
||||
'Anime4K_AutoDownscalePre_x4.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_S.glsl'
|
||||
];
|
||||
static List<String> get mpvAnime4KShadersLite => [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
'Anime4K_Restore_CNN_M.glsl',
|
||||
'Anime4K_Restore_CNN_S.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_M.glsl',
|
||||
'Anime4K_AutoDownscalePre_x2.glsl',
|
||||
'Anime4K_AutoDownscalePre_x4.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_S.glsl'
|
||||
];
|
||||
|
||||
//内容来自 https://passport.bilibili.com/web/generic/country/list
|
||||
static const List<Map<String, dynamic>> internationalDialingPrefix = [
|
||||
{"id": 1, "cname": "中国大陆", "country_id": "86"},
|
||||
{"id": 5, "cname": "中国香港特别行政区", "country_id": "852"},
|
||||
{"id": 2, "cname": "中国澳门特别行政区", "country_id": "853"},
|
||||
{"id": 3, "cname": "中国台湾", "country_id": "886"},
|
||||
{"id": 4, "cname": "美国", "country_id": "1"},
|
||||
{"id": 6, "cname": "比利时", "country_id": "32"},
|
||||
{"id": 7, "cname": "澳大利亚", "country_id": "61"},
|
||||
{"id": 8, "cname": "法国", "country_id": "33"},
|
||||
{"id": 9, "cname": "加拿大", "country_id": "1"},
|
||||
{"id": 10, "cname": "日本", "country_id": "81"},
|
||||
{"id": 11, "cname": "新加坡", "country_id": "65"},
|
||||
{"id": 12, "cname": "韩国", "country_id": "82"},
|
||||
{"id": 13, "cname": "马来西亚", "country_id": "60"},
|
||||
{"id": 14, "cname": "英国", "country_id": "44"},
|
||||
{"id": 15, "cname": "意大利", "country_id": "39"},
|
||||
{"id": 16, "cname": "德国", "country_id": "49"},
|
||||
{"id": 18, "cname": "俄罗斯", "country_id": "7"},
|
||||
{"id": 19, "cname": "新西兰", "country_id": "64"}, //common:1-19
|
||||
{"id": 153, "cname": "瓦利斯群岛和富图纳群岛", "country_id": "1681"},
|
||||
{"id": 152, "cname": "葡萄牙", "country_id": "351"},
|
||||
{"id": 151, "cname": "帕劳", "country_id": "680"},
|
||||
{"id": 150, "cname": "诺福克岛", "country_id": "672"},
|
||||
{"id": 149, "cname": "挪威", "country_id": "47"},
|
||||
{"id": 148, "cname": "纽埃岛", "country_id": "683"},
|
||||
{"id": 147, "cname": "尼日利亚", "country_id": "234"},
|
||||
{"id": 146, "cname": "尼日尔", "country_id": "227"},
|
||||
{"id": 145, "cname": "尼加拉瓜", "country_id": "505"},
|
||||
{"id": 144, "cname": "尼泊尔", "country_id": "977"},
|
||||
{"id": 143, "cname": "瑙鲁", "country_id": "674"},
|
||||
{"id": 154, "cname": "格鲁吉亚", "country_id": "995"},
|
||||
{"id": 155, "cname": "瑞典", "country_id": "46"},
|
||||
{"id": 165, "cname": "沙特阿拉伯", "country_id": "966"},
|
||||
{"id": 164, "cname": "桑给巴尔岛", "country_id": "259"},
|
||||
{"id": 163, "cname": "塞舌尔共和国", "country_id": "248"},
|
||||
{"id": 162, "cname": "塞浦路斯", "country_id": "357"},
|
||||
{"id": 161, "cname": "塞内加尔", "country_id": "221"},
|
||||
{"id": 160, "cname": "塞拉利昂", "country_id": "232"},
|
||||
{"id": 159, "cname": "萨摩亚,东部", "country_id": "684"},
|
||||
{"id": 158, "cname": "萨摩亚,西部", "country_id": "685"},
|
||||
{"id": 157, "cname": "萨尔瓦多", "country_id": "503"},
|
||||
{"id": 156, "cname": "瑞士", "country_id": "41"},
|
||||
{"id": 166, "cname": "圣多美和普林西比", "country_id": "239"},
|
||||
{"id": 142, "cname": "塞尔维亚", "country_id": "381"},
|
||||
{"id": 141, "cname": "南非", "country_id": "27"},
|
||||
{"id": 128, "cname": "毛里塔尼亚", "country_id": "222"},
|
||||
{"id": 127, "cname": "毛里求斯", "country_id": "230"},
|
||||
{"id": 126, "cname": "马歇尔岛", "country_id": "692"},
|
||||
{"id": 125, "cname": "马提尼克岛", "country_id": "596"},
|
||||
{"id": 124, "cname": "马其顿", "country_id": "389"},
|
||||
{"id": 123, "cname": "马里亚纳岛", "country_id": "1670"},
|
||||
{"id": 122, "cname": "马里", "country_id": "223"},
|
||||
{"id": 121, "cname": "马拉维", "country_id": "265"},
|
||||
{"id": 120, "cname": "马耳他", "country_id": "356"},
|
||||
{"id": 119, "cname": "马尔代夫", "country_id": "960"},
|
||||
{"id": 129, "cname": "蒙古", "country_id": "976"},
|
||||
{"id": 130, "cname": "蒙特塞拉特岛", "country_id": "1664"},
|
||||
{"id": 140, "cname": "纳米比亚", "country_id": "264"},
|
||||
{"id": 139, "cname": "墨西哥", "country_id": "52"},
|
||||
{"id": 138, "cname": "莫桑比克", "country_id": "258"},
|
||||
{"id": 137, "cname": "摩纳哥", "country_id": "377"},
|
||||
{"id": 136, "cname": "摩洛哥", "country_id": "212"},
|
||||
{"id": 135, "cname": "摩尔多瓦", "country_id": "373"},
|
||||
{"id": 134, "cname": "缅甸", "country_id": "95"},
|
||||
{"id": 133, "cname": "密克罗尼西亚", "country_id": "691"},
|
||||
{"id": 132, "cname": "秘鲁", "country_id": "51"},
|
||||
{"id": 131, "cname": "孟加拉国", "country_id": "880"},
|
||||
{"id": 118, "cname": "马达加斯加", "country_id": "261"},
|
||||
{"id": 167, "cname": "圣卢西亚", "country_id": "1784"},
|
||||
{"id": 216, "cname": "智利", "country_id": "56"},
|
||||
{"id": 203, "cname": "牙买加", "country_id": "1876"},
|
||||
{"id": 202, "cname": "叙利亚", "country_id": "963"},
|
||||
{"id": 201, "cname": "匈牙利", "country_id": "36"},
|
||||
{"id": 200, "cname": "科特迪瓦", "country_id": "225"},
|
||||
{"id": 199, "cname": "希腊", "country_id": "30"},
|
||||
{"id": 198, "cname": "西班牙", "country_id": "34"},
|
||||
{"id": 197, "cname": "乌兹别克斯坦", "country_id": "998"},
|
||||
{"id": 196, "cname": "乌拉圭", "country_id": "598"},
|
||||
{"id": 195, "cname": "乌克兰", "country_id": "380"},
|
||||
{"id": 194, "cname": "乌干达", "country_id": "256"},
|
||||
{"id": 204, "cname": "亚美尼亚", "country_id": "374"},
|
||||
{"id": 205, "cname": "也门", "country_id": "967"},
|
||||
{"id": 215, "cname": "直布罗陀", "country_id": "350"},
|
||||
{"id": 214, "cname": "乍得", "country_id": "235"},
|
||||
{"id": 213, "cname": "赞比亚", "country_id": "260"},
|
||||
{"id": 212, "cname": "越南", "country_id": "84"},
|
||||
{"id": 211, "cname": "约旦", "country_id": "962"},
|
||||
{"id": 210, "cname": "印尼", "country_id": "62"},
|
||||
{"id": 209, "cname": "印度", "country_id": "91"},
|
||||
{"id": 208, "cname": "以色列", "country_id": "972"},
|
||||
{"id": 207, "cname": "伊朗", "country_id": "98"},
|
||||
{"id": 206, "cname": "伊拉克", "country_id": "964"},
|
||||
{"id": 193, "cname": "文莱", "country_id": "673"},
|
||||
{"id": 192, "cname": "委内瑞拉", "country_id": "58"},
|
||||
{"id": 191, "cname": "维珍群岛(英属)", "country_id": "1284"},
|
||||
{"id": 178, "cname": "泰国", "country_id": "66"},
|
||||
{"id": 177, "cname": "索马里", "country_id": "252"},
|
||||
{"id": 176, "cname": "所罗门群岛", "country_id": "677"},
|
||||
{"id": 175, "cname": "苏里南", "country_id": "597"},
|
||||
{"id": 174, "cname": "苏丹", "country_id": "249"},
|
||||
{"id": 173, "cname": "斯威士兰", "country_id": "268"},
|
||||
{"id": 172, "cname": "斯洛文尼亚", "country_id": "386"},
|
||||
{"id": 171, "cname": "斯洛伐克", "country_id": "421"},
|
||||
{"id": 170, "cname": "斯里兰卡", "country_id": "94"},
|
||||
{"id": 169, "cname": "圣皮埃尔和密克隆群岛", "country_id": "508"},
|
||||
{"id": 179, "cname": "坦桑尼亚", "country_id": "255"},
|
||||
{"id": 180, "cname": "汤加", "country_id": "676"},
|
||||
{"id": 190, "cname": "维珍群岛(美属)", "country_id": "1340"},
|
||||
{"id": 189, "cname": "瓦努阿图", "country_id": "678"},
|
||||
{"id": 188, "cname": "托克劳岛", "country_id": "690"},
|
||||
{"id": 187, "cname": "土库曼斯坦", "country_id": "993"},
|
||||
{"id": 186, "cname": "土耳其", "country_id": "90"},
|
||||
{"id": 185, "cname": "图瓦卢", "country_id": "688"},
|
||||
{"id": 184, "cname": "突尼斯", "country_id": "216"},
|
||||
{"id": 183, "cname": "阿森松岛", "country_id": "247"},
|
||||
{"id": 182, "cname": "特立尼达和多巴哥", "country_id": "1868"},
|
||||
{"id": 181, "cname": "特克斯和凯科斯", "country_id": "1649"},
|
||||
{"id": 168, "cname": "圣马力诺", "country_id": "378"},
|
||||
{"id": 67, "cname": "法属圭亚那", "country_id": "594"},
|
||||
{"id": 54, "cname": "不丹", "country_id": "975"},
|
||||
{"id": 53, "cname": "博茨瓦纳", "country_id": "267"},
|
||||
{"id": 52, "cname": "伯利兹", "country_id": "501"},
|
||||
{"id": 51, "cname": "玻利维亚", "country_id": "591"},
|
||||
{"id": 50, "cname": "波兰", "country_id": "48"},
|
||||
{"id": 49, "cname": "波黑", "country_id": "387"},
|
||||
{"id": 48, "cname": "波多黎各", "country_id": "1787"},
|
||||
{"id": 47, "cname": "冰岛", "country_id": "354"},
|
||||
{"id": 46, "cname": "贝宁", "country_id": "229"},
|
||||
{"id": 45, "cname": "保加利亚", "country_id": "359"},
|
||||
{"id": 55, "cname": "布基纳法索", "country_id": "226"},
|
||||
{"id": 56, "cname": "布隆迪", "country_id": "257"},
|
||||
{"id": 66, "cname": "法属波利尼西亚", "country_id": "689"},
|
||||
{"id": 65, "cname": "法罗岛", "country_id": "298"},
|
||||
{"id": 64, "cname": "厄立特里亚", "country_id": "291"},
|
||||
{"id": 63, "cname": "厄瓜多尔", "country_id": "593"},
|
||||
{"id": 62, "cname": "多米尼加代表", "country_id": "1809"},
|
||||
{"id": 61, "cname": "多米尼加", "country_id": "1767"},
|
||||
{"id": 60, "cname": "多哥", "country_id": "228"},
|
||||
{"id": 59, "cname": "迪戈加西亚岛", "country_id": "246"},
|
||||
{"id": 58, "cname": "丹麦", "country_id": "45"},
|
||||
{"id": 57, "cname": "赤道几内亚", "country_id": "240"},
|
||||
{"id": 44, "cname": "百慕大群岛", "country_id": "1441"},
|
||||
{"id": 43, "cname": "白俄罗斯", "country_id": "375"},
|
||||
{"id": 42, "cname": "巴西", "country_id": "55"},
|
||||
{"id": 29, "cname": "爱尔兰", "country_id": "353"},
|
||||
{"id": 28, "cname": "埃塞俄比亚", "country_id": "251"},
|
||||
{"id": 27, "cname": "埃及", "country_id": "20"},
|
||||
{"id": 26, "cname": "阿塞拜疆", "country_id": "994"},
|
||||
{"id": 25, "cname": "阿曼", "country_id": "968"},
|
||||
{"id": 24, "cname": "阿联酋", "country_id": "971"},
|
||||
{"id": 23, "cname": "阿根廷", "country_id": "54"},
|
||||
{"id": 22, "cname": "阿富汗", "country_id": "93"},
|
||||
{"id": 21, "cname": "阿尔及利亚", "country_id": "213"},
|
||||
{"id": 20, "cname": "阿尔巴尼亚", "country_id": "355"},
|
||||
{"id": 30, "cname": "爱沙尼亚", "country_id": "372"},
|
||||
{"id": 31, "cname": "安道尔", "country_id": "376"},
|
||||
{"id": 41, "cname": "巴拿马", "country_id": "507"},
|
||||
{"id": 40, "cname": "巴林", "country_id": "973"},
|
||||
{"id": 39, "cname": "巴拉圭", "country_id": "595"},
|
||||
{"id": 38, "cname": "巴基斯坦", "country_id": "92"},
|
||||
{"id": 37, "cname": "巴哈马群岛", "country_id": "1242"},
|
||||
{"id": 36, "cname": "巴布亚新几内亚", "country_id": "675"},
|
||||
{"id": 35, "cname": "巴巴多斯", "country_id": "1246"},
|
||||
{"id": 34, "cname": "奥地利", "country_id": "43"},
|
||||
{"id": 33, "cname": "安提瓜岛和巴布达", "country_id": "1268"},
|
||||
{"id": 32, "cname": "安哥拉", "country_id": "244"},
|
||||
{"id": 68, "cname": "非洲中部", "country_id": "236"},
|
||||
{"id": 117, "cname": "罗马尼亚", "country_id": "40"},
|
||||
{"id": 104, "cname": "科威特", "country_id": "965"},
|
||||
{"id": 103, "cname": "科摩罗", "country_id": "269"},
|
||||
{"id": 102, "cname": "开曼群岛", "country_id": "1345"},
|
||||
{"id": 101, "cname": "卡塔尔", "country_id": "974"},
|
||||
{"id": 100, "cname": "喀麦隆", "country_id": "237"},
|
||||
{"id": 99, "cname": "聚会岛", "country_id": "262"},
|
||||
{"id": 98, "cname": "津巴布韦", "country_id": "263"},
|
||||
{"id": 97, "cname": "捷克", "country_id": "420"},
|
||||
{"id": 96, "cname": "柬埔寨", "country_id": "855"},
|
||||
{"id": 95, "cname": "加蓬", "country_id": "241"},
|
||||
{"id": 105, "cname": "克罗地亚", "country_id": "385"},
|
||||
{"id": 106, "cname": "肯尼亚", "country_id": "254"},
|
||||
{"id": 116, "cname": "卢旺达", "country_id": "250"},
|
||||
{"id": 115, "cname": "卢森堡", "country_id": "352"},
|
||||
{"id": 114, "cname": "利比亚", "country_id": "218"},
|
||||
{"id": 113, "cname": "利比里亚", "country_id": "231"},
|
||||
{"id": 112, "cname": "立陶宛", "country_id": "370"},
|
||||
{"id": 111, "cname": "黎巴嫩", "country_id": "961"},
|
||||
{"id": 110, "cname": "老挝", "country_id": "856"},
|
||||
{"id": 109, "cname": "莱索托", "country_id": "266"},
|
||||
{"id": 108, "cname": "拉脱维亚", "country_id": "371"},
|
||||
{"id": 107, "cname": "库克岛", "country_id": "682"},
|
||||
{"id": 94, "cname": "加纳", "country_id": "233"},
|
||||
{"id": 93, "cname": "几内亚比绍", "country_id": "245"},
|
||||
{"id": 92, "cname": "几内亚", "country_id": "224"},
|
||||
{"id": 79, "cname": "格林纳达", "country_id": "1473"},
|
||||
{"id": 78, "cname": "哥斯达黎加", "country_id": "506"},
|
||||
{"id": 77, "cname": "哥伦比亚", "country_id": "57"},
|
||||
{"id": 76, "cname": "刚果(金)", "country_id": "243"},
|
||||
{"id": 75, "cname": "刚果", "country_id": "242"},
|
||||
{"id": 74, "cname": "冈比亚", "country_id": "220"},
|
||||
{"id": 73, "cname": "福克兰岛", "country_id": "500"},
|
||||
{"id": 72, "cname": "佛得角", "country_id": "238"},
|
||||
{"id": 71, "cname": "芬兰", "country_id": "358"},
|
||||
{"id": 70, "cname": "斐济", "country_id": "679"},
|
||||
{"id": 80, "cname": "格陵兰岛", "country_id": "299"},
|
||||
{"id": 81, "cname": "古巴", "country_id": "53"},
|
||||
{"id": 91, "cname": "吉尔吉斯斯坦", "country_id": "996"},
|
||||
{"id": 90, "cname": "吉布提", "country_id": "253"},
|
||||
{"id": 89, "cname": "基里巴斯", "country_id": "686"},
|
||||
{"id": 88, "cname": "维克岛", "country_id": "1808"},
|
||||
{"id": 87, "cname": "洪都拉斯", "country_id": "504"},
|
||||
{"id": 86, "cname": "荷兰", "country_id": "31"},
|
||||
{"id": 85, "cname": "朝鲜", "country_id": "850"},
|
||||
{"id": 84, "cname": "海地", "country_id": "509"},
|
||||
{"id": 83, "cname": "关岛", "country_id": "1671"},
|
||||
{"id": 82, "cname": "瓜德罗普岛", "country_id": "590"},
|
||||
{"id": 69, "cname": "菲律宾", "country_id": "63"}
|
||||
];
|
||||
static List<Map<String, dynamic>> get internationalDialingPrefix => [
|
||||
{"id": 1, "cname": "中国大陆", "country_id": "86"},
|
||||
{"id": 5, "cname": "中国香港特别行政区", "country_id": "852"},
|
||||
{"id": 2, "cname": "中国澳门特别行政区", "country_id": "853"},
|
||||
{"id": 3, "cname": "中国台湾", "country_id": "886"},
|
||||
{"id": 4, "cname": "美国", "country_id": "1"},
|
||||
{"id": 6, "cname": "比利时", "country_id": "32"},
|
||||
{"id": 7, "cname": "澳大利亚", "country_id": "61"},
|
||||
{"id": 8, "cname": "法国", "country_id": "33"},
|
||||
{"id": 9, "cname": "加拿大", "country_id": "1"},
|
||||
{"id": 10, "cname": "日本", "country_id": "81"},
|
||||
{"id": 11, "cname": "新加坡", "country_id": "65"},
|
||||
{"id": 12, "cname": "韩国", "country_id": "82"},
|
||||
{"id": 13, "cname": "马来西亚", "country_id": "60"},
|
||||
{"id": 14, "cname": "英国", "country_id": "44"},
|
||||
{"id": 15, "cname": "意大利", "country_id": "39"},
|
||||
{"id": 16, "cname": "德国", "country_id": "49"},
|
||||
{"id": 18, "cname": "俄罗斯", "country_id": "7"},
|
||||
{"id": 19, "cname": "新西兰", "country_id": "64"}, //common:1-19
|
||||
{"id": 153, "cname": "瓦利斯群岛和富图纳群岛", "country_id": "1681"},
|
||||
{"id": 152, "cname": "葡萄牙", "country_id": "351"},
|
||||
{"id": 151, "cname": "帕劳", "country_id": "680"},
|
||||
{"id": 150, "cname": "诺福克岛", "country_id": "672"},
|
||||
{"id": 149, "cname": "挪威", "country_id": "47"},
|
||||
{"id": 148, "cname": "纽埃岛", "country_id": "683"},
|
||||
{"id": 147, "cname": "尼日利亚", "country_id": "234"},
|
||||
{"id": 146, "cname": "尼日尔", "country_id": "227"},
|
||||
{"id": 145, "cname": "尼加拉瓜", "country_id": "505"},
|
||||
{"id": 144, "cname": "尼泊尔", "country_id": "977"},
|
||||
{"id": 143, "cname": "瑙鲁", "country_id": "674"},
|
||||
{"id": 154, "cname": "格鲁吉亚", "country_id": "995"},
|
||||
{"id": 155, "cname": "瑞典", "country_id": "46"},
|
||||
{"id": 165, "cname": "沙特阿拉伯", "country_id": "966"},
|
||||
{"id": 164, "cname": "桑给巴尔岛", "country_id": "259"},
|
||||
{"id": 163, "cname": "塞舌尔共和国", "country_id": "248"},
|
||||
{"id": 162, "cname": "塞浦路斯", "country_id": "357"},
|
||||
{"id": 161, "cname": "塞内加尔", "country_id": "221"},
|
||||
{"id": 160, "cname": "塞拉利昂", "country_id": "232"},
|
||||
{"id": 159, "cname": "萨摩亚,东部", "country_id": "684"},
|
||||
{"id": 158, "cname": "萨摩亚,西部", "country_id": "685"},
|
||||
{"id": 157, "cname": "萨尔瓦多", "country_id": "503"},
|
||||
{"id": 156, "cname": "瑞士", "country_id": "41"},
|
||||
{"id": 166, "cname": "圣多美和普林西比", "country_id": "239"},
|
||||
{"id": 142, "cname": "塞尔维亚", "country_id": "381"},
|
||||
{"id": 141, "cname": "南非", "country_id": "27"},
|
||||
{"id": 128, "cname": "毛里塔尼亚", "country_id": "222"},
|
||||
{"id": 127, "cname": "毛里求斯", "country_id": "230"},
|
||||
{"id": 126, "cname": "马歇尔岛", "country_id": "692"},
|
||||
{"id": 125, "cname": "马提尼克岛", "country_id": "596"},
|
||||
{"id": 124, "cname": "马其顿", "country_id": "389"},
|
||||
{"id": 123, "cname": "马里亚纳岛", "country_id": "1670"},
|
||||
{"id": 122, "cname": "马里", "country_id": "223"},
|
||||
{"id": 121, "cname": "马拉维", "country_id": "265"},
|
||||
{"id": 120, "cname": "马耳他", "country_id": "356"},
|
||||
{"id": 119, "cname": "马尔代夫", "country_id": "960"},
|
||||
{"id": 129, "cname": "蒙古", "country_id": "976"},
|
||||
{"id": 130, "cname": "蒙特塞拉特岛", "country_id": "1664"},
|
||||
{"id": 140, "cname": "纳米比亚", "country_id": "264"},
|
||||
{"id": 139, "cname": "墨西哥", "country_id": "52"},
|
||||
{"id": 138, "cname": "莫桑比克", "country_id": "258"},
|
||||
{"id": 137, "cname": "摩纳哥", "country_id": "377"},
|
||||
{"id": 136, "cname": "摩洛哥", "country_id": "212"},
|
||||
{"id": 135, "cname": "摩尔多瓦", "country_id": "373"},
|
||||
{"id": 134, "cname": "缅甸", "country_id": "95"},
|
||||
{"id": 133, "cname": "密克罗尼西亚", "country_id": "691"},
|
||||
{"id": 132, "cname": "秘鲁", "country_id": "51"},
|
||||
{"id": 131, "cname": "孟加拉国", "country_id": "880"},
|
||||
{"id": 118, "cname": "马达加斯加", "country_id": "261"},
|
||||
{"id": 167, "cname": "圣卢西亚", "country_id": "1784"},
|
||||
{"id": 216, "cname": "智利", "country_id": "56"},
|
||||
{"id": 203, "cname": "牙买加", "country_id": "1876"},
|
||||
{"id": 202, "cname": "叙利亚", "country_id": "963"},
|
||||
{"id": 201, "cname": "匈牙利", "country_id": "36"},
|
||||
{"id": 200, "cname": "科特迪瓦", "country_id": "225"},
|
||||
{"id": 199, "cname": "希腊", "country_id": "30"},
|
||||
{"id": 198, "cname": "西班牙", "country_id": "34"},
|
||||
{"id": 197, "cname": "乌兹别克斯坦", "country_id": "998"},
|
||||
{"id": 196, "cname": "乌拉圭", "country_id": "598"},
|
||||
{"id": 195, "cname": "乌克兰", "country_id": "380"},
|
||||
{"id": 194, "cname": "乌干达", "country_id": "256"},
|
||||
{"id": 204, "cname": "亚美尼亚", "country_id": "374"},
|
||||
{"id": 205, "cname": "也门", "country_id": "967"},
|
||||
{"id": 215, "cname": "直布罗陀", "country_id": "350"},
|
||||
{"id": 214, "cname": "乍得", "country_id": "235"},
|
||||
{"id": 213, "cname": "赞比亚", "country_id": "260"},
|
||||
{"id": 212, "cname": "越南", "country_id": "84"},
|
||||
{"id": 211, "cname": "约旦", "country_id": "962"},
|
||||
{"id": 210, "cname": "印尼", "country_id": "62"},
|
||||
{"id": 209, "cname": "印度", "country_id": "91"},
|
||||
{"id": 208, "cname": "以色列", "country_id": "972"},
|
||||
{"id": 207, "cname": "伊朗", "country_id": "98"},
|
||||
{"id": 206, "cname": "伊拉克", "country_id": "964"},
|
||||
{"id": 193, "cname": "文莱", "country_id": "673"},
|
||||
{"id": 192, "cname": "委内瑞拉", "country_id": "58"},
|
||||
{"id": 191, "cname": "维珍群岛(英属)", "country_id": "1284"},
|
||||
{"id": 178, "cname": "泰国", "country_id": "66"},
|
||||
{"id": 177, "cname": "索马里", "country_id": "252"},
|
||||
{"id": 176, "cname": "所罗门群岛", "country_id": "677"},
|
||||
{"id": 175, "cname": "苏里南", "country_id": "597"},
|
||||
{"id": 174, "cname": "苏丹", "country_id": "249"},
|
||||
{"id": 173, "cname": "斯威士兰", "country_id": "268"},
|
||||
{"id": 172, "cname": "斯洛文尼亚", "country_id": "386"},
|
||||
{"id": 171, "cname": "斯洛伐克", "country_id": "421"},
|
||||
{"id": 170, "cname": "斯里兰卡", "country_id": "94"},
|
||||
{"id": 169, "cname": "圣皮埃尔和密克隆群岛", "country_id": "508"},
|
||||
{"id": 179, "cname": "坦桑尼亚", "country_id": "255"},
|
||||
{"id": 180, "cname": "汤加", "country_id": "676"},
|
||||
{"id": 190, "cname": "维珍群岛(美属)", "country_id": "1340"},
|
||||
{"id": 189, "cname": "瓦努阿图", "country_id": "678"},
|
||||
{"id": 188, "cname": "托克劳岛", "country_id": "690"},
|
||||
{"id": 187, "cname": "土库曼斯坦", "country_id": "993"},
|
||||
{"id": 186, "cname": "土耳其", "country_id": "90"},
|
||||
{"id": 185, "cname": "图瓦卢", "country_id": "688"},
|
||||
{"id": 184, "cname": "突尼斯", "country_id": "216"},
|
||||
{"id": 183, "cname": "阿森松岛", "country_id": "247"},
|
||||
{"id": 182, "cname": "特立尼达和多巴哥", "country_id": "1868"},
|
||||
{"id": 181, "cname": "特克斯和凯科斯", "country_id": "1649"},
|
||||
{"id": 168, "cname": "圣马力诺", "country_id": "378"},
|
||||
{"id": 67, "cname": "法属圭亚那", "country_id": "594"},
|
||||
{"id": 54, "cname": "不丹", "country_id": "975"},
|
||||
{"id": 53, "cname": "博茨瓦纳", "country_id": "267"},
|
||||
{"id": 52, "cname": "伯利兹", "country_id": "501"},
|
||||
{"id": 51, "cname": "玻利维亚", "country_id": "591"},
|
||||
{"id": 50, "cname": "波兰", "country_id": "48"},
|
||||
{"id": 49, "cname": "波黑", "country_id": "387"},
|
||||
{"id": 48, "cname": "波多黎各", "country_id": "1787"},
|
||||
{"id": 47, "cname": "冰岛", "country_id": "354"},
|
||||
{"id": 46, "cname": "贝宁", "country_id": "229"},
|
||||
{"id": 45, "cname": "保加利亚", "country_id": "359"},
|
||||
{"id": 55, "cname": "布基纳法索", "country_id": "226"},
|
||||
{"id": 56, "cname": "布隆迪", "country_id": "257"},
|
||||
{"id": 66, "cname": "法属波利尼西亚", "country_id": "689"},
|
||||
{"id": 65, "cname": "法罗岛", "country_id": "298"},
|
||||
{"id": 64, "cname": "厄立特里亚", "country_id": "291"},
|
||||
{"id": 63, "cname": "厄瓜多尔", "country_id": "593"},
|
||||
{"id": 62, "cname": "多米尼加代表", "country_id": "1809"},
|
||||
{"id": 61, "cname": "多米尼加", "country_id": "1767"},
|
||||
{"id": 60, "cname": "多哥", "country_id": "228"},
|
||||
{"id": 59, "cname": "迪戈加西亚岛", "country_id": "246"},
|
||||
{"id": 58, "cname": "丹麦", "country_id": "45"},
|
||||
{"id": 57, "cname": "赤道几内亚", "country_id": "240"},
|
||||
{"id": 44, "cname": "百慕大群岛", "country_id": "1441"},
|
||||
{"id": 43, "cname": "白俄罗斯", "country_id": "375"},
|
||||
{"id": 42, "cname": "巴西", "country_id": "55"},
|
||||
{"id": 29, "cname": "爱尔兰", "country_id": "353"},
|
||||
{"id": 28, "cname": "埃塞俄比亚", "country_id": "251"},
|
||||
{"id": 27, "cname": "埃及", "country_id": "20"},
|
||||
{"id": 26, "cname": "阿塞拜疆", "country_id": "994"},
|
||||
{"id": 25, "cname": "阿曼", "country_id": "968"},
|
||||
{"id": 24, "cname": "阿联酋", "country_id": "971"},
|
||||
{"id": 23, "cname": "阿根廷", "country_id": "54"},
|
||||
{"id": 22, "cname": "阿富汗", "country_id": "93"},
|
||||
{"id": 21, "cname": "阿尔及利亚", "country_id": "213"},
|
||||
{"id": 20, "cname": "阿尔巴尼亚", "country_id": "355"},
|
||||
{"id": 30, "cname": "爱沙尼亚", "country_id": "372"},
|
||||
{"id": 31, "cname": "安道尔", "country_id": "376"},
|
||||
{"id": 41, "cname": "巴拿马", "country_id": "507"},
|
||||
{"id": 40, "cname": "巴林", "country_id": "973"},
|
||||
{"id": 39, "cname": "巴拉圭", "country_id": "595"},
|
||||
{"id": 38, "cname": "巴基斯坦", "country_id": "92"},
|
||||
{"id": 37, "cname": "巴哈马群岛", "country_id": "1242"},
|
||||
{"id": 36, "cname": "巴布亚新几内亚", "country_id": "675"},
|
||||
{"id": 35, "cname": "巴巴多斯", "country_id": "1246"},
|
||||
{"id": 34, "cname": "奥地利", "country_id": "43"},
|
||||
{"id": 33, "cname": "安提瓜岛和巴布达", "country_id": "1268"},
|
||||
{"id": 32, "cname": "安哥拉", "country_id": "244"},
|
||||
{"id": 68, "cname": "非洲中部", "country_id": "236"},
|
||||
{"id": 117, "cname": "罗马尼亚", "country_id": "40"},
|
||||
{"id": 104, "cname": "科威特", "country_id": "965"},
|
||||
{"id": 103, "cname": "科摩罗", "country_id": "269"},
|
||||
{"id": 102, "cname": "开曼群岛", "country_id": "1345"},
|
||||
{"id": 101, "cname": "卡塔尔", "country_id": "974"},
|
||||
{"id": 100, "cname": "喀麦隆", "country_id": "237"},
|
||||
{"id": 99, "cname": "聚会岛", "country_id": "262"},
|
||||
{"id": 98, "cname": "津巴布韦", "country_id": "263"},
|
||||
{"id": 97, "cname": "捷克", "country_id": "420"},
|
||||
{"id": 96, "cname": "柬埔寨", "country_id": "855"},
|
||||
{"id": 95, "cname": "加蓬", "country_id": "241"},
|
||||
{"id": 105, "cname": "克罗地亚", "country_id": "385"},
|
||||
{"id": 106, "cname": "肯尼亚", "country_id": "254"},
|
||||
{"id": 116, "cname": "卢旺达", "country_id": "250"},
|
||||
{"id": 115, "cname": "卢森堡", "country_id": "352"},
|
||||
{"id": 114, "cname": "利比亚", "country_id": "218"},
|
||||
{"id": 113, "cname": "利比里亚", "country_id": "231"},
|
||||
{"id": 112, "cname": "立陶宛", "country_id": "370"},
|
||||
{"id": 111, "cname": "黎巴嫩", "country_id": "961"},
|
||||
{"id": 110, "cname": "老挝", "country_id": "856"},
|
||||
{"id": 109, "cname": "莱索托", "country_id": "266"},
|
||||
{"id": 108, "cname": "拉脱维亚", "country_id": "371"},
|
||||
{"id": 107, "cname": "库克岛", "country_id": "682"},
|
||||
{"id": 94, "cname": "加纳", "country_id": "233"},
|
||||
{"id": 93, "cname": "几内亚比绍", "country_id": "245"},
|
||||
{"id": 92, "cname": "几内亚", "country_id": "224"},
|
||||
{"id": 79, "cname": "格林纳达", "country_id": "1473"},
|
||||
{"id": 78, "cname": "哥斯达黎加", "country_id": "506"},
|
||||
{"id": 77, "cname": "哥伦比亚", "country_id": "57"},
|
||||
{"id": 76, "cname": "刚果(金)", "country_id": "243"},
|
||||
{"id": 75, "cname": "刚果", "country_id": "242"},
|
||||
{"id": 74, "cname": "冈比亚", "country_id": "220"},
|
||||
{"id": 73, "cname": "福克兰岛", "country_id": "500"},
|
||||
{"id": 72, "cname": "佛得角", "country_id": "238"},
|
||||
{"id": 71, "cname": "芬兰", "country_id": "358"},
|
||||
{"id": 70, "cname": "斐济", "country_id": "679"},
|
||||
{"id": 80, "cname": "格陵兰岛", "country_id": "299"},
|
||||
{"id": 81, "cname": "古巴", "country_id": "53"},
|
||||
{"id": 91, "cname": "吉尔吉斯斯坦", "country_id": "996"},
|
||||
{"id": 90, "cname": "吉布提", "country_id": "253"},
|
||||
{"id": 89, "cname": "基里巴斯", "country_id": "686"},
|
||||
{"id": 88, "cname": "维克岛", "country_id": "1808"},
|
||||
{"id": 87, "cname": "洪都拉斯", "country_id": "504"},
|
||||
{"id": 86, "cname": "荷兰", "country_id": "31"},
|
||||
{"id": 85, "cname": "朝鲜", "country_id": "850"},
|
||||
{"id": 84, "cname": "海地", "country_id": "509"},
|
||||
{"id": 83, "cname": "关岛", "country_id": "1671"},
|
||||
{"id": 82, "cname": "瓜德罗普岛", "country_id": "590"},
|
||||
{"id": 69, "cname": "菲律宾", "country_id": "63"}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -31,13 +32,17 @@ class AnimatedDialogState extends State<AnimatedDialog>
|
||||
opacityAnimation = Tween<double>(begin: 0.0, end: 0.6)
|
||||
.animate(CurvedAnimation(parent: controller, curve: Curves.linear));
|
||||
scaleAnimation = CurvedAnimation(parent: controller, curve: Curves.linear);
|
||||
controller.addListener(() => setState(() {}));
|
||||
controller.addListener(listener);
|
||||
controller.forward();
|
||||
}
|
||||
|
||||
void listener() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.removeListener(() {});
|
||||
controller.removeListener(listener);
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -484,6 +484,9 @@ class _RenderProgressBar extends RenderBox {
|
||||
}
|
||||
|
||||
void _onDragStart(DragStartDetails details) {
|
||||
if (onDragStart == null) {
|
||||
return;
|
||||
}
|
||||
_userIsDraggingThumb = true;
|
||||
_updateThumbPosition(details.localPosition);
|
||||
onDragStart?.call(ThumbDragDetails(
|
||||
@@ -494,6 +497,9 @@ class _RenderProgressBar extends RenderBox {
|
||||
}
|
||||
|
||||
void _onDragUpdate(DragUpdateDetails details) {
|
||||
if (onDragUpdate == null) {
|
||||
return;
|
||||
}
|
||||
_updateThumbPosition(details.localPosition);
|
||||
onDragUpdate?.call(ThumbDragDetails(
|
||||
timeStamp: _currentThumbDuration(),
|
||||
@@ -503,6 +509,9 @@ class _RenderProgressBar extends RenderBox {
|
||||
}
|
||||
|
||||
void _onDragEnd(DragEndDetails details) {
|
||||
if (onSeek == null) {
|
||||
return;
|
||||
}
|
||||
onDragEnd?.call();
|
||||
onSeek?.call(_currentThumbDuration());
|
||||
_finishDrag();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -31,3 +31,37 @@ class CustomToast extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoadingWidget extends StatelessWidget {
|
||||
const LoadingWidget({super.key, required this.msg});
|
||||
|
||||
///loading msg
|
||||
final String msg;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).dialogBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
//loading animation
|
||||
CircularProgressIndicator(
|
||||
strokeWidth: 3,
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
|
||||
//msg
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 20),
|
||||
child: Text(msg,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant)),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ void showConfirmDialog({
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onConfirm,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
onConfirm();
|
||||
},
|
||||
child: Text('确认'),
|
||||
),
|
||||
],
|
||||
@@ -30,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';
|
||||
@@ -28,17 +30,11 @@ Widget htmlRender({
|
||||
String imgUrl = key.contains('src')
|
||||
? attributes['src'] as String
|
||||
: attributes['data-src'] as String;
|
||||
if (imgUrl.startsWith('//')) {
|
||||
imgUrl = 'https:$imgUrl';
|
||||
}
|
||||
if (imgUrl.startsWith('http://')) {
|
||||
imgUrl = imgUrl.replaceAll('http://', 'https://');
|
||||
}
|
||||
imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl;
|
||||
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 ||
|
||||
@@ -49,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';
|
||||
@@ -15,7 +16,7 @@ void imageSaveDialog({
|
||||
final double imgWidth = min(Get.width, Get.height) - 8 * 2;
|
||||
SmartDialog.show(
|
||||
animationType: SmartAnimationType.centerScale_otherSlide,
|
||||
builder: (context) => Container(
|
||||
builder: (_) => Container(
|
||||
width: imgWidth,
|
||||
margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
|
||||
decoration: BoxDecoration(
|
||||
@@ -71,21 +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: const Icon(Icons.download, size: 20),
|
||||
)
|
||||
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,20 +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:dio/dio.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:flutter/services.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 'package:status_bar_control/status_bar_control.dart';
|
||||
import 'interactive_viewer_boundary.dart';
|
||||
import 'interactive_viewer.dart' as custom;
|
||||
|
||||
@@ -111,15 +107,12 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
|
||||
late final RxInt currentIndex = widget.initIndex.obs;
|
||||
|
||||
late List<bool> _thumbList;
|
||||
late final int _quality = GStorage.previewQ;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_thumbList = List.generate(widget.sources.length, (_) => true);
|
||||
|
||||
_pageController = PageController(initialPage: widget.initIndex);
|
||||
|
||||
_transformationController = custom.TransformationController();
|
||||
@@ -127,10 +120,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
)..addListener(() {
|
||||
_transformationController!.value =
|
||||
_animation?.value ?? Matrix4.identity();
|
||||
});
|
||||
)..addListener(listener);
|
||||
|
||||
if (widget.setStatusBar != false) {
|
||||
setStatusBar();
|
||||
@@ -141,25 +131,36 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
}
|
||||
}
|
||||
|
||||
void listener() {
|
||||
_transformationController!.value = _animation?.value ?? Matrix4.identity();
|
||||
}
|
||||
|
||||
SystemUiMode? mode;
|
||||
setStatusBar() async {
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
await StatusBarControl.setHidden(
|
||||
true,
|
||||
animation: StatusBarAnimation.FADE,
|
||||
SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.immersiveSticky,
|
||||
);
|
||||
}
|
||||
if (Platform.isAndroid &&
|
||||
(await DeviceInfoPlugin().androidInfo).version.sdkInt < 29) {
|
||||
mode = SystemUiMode.manual;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() async {
|
||||
void dispose() {
|
||||
widget.onClose?.call(true);
|
||||
_player?.dispose();
|
||||
_pageController?.dispose();
|
||||
_animationController.removeListener(() {});
|
||||
_animationController.removeListener(listener);
|
||||
_animationController.dispose();
|
||||
if (widget.setStatusBar != false) {
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE);
|
||||
SystemChrome.setEnabledSystemUIMode(
|
||||
mode ?? SystemUiMode.edgeToEdge,
|
||||
overlays: SystemUiOverlay.values,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (int index = 0; index < widget.sources.length; index++) {
|
||||
@@ -254,9 +255,11 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
}
|
||||
}
|
||||
|
||||
String _getActualUrl(int index) => _thumbList[index] && _quality != 100
|
||||
? '${widget.sources[index].url}@${_quality}q.webp'.http2https
|
||||
: widget.sources[index].url.http2https;
|
||||
String _getActualUrl(int index) {
|
||||
return _quality != 100
|
||||
? Utils.thumbnailImgUrl(widget.sources[index].url, _quality)
|
||||
: widget.sources[index].url.http2https;
|
||||
}
|
||||
|
||||
void onClose() {
|
||||
if (widget.onClose != null) {
|
||||
@@ -372,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("分享图片"),
|
||||
),
|
||||
@@ -386,7 +389,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
this.context,
|
||||
[widget.sources[currentIndex.value].url],
|
||||
);
|
||||
},
|
||||
@@ -396,7 +399,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
this.context,
|
||||
widget.sources
|
||||
.map((item) => item.url)
|
||||
.toList(),
|
||||
@@ -409,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!,
|
||||
@@ -434,20 +437,6 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
);
|
||||
}
|
||||
|
||||
// 图片分享
|
||||
void onShareImg(String imgUrl) async {
|
||||
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);
|
||||
Share.shareXFiles([XFile(path)], subject: imgUrl);
|
||||
}
|
||||
|
||||
Widget _itemBuilder(index) {
|
||||
return Center(
|
||||
child: Hero(
|
||||
@@ -458,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(() {
|
||||
@@ -551,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,
|
||||
@@ -569,7 +567,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
onTap: () {
|
||||
Get.back();
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
this.context,
|
||||
[widget.sources[currentIndex.value].url],
|
||||
);
|
||||
},
|
||||
@@ -581,7 +579,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
onTap: () {
|
||||
Get.back();
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
this.context,
|
||||
widget.sources.map((item) => item.url).toList(),
|
||||
);
|
||||
},
|
||||
@@ -594,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,491 +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: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';
|
||||
|
||||
class ListSheetContent extends StatefulWidget {
|
||||
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,
|
||||
});
|
||||
|
||||
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 State<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));
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (_isList) {
|
||||
_indexStream ??= StreamController<int>.broadcast();
|
||||
_ctr = TabController(
|
||||
vsync: this,
|
||||
length: widget.season.sections.length,
|
||||
initialIndex: _index,
|
||||
)..addListener(() {
|
||||
_indexStream?.add(_ctr?.index);
|
||||
});
|
||||
}
|
||||
itemScrollController = _isList
|
||||
? List.generate(
|
||||
widget.season.sections.length, (_) => ItemScrollController())
|
||||
: [ItemScrollController()];
|
||||
reverse = _isList
|
||||
? List.generate(widget.season.sections.length, (_) => false)
|
||||
: [false];
|
||||
if (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((_) {
|
||||
try {
|
||||
itemScrollController[_index].jumpTo(index: currentIndex);
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_favStream?.close();
|
||||
_favStream = null;
|
||||
_indexStream?.close();
|
||||
_indexStream = null;
|
||||
_ctr?.removeListener(() {});
|
||||
_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) {
|
||||
return 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),
|
||||
),
|
||||
),
|
||||
)
|
||||
: 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,
|
||||
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());
|
||||
@@ -18,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,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import '../constants.dart';
|
||||
|
||||
class NetworkImgLayer extends StatelessWidget {
|
||||
@@ -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) {
|
||||
@@ -56,43 +58,31 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildImage(context) {
|
||||
late final int defaultImgQuality = GlobalData().imgQuality;
|
||||
bool thumbnail = true;
|
||||
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);
|
||||
}
|
||||
return CachedNetworkImage(
|
||||
imageUrl:
|
||||
'${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${type != 'emote' && type != 'cover' && thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}',
|
||||
imageUrl: Utils.thumbnailImgUrl(src, quality),
|
||||
width: width,
|
||||
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),
|
||||
fadeInDuration: fadeInDuration ?? const Duration(milliseconds: 120),
|
||||
filterQuality: FilterQuality.low,
|
||||
// errorWidget: (BuildContext context, String url, Object error) =>
|
||||
// placeholder(context),
|
||||
placeholder: (BuildContext context, String url) =>
|
||||
getPlaceHolder?.call() ?? placeholder(context),
|
||||
imageBuilder: imageBuilder,
|
||||
// errorListener: (value) {
|
||||
// thumbnail = false;
|
||||
// if (context.mounted) {
|
||||
// (context as Element).markNeedsBuild();
|
||||
// }
|
||||
// },
|
||||
);
|
||||
}
|
||||
|
||||
Widget placeholder(BuildContext context) {
|
||||
int cacheWidth = width.cacheSize(context);
|
||||
return Container(
|
||||
width: width,
|
||||
height: height,
|
||||
@@ -115,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;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget radioWidget<T>({
|
||||
required T value,
|
||||
T? groupValue,
|
||||
required ValueChanged onChanged,
|
||||
required String title,
|
||||
double? paddingStart,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: () => onChanged(value),
|
||||
child: Row(
|
||||
children: [
|
||||
if (paddingStart != null) SizedBox(width: paddingStart),
|
||||
Radio(
|
||||
value: value,
|
||||
groupValue: groupValue,
|
||||
onChanged: onChanged,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
Text(title),
|
||||
],
|
||||
),
|
||||
);
|
||||
class RadioWidget<T> extends StatelessWidget {
|
||||
final T value;
|
||||
final T? groupValue;
|
||||
final ValueChanged<T?> onChanged;
|
||||
final String title;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
const RadioWidget({
|
||||
super.key,
|
||||
required this.value,
|
||||
this.groupValue,
|
||||
required this.onChanged,
|
||||
required this.title,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
Widget _child() => Row(
|
||||
children: [
|
||||
Radio<T>(
|
||||
value: value,
|
||||
groupValue: groupValue,
|
||||
onChanged: onChanged,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
Text(title),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: () => onChanged(value),
|
||||
child: padding != null
|
||||
? Padding(
|
||||
padding: padding!,
|
||||
child: _child(),
|
||||
)
|
||||
: _child(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WrapRadioOptionsGroup<T> extends StatelessWidget {
|
||||
final String groupTitle;
|
||||
final Map<T, String> options;
|
||||
final T? selectedValue;
|
||||
final ValueChanged<T?> onChanged;
|
||||
final EdgeInsetsGeometry? itemPadding;
|
||||
|
||||
const WrapRadioOptionsGroup({
|
||||
super.key,
|
||||
required this.groupTitle,
|
||||
required this.options,
|
||||
required this.selectedValue,
|
||||
required this.onChanged,
|
||||
this.itemPadding,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (groupTitle.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 22),
|
||||
child: Text(
|
||||
groupTitle,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Wrap(
|
||||
children: options.entries.map((entry) {
|
||||
return IntrinsicWidth(
|
||||
child: RadioWidget<T>(
|
||||
value: entry.key,
|
||||
groupValue: selectedValue,
|
||||
onChanged: onChanged,
|
||||
title: entry.value,
|
||||
padding: itemPadding ?? const EdgeInsets.only(right: 10),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
225
lib/common/widgets/report.dart
Normal file
@@ -0,0 +1,225 @@
|
||||
import 'package:PiliPlus/common/widgets/radio_widget.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
void autoWrapReportDialog(
|
||||
BuildContext context,
|
||||
Map<String, Map<int, String>> options,
|
||||
Future<Map> Function(int, String?, bool) onSuccess,
|
||||
) {
|
||||
int? reasonType;
|
||||
String? reasonDesc;
|
||||
bool banUid = false;
|
||||
late final key = GlobalKey<FormState>();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: const Text('举报'),
|
||||
titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 5),
|
||||
actionsPadding:
|
||||
const EdgeInsets.only(left: 16, right: 16, bottom: 10),
|
||||
content: Form(
|
||||
key: key,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 22,
|
||||
right: 22,
|
||||
bottom: 5,
|
||||
),
|
||||
child: Text('请选择举报的理由:'),
|
||||
),
|
||||
...options.entries.map(
|
||||
(entry) => WrapRadioOptionsGroup<int>(
|
||||
groupTitle: entry.key,
|
||||
options: entry.value,
|
||||
selectedValue: reasonType,
|
||||
onChanged: (value) =>
|
||||
setState(() => reasonType = value),
|
||||
),
|
||||
),
|
||||
if (reasonType == 0)
|
||||
ReasonField(
|
||||
onChanged: (value) => reasonDesc = value),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
BanUserCheckbox(onChanged: (value) => banUid = value),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (reasonType == null ||
|
||||
(reasonType == 0 && key.currentState?.validate() != true)) {
|
||||
return;
|
||||
}
|
||||
SmartDialog.showLoading();
|
||||
try {
|
||||
final data = await onSuccess(reasonType!, reasonDesc, banUid);
|
||||
SmartDialog.dismiss();
|
||||
if (data['code'] == 0) {
|
||||
Get.back();
|
||||
SmartDialog.showToast('举报成功');
|
||||
} else {
|
||||
SmartDialog.showToast(data['message']);
|
||||
}
|
||||
} catch (e) {
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast('提交失败:$e');
|
||||
}
|
||||
},
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class ReasonField extends StatefulWidget {
|
||||
final ValueChanged<String> onChanged;
|
||||
String? _validator(String? value) => value.isNullOrEmpty ? '理由不能为空' : null;
|
||||
|
||||
const ReasonField({super.key, required this.onChanged});
|
||||
|
||||
@override
|
||||
State<ReasonField> createState() => _ReasonFieldState();
|
||||
}
|
||||
|
||||
class _ReasonFieldState extends State<ReasonField> {
|
||||
final _controller = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 22, top: 5, right: 22),
|
||||
child: TextFormField(
|
||||
controller: _controller,
|
||||
autofocus: true,
|
||||
minLines: 4,
|
||||
maxLines: 4,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息',
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.all(10),
|
||||
),
|
||||
onChanged: (value) {
|
||||
widget.onChanged(value);
|
||||
},
|
||||
validator: widget._validator,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BanUserCheckbox extends StatefulWidget {
|
||||
final ValueChanged<bool> onChanged;
|
||||
|
||||
const BanUserCheckbox({super.key, required this.onChanged});
|
||||
|
||||
@override
|
||||
State<BanUserCheckbox> createState() => _BanUserCheckboxState();
|
||||
}
|
||||
|
||||
class _BanUserCheckboxState extends State<BanUserCheckbox> {
|
||||
bool _banUid = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() => _banUid = !_banUid);
|
||||
widget.onChanged(_banUid);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 18, top: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
size: 22,
|
||||
_banUid
|
||||
? Icons.check_box_outlined
|
||||
: Icons.check_box_outline_blank,
|
||||
color: _banUid
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
Text(
|
||||
' 拉黑该用户',
|
||||
style: TextStyle(
|
||||
color: _banUid ? Theme.of(context).colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReportOptions {
|
||||
// from https://s1.hdslb.com/bfs/seed/jinkela/comment-h5/static/js/605.chunks.js
|
||||
static Map<String, Map<int, String>> get commentReport => {
|
||||
'违反法律法规': {9: '违法违规', 2: '色情', 10: '低俗', 12: '赌博诈骗', 23: '违法信息外链'},
|
||||
'谣言类不实信息': {19: '涉政谣言', 22: '虚假不实信息', 20: '涉社会事件谣言'},
|
||||
'侵犯个人权益': {7: '人身攻击', 15: '侵犯隐私'},
|
||||
'有害社区环境': {
|
||||
1: '垃圾广告',
|
||||
4: '引战',
|
||||
5: '剧透',
|
||||
3: '刷屏',
|
||||
8: '视频不相关',
|
||||
18: '违规抽奖',
|
||||
17: '青少年不良信息',
|
||||
},
|
||||
'其他': {0: '其他'},
|
||||
};
|
||||
|
||||
static Map<String, Map<int, String>> get dynamicReport => {
|
||||
'': {
|
||||
4: '垃圾广告',
|
||||
8: '引战',
|
||||
1: '色情',
|
||||
5: '人身攻击',
|
||||
3: '违法信息',
|
||||
9: '涉政谣言',
|
||||
10: '涉社会事件谣言',
|
||||
12: '虚假不实信息',
|
||||
13: '违法信息外链',
|
||||
0: '其他',
|
||||
},
|
||||
};
|
||||
}
|
||||
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,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,15 +1,16 @@
|
||||
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 'package:get/get.dart';
|
||||
import '../../http/search.dart';
|
||||
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';
|
||||
|
||||
// 视频卡片 - 水平布局
|
||||
@@ -24,8 +25,9 @@ class VideoCardH extends StatelessWidget {
|
||||
this.showPubdate = false,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.onViewLater,
|
||||
});
|
||||
final dynamic videoItem;
|
||||
final BaseVideoItemModel videoItem;
|
||||
final String source;
|
||||
final bool showOwner;
|
||||
final bool showView;
|
||||
@@ -33,147 +35,184 @@ class VideoCardH extends StatelessWidget {
|
||||
final bool showPubdate;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
final ValueChanged<int>? onViewLater;
|
||||
|
||||
@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 (_) {}
|
||||
return Stack(
|
||||
children: [
|
||||
Semantics(
|
||||
label: Utils.videoItemSemantics(videoItem),
|
||||
excludeSemantics: true,
|
||||
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
|
||||
// for (var item in actions)
|
||||
// CustomSemanticsAction(
|
||||
// label: item.title.isEmpty ? 'label' : item.title): item.onTap!,
|
||||
// },
|
||||
child: InkWell(
|
||||
onLongPress: () {
|
||||
if (onLongPress != null) {
|
||||
onLongPress!();
|
||||
} else {
|
||||
imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.title is String
|
||||
? videoItem.title
|
||||
: videoItem.title is List
|
||||
? (videoItem.title as List)
|
||||
.map((item) => item['text'])
|
||||
.join()
|
||||
: '',
|
||||
cover: videoItem.pic,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: () async {
|
||||
if (onTap != null) {
|
||||
onTap?.call();
|
||||
return;
|
||||
}
|
||||
if (type == 'ketang') {
|
||||
SmartDialog.showToast('课堂视频暂不支持播放');
|
||||
return;
|
||||
}
|
||||
if (videoItem is HotVideoItemModel &&
|
||||
videoItem.redirectUrl?.isNotEmpty == true) {
|
||||
if (Utils.viewPgcFromUri(videoItem.redirectUrl!)) {
|
||||
// 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),
|
||||
excludeSemantics: true,
|
||||
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
|
||||
// for (var item in actions)
|
||||
// CustomSemanticsAction(
|
||||
// label: item.title.isEmpty ? 'label' : item.title): item.onTap!,
|
||||
// },
|
||||
child: InkWell(
|
||||
onLongPress: () {
|
||||
if (onLongPress != null) {
|
||||
onLongPress!();
|
||||
} else {
|
||||
imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.title,
|
||||
cover: videoItem.pic,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: () async {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
final int cid = videoItem.cid ??
|
||||
await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
'videoItem': videoItem,
|
||||
'heroTag': Utils.makeHeroTag(aid)
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context,
|
||||
BoxConstraints boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: videoItem.pic as String,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if (videoItem is HotVideoItemModel &&
|
||||
videoItem.pgcLabel?.isNotEmpty == true)
|
||||
PBadge(
|
||||
text: videoItem.pgcLabel,
|
||||
top: 6.0,
|
||||
right: 6.0,
|
||||
),
|
||||
if (videoItem.duration != 0)
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration!),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
if (type != 'video')
|
||||
PBadge(
|
||||
text: type,
|
||||
left: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'primary',
|
||||
),
|
||||
// if (videoItem.rcmdReason != null &&
|
||||
// videoItem.rcmdReason.content != '')
|
||||
// pBadge(videoItem.rcmdReason.content, context,
|
||||
// 6.0, 6.0, null, null),
|
||||
],
|
||||
);
|
||||
if (type == 'ketang') {
|
||||
SmartDialog.showToast('课堂视频暂不支持播放');
|
||||
return;
|
||||
}
|
||||
if ((videoItem is HotVideoItemModel) &&
|
||||
(videoItem as HotVideoItemModel).redirectUrl?.isNotEmpty ==
|
||||
true) {
|
||||
if (Utils.viewPgcFromUri(
|
||||
(videoItem as HotVideoItemModel).redirectUrl!)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
final int cid = videoItem.cid ??
|
||||
await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
if (source == 'later') {
|
||||
onViewLater!(cid);
|
||||
} else {
|
||||
Utils.toViewPage(
|
||||
'bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
'videoItem': videoItem,
|
||||
'heroTag': Utils.makeHeroTag(aid)
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context,
|
||||
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,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if (videoItem is HotVideoItemModel)
|
||||
PBadge(
|
||||
text:
|
||||
(videoItem as HotVideoItemModel).pgcLabel,
|
||||
top: 6.0,
|
||||
right: 6.0,
|
||||
),
|
||||
if (progress != null && progress != 0) ...[
|
||||
PBadge(
|
||||
text: progress == -1
|
||||
? '已看完'
|
||||
: '${Utils.timeFormat(progress)}/${Utils.timeFormat(videoItem.duration)}',
|
||||
right: 6,
|
||||
bottom: 8,
|
||||
type: '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',
|
||||
),
|
||||
if (type != 'video')
|
||||
PBadge(
|
||||
text: type,
|
||||
left: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'primary',
|
||||
),
|
||||
// if (videoItem.rcmdReason != null &&
|
||||
// videoItem.rcmdReason.content != '')
|
||||
// pBadge(videoItem.rcmdReason.content, context,
|
||||
// 6.0, 6.0, null, null),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
videoContent(context),
|
||||
],
|
||||
const SizedBox(width: 10),
|
||||
videoContent(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (source == 'normal')
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 12,
|
||||
child: VideoPopupMenu(
|
||||
size: 29,
|
||||
iconSize: 17,
|
||||
videoItem: videoItem,
|
||||
if (source == 'normal')
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 12,
|
||||
child: VideoPopupMenu(
|
||||
size: 29,
|
||||
iconSize: 17,
|
||||
videoItem: videoItem,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget videoContent(context) {
|
||||
Widget videoContent(BuildContext context) {
|
||||
String pubdate = showPubdate
|
||||
? Utils.dateFormat(videoItem.pubdate!, formatType: 'day')
|
||||
: '';
|
||||
@@ -182,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,
|
||||
@@ -195,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 &&
|
||||
@@ -257,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,12 +1,10 @@
|
||||
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';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../constants.dart';
|
||||
import 'badge.dart';
|
||||
@@ -19,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) {
|
||||
@@ -44,12 +44,12 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (videoItem.bvid == null || videoItem.firstCid == null) {
|
||||
if (videoItem.bvid == null || videoItem.cid == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Get.toNamed(
|
||||
'/video?bvid=${videoItem.bvid}&cid=${videoItem.firstCid}',
|
||||
Utils.toViewPage(
|
||||
'bvid=${videoItem.bvid}&cid=${videoItem.cid}',
|
||||
arguments: {
|
||||
'heroTag': Utils.makeHeroTag(videoItem.bvid),
|
||||
},
|
||||
@@ -84,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!
|
||||
@@ -91,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(
|
||||
@@ -115,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',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
@@ -149,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
|
||||
@@ -181,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,19 +30,19 @@ 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);
|
||||
}
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=$cid',
|
||||
Utils.toViewPage(
|
||||
'bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
// 'videoItem': videoItem,
|
||||
'pic': videoItem.pic,
|
||||
@@ -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,8 +1,9 @@
|
||||
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 'package:get/get.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../constants.dart';
|
||||
import 'badge.dart';
|
||||
@@ -29,9 +30,15 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
}
|
||||
String bvid = videoItem.bvid ?? '';
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=${videoItem.firstCid}',
|
||||
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 ?? 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(
|
||||
@@ -68,8 +69,7 @@ class VideoCustomActions {
|
||||
VideoCustomAction(
|
||||
'不感兴趣', 'dislike', Icon(MdiIcons.thumbDownOutline, size: 16),
|
||||
() async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
String? accessKey = Accounts.get(AccountType.recommend).accessKey;
|
||||
if (accessKey == null || accessKey == "") {
|
||||
SmartDialog.showToast("请退出账号后重新登录");
|
||||
return;
|
||||
@@ -85,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 {
|
||||
@@ -259,13 +259,11 @@ class VideoCustomActions {
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var res = await VideoHttp.relationMod(
|
||||
mid: videoItem.owner.mid,
|
||||
mid: videoItem.owner.mid!,
|
||||
act: 5,
|
||||
reSrc: 11,
|
||||
);
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
blackMidsList.insert(0, videoItem.owner.mid);
|
||||
GStorage.setBlackMidsList(blackMidsList);
|
||||
GStorage.setBlackMid(videoItem.owner.mid!);
|
||||
Get.back();
|
||||
SmartDialog.showToast(res['msg'] ?? '成功');
|
||||
},
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: bilibili/app/dynamic/v1/dynamic.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:async' as $async;
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:grpc/service_api.dart' as $grpc;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'dynamic.pb.dart' as $0;
|
||||
|
||||
export 'dynamic.pb.dart';
|
||||
|
||||
@$pb.GrpcServiceName('bilibili.app.dynamic.v1.Dynamic')
|
||||
class DynamicClient extends $grpc.Client {
|
||||
static final _$dynVideo = $grpc.ClientMethod<$0.DynVideoReq, $0.DynVideoReqReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/DynVideo',
|
||||
($0.DynVideoReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DynVideoReqReply.fromBuffer(value));
|
||||
static final _$dynDetails = $grpc.ClientMethod<$0.DynDetailsReq, $0.DynDetailsReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/DynDetails',
|
||||
($0.DynDetailsReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DynDetailsReply.fromBuffer(value));
|
||||
static final _$sVideo = $grpc.ClientMethod<$0.SVideoReq, $0.SVideoReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/SVideo',
|
||||
($0.SVideoReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.SVideoReply.fromBuffer(value));
|
||||
static final _$dynTab = $grpc.ClientMethod<$0.DynTabReq, $0.DynTabReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/DynTab',
|
||||
($0.DynTabReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DynTabReply.fromBuffer(value));
|
||||
static final _$dynOurCitySwitch = $grpc.ClientMethod<$0.DynOurCitySwitchReq, $0.NoReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/DynOurCitySwitch',
|
||||
($0.DynOurCitySwitchReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.NoReply.fromBuffer(value));
|
||||
static final _$dynOurCity = $grpc.ClientMethod<$0.DynOurCityReq, $0.DynOurCityReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/DynOurCity',
|
||||
($0.DynOurCityReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DynOurCityReply.fromBuffer(value));
|
||||
static final _$dynVideoPersonal = $grpc.ClientMethod<$0.DynVideoPersonalReq, $0.DynVideoPersonalReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/DynVideoPersonal',
|
||||
($0.DynVideoPersonalReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DynVideoPersonalReply.fromBuffer(value));
|
||||
static final _$dynUpdOffset = $grpc.ClientMethod<$0.DynUpdOffsetReq, $0.NoReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/DynUpdOffset',
|
||||
($0.DynUpdOffsetReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.NoReply.fromBuffer(value));
|
||||
static final _$dynRed = $grpc.ClientMethod<$0.DynRedReq, $0.DynRedReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/DynRed',
|
||||
($0.DynRedReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DynRedReply.fromBuffer(value));
|
||||
static final _$dynMixUpListViewMore = $grpc.ClientMethod<$0.NoReq, $0.DynMixUpListViewMoreReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/DynMixUpListViewMore',
|
||||
($0.NoReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DynMixUpListViewMoreReply.fromBuffer(value));
|
||||
static final _$dynMixUpListSearch = $grpc.ClientMethod<$0.DynMixUpListSearchReq, $0.DynMixUpListSearchReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/DynMixUpListSearch',
|
||||
($0.DynMixUpListSearchReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DynMixUpListSearchReply.fromBuffer(value));
|
||||
static final _$ourCityClickReport = $grpc.ClientMethod<$0.OurCityClickReportReq, $0.OurCityClickReportReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/OurCityClickReport',
|
||||
($0.OurCityClickReportReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.OurCityClickReportReply.fromBuffer(value));
|
||||
static final _$geoCoder = $grpc.ClientMethod<$0.GeoCoderReq, $0.GeoCoderReply>(
|
||||
'/bilibili.app.dynamic.v1.Dynamic/GeoCoder',
|
||||
($0.GeoCoderReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.GeoCoderReply.fromBuffer(value));
|
||||
|
||||
DynamicClient($grpc.ClientChannel channel,
|
||||
{$grpc.CallOptions? options,
|
||||
$core.Iterable<$grpc.ClientInterceptor>? interceptors})
|
||||
: super(channel, options: options,
|
||||
interceptors: interceptors);
|
||||
|
||||
$grpc.ResponseFuture<$0.DynVideoReqReply> dynVideo($0.DynVideoReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dynVideo, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DynDetailsReply> dynDetails($0.DynDetailsReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dynDetails, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.SVideoReply> sVideo($0.SVideoReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$sVideo, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DynTabReply> dynTab($0.DynTabReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dynTab, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.NoReply> dynOurCitySwitch($0.DynOurCitySwitchReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dynOurCitySwitch, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DynOurCityReply> dynOurCity($0.DynOurCityReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dynOurCity, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DynVideoPersonalReply> dynVideoPersonal($0.DynVideoPersonalReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dynVideoPersonal, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.NoReply> dynUpdOffset($0.DynUpdOffsetReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dynUpdOffset, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DynRedReply> dynRed($0.DynRedReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dynRed, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DynMixUpListViewMoreReply> dynMixUpListViewMore($0.NoReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dynMixUpListViewMore, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DynMixUpListSearchReply> dynMixUpListSearch($0.DynMixUpListSearchReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dynMixUpListSearch, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.OurCityClickReportReply> ourCityClickReport($0.OurCityClickReportReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$ourCityClickReport, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.GeoCoderReply> geoCoder($0.GeoCoderReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$geoCoder, request, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
@$pb.GrpcServiceName('bilibili.app.dynamic.v1.Dynamic')
|
||||
abstract class DynamicServiceBase extends $grpc.Service {
|
||||
$core.String get $name => 'bilibili.app.dynamic.v1.Dynamic';
|
||||
|
||||
DynamicServiceBase() {
|
||||
$addMethod($grpc.ServiceMethod<$0.DynVideoReq, $0.DynVideoReqReply>(
|
||||
'DynVideo',
|
||||
dynVideo_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DynVideoReq.fromBuffer(value),
|
||||
($0.DynVideoReqReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DynDetailsReq, $0.DynDetailsReply>(
|
||||
'DynDetails',
|
||||
dynDetails_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DynDetailsReq.fromBuffer(value),
|
||||
($0.DynDetailsReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.SVideoReq, $0.SVideoReply>(
|
||||
'SVideo',
|
||||
sVideo_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.SVideoReq.fromBuffer(value),
|
||||
($0.SVideoReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DynTabReq, $0.DynTabReply>(
|
||||
'DynTab',
|
||||
dynTab_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DynTabReq.fromBuffer(value),
|
||||
($0.DynTabReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DynOurCitySwitchReq, $0.NoReply>(
|
||||
'DynOurCitySwitch',
|
||||
dynOurCitySwitch_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DynOurCitySwitchReq.fromBuffer(value),
|
||||
($0.NoReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DynOurCityReq, $0.DynOurCityReply>(
|
||||
'DynOurCity',
|
||||
dynOurCity_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DynOurCityReq.fromBuffer(value),
|
||||
($0.DynOurCityReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DynVideoPersonalReq, $0.DynVideoPersonalReply>(
|
||||
'DynVideoPersonal',
|
||||
dynVideoPersonal_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DynVideoPersonalReq.fromBuffer(value),
|
||||
($0.DynVideoPersonalReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DynUpdOffsetReq, $0.NoReply>(
|
||||
'DynUpdOffset',
|
||||
dynUpdOffset_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DynUpdOffsetReq.fromBuffer(value),
|
||||
($0.NoReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DynRedReq, $0.DynRedReply>(
|
||||
'DynRed',
|
||||
dynRed_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DynRedReq.fromBuffer(value),
|
||||
($0.DynRedReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.NoReq, $0.DynMixUpListViewMoreReply>(
|
||||
'DynMixUpListViewMore',
|
||||
dynMixUpListViewMore_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.NoReq.fromBuffer(value),
|
||||
($0.DynMixUpListViewMoreReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DynMixUpListSearchReq, $0.DynMixUpListSearchReply>(
|
||||
'DynMixUpListSearch',
|
||||
dynMixUpListSearch_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DynMixUpListSearchReq.fromBuffer(value),
|
||||
($0.DynMixUpListSearchReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.OurCityClickReportReq, $0.OurCityClickReportReply>(
|
||||
'OurCityClickReport',
|
||||
ourCityClickReport_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.OurCityClickReportReq.fromBuffer(value),
|
||||
($0.OurCityClickReportReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.GeoCoderReq, $0.GeoCoderReply>(
|
||||
'GeoCoder',
|
||||
geoCoder_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.GeoCoderReq.fromBuffer(value),
|
||||
($0.GeoCoderReply value) => value.writeToBuffer()));
|
||||
}
|
||||
|
||||
$async.Future<$0.DynVideoReqReply> dynVideo_Pre($grpc.ServiceCall call, $async.Future<$0.DynVideoReq> request) async {
|
||||
return dynVideo(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DynDetailsReply> dynDetails_Pre($grpc.ServiceCall call, $async.Future<$0.DynDetailsReq> request) async {
|
||||
return dynDetails(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.SVideoReply> sVideo_Pre($grpc.ServiceCall call, $async.Future<$0.SVideoReq> request) async {
|
||||
return sVideo(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DynTabReply> dynTab_Pre($grpc.ServiceCall call, $async.Future<$0.DynTabReq> request) async {
|
||||
return dynTab(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.NoReply> dynOurCitySwitch_Pre($grpc.ServiceCall call, $async.Future<$0.DynOurCitySwitchReq> request) async {
|
||||
return dynOurCitySwitch(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DynOurCityReply> dynOurCity_Pre($grpc.ServiceCall call, $async.Future<$0.DynOurCityReq> request) async {
|
||||
return dynOurCity(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DynVideoPersonalReply> dynVideoPersonal_Pre($grpc.ServiceCall call, $async.Future<$0.DynVideoPersonalReq> request) async {
|
||||
return dynVideoPersonal(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.NoReply> dynUpdOffset_Pre($grpc.ServiceCall call, $async.Future<$0.DynUpdOffsetReq> request) async {
|
||||
return dynUpdOffset(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DynRedReply> dynRed_Pre($grpc.ServiceCall call, $async.Future<$0.DynRedReq> request) async {
|
||||
return dynRed(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DynMixUpListViewMoreReply> dynMixUpListViewMore_Pre($grpc.ServiceCall call, $async.Future<$0.NoReq> request) async {
|
||||
return dynMixUpListViewMore(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DynMixUpListSearchReply> dynMixUpListSearch_Pre($grpc.ServiceCall call, $async.Future<$0.DynMixUpListSearchReq> request) async {
|
||||
return dynMixUpListSearch(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.OurCityClickReportReply> ourCityClickReport_Pre($grpc.ServiceCall call, $async.Future<$0.OurCityClickReportReq> request) async {
|
||||
return ourCityClickReport(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.GeoCoderReply> geoCoder_Pre($grpc.ServiceCall call, $async.Future<$0.GeoCoderReq> request) async {
|
||||
return geoCoder(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DynVideoReqReply> dynVideo($grpc.ServiceCall call, $0.DynVideoReq request);
|
||||
$async.Future<$0.DynDetailsReply> dynDetails($grpc.ServiceCall call, $0.DynDetailsReq request);
|
||||
$async.Future<$0.SVideoReply> sVideo($grpc.ServiceCall call, $0.SVideoReq request);
|
||||
$async.Future<$0.DynTabReply> dynTab($grpc.ServiceCall call, $0.DynTabReq request);
|
||||
$async.Future<$0.NoReply> dynOurCitySwitch($grpc.ServiceCall call, $0.DynOurCitySwitchReq request);
|
||||
$async.Future<$0.DynOurCityReply> dynOurCity($grpc.ServiceCall call, $0.DynOurCityReq request);
|
||||
$async.Future<$0.DynVideoPersonalReply> dynVideoPersonal($grpc.ServiceCall call, $0.DynVideoPersonalReq request);
|
||||
$async.Future<$0.NoReply> dynUpdOffset($grpc.ServiceCall call, $0.DynUpdOffsetReq request);
|
||||
$async.Future<$0.DynRedReply> dynRed($grpc.ServiceCall call, $0.DynRedReq request);
|
||||
$async.Future<$0.DynMixUpListViewMoreReply> dynMixUpListViewMore($grpc.ServiceCall call, $0.NoReq request);
|
||||
$async.Future<$0.DynMixUpListSearchReply> dynMixUpListSearch($grpc.ServiceCall call, $0.DynMixUpListSearchReq request);
|
||||
$async.Future<$0.OurCityClickReportReply> ourCityClickReport($grpc.ServiceCall call, $0.OurCityClickReportReq request);
|
||||
$async.Future<$0.GeoCoderReply> geoCoder($grpc.ServiceCall call, $0.GeoCoderReq request);
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: bilibili/main/community/reply/v1/reply.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:async' as $async;
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:grpc/service_api.dart' as $grpc;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'reply.pb.dart' as $0;
|
||||
|
||||
export 'reply.pb.dart';
|
||||
|
||||
@$pb.GrpcServiceName('bilibili.main.community.reply.v1.Reply')
|
||||
class ReplyClient extends $grpc.Client {
|
||||
static final _$mainList = $grpc.ClientMethod<$0.MainListReq, $0.MainListReply>(
|
||||
'/bilibili.main.community.reply.v1.Reply/MainList',
|
||||
($0.MainListReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.MainListReply.fromBuffer(value));
|
||||
static final _$detailList = $grpc.ClientMethod<$0.DetailListReq, $0.DetailListReply>(
|
||||
'/bilibili.main.community.reply.v1.Reply/DetailList',
|
||||
($0.DetailListReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DetailListReply.fromBuffer(value));
|
||||
static final _$dialogList = $grpc.ClientMethod<$0.DialogListReq, $0.DialogListReply>(
|
||||
'/bilibili.main.community.reply.v1.Reply/DialogList',
|
||||
($0.DialogListReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DialogListReply.fromBuffer(value));
|
||||
static final _$previewList = $grpc.ClientMethod<$0.PreviewListReq, $0.PreviewListReply>(
|
||||
'/bilibili.main.community.reply.v1.Reply/PreviewList',
|
||||
($0.PreviewListReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.PreviewListReply.fromBuffer(value));
|
||||
static final _$searchItemPreHook = $grpc.ClientMethod<$0.SearchItemPreHookReq, $0.SearchItemPreHookReply>(
|
||||
'/bilibili.main.community.reply.v1.Reply/SearchItemPreHook',
|
||||
($0.SearchItemPreHookReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.SearchItemPreHookReply.fromBuffer(value));
|
||||
static final _$searchItem = $grpc.ClientMethod<$0.SearchItemReq, $0.SearchItemReply>(
|
||||
'/bilibili.main.community.reply.v1.Reply/SearchItem',
|
||||
($0.SearchItemReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.SearchItemReply.fromBuffer(value));
|
||||
static final _$atSearch = $grpc.ClientMethod<$0.AtSearchReq, $0.AtSearchReply>(
|
||||
'/bilibili.main.community.reply.v1.Reply/AtSearch',
|
||||
($0.AtSearchReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.AtSearchReply.fromBuffer(value));
|
||||
static final _$replyInfo = $grpc.ClientMethod<$0.ReplyInfoReq, $0.ReplyInfoReply>(
|
||||
'/bilibili.main.community.reply.v1.Reply/ReplyInfo',
|
||||
($0.ReplyInfoReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.ReplyInfoReply.fromBuffer(value));
|
||||
static final _$userCallback = $grpc.ClientMethod<$0.UserCallbackReq, $0.UserCallbackReply>(
|
||||
'/bilibili.main.community.reply.v1.Reply/UserCallback',
|
||||
($0.UserCallbackReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.UserCallbackReply.fromBuffer(value));
|
||||
static final _$shareRepliesInfo = $grpc.ClientMethod<$0.ShareRepliesInfoReq, $0.ShareRepliesInfoResp>(
|
||||
'/bilibili.main.community.reply.v1.Reply/ShareRepliesInfo',
|
||||
($0.ShareRepliesInfoReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.ShareRepliesInfoResp.fromBuffer(value));
|
||||
static final _$suggestEmotes = $grpc.ClientMethod<$0.SuggestEmotesReq, $0.SuggestEmotesResp>(
|
||||
'/bilibili.main.community.reply.v1.Reply/SuggestEmotes',
|
||||
($0.SuggestEmotesReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.SuggestEmotesResp.fromBuffer(value));
|
||||
|
||||
ReplyClient($grpc.ClientChannel channel,
|
||||
{$grpc.CallOptions? options,
|
||||
$core.Iterable<$grpc.ClientInterceptor>? interceptors})
|
||||
: super(channel, options: options,
|
||||
interceptors: interceptors);
|
||||
|
||||
$grpc.ResponseFuture<$0.MainListReply> mainList($0.MainListReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$mainList, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DetailListReply> detailList($0.DetailListReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$detailList, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DialogListReply> dialogList($0.DialogListReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dialogList, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.PreviewListReply> previewList($0.PreviewListReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$previewList, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.SearchItemPreHookReply> searchItemPreHook($0.SearchItemPreHookReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$searchItemPreHook, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.SearchItemReply> searchItem($0.SearchItemReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$searchItem, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.AtSearchReply> atSearch($0.AtSearchReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$atSearch, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.ReplyInfoReply> replyInfo($0.ReplyInfoReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$replyInfo, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.UserCallbackReply> userCallback($0.UserCallbackReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$userCallback, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.ShareRepliesInfoResp> shareRepliesInfo($0.ShareRepliesInfoReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$shareRepliesInfo, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.SuggestEmotesResp> suggestEmotes($0.SuggestEmotesReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$suggestEmotes, request, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
@$pb.GrpcServiceName('bilibili.main.community.reply.v1.Reply')
|
||||
abstract class ReplyServiceBase extends $grpc.Service {
|
||||
$core.String get $name => 'bilibili.main.community.reply.v1.Reply';
|
||||
|
||||
ReplyServiceBase() {
|
||||
$addMethod($grpc.ServiceMethod<$0.MainListReq, $0.MainListReply>(
|
||||
'MainList',
|
||||
mainList_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.MainListReq.fromBuffer(value),
|
||||
($0.MainListReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DetailListReq, $0.DetailListReply>(
|
||||
'DetailList',
|
||||
detailList_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DetailListReq.fromBuffer(value),
|
||||
($0.DetailListReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DialogListReq, $0.DialogListReply>(
|
||||
'DialogList',
|
||||
dialogList_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DialogListReq.fromBuffer(value),
|
||||
($0.DialogListReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.PreviewListReq, $0.PreviewListReply>(
|
||||
'PreviewList',
|
||||
previewList_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.PreviewListReq.fromBuffer(value),
|
||||
($0.PreviewListReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.SearchItemPreHookReq, $0.SearchItemPreHookReply>(
|
||||
'SearchItemPreHook',
|
||||
searchItemPreHook_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.SearchItemPreHookReq.fromBuffer(value),
|
||||
($0.SearchItemPreHookReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.SearchItemReq, $0.SearchItemReply>(
|
||||
'SearchItem',
|
||||
searchItem_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.SearchItemReq.fromBuffer(value),
|
||||
($0.SearchItemReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.AtSearchReq, $0.AtSearchReply>(
|
||||
'AtSearch',
|
||||
atSearch_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.AtSearchReq.fromBuffer(value),
|
||||
($0.AtSearchReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.ReplyInfoReq, $0.ReplyInfoReply>(
|
||||
'ReplyInfo',
|
||||
replyInfo_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.ReplyInfoReq.fromBuffer(value),
|
||||
($0.ReplyInfoReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.UserCallbackReq, $0.UserCallbackReply>(
|
||||
'UserCallback',
|
||||
userCallback_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.UserCallbackReq.fromBuffer(value),
|
||||
($0.UserCallbackReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.ShareRepliesInfoReq, $0.ShareRepliesInfoResp>(
|
||||
'ShareRepliesInfo',
|
||||
shareRepliesInfo_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.ShareRepliesInfoReq.fromBuffer(value),
|
||||
($0.ShareRepliesInfoResp value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.SuggestEmotesReq, $0.SuggestEmotesResp>(
|
||||
'SuggestEmotes',
|
||||
suggestEmotes_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.SuggestEmotesReq.fromBuffer(value),
|
||||
($0.SuggestEmotesResp value) => value.writeToBuffer()));
|
||||
}
|
||||
|
||||
$async.Future<$0.MainListReply> mainList_Pre($grpc.ServiceCall call, $async.Future<$0.MainListReq> request) async {
|
||||
return mainList(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DetailListReply> detailList_Pre($grpc.ServiceCall call, $async.Future<$0.DetailListReq> request) async {
|
||||
return detailList(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DialogListReply> dialogList_Pre($grpc.ServiceCall call, $async.Future<$0.DialogListReq> request) async {
|
||||
return dialogList(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.PreviewListReply> previewList_Pre($grpc.ServiceCall call, $async.Future<$0.PreviewListReq> request) async {
|
||||
return previewList(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.SearchItemPreHookReply> searchItemPreHook_Pre($grpc.ServiceCall call, $async.Future<$0.SearchItemPreHookReq> request) async {
|
||||
return searchItemPreHook(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.SearchItemReply> searchItem_Pre($grpc.ServiceCall call, $async.Future<$0.SearchItemReq> request) async {
|
||||
return searchItem(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.AtSearchReply> atSearch_Pre($grpc.ServiceCall call, $async.Future<$0.AtSearchReq> request) async {
|
||||
return atSearch(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.ReplyInfoReply> replyInfo_Pre($grpc.ServiceCall call, $async.Future<$0.ReplyInfoReq> request) async {
|
||||
return replyInfo(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.UserCallbackReply> userCallback_Pre($grpc.ServiceCall call, $async.Future<$0.UserCallbackReq> request) async {
|
||||
return userCallback(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.ShareRepliesInfoResp> shareRepliesInfo_Pre($grpc.ServiceCall call, $async.Future<$0.ShareRepliesInfoReq> request) async {
|
||||
return shareRepliesInfo(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.SuggestEmotesResp> suggestEmotes_Pre($grpc.ServiceCall call, $async.Future<$0.SuggestEmotesReq> request) async {
|
||||
return suggestEmotes(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.MainListReply> mainList($grpc.ServiceCall call, $0.MainListReq request);
|
||||
$async.Future<$0.DetailListReply> detailList($grpc.ServiceCall call, $0.DetailListReq request);
|
||||
$async.Future<$0.DialogListReply> dialogList($grpc.ServiceCall call, $0.DialogListReq request);
|
||||
$async.Future<$0.PreviewListReply> previewList($grpc.ServiceCall call, $0.PreviewListReq request);
|
||||
$async.Future<$0.SearchItemPreHookReply> searchItemPreHook($grpc.ServiceCall call, $0.SearchItemPreHookReq request);
|
||||
$async.Future<$0.SearchItemReply> searchItem($grpc.ServiceCall call, $0.SearchItemReq request);
|
||||
$async.Future<$0.AtSearchReply> atSearch($grpc.ServiceCall call, $0.AtSearchReq request);
|
||||
$async.Future<$0.ReplyInfoReply> replyInfo($grpc.ServiceCall call, $0.ReplyInfoReq request);
|
||||
$async.Future<$0.UserCallbackReply> userCallback($grpc.ServiceCall call, $0.UserCallbackReq request);
|
||||
$async.Future<$0.ShareRepliesInfoResp> shareRepliesInfo($grpc.ServiceCall call, $0.ShareRepliesInfoReq request);
|
||||
$async.Future<$0.SuggestEmotesResp> suggestEmotes($grpc.ServiceCall call, $0.SuggestEmotesReq request);
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: playeronline.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:async' as $async;
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:grpc/service_api.dart' as $grpc;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'playeronline.pb.dart' as $0;
|
||||
|
||||
export 'playeronline.pb.dart';
|
||||
|
||||
@$pb.GrpcServiceName('bilibili.app.playeronline.v1.PlayerOnline')
|
||||
class PlayerOnlineClient extends $grpc.Client {
|
||||
static final _$playerOnline = $grpc.ClientMethod<$0.PlayerOnlineReq, $0.PlayerOnlineReply>(
|
||||
'/bilibili.app.playeronline.v1.PlayerOnline/PlayerOnline',
|
||||
($0.PlayerOnlineReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.PlayerOnlineReply.fromBuffer(value));
|
||||
static final _$premiereInfo = $grpc.ClientMethod<$0.PremiereInfoReq, $0.PremiereInfoReply>(
|
||||
'/bilibili.app.playeronline.v1.PlayerOnline/PremiereInfo',
|
||||
($0.PremiereInfoReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.PremiereInfoReply.fromBuffer(value));
|
||||
static final _$reportWatch = $grpc.ClientMethod<$0.ReportWatchReq, $0.NoReply>(
|
||||
'/bilibili.app.playeronline.v1.PlayerOnline/ReportWatch',
|
||||
($0.ReportWatchReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.NoReply.fromBuffer(value));
|
||||
|
||||
PlayerOnlineClient($grpc.ClientChannel channel,
|
||||
{$grpc.CallOptions? options,
|
||||
$core.Iterable<$grpc.ClientInterceptor>? interceptors})
|
||||
: super(channel, options: options,
|
||||
interceptors: interceptors);
|
||||
|
||||
$grpc.ResponseFuture<$0.PlayerOnlineReply> playerOnline($0.PlayerOnlineReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$playerOnline, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.PremiereInfoReply> premiereInfo($0.PremiereInfoReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$premiereInfo, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.NoReply> reportWatch($0.ReportWatchReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$reportWatch, request, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
@$pb.GrpcServiceName('bilibili.app.playeronline.v1.PlayerOnline')
|
||||
abstract class PlayerOnlineServiceBase extends $grpc.Service {
|
||||
$core.String get $name => 'bilibili.app.playeronline.v1.PlayerOnline';
|
||||
|
||||
PlayerOnlineServiceBase() {
|
||||
$addMethod($grpc.ServiceMethod<$0.PlayerOnlineReq, $0.PlayerOnlineReply>(
|
||||
'PlayerOnline',
|
||||
playerOnline_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.PlayerOnlineReq.fromBuffer(value),
|
||||
($0.PlayerOnlineReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.PremiereInfoReq, $0.PremiereInfoReply>(
|
||||
'PremiereInfo',
|
||||
premiereInfo_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.PremiereInfoReq.fromBuffer(value),
|
||||
($0.PremiereInfoReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.ReportWatchReq, $0.NoReply>(
|
||||
'ReportWatch',
|
||||
reportWatch_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.ReportWatchReq.fromBuffer(value),
|
||||
($0.NoReply value) => value.writeToBuffer()));
|
||||
}
|
||||
|
||||
$async.Future<$0.PlayerOnlineReply> playerOnline_Pre($grpc.ServiceCall call, $async.Future<$0.PlayerOnlineReq> request) async {
|
||||
return playerOnline(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.PremiereInfoReply> premiereInfo_Pre($grpc.ServiceCall call, $async.Future<$0.PremiereInfoReq> request) async {
|
||||
return premiereInfo(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.NoReply> reportWatch_Pre($grpc.ServiceCall call, $async.Future<$0.ReportWatchReq> request) async {
|
||||
return reportWatch(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.PlayerOnlineReply> playerOnline($grpc.ServiceCall call, $0.PlayerOnlineReq request);
|
||||
$async.Future<$0.PremiereInfoReply> premiereInfo($grpc.ServiceCall call, $0.PremiereInfoReq request);
|
||||
$async.Future<$0.NoReply> reportWatch($grpc.ServiceCall call, $0.ReportWatchReq request);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: bilibili/app/show/popular/v1/popular.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:async' as $async;
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:grpc/service_api.dart' as $grpc;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'popular.pb.dart' as $0;
|
||||
|
||||
export 'popular.pb.dart';
|
||||
|
||||
@$pb.GrpcServiceName('bilibili.app.show.v1.Popular')
|
||||
class PopularClient extends $grpc.Client {
|
||||
static final _$index = $grpc.ClientMethod<$0.PopularResultReq, $0.PopularReply>(
|
||||
'/bilibili.app.show.v1.Popular/Index',
|
||||
($0.PopularResultReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.PopularReply.fromBuffer(value));
|
||||
|
||||
PopularClient($grpc.ClientChannel channel,
|
||||
{$grpc.CallOptions? options,
|
||||
$core.Iterable<$grpc.ClientInterceptor>? interceptors})
|
||||
: super(channel, options: options,
|
||||
interceptors: interceptors);
|
||||
|
||||
$grpc.ResponseFuture<$0.PopularReply> index($0.PopularResultReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$index, request, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
@$pb.GrpcServiceName('bilibili.app.show.v1.Popular')
|
||||
abstract class PopularServiceBase extends $grpc.Service {
|
||||
$core.String get $name => 'bilibili.app.show.v1.Popular';
|
||||
|
||||
PopularServiceBase() {
|
||||
$addMethod($grpc.ServiceMethod<$0.PopularResultReq, $0.PopularReply>(
|
||||
'Index',
|
||||
index_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.PopularResultReq.fromBuffer(value),
|
||||
($0.PopularReply value) => value.writeToBuffer()));
|
||||
}
|
||||
|
||||
$async.Future<$0.PopularReply> index_Pre($grpc.ServiceCall call, $async.Future<$0.PopularResultReq> request) async {
|
||||
return index(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.PopularReply> index($grpc.ServiceCall call, $0.PopularResultReq request);
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: bilibili/community/service/dm/v1/dm.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:async' as $async;
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:grpc/service_api.dart' as $grpc;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'dm.pb.dart' as $0;
|
||||
|
||||
export 'dm.pb.dart';
|
||||
|
||||
@$pb.GrpcServiceName('bilibili.community.service.dm.v1.DM')
|
||||
class DMClient extends $grpc.Client {
|
||||
static final _$dmSegMobile = $grpc.ClientMethod<$0.DmSegMobileReq, $0.DmSegMobileReply>(
|
||||
'/bilibili.community.service.dm.v1.DM/DmSegMobile',
|
||||
($0.DmSegMobileReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DmSegMobileReply.fromBuffer(value));
|
||||
static final _$dmView = $grpc.ClientMethod<$0.DmViewReq, $0.DmViewReply>(
|
||||
'/bilibili.community.service.dm.v1.DM/DmView',
|
||||
($0.DmViewReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DmViewReply.fromBuffer(value));
|
||||
static final _$dmPlayerConfig = $grpc.ClientMethod<$0.DmPlayerConfigReq, $0.Response>(
|
||||
'/bilibili.community.service.dm.v1.DM/DmPlayerConfig',
|
||||
($0.DmPlayerConfigReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.Response.fromBuffer(value));
|
||||
static final _$dmSegOtt = $grpc.ClientMethod<$0.DmSegOttReq, $0.DmSegOttReply>(
|
||||
'/bilibili.community.service.dm.v1.DM/DmSegOtt',
|
||||
($0.DmSegOttReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DmSegOttReply.fromBuffer(value));
|
||||
static final _$dmSegSDK = $grpc.ClientMethod<$0.DmSegSDKReq, $0.DmSegSDKReply>(
|
||||
'/bilibili.community.service.dm.v1.DM/DmSegSDK',
|
||||
($0.DmSegSDKReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DmSegSDKReply.fromBuffer(value));
|
||||
static final _$dmExpoReport = $grpc.ClientMethod<$0.DmExpoReportReq, $0.DmExpoReportRes>(
|
||||
'/bilibili.community.service.dm.v1.DM/DmExpoReport',
|
||||
($0.DmExpoReportReq value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.DmExpoReportRes.fromBuffer(value));
|
||||
|
||||
DMClient($grpc.ClientChannel channel,
|
||||
{$grpc.CallOptions? options,
|
||||
$core.Iterable<$grpc.ClientInterceptor>? interceptors})
|
||||
: super(channel, options: options,
|
||||
interceptors: interceptors);
|
||||
|
||||
$grpc.ResponseFuture<$0.DmSegMobileReply> dmSegMobile($0.DmSegMobileReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dmSegMobile, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DmViewReply> dmView($0.DmViewReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dmView, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.Response> dmPlayerConfig($0.DmPlayerConfigReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dmPlayerConfig, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DmSegOttReply> dmSegOtt($0.DmSegOttReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dmSegOtt, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DmSegSDKReply> dmSegSDK($0.DmSegSDKReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dmSegSDK, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.DmExpoReportRes> dmExpoReport($0.DmExpoReportReq request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$dmExpoReport, request, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
@$pb.GrpcServiceName('bilibili.community.service.dm.v1.DM')
|
||||
abstract class DMServiceBase extends $grpc.Service {
|
||||
$core.String get $name => 'bilibili.community.service.dm.v1.DM';
|
||||
|
||||
DMServiceBase() {
|
||||
$addMethod($grpc.ServiceMethod<$0.DmSegMobileReq, $0.DmSegMobileReply>(
|
||||
'DmSegMobile',
|
||||
dmSegMobile_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DmSegMobileReq.fromBuffer(value),
|
||||
($0.DmSegMobileReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DmViewReq, $0.DmViewReply>(
|
||||
'DmView',
|
||||
dmView_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DmViewReq.fromBuffer(value),
|
||||
($0.DmViewReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DmPlayerConfigReq, $0.Response>(
|
||||
'DmPlayerConfig',
|
||||
dmPlayerConfig_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DmPlayerConfigReq.fromBuffer(value),
|
||||
($0.Response value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DmSegOttReq, $0.DmSegOttReply>(
|
||||
'DmSegOtt',
|
||||
dmSegOtt_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DmSegOttReq.fromBuffer(value),
|
||||
($0.DmSegOttReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DmSegSDKReq, $0.DmSegSDKReply>(
|
||||
'DmSegSDK',
|
||||
dmSegSDK_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DmSegSDKReq.fromBuffer(value),
|
||||
($0.DmSegSDKReply value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.DmExpoReportReq, $0.DmExpoReportRes>(
|
||||
'DmExpoReport',
|
||||
dmExpoReport_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.DmExpoReportReq.fromBuffer(value),
|
||||
($0.DmExpoReportRes value) => value.writeToBuffer()));
|
||||
}
|
||||
|
||||
$async.Future<$0.DmSegMobileReply> dmSegMobile_Pre($grpc.ServiceCall call, $async.Future<$0.DmSegMobileReq> request) async {
|
||||
return dmSegMobile(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DmViewReply> dmView_Pre($grpc.ServiceCall call, $async.Future<$0.DmViewReq> request) async {
|
||||
return dmView(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.Response> dmPlayerConfig_Pre($grpc.ServiceCall call, $async.Future<$0.DmPlayerConfigReq> request) async {
|
||||
return dmPlayerConfig(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DmSegOttReply> dmSegOtt_Pre($grpc.ServiceCall call, $async.Future<$0.DmSegOttReq> request) async {
|
||||
return dmSegOtt(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DmSegSDKReply> dmSegSDK_Pre($grpc.ServiceCall call, $async.Future<$0.DmSegSDKReq> request) async {
|
||||
return dmSegSDK(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DmExpoReportRes> dmExpoReport_Pre($grpc.ServiceCall call, $async.Future<$0.DmExpoReportReq> request) async {
|
||||
return dmExpoReport(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.DmSegMobileReply> dmSegMobile($grpc.ServiceCall call, $0.DmSegMobileReq request);
|
||||
$async.Future<$0.DmViewReply> dmView($grpc.ServiceCall call, $0.DmViewReq request);
|
||||
$async.Future<$0.Response> dmPlayerConfig($grpc.ServiceCall call, $0.DmPlayerConfigReq request);
|
||||
$async.Future<$0.DmSegOttReply> dmSegOtt($grpc.ServiceCall call, $0.DmSegOttReq request);
|
||||
$async.Future<$0.DmSegSDKReply> dmSegSDK($grpc.ServiceCall call, $0.DmSegSDKReq request);
|
||||
$async.Future<$0.DmExpoReportRes> dmExpoReport($grpc.ServiceCall call, $0.DmExpoReportReq request);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import 'package:PiliPlus/grpc/app/dynamic/v1/dynamic.pbgrpc.dart' as v1;
|
||||
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pbgrpc.dart' as v2;
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pbgrpc.dart';
|
||||
import 'package:PiliPlus/grpc/app/playeronline/v1/playeronline.pbgrpc.dart';
|
||||
import 'package:PiliPlus/grpc/app/show/popular/v1/popular.pbgrpc.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
|
||||
class GrpcClient {
|
||||
ClientChannel? _channel;
|
||||
PlayerOnlineClient? _playerOnlineClient;
|
||||
PopularClient? _popularClient;
|
||||
ReplyClient? _replyClient;
|
||||
v2.DynamicClient? _dynamicClientV2;
|
||||
v1.DynamicClient? _dynamicClientV1;
|
||||
|
||||
GrpcClient._internal() {
|
||||
_channel = ClientChannel(
|
||||
'grpc.biliapi.net',
|
||||
port: 443,
|
||||
options: const ChannelOptions(
|
||||
credentials: ChannelCredentials.secure(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final GrpcClient _instance = GrpcClient._internal();
|
||||
static GrpcClient get instance => _instance;
|
||||
|
||||
PlayerOnlineClient get playerOnlineClient {
|
||||
_playerOnlineClient ??= PlayerOnlineClient(_channel!);
|
||||
return _playerOnlineClient!;
|
||||
}
|
||||
|
||||
PopularClient get popularClient {
|
||||
_popularClient ??= PopularClient(_channel!);
|
||||
return _popularClient!;
|
||||
}
|
||||
|
||||
ReplyClient get replyClient {
|
||||
_replyClient ??= ReplyClient(_channel!);
|
||||
return _replyClient!;
|
||||
}
|
||||
|
||||
v2.DynamicClient get dynamicClientV2 {
|
||||
_dynamicClientV2 ??= v2.DynamicClient(_channel!);
|
||||
return _dynamicClientV2!;
|
||||
}
|
||||
|
||||
v1.DynamicClient get dynamicClientV1 {
|
||||
_dynamicClientV1 ??= v1.DynamicClient(_channel!);
|
||||
return _dynamicClientV1!;
|
||||
}
|
||||
|
||||
Future<void> shutdown() async {
|
||||
await _channel?.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -1,130 +1,182 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/grpc/app/dynamic/v1/dynamic.pb.dart';
|
||||
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart';
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/grpc/app/playeronline/v1/playeronline.pbgrpc.dart';
|
||||
import 'package:PiliPlus/grpc/app/playeronline/v1/playeronline.pb.dart';
|
||||
import 'package:PiliPlus/grpc/app/show/popular/v1/popular.pb.dart';
|
||||
import 'package:PiliPlus/grpc/device/device.pb.dart';
|
||||
import 'package:PiliPlus/grpc/dm/v1/dm.pb.dart';
|
||||
import 'package:PiliPlus/grpc/fawkes/fawkes.pb.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_client.dart';
|
||||
import 'package:PiliPlus/grpc/locale/locale.pb.dart';
|
||||
import 'package:PiliPlus/grpc/metadata/metadata.pb.dart';
|
||||
import 'package:PiliPlus/grpc/network/network.pb.dart' as network;
|
||||
import 'package:PiliPlus/grpc/restriction/restriction.pb.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/utils/login.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fixnum/src/int64.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
import 'package:protobuf/protobuf.dart' show GeneratedMessage;
|
||||
|
||||
class GrpcUrl {
|
||||
static const playerOnline =
|
||||
'/bilibili.app.playeronline.v1.PlayerOnline/PlayerOnline';
|
||||
static const popular = '/bilibili.app.show.v1.Popular/Index';
|
||||
static const dialogList =
|
||||
'/bilibili.main.community.reply.v1.Reply/DialogList';
|
||||
static const detailList =
|
||||
'/bilibili.main.community.reply.v1.Reply/DetailList';
|
||||
static const replyInfo = '/bilibili.main.community.reply.v1.Reply/ReplyInfo';
|
||||
static const mainList = '/bilibili.main.community.reply.v1.Reply/MainList';
|
||||
static const dynSpace = '/bilibili.app.dynamic.v2.Dynamic/DynSpace';
|
||||
static const dynRed = '/bilibili.app.dynamic.v1.Dynamic/DynRed';
|
||||
static const dmSegMobile = '/bilibili.community.service.dm.v1.DM/DmSegMobile';
|
||||
}
|
||||
|
||||
class GrpcRepo {
|
||||
static final bool _isLogin = GStorage.userInfo.get('userInfoCache') != null;
|
||||
static final int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
static final String? _accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
static const gzipEncoder = GZipEncoder();
|
||||
static const gzipDecoder = GZipDecoder();
|
||||
|
||||
static final String? _accessKey = Accounts.main.accessKey;
|
||||
static const _build = 1462100;
|
||||
static const _biliChannel = 'bili';
|
||||
static const _mobiApp = 'android_hd';
|
||||
static const _phone = 'phone';
|
||||
|
||||
static final _eId = _isLogin ? Utils.genAuroraEid(_mid!) : '';
|
||||
static final _buvid = LoginUtils.buvid();
|
||||
static final _buvid = LoginUtils.buvid;
|
||||
static final _traceId = Utils.genTraceId();
|
||||
static final _sessionId = Utils.generateRandomString(8);
|
||||
|
||||
static final Map<String, String> metadata = {
|
||||
static final Map<String, String> headers = {
|
||||
Headers.contentTypeHeader: 'application/grpc',
|
||||
'grpc-encoding': 'gzip',
|
||||
'gzip-accept-encoding': 'gzip,identity',
|
||||
'user-agent': '${Constants.userAgent} grpc-java-cronet/1.36.1',
|
||||
'x-bili-gaia-vtoken': '',
|
||||
'x-bili-aurora-eid': _isLogin ? _eId : '',
|
||||
'x-bili-mid': _isLogin ? _mid.toString() : '0',
|
||||
'x-bili-aurora-zone': '',
|
||||
'x-bili-trace-id': _traceId,
|
||||
if (_isLogin) 'authorization': 'identify_v1 $_accessKey',
|
||||
if (_accessKey != null) 'authorization': 'identify_v1 $_accessKey',
|
||||
'buvid': _buvid,
|
||||
'bili-http-engine': 'cronet',
|
||||
'te': 'trailers',
|
||||
'x-bili-fawkes-req-bin': base64Encode((FawkesReq()
|
||||
..appkey = _mobiApp
|
||||
..env = 'prod'
|
||||
..sessionId = _sessionId)
|
||||
'x-bili-fawkes-req-bin': base64Encode(
|
||||
FawkesReq(appkey: _mobiApp, env: 'prod', sessionId: _sessionId)
|
||||
.writeToBuffer()),
|
||||
'x-bili-metadata-bin': base64Encode(Metadata(
|
||||
accessKey: _accessKey ?? '',
|
||||
mobiApp: _mobiApp,
|
||||
device: _phone,
|
||||
build: _build,
|
||||
channel: _biliChannel,
|
||||
buvid: _buvid,
|
||||
platform: _mobiApp,
|
||||
).writeToBuffer()),
|
||||
'x-bili-device-bin': base64Encode(Device(
|
||||
appId: 1,
|
||||
build: _build,
|
||||
buvid: _buvid,
|
||||
mobiApp: _mobiApp,
|
||||
platform: _mobiApp,
|
||||
device: _phone,
|
||||
channel: _biliChannel,
|
||||
brand: _phone,
|
||||
model: _phone,
|
||||
osver: '14',
|
||||
fpLocal: '',
|
||||
fpRemote: '',
|
||||
versionName: _build.toString(),
|
||||
fp: '',
|
||||
fts: Int64())
|
||||
.writeToBuffer()),
|
||||
'x-bili-metadata-bin': base64Encode((Metadata()
|
||||
..accessKey = _accessKey ?? ''
|
||||
..mobiApp = _mobiApp
|
||||
..device = _phone
|
||||
..build = _build
|
||||
..channel = _biliChannel
|
||||
..buvid = _buvid
|
||||
..platform = _mobiApp)
|
||||
'x-bili-network-bin': base64Encode(network.Network(
|
||||
type: network.NetworkType.WIFI,
|
||||
tf: network.TFType.TF_UNKNOWN,
|
||||
oid: '')
|
||||
.writeToBuffer()),
|
||||
'x-bili-device-bin': base64Encode((Device()
|
||||
..appId = 1
|
||||
..build = _build
|
||||
..buvid = _buvid
|
||||
..mobiApp = _mobiApp
|
||||
..platform = _mobiApp
|
||||
..device = _phone
|
||||
..channel = _biliChannel
|
||||
..brand = _phone
|
||||
..model = _phone
|
||||
..osver = '14'
|
||||
..fpLocal = ''
|
||||
..fpRemote = ''
|
||||
..versionName = _build.toString()
|
||||
..fp = ''
|
||||
..fts = Int64())
|
||||
'x-bili-restriction-bin': base64Encode(Restriction(
|
||||
teenagersMode: false,
|
||||
lessonsMode: false,
|
||||
mode: ModeType.NORMAL,
|
||||
review: false,
|
||||
disableRcmd: false,
|
||||
basicMode: false)
|
||||
.writeToBuffer()),
|
||||
'x-bili-network-bin': base64Encode((network.Network()
|
||||
..type = network.NetworkType.WIFI
|
||||
..tf = network.TFType.TF_UNKNOWN
|
||||
..oid = '')
|
||||
.writeToBuffer()),
|
||||
'x-bili-restriction-bin': base64Encode((Restriction()
|
||||
..teenagersMode = false
|
||||
..lessonsMode = false
|
||||
..mode = ModeType.NORMAL
|
||||
..review = false
|
||||
..disableRcmd = false
|
||||
..basicMode = false)
|
||||
.writeToBuffer()),
|
||||
'x-bili-locale-bin': base64Encode((Locale()
|
||||
..cLocale = LocaleIds(language: 'zh', region: 'CN')
|
||||
..sLocale = LocaleIds(language: 'zh', region: 'CN')
|
||||
..simCode = ''
|
||||
..timezone = 'Asia/Shanghai')
|
||||
'x-bili-locale-bin': base64Encode(Locale(
|
||||
cLocale: LocaleIds(language: 'zh', region: 'CN'),
|
||||
sLocale: LocaleIds(language: 'zh', region: 'CN'),
|
||||
simCode: '',
|
||||
timezone: 'Asia/Shanghai')
|
||||
.writeToBuffer()),
|
||||
'x-bili-exps-bin': '',
|
||||
};
|
||||
|
||||
static final CallOptions options = CallOptions(metadata: metadata);
|
||||
static final unprintableRegExp = RegExp(r"[^\u4e00-\u9fa5,。;!?UP]");
|
||||
|
||||
static Future _request(Function request) async {
|
||||
try {
|
||||
return await request();
|
||||
} catch (e) {
|
||||
dynamic defMsg() => {'status': false, 'msg': e.toString()};
|
||||
if (e is GrpcError) {
|
||||
try {
|
||||
String msg = utf8.decode(
|
||||
e.details?.firstOrNull?.getFieldOrNull(2),
|
||||
allowMalformed: true,
|
||||
);
|
||||
msg =
|
||||
msg.replaceAll(RegExp(r"[^a-zA-Z0-9\u4e00-\u9fa5,.;?,。;!?]"), '');
|
||||
if (msg.isNotEmpty) {
|
||||
return {'status': false, 'msg': msg};
|
||||
} else {
|
||||
return defMsg();
|
||||
}
|
||||
} catch (e1) {
|
||||
debugPrint(e1.toString());
|
||||
return defMsg();
|
||||
}
|
||||
static Uint8List compressProtobuf(Uint8List proto) {
|
||||
proto = gzipEncoder.encodeBytes(proto, level: 0);
|
||||
var byteLength = ByteData(4);
|
||||
byteLength.setInt32(0, proto.length, Endian.big);
|
||||
var compressed = Uint8List(5 + proto.length);
|
||||
compressed[0] = 1;
|
||||
compressed.setRange(1, 5, byteLength.buffer.asUint8List());
|
||||
compressed.setAll(5, proto);
|
||||
return compressed;
|
||||
}
|
||||
|
||||
static Uint8List decompressProtobuf(Uint8List data) {
|
||||
var length = ByteData.sublistView(data, 1, 5).getInt32(0, Endian.big);
|
||||
|
||||
if (data[0] == 1) {
|
||||
return gzipDecoder.decodeBytes(data.sublist(5, length + 5));
|
||||
} else {
|
||||
return data.sublist(5, length + 5);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> _request(
|
||||
url, GeneratedMessage request, Function grpcParser,
|
||||
{Function? onSuccess}) async {
|
||||
final response = await Request().post(HttpString.appBaseUrl + url,
|
||||
data: compressProtobuf(request.writeToBuffer()),
|
||||
options: Options(headers: headers, responseType: ResponseType.bytes));
|
||||
|
||||
if (response.data is Map) {
|
||||
return {'status': false, 'msg': response.data['message']};
|
||||
}
|
||||
|
||||
if (response.headers.value('Grpc-Status') == '0') {
|
||||
try {
|
||||
Uint8List data = response.data;
|
||||
data = decompressProtobuf(data);
|
||||
final grpcResponse = grpcParser(data);
|
||||
return {
|
||||
'status': true,
|
||||
'data': onSuccess == null ? grpcResponse : onSuccess(grpcResponse),
|
||||
};
|
||||
} catch (e) {
|
||||
return {'status': false, 'msg': e.toString()};
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
String msg = response.headers.value('Grpc-Status-Details-Bin') ?? '';
|
||||
if (msg != '') {
|
||||
while (msg.length % 4 != 0) {
|
||||
msg += '=';
|
||||
}
|
||||
msg = utf8
|
||||
.decode(base64Decode(msg), allowMalformed: true)
|
||||
.replaceAll(unprintableRegExp, '');
|
||||
}
|
||||
return {'status': false, 'msg': msg};
|
||||
} catch (e) {
|
||||
return {'status': false, 'msg': e.toString()};
|
||||
}
|
||||
return defMsg();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,22 +184,16 @@ class GrpcRepo {
|
||||
int aid = 0,
|
||||
int cid = 0,
|
||||
}) async {
|
||||
return await _request(() async {
|
||||
final request = PlayerOnlineReq()
|
||||
..aid = Int64(aid)
|
||||
..cid = Int64(cid)
|
||||
..playOpen = true;
|
||||
final response = await GrpcClient.instance.playerOnlineClient
|
||||
.playerOnline(request, options: options);
|
||||
return {'status': true, 'data': response.totalNumberText};
|
||||
});
|
||||
return await _request(
|
||||
GrpcUrl.playerOnline,
|
||||
PlayerOnlineReq(aid: Int64(aid), cid: Int64(cid), playOpen: true),
|
||||
PlayerOnlineReply.fromBuffer,
|
||||
onSuccess: (response) => response.totalNumberText);
|
||||
}
|
||||
|
||||
static Future popular(int idx) async {
|
||||
return await _request(() async {
|
||||
final request = PopularResultReq()..idx = Int64(idx);
|
||||
final response = await GrpcClient.instance.popularClient
|
||||
.index(request, options: options);
|
||||
return await _request(GrpcUrl.popular, PopularResultReq(idx: Int64(idx)),
|
||||
PopularReply.fromBuffer, onSuccess: (response) {
|
||||
response.items.retainWhere((item) => item.smallCoverV5.base.goto == 'av');
|
||||
return {'status': true, 'data': response.items};
|
||||
});
|
||||
@@ -161,17 +207,15 @@ class GrpcRepo {
|
||||
required CursorReq cursor,
|
||||
DetailListScene scene = DetailListScene.REPLY,
|
||||
}) async {
|
||||
return await _request(() async {
|
||||
final request = DialogListReq()
|
||||
..oid = Int64(oid)
|
||||
..type = Int64(type)
|
||||
..root = Int64(root)
|
||||
..rpid = Int64(rpid)
|
||||
..cursor = cursor;
|
||||
final response = await GrpcClient.instance.replyClient
|
||||
.dialogList(request, options: options);
|
||||
return {'status': true, 'data': response};
|
||||
});
|
||||
return await _request(
|
||||
GrpcUrl.dialogList,
|
||||
DialogListReq(
|
||||
oid: Int64(oid),
|
||||
type: Int64(type),
|
||||
root: Int64(root),
|
||||
rpid: Int64(rpid),
|
||||
cursor: cursor),
|
||||
DialogListReply.fromBuffer);
|
||||
}
|
||||
|
||||
static Future detailList({
|
||||
@@ -182,29 +226,22 @@ class GrpcRepo {
|
||||
required CursorReq cursor,
|
||||
DetailListScene scene = DetailListScene.REPLY,
|
||||
}) async {
|
||||
return await _request(() async {
|
||||
final request = DetailListReq()
|
||||
..oid = Int64(oid)
|
||||
..type = Int64(type)
|
||||
..root = Int64(root)
|
||||
..rpid = Int64(rpid)
|
||||
..cursor = cursor
|
||||
..scene = scene;
|
||||
final response = await GrpcClient.instance.replyClient
|
||||
.detailList(request, options: options);
|
||||
return {'status': true, 'data': response};
|
||||
});
|
||||
return await _request(
|
||||
GrpcUrl.detailList,
|
||||
DetailListReq(
|
||||
oid: Int64(oid),
|
||||
type: Int64(type),
|
||||
root: Int64(root),
|
||||
rpid: Int64(rpid),
|
||||
cursor: cursor,
|
||||
scene: scene),
|
||||
DetailListReply.fromBuffer);
|
||||
}
|
||||
|
||||
static Future replyInfo({
|
||||
required int rpid,
|
||||
}) async {
|
||||
return await _request(() async {
|
||||
final request = ReplyInfoReq()..rpid = Int64(rpid);
|
||||
final response = await GrpcClient.instance.replyClient
|
||||
.replyInfo(request, options: options);
|
||||
return {'status': true, 'data': response.reply};
|
||||
});
|
||||
static Future replyInfo({required int rpid}) async {
|
||||
return await _request(GrpcUrl.replyInfo, ReplyInfoReq(rpid: Int64(rpid)),
|
||||
ReplyInfoReply.fromBuffer,
|
||||
onSuccess: (response) => response.reply);
|
||||
}
|
||||
|
||||
static Future mainList({
|
||||
@@ -212,40 +249,39 @@ class GrpcRepo {
|
||||
required int oid,
|
||||
required CursorReq cursor,
|
||||
}) async {
|
||||
return await _request(() async {
|
||||
final request = MainListReq()
|
||||
..oid = Int64(oid)
|
||||
..type = Int64(type)
|
||||
..rpid = Int64(0)
|
||||
..cursor = cursor;
|
||||
final response = await GrpcClient.instance.replyClient
|
||||
.mainList(request, options: options);
|
||||
return {'status': true, 'data': response};
|
||||
});
|
||||
return await _request(
|
||||
GrpcUrl.mainList,
|
||||
MainListReq(
|
||||
oid: Int64(oid), type: Int64(type), rpid: Int64(0), cursor: cursor),
|
||||
MainListReply.fromBuffer);
|
||||
}
|
||||
|
||||
static Future dynSpace({
|
||||
required int uid,
|
||||
required int page,
|
||||
}) async {
|
||||
return await _request(() async {
|
||||
final request = DynSpaceReq()
|
||||
..hostUid = Int64(uid)
|
||||
..localTime = 8
|
||||
..page = Int64(page)
|
||||
..from = 'space';
|
||||
final DynSpaceRsp response = await GrpcClient.instance.dynamicClientV2
|
||||
.dynSpace(request, options: options);
|
||||
return {'status': true, 'data': response};
|
||||
});
|
||||
return await _request(
|
||||
GrpcUrl.dynSpace,
|
||||
DynSpaceReq(
|
||||
hostUid: Int64(uid),
|
||||
localTime: 8,
|
||||
page: Int64(page),
|
||||
from: 'space'),
|
||||
DynSpaceRsp.fromBuffer);
|
||||
}
|
||||
|
||||
static Future dynRed() async {
|
||||
return await _request(() async {
|
||||
final request = DynRedReq()..tabOffset.add(TabOffset(tab: 1));
|
||||
final DynRedReply response = await GrpcClient.instance.dynamicClientV1
|
||||
.dynRed(request, options: options);
|
||||
return {'status': true, 'data': response.dynRedItem.count.toInt()};
|
||||
});
|
||||
return await _request(GrpcUrl.dynRed,
|
||||
DynRedReq(tabOffset: [TabOffset(tab: 1)]), DynRedReply.fromBuffer,
|
||||
onSuccess: (response) => response.dynRedItem.count.toInt());
|
||||
}
|
||||
|
||||
static Future dmSegMobile(
|
||||
{required int cid, required int segmentIndex, int type = 1}) async {
|
||||
return await _request(
|
||||
GrpcUrl.dmSegMobile,
|
||||
DmSegMobileReq(
|
||||
oid: Int64(cid), segmentIndex: Int64(segmentIndex), type: type),
|
||||
DmSegMobileReply.fromBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -265,6 +276,9 @@ class Api {
|
||||
// 番剧/剧集明细
|
||||
static const String bangumiInfo = '/pgc/view/web/season';
|
||||
|
||||
// https://api.bilibili.com/pgc/season/episode/web/info?ep_id=12345678
|
||||
static const String episodeInfo = '/pgc/season/episode/web/info';
|
||||
|
||||
// 全部关注的up
|
||||
// vmid 用户id pn 页码 ps 每页个数,最大50 order: desc
|
||||
// order_type 排序规则 最近访问传空,最常访问传 attention
|
||||
@@ -274,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';
|
||||
|
||||
@@ -321,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';
|
||||
|
||||
@@ -359,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';
|
||||
@@ -368,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';
|
||||
@@ -403,14 +418,11 @@ 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';
|
||||
|
||||
// 移除黑名单
|
||||
static const String removeBlack = '/x/relation/modify';
|
||||
|
||||
// github 获取最新版
|
||||
static const String latestApp =
|
||||
'https://api.github.com/repos/bggRGjQaUbCoE/PiliPlus/releases';
|
||||
@@ -419,7 +431,7 @@ class Api {
|
||||
// https://api.bilibili.com/x/player/online/total?aid=913663681&cid=1203559746&bvid=BV1MM4y1s7NZ&ts=56427838
|
||||
static const String onlineTotal = '/x/player/online/total';
|
||||
|
||||
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
||||
// static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
||||
|
||||
// 发送视频弹幕
|
||||
//https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/danmaku/action.md
|
||||
@@ -468,12 +480,9 @@ class Api {
|
||||
static const String msgFeedAt = '/x/msgfeed/at';
|
||||
//https://api.bilibili.com/x/msgfeed/like?platform=web&build=0&mobi_app=web
|
||||
static const String msgFeedLike = '/x/msgfeed/like';
|
||||
//https://message.bilibili.com/x/sys-msg/query_user_notify?csrf=xxxx&csrf=xxxx&page_size=20&build=0&mobi_app=web
|
||||
static const String msgSysUserNotify =
|
||||
'${HttpString.messageBaseUrl}/x/sys-msg/query_user_notify';
|
||||
//https://message.bilibili.com/x/sys-msg/query_unified_notify?csrf=xxxx&csrf=xxxx&page_size=10&build=0&mobi_app=web
|
||||
static const String msgSysUnifiedNotify =
|
||||
'${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify';
|
||||
//https://message.bilibili.com/x/sys-msg/query_notify_list?page_size=20&cursor=xxx
|
||||
static const String msgSysNotify =
|
||||
'${HttpString.messageBaseUrl}/x/sys-msg/query_notify_list';
|
||||
|
||||
// 系统信息光标更新(已读标记)
|
||||
//https://message.bilibili.com/x/sys-msg/update_cursor?csrf=xxxx&csrf=xxxx&cursor=1705288500000000000&has_up=0&build=0&mobi_app=web
|
||||
@@ -619,12 +628,14 @@ class Api {
|
||||
|
||||
/// 申请二维码(TV端)
|
||||
static const getTVCode =
|
||||
'https://passport.bilibili.com/x/passport-tv-login/qrcode/auth_code';
|
||||
'${HttpString.passBaseUrl}/x/passport-tv-login/qrcode/auth_code';
|
||||
|
||||
///扫码登录(TV端)
|
||||
static const qrcodePoll =
|
||||
'${HttpString.passBaseUrl}/x/passport-tv-login/qrcode/poll';
|
||||
|
||||
static const logout = '${HttpString.passBaseUrl}/login/exit/v2';
|
||||
|
||||
/// 置顶视频
|
||||
static const getTopVideoApi = '/x/space/top/arc';
|
||||
|
||||
@@ -639,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';
|
||||
|
||||
@@ -662,7 +670,7 @@ class Api {
|
||||
static const getUnreadDynamic = '/x/web-interface/dynamic/entrance';
|
||||
|
||||
/// 用户动态主页
|
||||
static const dynamicSpmPrefix = 'https://space.bilibili.com/1/dynamic';
|
||||
static const dynamicSpmPrefix = '${HttpString.spaceBaseUrl}/1/dynamic';
|
||||
|
||||
/// 激活buvid3
|
||||
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
|
||||
@@ -673,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';
|
||||
|
||||
@@ -694,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';
|
||||
|
||||
@@ -708,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,4 +730,38 @@ class Api {
|
||||
static const String pgcIndexCondition = '/pgc/season/index/condition';
|
||||
|
||||
static const String pgcIndexResult = '/pgc/season/index/result';
|
||||
|
||||
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,
|
||||
@@ -19,31 +20,4 @@ class BlackHttp {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除黑名单
|
||||
static Future removeBlack({required int fid}) async {
|
||||
var res = await Request().post(
|
||||
Api.removeBlack,
|
||||
queryParameters: {
|
||||
'act': 6,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'fid': fid,
|
||||
'jsonp': 'jsonp',
|
||||
're_src': 116,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': [],
|
||||
'msg': '操作成功',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,38 +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';
|
||||
static const List<int> validateStatusCodes = [
|
||||
302,
|
||||
304,
|
||||
307,
|
||||
400,
|
||||
401,
|
||||
403,
|
||||
404,
|
||||
405,
|
||||
409,
|
||||
412,
|
||||
500,
|
||||
503,
|
||||
504,
|
||||
509,
|
||||
616,
|
||||
617,
|
||||
625,
|
||||
626,
|
||||
628,
|
||||
629,
|
||||
632,
|
||||
643,
|
||||
650,
|
||||
652,
|
||||
658,
|
||||
662,
|
||||
688,
|
||||
689,
|
||||
701,
|
||||
799,
|
||||
8888
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPlus/grpc/dm/v1/dm.pb.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'index.dart';
|
||||
|
||||
@@ -9,26 +9,41 @@ class DanmakuHttp {
|
||||
required int cid,
|
||||
required int segmentIndex,
|
||||
required bool mergeDanmaku,
|
||||
int queryCount = 1,
|
||||
}) async {
|
||||
// 构建参数对象
|
||||
Map<String, int> params = {
|
||||
'type': 1,
|
||||
'oid': cid,
|
||||
'segment_index': segmentIndex,
|
||||
};
|
||||
var response = await Request().get(
|
||||
Api.webDanmaku,
|
||||
queryParameters: params,
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
if (response.statusCode != 200 || response.data == null) {
|
||||
return DmSegMobileReply();
|
||||
final response =
|
||||
await GrpcRepo.dmSegMobile(cid: cid, segmentIndex: segmentIndex);
|
||||
if (!response['status']) {
|
||||
if (queryCount >= 3) {
|
||||
return {'status': false};
|
||||
} else {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return await queryDanmaku(
|
||||
cid: cid,
|
||||
segmentIndex: segmentIndex,
|
||||
mergeDanmaku: mergeDanmaku,
|
||||
queryCount: ++queryCount,
|
||||
);
|
||||
}
|
||||
}
|
||||
DmSegMobileReply data = DmSegMobileReply.fromBuffer(response.data);
|
||||
if (mergeDanmaku) {
|
||||
data.elems.unique((item) => item.content);
|
||||
DmSegMobileReply data = response['data'];
|
||||
if (mergeDanmaku && data.elems.isNotEmpty) {
|
||||
final Map counts = <String, int>{};
|
||||
data.elems.retainWhere((item) {
|
||||
int? count = counts[item.content];
|
||||
counts[item.content] = count != null ? count + 1 : 1;
|
||||
return count == null;
|
||||
});
|
||||
for (DanmakuElem item in data.elems) {
|
||||
item.clearAttr();
|
||||
final count = counts[item.content];
|
||||
if (count != 1) {
|
||||
item.attr = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
return {'status': true, 'data': data};
|
||||
}
|
||||
|
||||
static Future shootDanmaku({
|
||||
@@ -45,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 方式必要
|
||||
@@ -62,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,
|
||||
@@ -82,7 +97,6 @@ class DanmakuHttp {
|
||||
if (response.statusCode != 200) {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '弹幕发送失败,状态码:${response.statusCode}',
|
||||
};
|
||||
}
|
||||
@@ -94,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'],
|
||||
};
|
||||
}
|
||||
@@ -39,7 +38,8 @@ class DanmakuFilterHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future danmakuFilterAdd({required String filter, required int type}) async {
|
||||
static Future danmakuFilterAdd(
|
||||
{required String filter, required int type}) async {
|
||||
var res = await Request().post(
|
||||
Api.danmakuFilterAdd,
|
||||
queryParameters: {
|
||||
@@ -51,7 +51,7 @@ class DanmakuFilterHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': Rule.fromJson(res.data['data']),
|
||||
'data': SimpleRule.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../models/dynamics/result.dart';
|
||||
@@ -6,11 +8,10 @@ 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,
|
||||
required bool antiGoodsDyn,
|
||||
}) async {
|
||||
Map<String, dynamic> data = {
|
||||
'type': type ?? 'all',
|
||||
@@ -26,10 +27,14 @@ class DynamicsHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
try {
|
||||
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
|
||||
if (antiGoodsDyn) {
|
||||
data.items?.removeWhere((item) =>
|
||||
item.modules?.moduleDynamic?.additional?.type ==
|
||||
'ADDITIONAL_TYPE_GOODS');
|
||||
if (GStorage.antiGoodsDyn) {
|
||||
data.items?.removeWhere(
|
||||
(item) =>
|
||||
item.orig?.modules?.moduleDynamic?.additional?.type ==
|
||||
'ADDITIONAL_TYPE_GOODS' ||
|
||||
item.modules?.moduleDynamic?.additional?.type ==
|
||||
'ADDITIONAL_TYPE_GOODS',
|
||||
);
|
||||
}
|
||||
return LoadingState.success(data);
|
||||
} catch (err) {
|
||||
@@ -48,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +85,7 @@ class DynamicsHttp {
|
||||
dynamic id,
|
||||
dynamic rid,
|
||||
dynamic type,
|
||||
bool? clearCookie,
|
||||
bool clearCookie = false,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.dynamicDetail,
|
||||
@@ -97,10 +94,10 @@ class DynamicsHttp {
|
||||
if (id != null) 'id': id,
|
||||
if (rid != null) 'rid': rid,
|
||||
if (type != null) 'type': type,
|
||||
'features': 'itemOpusStyle',
|
||||
'features': 'itemOpusStyle,listOnlyfans',
|
||||
},
|
||||
options:
|
||||
clearCookie == true ? Options(extra: {'clearCookie': true}) : null,
|
||||
clearCookie ? Options(extra: {'account': AnonymousAccount()}) : null,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
try {
|
||||
@@ -111,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']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPlus/models/dynamics/article_content_model.dart';
|
||||
import 'package:PiliPlus/utils/url_utils.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:html/dom.dart' as dom;
|
||||
@@ -13,12 +14,21 @@ class HtmlHttp {
|
||||
var response = await Request().get(
|
||||
"https://www.bilibili.com/opus/$id",
|
||||
extra: {'ua': 'pc'},
|
||||
options: Options(
|
||||
followRedirects: false,
|
||||
validateStatus: (status) => true,
|
||||
),
|
||||
);
|
||||
if (response.data is! String && response.data is! List<int>) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (response.data.contains('Redirecting to')) {
|
||||
String? cvid = RegExp(r'cv\d+').firstMatch(response.data)?.group(0);
|
||||
if (cvid != null) {
|
||||
return await reqReadHtml(cvid, dynamicType, false);
|
||||
}
|
||||
|
||||
RegExp regex = RegExp(r'//([\w\.]+)/(\w+)/(\w+)');
|
||||
Match match = regex.firstMatch(response.data)!;
|
||||
String matchedString = match.group(0)!;
|
||||
@@ -48,24 +58,38 @@ class HtmlHttp {
|
||||
String opusContent =
|
||||
opusDetail.querySelector('.opus-module-content')!.innerHtml;
|
||||
String? test;
|
||||
test = opusDetail
|
||||
.querySelector('.horizontal-scroll-album__pic__img')
|
||||
?.innerHtml ??
|
||||
'';
|
||||
try {
|
||||
test = opusDetail
|
||||
.querySelector('.horizontal-scroll-album__pic__img')!
|
||||
.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)
|
||||
'content': (test ?? '') + opusContent,
|
||||
'commentType': int.parse(comment[1]),
|
||||
'commentId': int.parse(comment[2]),
|
||||
};
|
||||
} catch (err) {
|
||||
debugPrint('err: $err');
|
||||
@@ -73,7 +97,15 @@ class HtmlHttp {
|
||||
}
|
||||
|
||||
// read
|
||||
static Future reqReadHtml(id, dynamicType) async {
|
||||
static Future reqReadHtml(id, dynamicType, [bool redirect = true]) async {
|
||||
if (redirect) {
|
||||
String? redirectUrl = await UrlUtils.parseRedirectUrl(
|
||||
'https://www.bilibili.com/$dynamicType/$id/');
|
||||
if (redirectUrl != null) {
|
||||
return await reqHtml(redirectUrl.split('/').last, dynamicType);
|
||||
}
|
||||
}
|
||||
|
||||
var response = await Request().get(
|
||||
"https://www.bilibili.com/$dynamicType/$id/",
|
||||
extra: {'ua': 'pc'},
|
||||
@@ -95,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();
|
||||
// 动态详情
|
||||
@@ -140,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,128 +1,101 @@
|
||||
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:cookie_jar/cookie_jar.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_cookie_manager/dio_cookie_manager.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/utils.dart';
|
||||
import 'api.dart';
|
||||
import 'constants.dart';
|
||||
import 'interceptor.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
|
||||
|
||||
class Request {
|
||||
static const _gzipDecoder = GZipDecoder();
|
||||
static const _brotilDecoder = BrotliDecoder();
|
||||
|
||||
static final Request _instance = Request._internal();
|
||||
static late CookieManager cookieManager;
|
||||
static late AccountManager accountManager;
|
||||
static late final Dio dio;
|
||||
factory Request() => _instance;
|
||||
late bool enableSystemProxy;
|
||||
late String systemProxyHost;
|
||||
late String systemProxyPort;
|
||||
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 {
|
||||
final String cookiePath = await Utils.getCookiePath();
|
||||
final PersistCookieJar cookieJar = PersistCookieJar(
|
||||
ignoreExpires: true,
|
||||
storage: FileStorage(cookiePath),
|
||||
);
|
||||
cookieManager = CookieManager(cookieJar);
|
||||
dio.interceptors.add(cookieManager);
|
||||
dio.interceptors.add(ApiInterceptor());
|
||||
final List<Cookie> cookies = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||
for (Cookie item in cookies) {
|
||||
await web.CookieManager().setCookie(
|
||||
url: web.WebUri(item.domain ?? ''),
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
path: item.path ?? '',
|
||||
domain: item.domain,
|
||||
isSecure: item.secure,
|
||||
isHttpOnly: item.httpOnly,
|
||||
);
|
||||
}
|
||||
final userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
if (userInfo != null && userInfo.mid != null) {
|
||||
final List<Cookie> cookie2 = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.tUrl));
|
||||
if (cookie2.isEmpty) {
|
||||
try {
|
||||
await Request().get(HttpString.tUrl);
|
||||
} catch (e) {
|
||||
log("setCookie, ${e.toString()}");
|
||||
}
|
||||
static Future<void> setCookie() async {
|
||||
accountManager = AccountManager();
|
||||
dio.interceptors.add(accountManager);
|
||||
await Accounts.refresh();
|
||||
final List<Cookie> cookies = Accounts.main.cookieJar.toList();
|
||||
final webManager = web.CookieManager();
|
||||
await Future.wait(cookies.map((item) => webManager.setCookie(
|
||||
url: web.WebUri(item.domain ?? ''),
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
path: item.path ?? '',
|
||||
domain: item.domain,
|
||||
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;
|
||||
}
|
||||
}
|
||||
setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null);
|
||||
}
|
||||
|
||||
try {
|
||||
await buvidActivate();
|
||||
} catch (e) {
|
||||
log("setCookie, ${e.toString()}");
|
||||
static Future<void> setCoin() async {
|
||||
final res = await UserHttp.getCoin();
|
||||
if (res['status']) {
|
||||
GlobalData().coins = res['data'];
|
||||
}
|
||||
|
||||
// final String cookieString = cookies
|
||||
// .map((Cookie cookie) => '${cookie.name}=${cookie.value}')
|
||||
// .join('; ');
|
||||
// dio.options.headers['cookie'] = cookieString;
|
||||
}
|
||||
|
||||
// 从cookie中获取 csrf token
|
||||
static Future<String> getCsrf() async {
|
||||
List<Cookie> cookies = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.apiBaseUrl));
|
||||
String token = '';
|
||||
if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) {
|
||||
token = cookies.firstWhere((e) => e.name == 'bili_jct').value;
|
||||
}
|
||||
return token;
|
||||
return Accounts.main.csrf;
|
||||
}
|
||||
|
||||
static setOptionsHeaders(userInfo, bool status) {
|
||||
if (status) {
|
||||
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
|
||||
dio.options.headers['x-bili-aurora-eid'] =
|
||||
IdUtils.genAuroraEid(userInfo.mid);
|
||||
}
|
||||
dio.options.headers['env'] = 'prod';
|
||||
dio.options.headers['app-key'] = 'android64';
|
||||
dio.options.headers['x-bili-aurora-zone'] = 'sh001';
|
||||
dio.options.headers['referer'] = 'https://www.bilibili.com/';
|
||||
}
|
||||
// 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 buvidActivate() async {
|
||||
var html = await Request().get(Api.dynamicSpmPrefix);
|
||||
String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!;
|
||||
Random rand = Random();
|
||||
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: 'application/json'));
|
||||
}
|
||||
// await Request().post(Api.activateBuvidApi,
|
||||
// data: {'payload': jsonData},
|
||||
// options: Options(contentType: Headers.jsonContentType));
|
||||
// } catch (e) {
|
||||
// log("setCookie, $e");
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
* config it and create
|
||||
@@ -130,49 +103,75 @@ class Request {
|
||||
Request._internal() {
|
||||
//BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
|
||||
BaseOptions options = BaseOptions(
|
||||
//请求基地址,可以包含子路径
|
||||
baseUrl: HttpString.apiBaseUrl,
|
||||
//连接服务器超时时间,单位是毫秒.
|
||||
connectTimeout: const Duration(milliseconds: 12000),
|
||||
//响应流上前后两次接受到数据的间隔,单位为毫秒。
|
||||
receiveTimeout: const Duration(milliseconds: 12000),
|
||||
//Http请求头.
|
||||
headers: {},
|
||||
);
|
||||
|
||||
enableSystemProxy = GStorage.setting
|
||||
.get(SettingBoxKey.enableSystemProxy, defaultValue: false) as bool;
|
||||
systemProxyHost =
|
||||
GStorage.setting.get(SettingBoxKey.systemProxyHost, defaultValue: '');
|
||||
systemProxyPort =
|
||||
GStorage.setting.get(SettingBoxKey.systemProxyPort, defaultValue: '');
|
||||
|
||||
dio = Dio(options);
|
||||
|
||||
/// fix 第三方登录 302重定向 跟iOS代理问题冲突
|
||||
// ..httpClientAdapter = Http2Adapter(
|
||||
// ConnectionManager(
|
||||
// idleTimeout: const Duration(milliseconds: 10000),
|
||||
// onClientCreate: (context, ClientSetting config) =>
|
||||
// config.onBadCertificate = (_) => true,
|
||||
// ),
|
||||
// );
|
||||
|
||||
/// 设置代理
|
||||
if (enableSystemProxy) {
|
||||
dio.httpClientAdapter = IOHttpClientAdapter(
|
||||
createHttpClient: () {
|
||||
final HttpClient client = HttpClient();
|
||||
// Config the client.
|
||||
client.findProxy = (Uri uri) {
|
||||
// return 'PROXY host:port';
|
||||
return 'PROXY $systemProxyHost:$systemProxyPort';
|
||||
};
|
||||
client.badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true;
|
||||
return client;
|
||||
//请求基地址,可以包含子路径
|
||||
baseUrl: HttpString.apiBaseUrl,
|
||||
//连接服务器超时时间,单位是毫秒.
|
||||
connectTimeout: const Duration(milliseconds: 10000),
|
||||
//响应流上前后两次接受到数据的间隔,单位为毫秒。
|
||||
receiveTimeout: const Duration(milliseconds: 10000),
|
||||
//Http请求头.
|
||||
headers: {
|
||||
'connection': 'keep-alive',
|
||||
'accept-encoding': 'br,gzip',
|
||||
'user-agent': 'Dart/3.6 (dart:io)', // Http2Adapter不会自动添加标头
|
||||
'referer': HttpString.baseUrl,
|
||||
'env': 'prod',
|
||||
'app-key': 'android64',
|
||||
'x-bili-aurora-zone': 'sh001',
|
||||
},
|
||||
);
|
||||
responseDecoder: responseDecoder, // Http2Adapter没有自动解压
|
||||
persistentConnection: true);
|
||||
|
||||
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()
|
||||
..idleTimeout = const Duration(seconds: 15)
|
||||
..autoUncompress = false; // Http2Adapter没有自动解压, 统一行为
|
||||
// 设置代理
|
||||
if (enableSystemProxy) {
|
||||
client.findProxy = (_) => 'PROXY $systemProxyHost:$systemProxyPort';
|
||||
client.badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true;
|
||||
}
|
||||
return client;
|
||||
});
|
||||
|
||||
late Uri proxy;
|
||||
if (enableSystemProxy) {
|
||||
proxy = Uri(
|
||||
scheme: 'http',
|
||||
host: systemProxyHost,
|
||||
port: int.parse(systemProxyPort));
|
||||
}
|
||||
|
||||
dio = Dio(options)
|
||||
..httpClientAdapter =
|
||||
GStorage.setting.get(SettingBoxKey.enableHttp2, defaultValue: false)
|
||||
? Http2Adapter(
|
||||
ConnectionManager(
|
||||
idleTimeout: const Duration(seconds: 15),
|
||||
onClientCreate: enableSystemProxy
|
||||
? (_, config) {
|
||||
config
|
||||
..proxy = proxy
|
||||
..onBadCertificate = (_) => true;
|
||||
}
|
||||
: GStorage.badCertificateCallback
|
||||
? (_, config) {
|
||||
config.onBadCertificate = (_) => true;
|
||||
}
|
||||
: null),
|
||||
fallbackAdapter: http11Adapter)
|
||||
: http11Adapter;
|
||||
|
||||
// 先于其他Interceptor
|
||||
if (GStorage.retryCount > 0) {
|
||||
dio.interceptors
|
||||
.add(RetryInterceptor(GStorage.retryCount, GStorage.retryDelay));
|
||||
}
|
||||
|
||||
// 日志拦截器 输出请求、响应内容
|
||||
@@ -186,8 +185,7 @@ class Request {
|
||||
|
||||
dio.transformer = BackgroundTransformer();
|
||||
dio.options.validateStatus = (int? status) {
|
||||
return status! >= 200 && status < 300 ||
|
||||
HttpString.validateStatusCodes.contains(status);
|
||||
return status! >= 200 && status < 300;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -216,7 +214,7 @@ class Request {
|
||||
} on DioException catch (e) {
|
||||
Response errResponse = Response(
|
||||
data: {
|
||||
'message': await ApiInterceptor.dioError(e)
|
||||
'message': await AccountManager.dioError(e)
|
||||
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
|
||||
statusCode: -1,
|
||||
requestOptions: RequestOptions(),
|
||||
@@ -245,7 +243,7 @@ class Request {
|
||||
} on DioException catch (e) {
|
||||
Response errResponse = Response(
|
||||
data: {
|
||||
'message': await ApiInterceptor.dioError(e)
|
||||
'message': await AccountManager.dioError(e)
|
||||
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
|
||||
statusCode: -1,
|
||||
requestOptions: RequestOptions(),
|
||||
@@ -257,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(ApiInterceptor.dioError(e));
|
||||
// debugPrint('downloadFile error: $e');
|
||||
return Response(
|
||||
data: {
|
||||
'message': await AccountManager.dioError(e),
|
||||
},
|
||||
statusCode: -1,
|
||||
requestOptions: RequestOptions(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,19 +291,25 @@ class Request {
|
||||
}
|
||||
|
||||
static String headerUa({type = 'mob'}) {
|
||||
String headerUa = '';
|
||||
if (type == 'mob') {
|
||||
if (Platform.isIOS) {
|
||||
headerUa =
|
||||
'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';
|
||||
} else {
|
||||
headerUa =
|
||||
'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36';
|
||||
}
|
||||
} else {
|
||||
headerUa =
|
||||
'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),
|
||||
allowMalformed: true);
|
||||
case 'br':
|
||||
return utf8.decode(_brotilDecoder.convert(responseBytes),
|
||||
allowMalformed: true);
|
||||
default:
|
||||
return utf8.decode(responseBytes, allowMalformed: true);
|
||||
}
|
||||
return headerUa;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/pages/mine/controller.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
|
||||
class ApiInterceptor extends Interceptor {
|
||||
static const List<String> anonymityList = [
|
||||
Api.videoUrl,
|
||||
Api.videoIntro,
|
||||
Api.relatedList,
|
||||
Api.replyList,
|
||||
Api.replyReplyList,
|
||||
Api.searchSuggest,
|
||||
Api.searchByType,
|
||||
Api.heartBeat,
|
||||
Api.ab2c,
|
||||
Api.bangumiInfo,
|
||||
Api.liveRoomInfo,
|
||||
Api.onlineTotal,
|
||||
Api.webDanmaku,
|
||||
Api.dynamicDetail,
|
||||
Api.aiConclusion,
|
||||
Api.getSeasonDetailApi,
|
||||
Api.liveRoomDmToken,
|
||||
Api.liveRoomDmPrefetch,
|
||||
];
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
void onRemoveCookie() {
|
||||
options.headers.remove('x-bili-mid');
|
||||
options.headers.remove('x-bili-aurora-eid');
|
||||
options.headers.remove('x-bili-aurora-zone');
|
||||
options.headers['cookie'] = '';
|
||||
options.queryParameters.remove('access_key');
|
||||
options.queryParameters.remove('csrf');
|
||||
options.queryParameters.remove('csrf_token');
|
||||
if (options.data is Map) {
|
||||
options.data.remove('access_key');
|
||||
options.data.remove('csrf');
|
||||
options.data.remove('csrf_token');
|
||||
}
|
||||
}
|
||||
|
||||
if (options.extra['clearCookie'] == true) {
|
||||
onRemoveCookie();
|
||||
} else if (MineController.anonymity.value) {
|
||||
String uri = options.uri.toString();
|
||||
for (var i in anonymityList) {
|
||||
// 如果请求的url包含无痕列表中的url,则清空cookie
|
||||
// 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤
|
||||
int index = uri.indexOf(i);
|
||||
if (index == -1) continue;
|
||||
if (uri.lastIndexOf('/') >= index + i.length) continue;
|
||||
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
|
||||
onRemoveCookie();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
// @override
|
||||
// void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
// try {
|
||||
// if (response.statusCode == 302) {
|
||||
// final List<String> locations = response.headers['location']!;
|
||||
// if (locations.isNotEmpty) {
|
||||
// if (locations.first.startsWith('https://www.mcbbs.net')) {
|
||||
// debugPrint('ApiInterceptor@@@@@: ${locations.first}');
|
||||
// final Uri uri = Uri.parse(locations.first);
|
||||
// final String? accessKey = uri.queryParameters['access_key'];
|
||||
// final String? mid = uri.queryParameters['mid'];
|
||||
// try {
|
||||
// GStorage.localCache.put(LocalCacheKey.accessKey,
|
||||
// <String, String?>{'mid': mid, 'value': accessKey});
|
||||
// } catch (_) {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (err) {
|
||||
// debugPrint('ApiInterceptor: $err');
|
||||
// }
|
||||
|
||||
// handler.next(response);
|
||||
// }
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||
// 处理网络请求错误
|
||||
// handler.next(err);
|
||||
String url = err.requestOptions.uri.toString();
|
||||
debugPrint('🌹🌹ApiInterceptor: $url');
|
||||
if (url.contains('heartbeat') ||
|
||||
url.contains('seg.so') ||
|
||||
url.contains('online/total') ||
|
||||
url.contains('github') ||
|
||||
(url.contains('skipSegments') && err.requestOptions.method == 'GET')) {
|
||||
// skip
|
||||
} else {
|
||||
SmartDialog.showToast(
|
||||
await dioError(err) + url,
|
||||
displayType: SmartToastType.onlyRefresh,
|
||||
displayTime: const Duration(milliseconds: 1200),
|
||||
);
|
||||
}
|
||||
super.onError(err, handler);
|
||||
}
|
||||
|
||||
static Future<String> dioError(DioException error) async {
|
||||
switch (error.type) {
|
||||
case DioExceptionType.badCertificate:
|
||||
return '证书有误!';
|
||||
case DioExceptionType.badResponse:
|
||||
return '服务器异常,请稍后重试!';
|
||||
case DioExceptionType.cancel:
|
||||
return '请求已被取消,请重新请求';
|
||||
case DioExceptionType.connectionError:
|
||||
return '连接错误,请检查网络设置';
|
||||
case DioExceptionType.connectionTimeout:
|
||||
return '网络连接超时,请检查网络设置';
|
||||
case DioExceptionType.receiveTimeout:
|
||||
return '响应超时,请稍后重试!';
|
||||
case DioExceptionType.sendTimeout:
|
||||
return '发送请求超时,请检查网络设置';
|
||||
case DioExceptionType.unknown:
|
||||
final String res =
|
||||
(await Connectivity().checkConnectivity()).first.title;
|
||||
return '$res网络异常 ${error.error}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _ConnectivityResultExt on ConnectivityResult {
|
||||
String get title => ['蓝牙', 'Wi-Fi', '局域', '流量', '无', '代理', '其他'][index];
|
||||
}
|
||||
@@ -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,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
@@ -9,11 +10,9 @@ import '../utils/utils.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class LoginHttp {
|
||||
static String deviceId = LoginUtils.genDeviceId();
|
||||
static String buvid = LoginUtils.buvid();
|
||||
static String host = 'passport.bilibili.com';
|
||||
static Map<String, String> headers = {
|
||||
'Host': host,
|
||||
static final String deviceId = LoginUtils.genDeviceId();
|
||||
static final String buvid = LoginUtils.buvid;
|
||||
static final Map<String, String> headers = {
|
||||
'buvid': buvid,
|
||||
'env': 'prod',
|
||||
'app-key': 'android_hd',
|
||||
@@ -27,21 +26,14 @@ class LoginHttp {
|
||||
|
||||
static Future<Map<String, dynamic>> getHDcode() async {
|
||||
var params = {
|
||||
'appkey': Constants.appKey,
|
||||
// 'local_id': 'Y952A395BB157D305D8A8340FC2AAECECE17',
|
||||
'local_id': '0',
|
||||
//精确到秒的时间戳
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'platform': 'android',
|
||||
'mobi_app': 'android_hd',
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
params,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
var res = await Request()
|
||||
.post(Api.getTVCode, queryParameters: {...params, 'sign': sign});
|
||||
Utils.appSign(params);
|
||||
var res = await Request().post(Api.getTVCode, queryParameters: params);
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
@@ -52,18 +44,12 @@ class LoginHttp {
|
||||
|
||||
static Future codePoll(String authCode) async {
|
||||
var params = {
|
||||
'appkey': Constants.appKey,
|
||||
'auth_code': authCode,
|
||||
'local_id': '0',
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
params,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
var res = await Request()
|
||||
.post(Api.qrcodePoll, queryParameters: {...params, 'sign': sign});
|
||||
Utils.appSign(params);
|
||||
var res = await Request().post(Api.qrcodePoll, queryParameters: params);
|
||||
return {
|
||||
'status': res.data['code'] == 0,
|
||||
'code': res.data['code'],
|
||||
@@ -106,7 +92,6 @@ class LoginHttp {
|
||||
}) async {
|
||||
int timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
var data = {
|
||||
'appkey': Constants.appKey,
|
||||
'build': '2001100',
|
||||
'buvid': buvid,
|
||||
'c_locale': 'zh_CN',
|
||||
@@ -129,15 +114,11 @@ class LoginHttp {
|
||||
'tel': tel,
|
||||
'ts': (timestamp ~/ 1000).toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
Utils.appSign(data);
|
||||
|
||||
var res = await Request().post(
|
||||
Api.appSmsCode,
|
||||
data: {...data, 'sign': sign},
|
||||
data: data,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
headers: headers,
|
||||
@@ -211,7 +192,6 @@ class LoginHttp {
|
||||
Encrypter(RSA(publicKey: publicKey)).encrypt(salt + password).base64;
|
||||
|
||||
Map<String, String> data = {
|
||||
'appkey': Constants.appKey,
|
||||
'bili_local_id': deviceId,
|
||||
'build': '2001100',
|
||||
'buvid': buvid,
|
||||
@@ -224,7 +204,7 @@ class LoginHttp {
|
||||
'device_platform': 'Android14vivo',
|
||||
'disable_rcmd': '0',
|
||||
'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey))
|
||||
.encrypt(LoginUtils.generateRandomString(16))
|
||||
.encrypt(Utils.generateRandomString(16))
|
||||
.base64),
|
||||
'from_pv': 'main.homepage.avatar-nologin.all.click',
|
||||
'from_url': Uri.encodeComponent('bilibili://pegasus/promo'),
|
||||
@@ -242,15 +222,7 @@ class LoginHttp {
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'username': username,
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
data.map((key, value) {
|
||||
return MapEntry<String, dynamic>(key, value);
|
||||
});
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.loginByPwdApi,
|
||||
data: data,
|
||||
@@ -287,7 +259,6 @@ class LoginHttp {
|
||||
}) async {
|
||||
dynamic publicKey = RSAKeyParser().parse(key);
|
||||
Map<String, String> data = {
|
||||
'appkey': Constants.appKey,
|
||||
'bili_local_id': deviceId,
|
||||
'build': '2001100',
|
||||
'buvid': buvid,
|
||||
@@ -304,7 +275,7 @@ class LoginHttp {
|
||||
// 'device_tourist_id': '',
|
||||
'disable_rcmd': '0',
|
||||
'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey))
|
||||
.encrypt(LoginUtils.generateRandomString(16))
|
||||
.encrypt(Utils.generateRandomString(16))
|
||||
.base64),
|
||||
'from_pv': 'main.my-information.my-login.0.click',
|
||||
'from_url': Uri.encodeComponent('bilibili://user_center/mine'),
|
||||
@@ -316,15 +287,7 @@ class LoginHttp {
|
||||
'tel': tel,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
data.map((key, value) {
|
||||
return MapEntry<String, dynamic>(key, value);
|
||||
});
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.logInByAppSms,
|
||||
data: data,
|
||||
@@ -404,12 +367,7 @@ class LoginHttp {
|
||||
if (geeValidate != null) 'gee_validate': geeValidate,
|
||||
if (recaptchaToken != null) 'recaptcha_token': recaptchaToken,
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.safeCenterSmsCode,
|
||||
data: data,
|
||||
@@ -449,12 +407,7 @@ class LoginHttp {
|
||||
'source': source,
|
||||
'captcha_key': captchaKey,
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.safeCenterSmsVerify,
|
||||
data: data,
|
||||
@@ -500,15 +453,7 @@ class LoginHttp {
|
||||
// 'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
data.map((key, value) {
|
||||
return MapEntry<String, dynamic>(key, value);
|
||||
});
|
||||
Utils.appSign(data);
|
||||
var res = await Request().post(
|
||||
Api.oauth2AccessToken,
|
||||
data: data,
|
||||
@@ -529,4 +474,15 @@ class LoginHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map> logout(Account account) async {
|
||||
dynamic res = await Request().post(
|
||||
Api.logout,
|
||||
data: {'biliCSRF': account.csrf},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
extra: {'account': account}),
|
||||
);
|
||||
return {'status': res.data['code'] == 0, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,15 +65,11 @@ class MemberHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> spaceArticle({
|
||||
static Future<LoadingState<space_article.Data>> spaceArticle({
|
||||
required int mid,
|
||||
required int page,
|
||||
}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
Map<String, String> data = {
|
||||
if (accessKey?.isNotEmpty == true) 'access_key': accessKey!,
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -79,24 +79,13 @@ class MemberHttp {
|
||||
'ps': '10',
|
||||
's_locale': 'zh_CN',
|
||||
'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'vmid': mid.toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
dynamic res = await Request().get(
|
||||
Api.spaceArticle,
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
'env': 'prod',
|
||||
'app-key': 'android_hd',
|
||||
'x-bili-mid': _mid,
|
||||
'bili-http-engine': 'cronet',
|
||||
'user-agent': Constants.userAgent,
|
||||
},
|
||||
@@ -112,11 +101,7 @@ class MemberHttp {
|
||||
static Future<LoadingState> spaceFav({
|
||||
required int mid,
|
||||
}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
Map<String, String> data = {
|
||||
if (accessKey?.isNotEmpty == true) 'access_key': accessKey!,
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -124,24 +109,13 @@ class MemberHttp {
|
||||
'platform': 'android',
|
||||
's_locale': 'zh_CN',
|
||||
'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'up_mid': mid.toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
dynamic res = await Request().get(
|
||||
Api.spaceFav,
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
'env': 'prod',
|
||||
'app-key': 'android_hd',
|
||||
'x-bili-mid': _mid,
|
||||
'bili-http-engine': 'cronet',
|
||||
'user-agent': Constants.userAgent,
|
||||
},
|
||||
@@ -167,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,
|
||||
@@ -183,13 +157,10 @@ class MemberHttp {
|
||||
int? next,
|
||||
int? seasonId,
|
||||
int? seriesId,
|
||||
includeCursor,
|
||||
}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
Map<String, String> data = {
|
||||
if (accessKey?.isNotEmpty == true) 'access_key': accessKey!,
|
||||
if (aid != null) 'aid': aid.toString(),
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -204,33 +175,21 @@ 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,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'vmid': mid.toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
dynamic res = await Request().get(
|
||||
type == ContributeType.video
|
||||
? Api.spaceArchive
|
||||
: type == ContributeType.charging
|
||||
? Api.spaceChargingArchive
|
||||
: type == ContributeType.season
|
||||
? Api.spaceSeason
|
||||
: type == ContributeType.series
|
||||
? Api.spaceSeries
|
||||
: Api.spaceBangumi,
|
||||
switch (type) {
|
||||
ContributeType.video => Api.spaceArchive,
|
||||
ContributeType.charging => Api.spaceChargingArchive,
|
||||
ContributeType.season => Api.spaceSeason,
|
||||
ContributeType.series => Api.spaceSeries,
|
||||
ContributeType.bangumi => Api.spaceBangumi,
|
||||
},
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
'env': 'prod',
|
||||
'app-key': 'android_hd',
|
||||
'x-bili-mid': _mid,
|
||||
'bili-http-engine': 'cronet',
|
||||
'user-agent': Constants.userAgent,
|
||||
},
|
||||
@@ -243,14 +202,22 @@ class MemberHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> space({
|
||||
int? mid,
|
||||
static Future<LoadingState> spaceStory({
|
||||
required mid,
|
||||
required aid,
|
||||
required beforeSize,
|
||||
required afterSize,
|
||||
required cid,
|
||||
required contain,
|
||||
required index,
|
||||
}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
Map<String, String> data = {
|
||||
if (accessKey?.isNotEmpty == true) 'access_key': accessKey!,
|
||||
'appkey': Constants.appKey,
|
||||
'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',
|
||||
@@ -258,24 +225,45 @@ class MemberHttp {
|
||||
'platform': 'android',
|
||||
's_locale': 'zh_CN',
|
||||
'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'vmid': mid.toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
dynamic res = await Request().get(
|
||||
Api.spaceStory,
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
'bili-http-engine': 'cronet',
|
||||
'user-agent': Constants.userAgent,
|
||||
},
|
||||
),
|
||||
);
|
||||
data['sign'] = sign;
|
||||
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
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',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
'mobi_app': 'android_hd',
|
||||
'platform': 'android',
|
||||
's_locale': 'zh_CN',
|
||||
if (fromViewAid != null) 'from_view_aid': fromViewAid,
|
||||
'statistics': Constants.statistics,
|
||||
'vmid': mid.toString(),
|
||||
};
|
||||
dynamic res = await Request().get(
|
||||
Api.space,
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
'env': 'prod',
|
||||
'app-key': 'android_hd',
|
||||
'x-bili-mid': _mid,
|
||||
'bili-http-engine': 'cronet',
|
||||
'user-agent': Constants.userAgent,
|
||||
},
|
||||
@@ -294,7 +282,7 @@ class MemberHttp {
|
||||
dynamic wwebid,
|
||||
}) async {
|
||||
space(mid: mid);
|
||||
Map params = await WbiSign().makSign({
|
||||
Map params = await WbiSign.makSign({
|
||||
'mid': mid,
|
||||
'token': token,
|
||||
'platform': 'web',
|
||||
@@ -312,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,27 +324,23 @@ 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);
|
||||
Map params = await WbiSign().makSign({
|
||||
Map params = await WbiSign.makSign({
|
||||
'mid': mid,
|
||||
'ps': ps,
|
||||
'tid': tid,
|
||||
@@ -372,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,
|
||||
@@ -383,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 {
|
||||
@@ -402,14 +382,13 @@ class MemberHttp {
|
||||
}
|
||||
|
||||
// 用户动态
|
||||
static Future<LoadingState> memberDynamic({
|
||||
static Future<LoadingState<DynamicsDataModel>> memberDynamic({
|
||||
String? offset,
|
||||
int? mid,
|
||||
required bool antiGoodsDyn,
|
||||
}) async {
|
||||
String dmImgStr = Utils.base64EncodeRandomString(16, 64);
|
||||
String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
|
||||
Map params = await WbiSign().makSign({
|
||||
Map params = await WbiSign.makSign({
|
||||
'offset': offset ?? '',
|
||||
'host_mid': mid,
|
||||
'timezone_offset': '-480',
|
||||
@@ -426,10 +405,12 @@ class MemberHttp {
|
||||
var res = await Request().get(Api.memberDynamic, queryParameters: params);
|
||||
if (res.data['code'] == 0) {
|
||||
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
|
||||
if (antiGoodsDyn) {
|
||||
if (GStorage.antiGoodsDyn) {
|
||||
data.items?.removeWhere((item) =>
|
||||
item.orig?.modules?.moduleDynamic?.additional?.type ==
|
||||
'ADDITIONAL_TYPE_GOODS' ||
|
||||
item.modules?.moduleDynamic?.additional?.type ==
|
||||
'ADDITIONAL_TYPE_GOODS');
|
||||
'ADDITIONAL_TYPE_GOODS');
|
||||
}
|
||||
return LoadingState.success(data);
|
||||
} else {
|
||||
@@ -443,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 {
|
||||
@@ -480,11 +464,7 @@ class MemberHttp {
|
||||
.toList()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,11 +535,7 @@ class MemberHttp {
|
||||
.toList()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,11 +550,7 @@ class MemberHttp {
|
||||
.toList()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,17 +567,14 @@ 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 {
|
||||
Map params = await WbiSign().makSign({
|
||||
static Future<LoadingState<List<MemberCoinsDataModel>?>> getRecentCoinVideo(
|
||||
{required int mid}) async {
|
||||
Map params = await WbiSign.makSign({
|
||||
'mid': mid,
|
||||
'gaia_source': 'main_web',
|
||||
'web_location': 333.999,
|
||||
@@ -625,17 +590,19 @@ 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 {
|
||||
Map params = await WbiSign().makSign({
|
||||
static Future<LoadingState<List<MemberCoinsDataModel>?>> getRecentLikeVideo(
|
||||
{required int mid}) async {
|
||||
Map params = await WbiSign.makSign({
|
||||
'mid': mid,
|
||||
'gaia_source': 'main_web',
|
||||
'web_location': 333.999,
|
||||
@@ -651,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']);
|
||||
}
|
||||
@@ -687,11 +655,7 @@ class MemberHttp {
|
||||
debugPrint(err.toString());
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -727,7 +687,7 @@ class MemberHttp {
|
||||
'name': name,
|
||||
'web_location': 333.999,
|
||||
};
|
||||
Map params = await WbiSign().makSign(data);
|
||||
Map params = await WbiSign.makSign(data);
|
||||
var res = await Request().get(Api.followSearch, queryParameters: {
|
||||
...data,
|
||||
'w_rid': params['w_rid'],
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import 'dart:math';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/msg/msgfeed_at_me.dart';
|
||||
import 'package:PiliPlus/models/msg/msgfeed_like_me.dart';
|
||||
import 'package:PiliPlus/models/msg/msgfeed_reply_me.dart';
|
||||
import 'package:PiliPlus/models/msg/msgfeed_sys_msg.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/view.dart' show ReplyOption;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../models/msg/account.dart';
|
||||
import '../models/msg/session.dart';
|
||||
@@ -11,100 +17,70 @@ import 'api.dart';
|
||||
import 'init.dart';
|
||||
|
||||
class MsgHttp {
|
||||
static Future msgFeedReplyMe({int cursor = -1, int cursorTime = -1}) async {
|
||||
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,
|
||||
'reply_time': cursorTime == -1 ? null : cursorTime,
|
||||
'platform': 'android',
|
||||
'mobi_app': 'android',
|
||||
'build': '8350200',
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
MsgFeedReplyMe data = MsgFeedReplyMe.fromJson(res.data['data']);
|
||||
return LoadingState.success(data);
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future msgFeedAtMe({int cursor = -1, int cursorTime = -1}) async {
|
||||
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,
|
||||
'at_time': cursorTime == -1 ? null : cursorTime,
|
||||
'platform': 'android',
|
||||
'mobi_app': 'android',
|
||||
'build': '8350200',
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
MsgFeedAtMe data = MsgFeedAtMe.fromJson(res.data['data']);
|
||||
return LoadingState.success(data);
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future msgFeedLikeMe({int cursor = -1, int cursorTime = -1}) async {
|
||||
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,
|
||||
'like_time': cursorTime == -1 ? null : cursorTime,
|
||||
'platform': 'android',
|
||||
'mobi_app': 'android',
|
||||
'build': '8350200',
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
MsgFeedLikeMe data = MsgFeedLikeMe.fromJson(res.data['data']);
|
||||
return LoadingState.success(data);
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future msgFeedSysUserNotify() async {
|
||||
String csrf = await Request.getCsrf();
|
||||
var res = await Request().get(Api.msgSysUserNotify, queryParameters: {
|
||||
'csrf': csrf,
|
||||
'page_size': 20,
|
||||
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,
|
||||
'page_size': pageSize,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
List<SystemNotifyList>? list = (res.data['data'] as List?)
|
||||
?.map((e) => SystemNotifyList.fromJson(e))
|
||||
.toList();
|
||||
return LoadingState.success(list);
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future msgFeedSysUnifiedNotify() async {
|
||||
String csrf = await Request.getCsrf();
|
||||
var res = await Request().get(Api.msgSysUnifiedNotify, queryParameters: {
|
||||
'csrf': csrf,
|
||||
'page_size': 10,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,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 {
|
||||
@@ -284,7 +258,7 @@ class MsgHttp {
|
||||
dynamic content,
|
||||
) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
Map<String, dynamic> data = await WbiSign().makSign({
|
||||
Map<String, dynamic> data = await WbiSign.makSign({
|
||||
'dynamic_id': 0,
|
||||
'type': 4,
|
||||
'rid': 0,
|
||||
@@ -310,7 +284,7 @@ class MsgHttp {
|
||||
dynamic dynamicId,
|
||||
) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
Map<String, dynamic> data = await WbiSign().makSign({
|
||||
Map<String, dynamic> data = await WbiSign.makSign({
|
||||
'dynamic_id': dynamicId,
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
@@ -333,7 +307,7 @@ class MsgHttp {
|
||||
dynamic talkerId,
|
||||
) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
Map<String, dynamic> data = await WbiSign().makSign({
|
||||
Map<String, dynamic> data = await WbiSign.makSign({
|
||||
'talker_id': talkerId,
|
||||
'session_type': 1,
|
||||
'build': 0,
|
||||
@@ -353,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,
|
||||
@@ -386,7 +389,7 @@ class MsgHttp {
|
||||
int opType,
|
||||
) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
Map<String, dynamic> data = await WbiSign().makSign({
|
||||
Map<String, dynamic> data = await WbiSign.makSign({
|
||||
'talker_id': talkerId,
|
||||
'session_type': 1,
|
||||
'op_type': opType,
|
||||
@@ -421,27 +424,19 @@ class MsgHttp {
|
||||
params['end_ts'] = endTs;
|
||||
}
|
||||
|
||||
Map signParams = await WbiSign().makSign(params);
|
||||
Map signParams = await WbiSign.makSign(params);
|
||||
var res = await Request().get(Api.sessionList, queryParameters: signParams);
|
||||
if (res.data['code'] == 0) {
|
||||
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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,18 +458,14 @@ class MsgHttp {
|
||||
debugPrint('err🔟: $err');
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future sessionMsg({
|
||||
int? talkerId,
|
||||
}) async {
|
||||
Map params = await WbiSign().makSign({
|
||||
Map params = await WbiSign.makSign({
|
||||
'talker_id': talkerId,
|
||||
'session_type': 1,
|
||||
'size': 20,
|
||||
@@ -493,11 +484,7 @@ class MsgHttp {
|
||||
debugPrint(err.toString());
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,7 +494,7 @@ class MsgHttp {
|
||||
int? ackSeqno,
|
||||
}) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
Map params = await WbiSign().makSign({
|
||||
Map params = await WbiSign.makSign({
|
||||
'talker_id': talkerId,
|
||||
'session_type': 1,
|
||||
'ack_seqno': ackSeqno,
|
||||
@@ -525,7 +512,6 @@ class MsgHttp {
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': "message: ${res.data['message']},"
|
||||
" msg: ${res.data['msg']},"
|
||||
" code: ${res.data['code']}",
|
||||
@@ -558,7 +544,7 @@ class MsgHttp {
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
};
|
||||
Map<String, dynamic> params = await WbiSign().makSign(base);
|
||||
Map<String, dynamic> params = await WbiSign.makSign(base);
|
||||
var res = await Request().post(Api.sendMsg,
|
||||
queryParameters: <String, dynamic>{
|
||||
'w_sender_uid': params['msg[sender_uid]'],
|
||||
@@ -574,45 +560,42 @@ 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']};
|
||||
}
|
||||
}
|
||||
|
||||
static String getDevId() {
|
||||
final List<String> b = [
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F'
|
||||
];
|
||||
final List<String> s = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".split('');
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
if ('-' == s[i] || '4' == s[i]) {
|
||||
continue;
|
||||
}
|
||||
final int randomInt = Random().nextInt(16);
|
||||
if ('x' == s[i]) {
|
||||
s[i] = b[randomInt];
|
||||
} else {
|
||||
s[i] = b[3 & randomInt | 8];
|
||||
}
|
||||
}
|
||||
return s.join();
|
||||
return Uuid().v4();
|
||||
// final List<String> b = [
|
||||
// '0',
|
||||
// '1',
|
||||
// '2',
|
||||
// '3',
|
||||
// '4',
|
||||
// '5',
|
||||
// '6',
|
||||
// '7',
|
||||
// '8',
|
||||
// '9',
|
||||
// 'A',
|
||||
// 'B',
|
||||
// 'C',
|
||||
// 'D',
|
||||
// 'E',
|
||||
// 'F'
|
||||
// ];
|
||||
// final List<String> s = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".split('');
|
||||
// for (int i = 0; i < s.length; i++) {
|
||||
// if ('-' == s[i] || '4' == s[i]) {
|
||||
// continue;
|
||||
// }
|
||||
// final int randomInt = Random().nextInt(16);
|
||||
// if ('x' == s[i]) {
|
||||
// s[i] = b[randomInt];
|
||||
// } else {
|
||||
// s[i] = b[3 & randomInt | 8];
|
||||
// }
|
||||
// }
|
||||
// return s.join();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/video/reply/item.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -13,11 +14,13 @@ import 'api.dart';
|
||||
import 'init.dart';
|
||||
|
||||
class ReplyHttp {
|
||||
static Options get _options => Options(extra: {'clearCookie': true});
|
||||
static Options get _options =>
|
||||
Options(extra: {'account': AnonymousAccount()});
|
||||
|
||||
static RegExp replyRegExp =
|
||||
RegExp(GStorage.banWordForReply, caseSensitive: false);
|
||||
|
||||
@Deprecated('Use replyListGrpc instead')
|
||||
static Future<LoadingState> replyList({
|
||||
required bool isLogin,
|
||||
required int oid,
|
||||
@@ -121,7 +124,7 @@ class ReplyHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> replyListGrpc({
|
||||
static Future<LoadingState<MainListReply>> replyListGrpc({
|
||||
int type = 1,
|
||||
required int oid,
|
||||
required CursorReq cursor,
|
||||
@@ -212,6 +215,7 @@ class ReplyHttp {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Deprecated('Use replyReplyListGrpc instead')
|
||||
static Future<LoadingState> replyReplyList({
|
||||
required bool isLogin,
|
||||
required int oid,
|
||||
@@ -347,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,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',
|
||||
@@ -395,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']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
35
lib/http/retry_interceptor.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import 'index.dart';
|
||||
|
||||
class RetryInterceptor extends Interceptor {
|
||||
final int _count;
|
||||
final int _delay;
|
||||
|
||||
RetryInterceptor(this._count, this._delay);
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
if (err.response != null) return handler.next(err);
|
||||
switch (err.type) {
|
||||
case DioExceptionType.connectionError:
|
||||
case DioExceptionType.connectionTimeout:
|
||||
case DioExceptionType.sendTimeout:
|
||||
case DioExceptionType.unknown:
|
||||
if ((err.requestOptions.extra['_rt'] ??= 0) < _count) {
|
||||
Future.delayed(
|
||||
Duration(
|
||||
milliseconds: ++err.requestOptions.extra['_rt'] * _delay),
|
||||
() => Request.dio
|
||||
.fetch(err.requestOptions)
|
||||
.then(handler.resolve)
|
||||
.onError<DioException>((error, _) => handler.reject(error)));
|
||||
} else {
|
||||
handler.next(err);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return handler.next(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
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';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
@@ -28,11 +30,7 @@ class SearchHttp {
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '请求错误',
|
||||
};
|
||||
return {'status': false, 'msg': '请求错误'};
|
||||
}
|
||||
|
||||
// 获取搜索建议
|
||||
@@ -49,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': '请求错误 🙅',
|
||||
};
|
||||
}
|
||||
@@ -105,11 +101,11 @@ class SearchHttp {
|
||||
try {
|
||||
switch (searchType) {
|
||||
case SearchType.video:
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
if (res.data['data']['result'] != null) {
|
||||
for (var i in res.data['data']['result']) {
|
||||
// 屏蔽推广和拉黑用户
|
||||
i['available'] = !blackMidsList.contains(i['mid']);
|
||||
i['available'] = !blackMids.contains(i['mid']);
|
||||
}
|
||||
}
|
||||
data = SearchVideoModel.fromJson(res.data['data']);
|
||||
@@ -140,24 +136,27 @@ class SearchHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<int> ab2c({dynamic aid, dynamic bvid}) async {
|
||||
static Future<int> ab2c({dynamic aid, dynamic bvid, int? part}) async {
|
||||
Map<String, dynamic> data = {};
|
||||
if (aid != null) {
|
||||
data['aid'] = aid;
|
||||
} 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 res.data['data'].first['cid'];
|
||||
return part != null
|
||||
? ((res.data['data'] as List).getOrNull(part - 1)?['cid'] ??
|
||||
res.data['data'].first['cid'])
|
||||
: res.data['data'].first['cid'];
|
||||
} else {
|
||||
SmartDialog.showToast("ab2c error: ${res.data['message']}");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
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: {
|
||||
@@ -173,6 +172,20 @@ class SearchHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> episodeInfo({int? epId}) async {
|
||||
final dynamic res = await Request().get(
|
||||
Api.episodeInfo,
|
||||
queryParameters: {
|
||||
if (epId != null) 'ep_id': epId,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(res.data['data']);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> bangumiInfo({
|
||||
dynamic seasonId,
|
||||
dynamic epId,
|
||||
@@ -183,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 {
|
||||
@@ -192,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,18 +220,33 @@ 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 = [];
|
||||
for (var i in res.data['data']['list']) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||
if (res.data['data']?['list'] != null) {
|
||||
for (var i in res.data['data']['list']) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
}
|
||||
return LoadingState.success({
|
||||
'list': list,
|
||||
@@ -193,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,
|
||||
@@ -230,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,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,
|
||||
@@ -305,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(),
|
||||
},
|
||||
);
|
||||
@@ -322,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: {
|
||||
@@ -343,7 +406,7 @@ class UserHttp {
|
||||
|
||||
static Future hasFollow(int mid) async {
|
||||
var res = await Request().get(
|
||||
Api.hasFollow,
|
||||
Api.relation,
|
||||
queryParameters: {
|
||||
'fid': mid,
|
||||
},
|
||||
@@ -356,7 +419,7 @@ class UserHttp {
|
||||
}
|
||||
// // 相互关系查询
|
||||
// static Future relationSearch(int mid) async {
|
||||
// Map params = await WbiSign().makSign({
|
||||
// Map params = await WbiSign.makSign({
|
||||
// 'mid': mid,
|
||||
// 'token': '',
|
||||
// 'platform': 'web',
|
||||
@@ -398,7 +461,7 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 我的订阅
|
||||
static Future<LoadingState> userSubFolder({
|
||||
static Future<LoadingState<List<SubFolderItemData>?>> userSubFolder({
|
||||
required int mid,
|
||||
required int pn,
|
||||
required int ps,
|
||||
@@ -440,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,
|
||||
@@ -498,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,
|
||||
@@ -569,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';
|
||||
@@ -18,9 +19,7 @@ import '../models/video_detail_res.dart';
|
||||
import '../utils/id_utils.dart';
|
||||
import '../utils/recommend_filter.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../utils/wbi_sign.dart';
|
||||
import '../pages/mine/controller.dart';
|
||||
import 'api.dart';
|
||||
import 'init.dart';
|
||||
import 'login.dart';
|
||||
@@ -51,13 +50,12 @@ class VideoHttp {
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemModel> list = [];
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
List<RecVideoItemModel> list = <RecVideoItemModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['item']) {
|
||||
//过滤掉live与ad,以及拉黑用户
|
||||
if (i['goto'] == 'av' &&
|
||||
(i['owner'] != null &&
|
||||
!blackMidsList.contains(i['owner']['mid']))) {
|
||||
(i['owner'] != null && !blackMids.contains(i['owner']['mid']))) {
|
||||
RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i);
|
||||
if (!RecommendFilter.filter(videoItem)) {
|
||||
list.add(videoItem);
|
||||
@@ -71,15 +69,8 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 添加额外的loginState变量模拟未登录状态
|
||||
static Future<LoadingState> rcmdVideoListApp(
|
||||
{bool loginStatus = true, required int freshIdx}) async {
|
||||
static Future<LoadingState> rcmdVideoListApp({required int freshIdx}) async {
|
||||
Map<String, String> data = {
|
||||
'access_key': loginStatus
|
||||
? (GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
'')
|
||||
: '',
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -106,21 +97,12 @@ class VideoHttp {
|
||||
's_locale': 'zh_CN',
|
||||
'splash_id': '',
|
||||
'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
'voice_balance': '0'
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
|
||||
var res = await Request().get(
|
||||
Api.recommendListApp,
|
||||
queryParameters: data,
|
||||
options: Options(headers: {
|
||||
'Host': 'app.bilibili.com',
|
||||
'buvid': LoginHttp.buvid,
|
||||
'fp_local':
|
||||
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||
@@ -137,20 +119,20 @@ class VideoHttp {
|
||||
}),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemAppModel> list = [];
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
List<RecVideoItemAppModel> list = <RecVideoItemAppModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['items']) {
|
||||
// 屏蔽推广和拉黑用户
|
||||
if (i['card_goto'] != 'ad_av' &&
|
||||
i['card_goto'] != 'ad_web_s' &&
|
||||
i['ad_info'] == null &&
|
||||
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
|
||||
(i['args'] != null &&
|
||||
!blackMidsList.contains(i['args']['up_id']))) {
|
||||
if (zoneRegExp.pattern.isNotEmpty &&
|
||||
zoneRegExp.hasMatch(i['args']['rname'])) {
|
||||
continue;
|
||||
}
|
||||
(i['args'] != null && !blackMids.contains(i['args']['up_id']))) {
|
||||
// if (zoneRegExp.pattern.isNotEmpty &&
|
||||
// i['args']?['rname'] != null &&
|
||||
// zoneRegExp.hasMatch(i['args']['rname'])) {
|
||||
// continue;
|
||||
// }
|
||||
RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i);
|
||||
if (!RecommendFilter.filter(videoItem)) {
|
||||
list.add(videoItem);
|
||||
@@ -164,21 +146,22 @@ 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<int> blackMidsList = GStorage.blackMidsList;
|
||||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMidsList.contains(i['owner']['mid']) &&
|
||||
if (!blackMids.contains(i['owner']['mid']) &&
|
||||
!RecommendFilter.filterTitle(i['title']) &&
|
||||
!RecommendFilter.filterLikeRatio(
|
||||
i['stat']['like'], i['stat']['view'])) {
|
||||
if (zoneRegExp.pattern.isNotEmpty &&
|
||||
i['tname'] != null &&
|
||||
zoneRegExp.hasMatch(i['tname'])) {
|
||||
continue;
|
||||
}
|
||||
@@ -194,10 +177,10 @@ class VideoHttp {
|
||||
static Future<LoadingState> hotVideoListGrpc({required int idx}) async {
|
||||
dynamic res = await GrpcRepo.popular(idx);
|
||||
if (res['status']) {
|
||||
List<card.Card> list = [];
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
List<card.Card> list = <card.Card>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (card.Card item in res['data']) {
|
||||
if (!blackMidsList.contains(item.smallCoverV5.up.id.toInt())) {
|
||||
if (!blackMids.contains(item.smallCoverV5.up.id.toInt())) {
|
||||
list.add(item);
|
||||
}
|
||||
}
|
||||
@@ -226,24 +209,22 @@ class VideoHttp {
|
||||
'qn': qn ?? 80,
|
||||
// 获取所有格式的视频
|
||||
'fnval': 4048,
|
||||
};
|
||||
|
||||
// 免登录查看1080p
|
||||
if ((GStorage.userInfo.get('userInfoCache') == null ||
|
||||
MineController.anonymity.value) &&
|
||||
GStorage.setting.get(SettingBoxKey.p1080, defaultValue: true)) {
|
||||
data['try_look'] = 1;
|
||||
}
|
||||
|
||||
Map params = await WbiSign().makSign({
|
||||
...data,
|
||||
'fourk': 1,
|
||||
'voice_balance': 1,
|
||||
'gaia_source': 'pre-load',
|
||||
'web_location': 1550101,
|
||||
});
|
||||
};
|
||||
|
||||
late final usePgcApi = forcePgcApi == true || GStorage.isLogin;
|
||||
// 免登录查看1080p
|
||||
if (!Accounts.get(AccountType.video).isLogin &&
|
||||
GStorage.setting.get(SettingBoxKey.p1080, defaultValue: true)) {
|
||||
data['try_look'] = 1;
|
||||
}
|
||||
|
||||
Map params = await WbiSign.makSign(data);
|
||||
|
||||
late final usePgcApi =
|
||||
forcePgcApi == true || Accounts.get(AccountType.video).isLogin;
|
||||
|
||||
try {
|
||||
var res = await Request().get(
|
||||
@@ -278,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};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +305,7 @@ class VideoHttp {
|
||||
data: {
|
||||
'platform': 'web',
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
@@ -365,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']);
|
||||
@@ -396,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({
|
||||
@@ -426,52 +404,54 @@ class VideoHttp {
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.coinVideo,
|
||||
queryParameters: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
data: {
|
||||
'aid': IdUtils.bv2av(bvid).toString(),
|
||||
// 'bvid': bvid,
|
||||
'multiply': multiply,
|
||||
'select_like': selectLike,
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
|
||||
// 'csrf': await Request.getCsrf(),
|
||||
'multiply': multiply.toString(),
|
||||
'select_like': selectLike.toString(),
|
||||
// 'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
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}) async {
|
||||
static Future triple({dynamic epId, required dynamic seasonId}) async {
|
||||
var res = await Request().post(
|
||||
Api.triple,
|
||||
data: {
|
||||
'ep_id': epId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
headers: {
|
||||
'Content-Type': Headers.formUrlEncodedContentType,
|
||||
'origin': 'https://www.bilibili.com',
|
||||
'referer': 'https://www.bilibili.com/bangumi/play/ss$seasonId',
|
||||
'user-agent': Request.headerUa(type: 'pc'),
|
||||
},
|
||||
),
|
||||
);
|
||||
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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,54 +465,58 @@ class VideoHttp {
|
||||
'ramval': 0,
|
||||
'source': 'web_normal',
|
||||
'ga': 1,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
headers: {
|
||||
'origin': 'https://www.bilibili.com',
|
||||
'referer': 'https://www.bilibili.com/video/$bvid',
|
||||
'user-agent': Request.headerUa(type: 'pc'),
|
||||
},
|
||||
),
|
||||
);
|
||||
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 likeVideo({required String bvid, required bool type}) async {
|
||||
var res = await Request().post(Api.likeVideo, queryParameters: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
'like': type ? 0 : 1,
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
|
||||
}
|
||||
// queryParameters: {
|
||||
// 'bvid': bvid,
|
||||
// 'like': type ? 1 : 2,
|
||||
// 'csrf': await Request.getCsrf(),
|
||||
// },
|
||||
);
|
||||
var res = await Request().post(
|
||||
Api.likeVideo,
|
||||
data: {
|
||||
'aid': IdUtils.bv2av(bvid).toString(),
|
||||
'like': type ? '0' : '1',
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
// queryParameters: {
|
||||
// 'bvid': bvid,
|
||||
// 'like': type ? 1 : 2,
|
||||
// 'csrf': Accounts.main.csrf,
|
||||
// },
|
||||
);
|
||||
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 dislikeVideo({required String bvid, required bool type}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
if (accessKey == null || accessKey == "") {
|
||||
if (Accounts.main.accessKey.isNullOrEmpty) {
|
||||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||||
}
|
||||
var res = await Request().post(
|
||||
Api.dislikeVideo,
|
||||
queryParameters: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
'dislike': type ? 0 : 1,
|
||||
'access_key': accessKey,
|
||||
data: {
|
||||
'aid': IdUtils.bv2av(bvid).toString(),
|
||||
'dislike': type ? '0' : '1',
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data is! String && res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -550,9 +534,7 @@ class VideoHttp {
|
||||
required int id,
|
||||
int? reasonId,
|
||||
int? feedbackId}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
if (accessKey == null || accessKey == "") {
|
||||
if (Accounts.get(AccountType.recommend).accessKey.isNullOrEmpty) {
|
||||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||||
}
|
||||
assert((reasonId != null) ^ (feedbackId != null));
|
||||
@@ -562,10 +544,8 @@ class VideoHttp {
|
||||
// 'mid': mid,
|
||||
if (reasonId != null) 'reason_id': reasonId,
|
||||
if (feedbackId != null) 'feedback_id': feedbackId,
|
||||
'build': 1,
|
||||
'build': '1',
|
||||
'mobi_app': 'android',
|
||||
'access_key': accessKey,
|
||||
'appkey': Constants.appKey,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -580,9 +560,7 @@ class VideoHttp {
|
||||
required int id,
|
||||
int? reasonId,
|
||||
int? feedbackId}) async {
|
||||
String? accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
if (accessKey == null || accessKey == "") {
|
||||
if (Accounts.get(AccountType.recommend).accessKey.isNullOrEmpty) {
|
||||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||||
}
|
||||
// assert ((reasonId != null) ^ (feedbackId != null));
|
||||
@@ -592,10 +570,8 @@ class VideoHttp {
|
||||
// 'mid': mid,
|
||||
if (reasonId != null) 'reason_id': reasonId,
|
||||
if (feedbackId != null) 'feedback_id': feedbackId,
|
||||
'build': 1,
|
||||
'build': '1',
|
||||
'mobi_app': 'android',
|
||||
'access_key': accessKey,
|
||||
'appkey': Constants.appKey,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true};
|
||||
@@ -615,7 +591,7 @@ class VideoHttp {
|
||||
'resources': ids?.join(','),
|
||||
'media_id': delIds,
|
||||
'platform': 'web',
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
@@ -640,7 +616,7 @@ class VideoHttp {
|
||||
'type': type ?? 2,
|
||||
'add_media_ids': addIds ?? '',
|
||||
'del_media_ids': delIds ?? '',
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
@@ -663,7 +639,7 @@ class VideoHttp {
|
||||
// 'resources': '$epId:24',
|
||||
// 'add_media_ids': addIds ?? '',
|
||||
// 'del_media_ids': delIds ?? '',
|
||||
// 'csrf': await Request.getCsrf(),
|
||||
// 'csrf': Accounts.main.csrf,
|
||||
// },
|
||||
// options: Options(
|
||||
// headers: {
|
||||
@@ -700,7 +676,7 @@ class VideoHttp {
|
||||
if (mid != null) 'mid': mid,
|
||||
'resources': resources.join(','),
|
||||
'platform': 'web',
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
@@ -742,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -764,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,
|
||||
@@ -774,7 +750,7 @@ class VideoHttp {
|
||||
'message': message,
|
||||
if (pictures != null) 'pictures': jsonEncode(pictures),
|
||||
if (syncToDynamic == true) 'sync_to_dynamic': 1,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
};
|
||||
var res = await Request().post(
|
||||
Api.replyAdd,
|
||||
@@ -784,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']};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -797,7 +773,7 @@ class VideoHttp {
|
||||
'type': type, //type.index
|
||||
'oid': oid,
|
||||
'rpid': rpid,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
log(res.toString());
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -807,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 {
|
||||
@@ -833,7 +799,7 @@ class VideoHttp {
|
||||
"entity_id": mid,
|
||||
'fp': Request.headerUa(type: 'pc'),
|
||||
},
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
@@ -851,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,
|
||||
@@ -869,7 +861,7 @@ class VideoHttp {
|
||||
if (epid != null) 'type': 4,
|
||||
if (subType != null) 'sub_type': subType,
|
||||
'played_time': progress,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -882,7 +874,7 @@ class VideoHttp {
|
||||
'desc': desc,
|
||||
'oid': oid,
|
||||
'upper_mid': upperMid,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -890,7 +882,7 @@ class VideoHttp {
|
||||
static Future bangumiAdd({int? seasonId}) async {
|
||||
var res = await Request().post(Api.bangumiAdd, queryParameters: {
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
@@ -911,7 +903,7 @@ class VideoHttp {
|
||||
static Future bangumiDel({int? seasonId}) async {
|
||||
var res = await Request().post(Api.bangumiDel, queryParameters: {
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
@@ -929,15 +921,15 @@ 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': await Request.getCsrf(),
|
||||
'csrf': Accounts.main.csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
@@ -968,7 +960,7 @@ class VideoHttp {
|
||||
int? cid,
|
||||
int? upMid,
|
||||
}) async {
|
||||
Map params = await WbiSign().makSign({
|
||||
Map params = await WbiSign.makSign({
|
||||
'bvid': bvid,
|
||||
'cid': cid,
|
||||
'up_mid': upMid,
|
||||
@@ -980,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(
|
||||
@@ -1014,105 +1006,61 @@ 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<int> blackMidsList = GStorage.blackMidsList;
|
||||
List<HotVideoItemModel> list = <HotVideoItemModel>[];
|
||||
Set<int> blackMids = GStorage.blackMids;
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMidsList.contains(i['owner']['mid']) &&
|
||||
if (!blackMids.contains(i['owner']['mid']) &&
|
||||
!RecommendFilter.filterTitle(i['title']) &&
|
||||
!RecommendFilter.filterLikeRatio(
|
||||
i['stat']['like'], i['stat']['view'])) {
|
||||
if (zoneRegExp.pattern.isNotEmpty &&
|
||||
i['tname'] != null &&
|
||||
zoneRegExp.hasMatch(i['tname'])) {
|
||||
continue;
|
||||
}
|
||||
@@ -1124,4 +1072,138 @@ class VideoHttp {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> getVideoNoteList({
|
||||
dynamic oid,
|
||||
dynamic uperMid,
|
||||
required int page,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.archiveNoteList,
|
||||
queryParameters: {
|
||||
'csrf': Accounts.main.csrf,
|
||||
'oid': oid,
|
||||
'oid_type': 0,
|
||||
'pn': page,
|
||||
'ps': 10,
|
||||
if (uperMid != null) 'uper_mid': uperMid,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(res.data['data']);
|
||||
} else {
|
||||
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']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
111
lib/main.dart
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/view_v.dart';
|
||||
import 'package:PiliPlus/utils/cache_manage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
|
||||
@@ -15,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';
|
||||
@@ -31,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
|
||||
@@ -53,15 +53,11 @@ void main() async {
|
||||
],
|
||||
);
|
||||
}
|
||||
if (BuildConfig.isDebug || GStorage.badCertificateCallback) {
|
||||
HttpOverrides.global = _CustomHttpOverrides();
|
||||
}
|
||||
HttpOverrides.global = _CustomHttpOverrides();
|
||||
await setupServiceLocator();
|
||||
Request();
|
||||
await Request.setCookie();
|
||||
RecommendFilter();
|
||||
SmartDialog.config.loading =
|
||||
SmartConfigLoading(backType: SmartBackType.normal);
|
||||
// 异常捕获 logo记录
|
||||
final String buildConfig = '''\n
|
||||
Build Time: ${BuildConfig.buildTime}
|
||||
@@ -119,6 +115,8 @@ class MyApp extends StatelessWidget {
|
||||
|
||||
Box get setting => GStorage.setting;
|
||||
|
||||
static ThemeData? darkThemeData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 主题色
|
||||
@@ -184,12 +182,12 @@ class MyApp extends StatelessWidget {
|
||||
return GetMaterialApp(
|
||||
// showSemanticsDebugger: true,
|
||||
title: 'PiliPlus',
|
||||
theme: _getThemeData(
|
||||
theme: Utils.getThemeData(
|
||||
colorScheme: lightColorScheme,
|
||||
isDynamic: lightDynamic != null && isDynamicColor,
|
||||
variant: variant,
|
||||
),
|
||||
darkTheme: _getThemeData(
|
||||
darkTheme: Utils.getThemeData(
|
||||
colorScheme: darkColorScheme,
|
||||
isDynamic: darkDynamic != null && isDynamicColor,
|
||||
isDark: true,
|
||||
@@ -206,96 +204,41 @@ class MyApp extends StatelessWidget {
|
||||
fallbackLocale: const Locale("zh", "CN"),
|
||||
getPages: Routes.getPages,
|
||||
home: const MainApp(),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return FlutterSmartDialog(
|
||||
toastBuilder: (String msg) => CustomToast(msg: msg),
|
||||
child: MediaQuery(
|
||||
builder: FlutterSmartDialog.init(
|
||||
toastBuilder: (String msg) => CustomToast(msg: msg),
|
||||
loadingBuilder: (msg) => LoadingWidget(msg: msg),
|
||||
builder: (context, child) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context)
|
||||
.copyWith(textScaler: TextScaler.linear(textScale)),
|
||||
child: child!,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
navigatorObservers: [
|
||||
VideoDetailPage.routeObserver,
|
||||
FlutterSmartDialog.observer,
|
||||
VideoDetailPageV.routeObserver,
|
||||
MainApp.routeObserver,
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData _getThemeData({
|
||||
required ColorScheme colorScheme,
|
||||
required bool isDynamic,
|
||||
bool isDark = false,
|
||||
required FlexSchemeVariant variant,
|
||||
}) {
|
||||
ThemeData themeData = ThemeData(
|
||||
colorScheme: colorScheme,
|
||||
useMaterial3: true,
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
titleSpacing: 0,
|
||||
centerTitle: false,
|
||||
scrolledUnderElevation: 0,
|
||||
backgroundColor: isDynamic ? null : colorScheme.surface,
|
||||
titleTextStyle: TextStyle(fontSize: 16, color: colorScheme.onSurface),
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
surfaceTintColor: isDynamic ? colorScheme.onSurfaceVariant : null,
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
actionTextColor: colorScheme.primary,
|
||||
backgroundColor: colorScheme.secondaryContainer,
|
||||
closeIconColor: colorScheme.secondary,
|
||||
contentTextStyle: TextStyle(color: colorScheme.secondary),
|
||||
elevation: 20,
|
||||
),
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: <TargetPlatform, PageTransitionsBuilder>{
|
||||
TargetPlatform.android: ZoomPageTransitionsBuilder(
|
||||
allowEnterRouteSnapshotting: false,
|
||||
),
|
||||
},
|
||||
),
|
||||
popupMenuTheme: PopupMenuThemeData(
|
||||
surfaceTintColor: isDynamic ? colorScheme.onSurfaceVariant : null,
|
||||
),
|
||||
cardTheme: CardTheme(
|
||||
elevation: 1,
|
||||
surfaceTintColor: isDynamic
|
||||
? colorScheme.onSurfaceVariant
|
||||
: isDark
|
||||
? colorScheme.onSurfaceVariant
|
||||
: null,
|
||||
shadowColor: Colors.transparent,
|
||||
),
|
||||
// dialogTheme: DialogTheme(
|
||||
// surfaceTintColor: isDark ? colorScheme.onSurfaceVariant : null,
|
||||
// ),
|
||||
progressIndicatorTheme: ProgressIndicatorThemeData(
|
||||
refreshBackgroundColor: colorScheme.onSecondary,
|
||||
),
|
||||
dialogTheme: DialogTheme(
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 18,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (isDark && GStorage.isPureBlackTheme) {
|
||||
themeData = Utils.darkenTheme(themeData);
|
||||
}
|
||||
return themeData;
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomHttpOverrides extends HttpOverrides {
|
||||
final badCertificateCallback =
|
||||
BuildConfig.isDebug || GStorage.badCertificateCallback;
|
||||
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
return super.createHttpClient(context)
|
||||
..badCertificateCallback =
|
||||
final client = super.createHttpClient(context)
|
||||
// ..maxConnectionsPerHost = 32
|
||||
..idleTimeout = const Duration(seconds: 15);
|
||||
if (badCertificateCallback) {
|
||||
client.badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// 首页推荐类型
|
||||
enum RcmdType { web, app, notLogin }
|
||||
|
||||
extension RcmdTypeExtension on RcmdType {
|
||||
String get values => ['web', 'app', 'notLogin'][index];
|
||||
String get labels => ['web端', 'app端', '游客模式'][index];
|
||||
}
|
||||
@@ -8,7 +8,7 @@ class PostSegmentModel {
|
||||
required this.category,
|
||||
required this.actionType,
|
||||
});
|
||||
Pair<int, int> segment;
|
||||
Pair<double, double> segment;
|
||||
SegmentType category;
|
||||
ActionType actionType;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:PiliPlus/models/common/sponsor_block/action_type.dart';
|
||||
|
||||
enum SegmentType {
|
||||
sponsor,
|
||||
selfpromo,
|
||||
@@ -13,6 +15,74 @@ enum SegmentType {
|
||||
exclusive_access
|
||||
}
|
||||
|
||||
// List<SegmentType> _actionType2SegmentType(ActionType actionType) {
|
||||
// return switch (actionType) {
|
||||
// ActionType.skip => [
|
||||
// SegmentType.sponsor,
|
||||
// SegmentType.selfpromo,
|
||||
// SegmentType.interaction,
|
||||
// SegmentType.intro,
|
||||
// SegmentType.outro,
|
||||
// SegmentType.preview,
|
||||
// SegmentType.filler,
|
||||
// ],
|
||||
// ActionType.mute => [
|
||||
// SegmentType.sponsor,
|
||||
// SegmentType.selfpromo,
|
||||
// SegmentType.interaction,
|
||||
// SegmentType.intro,
|
||||
// SegmentType.outro,
|
||||
// SegmentType.preview,
|
||||
// SegmentType.music_offtopic,
|
||||
// SegmentType.filler,
|
||||
// ],
|
||||
// ActionType.full => [
|
||||
// SegmentType.sponsor,
|
||||
// SegmentType.selfpromo,
|
||||
// SegmentType.exclusive_access,
|
||||
// ],
|
||||
// ActionType.poi => [
|
||||
// SegmentType.poi_highlight,
|
||||
// ],
|
||||
// };
|
||||
// }
|
||||
|
||||
List<ActionType> segmentType2ActionType(SegmentType segmentType) {
|
||||
return switch (segmentType) {
|
||||
SegmentType.sponsor => [ActionType.skip, ActionType.mute, ActionType.full],
|
||||
SegmentType.selfpromo => [
|
||||
ActionType.skip,
|
||||
ActionType.mute,
|
||||
ActionType.full
|
||||
],
|
||||
SegmentType.interaction => [
|
||||
ActionType.skip,
|
||||
ActionType.mute,
|
||||
],
|
||||
SegmentType.intro => [
|
||||
ActionType.skip,
|
||||
ActionType.mute,
|
||||
],
|
||||
SegmentType.outro => [
|
||||
ActionType.skip,
|
||||
ActionType.mute,
|
||||
],
|
||||
SegmentType.preview => [
|
||||
ActionType.skip,
|
||||
ActionType.mute,
|
||||
],
|
||||
SegmentType.music_offtopic => [
|
||||
ActionType.skip,
|
||||
],
|
||||
SegmentType.poi_highlight => [ActionType.poi],
|
||||
SegmentType.filler => [
|
||||
ActionType.skip,
|
||||
ActionType.mute,
|
||||
],
|
||||
SegmentType.exclusive_access => [ActionType.full],
|
||||
};
|
||||
}
|
||||
|
||||
extension SegmentTypeExt on SegmentType {
|
||||
/// from https://github.com/hanydd/BilibiliSponsorBlock/blob/master/public/_locales/zh_CN/messages.json
|
||||
String get title => [
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,6 +413,7 @@ class DynamicMajorModel {
|
||||
this.common,
|
||||
this.music,
|
||||
this.blocked,
|
||||
this.medialist,
|
||||
});
|
||||
|
||||
DynamicArchiveModel? archive;
|
||||
@@ -428,6 +432,7 @@ class DynamicMajorModel {
|
||||
Map? common;
|
||||
Map? music;
|
||||
Map? blocked;
|
||||
Map? medialist;
|
||||
|
||||
DynamicMajorModel.fromJson(Map<String, dynamic> json) {
|
||||
archive = json['archive'] != null
|
||||
@@ -454,6 +459,7 @@ class DynamicMajorModel {
|
||||
common = json['common'] ?? {};
|
||||
music = json['music'] ?? {};
|
||||
blocked = json['blocked'];
|
||||
medialist = json['medialist'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,6 @@ enum LiveQuality {
|
||||
dolby,
|
||||
super4K,
|
||||
origin,
|
||||
veryHigh,
|
||||
bluRay,
|
||||
superHD,
|
||||
smooth,
|
||||
@@ -37,7 +36,8 @@ 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 = '';
|
||||
}
|
||||
}
|
||||
|
||||