Compare commits

...

637 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
bc8907b3ef mod: show lv6_s
Closes #687

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-15 18:18:53 +08:00
bggRGjQaUbCoE
14f8ec37c5 opt: dyn tab
opt: reload

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-15 16:19:51 +08:00
bggRGjQaUbCoE
2b567e7cb3 fix: set pageTransition
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-15 13:46:46 +08:00
bggRGjQaUbCoE
b58a3ec044 Revert "mod: show hot label"
This reverts commit 2d0d578bb4.
2025-04-15 13:32:47 +08:00
bggRGjQaUbCoE
2d0d578bb4 mod: show hot label
Closes #683

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-15 13:11:47 +08:00
bggRGjQaUbCoE
54ba05c4aa Revert "opt: video cover"
This reverts commit 7cc0c83df1.
2025-04-15 13:09:46 +08:00
bggRGjQaUbCoE
27b251b06e opt: member page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-15 11:48:09 +08:00
bggRGjQaUbCoE
5643ebfe48 feat: custom page transition
Closes #682

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-15 11:48:09 +08:00
bggRGjQaUbCoE
d9c2f6bf91 revert: dm color panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-15 11:18:47 +08:00
bggRGjQaUbCoE
3eb404a9e2 opt: search trending page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-15 11:18:41 +08:00
bggRGjQaUbCoE
bc9c20c509 feat: search trending page
Closes #684

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-14 23:43:47 +08:00
bggRGjQaUbCoE
7cc0c83df1 opt: video cover
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-14 23:35:22 +08:00
bggRGjQaUbCoE
41daefa6c4 fix: MsgType.share_v2
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-14 18:04:02 +08:00
bggRGjQaUbCoE
38fa8a10b7 opt: emote panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-14 15:41:41 +08:00
bggRGjQaUbCoE
07d37a1209 feat: search trending
Closes #678

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-14 15:25:45 +08:00
bggRGjQaUbCoE
509f0d1266 fix: #680
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-14 15:25:45 +08:00
bggRGjQaUbCoE
7966bab62d opt: episode panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-14 13:17:04 +08:00
bggRGjQaUbCoE
a136c150ad opt: download dialog
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-14 11:16:56 +08:00
bggRGjQaUbCoE
a89fe6b026 mod: remove add live dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-14 11:01:49 +08:00
bggRGjQaUbCoE
56460c937d mod: send vip colorful dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-14 10:48:40 +08:00
bggRGjQaUbCoE
f2080bfb7b opt: view later from dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 21:41:48 +08:00
bggRGjQaUbCoE
012d55452e opt: timeline tabbar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 21:19:26 +08:00
bggRGjQaUbCoE
6ac482ed5e opt: pgc timeline
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 21:08:22 +08:00
bggRGjQaUbCoE
68df173558 feat: pgc timeline
Closes #653

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 20:38:24 +08:00
bggRGjQaUbCoE
d9c6c31a4d mod: remove refresh fav folder
Closes #675

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 17:18:26 +08:00
bggRGjQaUbCoE
d3d2715418 opt: add live emoticonUnique
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 15:36:30 +08:00
bggRGjQaUbCoE
a93fbd4444 opt: live send dm panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 15:01:26 +08:00
bggRGjQaUbCoE
9fee9a4cf1 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 14:49:31 +08:00
bggRGjQaUbCoE
4bbc008788 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 14:44:35 +08:00
bggRGjQaUbCoE
671b6e1ef7 feat: send live emote
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 14:40:19 +08:00
bggRGjQaUbCoE
634bae915a mod: save dm config
Closes #673

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-13 11:22:41 +08:00
bggRGjQaUbCoE
a7bbfc983e mod: show pgc renewalTime
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 21:38:13 +08:00
bggRGjQaUbCoE
17548e935e mod: show pgc renewalTime
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 21:26:50 +08:00
bggRGjQaUbCoE
15f84712cd fix: parse reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 20:53:59 +08:00
bggRGjQaUbCoE
2f34ae7d45 opt: show video fav menu
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 20:17:33 +08:00
bggRGjQaUbCoE
16cbe7e43c fix: query reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 20:12:34 +08:00
bggRGjQaUbCoE
8d633377ae feat: sort fav folder
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 18:52:29 +08:00
bggRGjQaUbCoE
0b867c254f fix: log date
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 17:00:08 +08:00
bggRGjQaUbCoE
08a47e6c1d mod: clear outdated logs
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 16:42:35 +08:00
bggRGjQaUbCoE
6c9cd8b120 fix: play all later
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 16:23:02 +08:00
bggRGjQaUbCoE
71e7219084 mod: enable autoClearCache by def
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 15:55:09 +08:00
bggRGjQaUbCoE
c13063b230 opt: FavVideoCardH
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 15:50:16 +08:00
bggRGjQaUbCoE
26ca69cb83 opt: view later tabbar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 15:36:53 +08:00
bggRGjQaUbCoE
afc8c5f873 refa: later view page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 15:06:44 +08:00
bggRGjQaUbCoE
4d3f739a0c mod: disable preload reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 09:40:29 +08:00
bggRGjQaUbCoE
1781fdb7ca fix: search arc
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 09:24:33 +08:00
bggRGjQaUbCoE
32aa37505c Update README.md
Closes #669

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 09:24:33 +08:00
bggRGjQaUbCoE
9f9ed7dd4b fix: #668
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-12 00:11:51 +08:00
bggRGjQaUbCoE
03e3b897cf opt: check coin
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 21:49:43 +08:00
bggRGjQaUbCoE
3bc20ce1d4 opt: DynamicCardSkeleton
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 21:44:25 +08:00
bggRGjQaUbCoE
9ce9940306 mod: specify list type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 21:35:47 +08:00
bggRGjQaUbCoE
da35cf471e Revert "opt: DynamicCardSkeleton"
This reverts commit 3a52c1199c.
2025-04-11 20:54:53 +08:00
bggRGjQaUbCoE
c517df2c09 opt: keep member search page
fix: search arc

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 20:52:16 +08:00
My-Responsitories
02dee71670 mod: use cached coin (#667) 2025-04-11 12:15:09 +00:00
bggRGjQaUbCoE
1eadcd41f6 fix: live follow list
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 18:42:57 +08:00
bggRGjQaUbCoE
e8185535b0 opt: coin pic
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 18:20:19 +08:00
bggRGjQaUbCoE
b68bebfa2e fix: #665
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 17:56:34 +08:00
bggRGjQaUbCoE
3801bdf9d7 feat: get/check coin
Closes #661

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 17:28:58 +08:00
bggRGjQaUbCoE
9a6ba82467 fix: save panel: pgc uri
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 13:01:16 +08:00
bggRGjQaUbCoE
3a52c1199c opt: DynamicCardSkeleton
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 12:25:47 +08:00
bggRGjQaUbCoE
ea5c0584cc fix: footer
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 11:06:23 +08:00
bggRGjQaUbCoE
01b30d942b fix: state
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-11 10:53:59 +08:00
My-Responsitories
5aa5308a50 feat: relative slide (#662) 2025-04-11 01:57:22 +00:00
allllllllllla
de029b7043 Adapt to 16:10 tablet (#660)
* Update view.dart

* Update view_v.dart
2025-04-11 00:18:24 +08:00
bggRGjQaUbCoE
a45da453ce Revert "mod: revert success"
This reverts commit 68f03f2311.
2025-04-10 22:14:11 +08:00
dom
e1b73f4766 refa: query data (#659)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-10 21:43:01 +08:00
bggRGjQaUbCoE
99b19e7b03 opt: anim save panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-10 21:15:41 +08:00
bggRGjQaUbCoE
37bd849a86 fix: #658
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-10 20:41:19 +08:00
bggRGjQaUbCoE
4eb6f78a38 opt: reply item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-10 14:19:38 +08:00
bggRGjQaUbCoE
68f03f2311 mod: revert success
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-10 14:18:20 +08:00
My-Responsitories
2a60a9b393 mod: save panel use absolute time (#657) 2025-04-10 04:34:40 +00:00
My-Responsitories
1d4b08672b fix: init before speed test 2025-04-10 12:27:22 +08:00
bggRGjQaUbCoE
b0d9a1dada fix: #656
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-10 11:23:43 +08:00
bggRGjQaUbCoE
796494e53f mod: replace stream
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-10 09:38:33 +08:00
bggRGjQaUbCoE
cef7bfd534 opt: query data
fix: webdav backup

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-10 00:08:19 +08:00
bggRGjQaUbCoE
36ff4a0ed3 Revert "mod: tweak for xiaomi"
This reverts commit 44fa2a8c3e.
2025-04-09 20:19:39 +08:00
bggRGjQaUbCoE
6a6894030b fix: save reply type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 18:39:19 +08:00
bggRGjQaUbCoE
497d31ddf7 opt: nav stream
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 18:22:09 +08:00
bggRGjQaUbCoE
783218429c opt: nav/searchbar stream
Closes #648

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 18:04:27 +08:00
bggRGjQaUbCoE
0ccd15047b opt: bottom sheet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 17:06:35 +08:00
bggRGjQaUbCoE
fe2a6ec006 opt: more panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 15:39:39 +08:00
bggRGjQaUbCoE
a3ecf59fae opt: medialist page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 15:24:47 +08:00
bggRGjQaUbCoE
4f4f89a1d7 opt: reply header
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 14:42:38 +08:00
bggRGjQaUbCoE
ece3bdd2e8 opt: reply page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 14:30:08 +08:00
bggRGjQaUbCoE
f403ed1a21 opt: webdav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 14:04:53 +08:00
bggRGjQaUbCoE
17e3a0206a opt: video play
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 13:30:52 +08:00
My-Responsitories
5da86d85de fix: memberArchive challenge (#646) 2025-04-09 05:20:39 +00:00
My-Responsitories
d3cbc95235 feat: scroll spring in history & fav (#645) 2025-04-09 05:16:29 +00:00
bggRGjQaUbCoE
a7eebcc209 opt: showEpisodes
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 12:28:02 +08:00
bggRGjQaUbCoE
fca22eb592 opt: slide padding
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 12:27:56 +08:00
bggRGjQaUbCoE
1202e5ec0f opt: search dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 11:50:06 +08:00
bggRGjQaUbCoE
03830533eb opt: pages
Closes #644

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-09 00:28:44 +08:00
bggRGjQaUbCoE
850e5a199e fix: slide padding
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-08 22:33:04 +08:00
bggRGjQaUbCoE
2d11158ecd refa: search dyn
Closes #641

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-08 22:09:47 +08:00
bggRGjQaUbCoE
a34c18b262 feat: webdav
Closes #432

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-08 20:59:21 +08:00
bggRGjQaUbCoE
560b1e40cc fix: #639
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-08 19:09:34 +08:00
My-Responsitories
3cd512857c mod: dyn search (#638) 2025-04-08 08:38:05 +00:00
bggRGjQaUbCoE
356adbef5c opt: reply ctr tag
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-08 11:26:11 +08:00
bggRGjQaUbCoE
42d7445d83 opt: skip segment
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-08 10:50:01 +08:00
bggRGjQaUbCoE
3a0f32fce7 chore: update dm dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-08 00:22:26 +08:00
bggRGjQaUbCoE
6bc128cfda opt: save reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 22:49:04 +08:00
bggRGjQaUbCoE
6f2d697748 opt: query sponsor block
Closes #633

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 22:32:26 +08:00
bggRGjQaUbCoE
4de180c23a opt: save panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 22:22:10 +08:00
My-Responsitories
af289c533f some modifitions (#636)
* opt: MsgUnReadTypeV2

* mod: recoard history in anonymity mode
2025-04-07 13:19:24 +00:00
bggRGjQaUbCoE
82d615fbbf feat: save dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 17:52:04 +08:00
dom
457f2ea6c7 Update bug-反馈.yml 2025-04-07 14:04:57 +08:00
bggRGjQaUbCoE
41ad5c45ed fix: #634
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 14:04:14 +08:00
bggRGjQaUbCoE
e9da2e8d6b opt: save reply page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 13:48:32 +08:00
bggRGjQaUbCoE
a8cfbb12fd fix: block page state
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 13:39:57 +08:00
bggRGjQaUbCoE
6d89b7769e chore: rename imageview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 12:40:48 +08:00
bggRGjQaUbCoE
2d86daec83 fix: #630
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 12:39:01 +08:00
bggRGjQaUbCoE
a5e8594611 feat: del msg feed
opt: msg feed

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 12:24:14 +08:00
bggRGjQaUbCoE
99810ef512 fix: dyn pubtime
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 11:30:56 +08:00
bggRGjQaUbCoE
2317b831db opt: save reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 11:22:44 +08:00
bggRGjQaUbCoE
e073086cf4 opt: reply item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 10:09:34 +08:00
bggRGjQaUbCoE
b14844f459 fix: save reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 09:50:19 +08:00
dom
8719c8f639 feat: save reply (#629)
Closes #614

opt: more panel

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-07 09:22:33 +08:00
My-Responsitories
d3cec0ec72 fix #624 (#625) 2025-04-07 09:17:19 +08:00
bggRGjQaUbCoE
a8daf02610 opt: video sheet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-06 15:49:40 +08:00
bggRGjQaUbCoE
f9b844fb1a mod: open link verify settings btn
Closes #622

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-06 12:12:17 +08:00
bggRGjQaUbCoE
6d1d6b575a opt: send dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-06 11:35:04 +08:00
bggRGjQaUbCoE
0a5a094e54 chore: update dm dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-06 11:28:05 +08:00
bggRGjQaUbCoE
754da4777a opt: live: send danmaku
Closes #618

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-06 11:22:19 +08:00
bggRGjQaUbCoE
216e3e606e mod: live: double tap
Closes #619

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-06 10:52:05 +08:00
bggRGjQaUbCoE
bb013a8fe6 mod: hide special dm by def
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 20:48:50 +08:00
bggRGjQaUbCoE
6b6449f023 mod: article: get user mid
Closes #615

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 20:28:56 +08:00
bggRGjQaUbCoE
fcf3348371 fix: add special dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 20:28:44 +08:00
bggRGjQaUbCoE
f90f759667 opt: jump to reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 16:42:34 +08:00
bggRGjQaUbCoE
b02e6c04b9 feat: special danmaku by @My-Responsitories
Closes #91
Closes #219
Closes #394
Closes #602
Closes #613

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 16:32:30 +08:00
bggRGjQaUbCoE
08dc04f874 opt: show dyn blocked item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 14:11:01 +08:00
bggRGjQaUbCoE
4776b84c7c mod: sys msg: push dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 14:10:52 +08:00
bggRGjQaUbCoE
78d13b586a fix: sys msg: push video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 11:49:50 +08:00
bggRGjQaUbCoE
f522ecd42d chore: remove unused pack
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 11:18:15 +08:00
bggRGjQaUbCoE
44fa2a8c3e mod: tweak for xiaomi
Closes #608

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 11:18:02 +08:00
bggRGjQaUbCoE
ff30c8c2bf opt: push dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-05 10:54:09 +08:00
bggRGjQaUbCoE
4aaaffbcea fix: dyn comment type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 23:40:11 +08:00
bggRGjQaUbCoE
21da122902 opt: push opus
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 23:22:33 +08:00
bggRGjQaUbCoE
849904ad45 revert: push opus
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 23:16:42 +08:00
bggRGjQaUbCoE
1c0bae600f mod: add nav/search debounce option
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 21:53:30 +08:00
bggRGjQaUbCoE
f1433c6e9b mod: show reply rootText
Closes #605

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 21:17:22 +08:00
bggRGjQaUbCoE
2dc106adcb fix: reply error widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 20:56:37 +08:00
bggRGjQaUbCoE
df6738f607 mod: remove webview msg btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 18:44:26 +08:00
bggRGjQaUbCoE
ee64f1e7f1 mod: note: check login
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 18:39:36 +08:00
bggRGjQaUbCoE
d921f6176b opt: get cacheSize
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 18:31:19 +08:00
bggRGjQaUbCoE
7009c3400a mod: remove ios ua
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 18:22:47 +08:00
bggRGjQaUbCoE
7bd481b090 fix: ios message webview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 18:00:14 +08:00
bggRGjQaUbCoE
7fafa88eb7 opt: load previous
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 17:53:11 +08:00
bggRGjQaUbCoE
cb3e57feec fix: load previous
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 16:04:59 +08:00
bggRGjQaUbCoE
9a7d73cb6b opt: load previous data
Closes #597

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 15:26:27 +08:00
bggRGjQaUbCoE
f5c2bd47d5 fix: fav: play all
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 12:14:55 +08:00
bggRGjQaUbCoE
c154d25f7a opt: episode item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 12:03:22 +08:00
bggRGjQaUbCoE
8c259205f5 feat: record search history option
Closes #592

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 12:00:13 +08:00
bggRGjQaUbCoE
849329b66b opt: bottomnav/searchbar stream debounce
Closes #590

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 11:26:53 +08:00
bggRGjQaUbCoE
f542565dc5 mod: filter advanced dm
Closes #602

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 10:59:57 +08:00
bggRGjQaUbCoE
08aedbf0b0 feat: custom for/backward duration
Closes #366
Closes #601

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-04 10:43:03 +08:00
bggRGjQaUbCoE
09c8a41c52 feat: drag subtitle
Closes #588

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-03 20:04:56 +08:00
bggRGjQaUbCoE
6a7d14a3f8 fix: view reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-03 18:04:41 +08:00
bggRGjQaUbCoE
5b171ec044 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-03 15:23:09 +08:00
bggRGjQaUbCoE
978d634cb3 feat: set top reply
Closes #589

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-03 14:50:42 +08:00
bggRGjQaUbCoE
7437d8c592 feat: manual check dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-03 13:18:24 +08:00
My-Responsitories
e190ca5868 mod: account (#591)
* fix: onLoginMain

* fix: account override

* opt: sponsor block url
2025-04-03 04:18:30 +00:00
bggRGjQaUbCoE
64fc995f6b fix: report video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-03 12:02:25 +08:00
bggRGjQaUbCoE
2d0e801a1a opt: override note title
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 22:21:18 +08:00
bggRGjQaUbCoE
d409424871 opt: webview: add note
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 22:06:17 +08:00
bggRGjQaUbCoE
b855ef9865 mod: webview: intercept refresh url
Closes #587

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 21:25:39 +08:00
bggRGjQaUbCoE
86abf006d0 mod: live schedule
Closes #581

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 20:47:11 +08:00
My-Responsitories
d1a6798f2e fix: wbiSign 2025-04-02 18:52:13 +08:00
bggRGjQaUbCoE
f64d543ec7 mod: show live media notification
Closes #584

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 17:34:17 +08:00
bggRGjQaUbCoE
2abf01362c opt: medialist page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 16:37:01 +08:00
bggRGjQaUbCoE
9bbd934f8e fix: episode panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 15:52:29 +08:00
bggRGjQaUbCoE
6ab72b65aa fix: video gesture
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 15:12:24 +08:00
bggRGjQaUbCoE
c39de1e245 opt: download img
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 15:05:14 +08:00
bggRGjQaUbCoE
d112843a8a fix: mid
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 13:59:52 +08:00
bggRGjQaUbCoE
89df091542 opt: video gesture
Closes #586

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 12:42:00 +08:00
bggRGjQaUbCoE
d870c36a96 mod: custom slide fs
Closes #585

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 12:33:31 +08:00
bggRGjQaUbCoE
fc55bf33d0 fix: episode cid
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-02 12:33:11 +08:00
bggRGjQaUbCoE
f99740ef2d refa: list sheet
Closes #369

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-01 23:53:32 +08:00
bggRGjQaUbCoE
f9f30a5f13 mod: tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-01 17:19:35 +08:00
bggRGjQaUbCoE
f70cf05870 opt: member page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-01 16:22:27 +08:00
bggRGjQaUbCoE
22866012ca mod: remove unused pack
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-01 14:06:02 +08:00
bggRGjQaUbCoE
cdb2718aeb chore: deprecate prev reply, videopage
Closes #579

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-01 12:48:59 +08:00
bggRGjQaUbCoE
e4f3203351 opt: show pubTs
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 21:41:15 +08:00
bggRGjQaUbCoE
d6b388ad5c opt: duration
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 18:20:50 +08:00
bggRGjQaUbCoE
61819d9f27 opt: image preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 17:53:35 +08:00
bggRGjQaUbCoE
3ccc7ef69d fix: bangumi mid
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 17:37:42 +08:00
bggRGjQaUbCoE
e834311664 mod: set buffer value
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 17:37:42 +08:00
bggRGjQaUbCoE
fd8dff327c fix: view staff from intro
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 16:44:41 +08:00
bggRGjQaUbCoE
a9df8cd883 opt: push following detail
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 16:38:58 +08:00
bggRGjQaUbCoE
909394965e fix: push bilibili://following/detail
Closes #576

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 16:29:49 +08:00
bggRGjQaUbCoE
2d5991e0c5 opt: query history
Closes #578

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 16:02:49 +08:00
bggRGjQaUbCoE
e7ae66a3dc fix: push bilibili://comment/msg_fold
Closes #576

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 15:34:11 +08:00
bggRGjQaUbCoE
5929150047 fix: push bilibili://following/detail
related #576

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 14:54:53 +08:00
bggRGjQaUbCoE
acb6bc569e opt: btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-31 14:54:53 +08:00
bggRGjQaUbCoE
678db34c81 opt: member page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-30 21:32:36 +08:00
bggRGjQaUbCoE
134bfd43ff opt: video: charged badge
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-30 21:14:07 +08:00
bggRGjQaUbCoE
8712248ef2 opt: member video card
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-30 21:00:37 +08:00
bggRGjQaUbCoE
c97227e807 opt: hmember page 2025-03-30 20:24:32 +08:00
bggRGjQaUbCoE
40429021be feat: locate last viewed video
Closes #453

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-30 19:54:57 +08:00
bggRGjQaUbCoE
e89bd2fedf opt: member prInfo
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-30 17:16:10 +08:00
bggRGjQaUbCoE
9da3a538fb refa: horizontal member page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-30 16:20:40 +08:00
bggRGjQaUbCoE
a904414f3d mod: prinfo
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-30 13:47:59 +08:00
bggRGjQaUbCoE
f003e8bf35 mod: member card: show prInfo
Closes #571

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-30 12:17:54 +08:00
bggRGjQaUbCoE
7399915357 mod: later: show progress
Closes #569

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-30 11:31:46 +08:00
My-Responsitories
7aa0289c1f fix: audioNormalization 2025-03-29 23:51:32 +08:00
bggRGjQaUbCoE
99d0b1c468 mod: btn to view all fav pgc
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 21:03:19 +08:00
bggRGjQaUbCoE
84a342a0e0 mod: enable retry by def
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 20:39:20 +08:00
bggRGjQaUbCoE
db1c836a3e opt: share/save video cover
Closes #563

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 20:39:20 +08:00
My-Responsitories
6539457f83 fix: select group sheet 2025-03-29 20:30:19 +08:00
My-Responsitories
82f9f48a8e opt: select dialog & feat: select subtitle if muted (#564)
* opt: select dialog

* opt: subtitle

* feat: select subtitle if muted
2025-03-29 09:52:06 +00:00
bggRGjQaUbCoE
2ddfea5cf3 fix: webview params
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 15:37:19 +08:00
bggRGjQaUbCoE
79aee2fdd9 opt: view note
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 15:13:11 +08:00
bggRGjQaUbCoE
5dc8b8e54f feat: fav article page
Closes #402

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 14:41:13 +08:00
bggRGjQaUbCoE
72fa9c51f0 feat: fav pgc page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 13:45:14 +08:00
bggRGjQaUbCoE
6ea8ffea7a opt: note
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 11:24:39 +08:00
bggRGjQaUbCoE
eea5257da2 fix: only play audio
related #558

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 10:05:43 +08:00
bggRGjQaUbCoE
385cffefb8 fix(dep): downgrade media-kit
Closes #543
Closes #546
Closes #551
Closes #558

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 10:00:30 +08:00
bggRGjQaUbCoE
347420c531 feat: del note
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-29 09:53:02 +08:00
bggRGjQaUbCoE
cc774015f9 feat: fav note page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-28 22:35:38 +08:00
bggRGjQaUbCoE
da3f64feab refa: fav page [wip]
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-28 18:38:31 +08:00
My-Responsitories
76d031e8d1 feat: parallel upload & download image (#556)
* feat: parallel upload file

* feat: parallel download file
2025-03-28 10:30:15 +00:00
bggRGjQaUbCoE
25995b0ed6 fix: #555
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-28 17:46:45 +08:00
bggRGjQaUbCoE
3cdd40a710 feat: create note
related #554

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-28 17:32:27 +08:00
bggRGjQaUbCoE
f36f8d69fc refa: subtitle
Closes #553

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-28 16:13:21 +08:00
bggRGjQaUbCoE
5655e6ccdf opt: article report
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-28 11:06:00 +08:00
bggRGjQaUbCoE
49fff821b1 feat: live/article report
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-28 10:57:54 +08:00
bggRGjQaUbCoE
3b34cecdcd mod: set pos
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-28 10:09:11 +08:00
My-Responsitories
3693d6c350 change status api (#552) 2025-03-28 01:57:50 +00:00
bggRGjQaUbCoE
54cb1a6fc0 fix: #547
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-27 21:54:45 +08:00
bggRGjQaUbCoE
ab7b1524b6 opt: show dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-27 21:18:17 +08:00
bggRGjQaUbCoE
2b4a27076c fix: #344
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-27 20:59:35 +08:00
bggRGjQaUbCoE
63a7fa95f5 fix: pip aspectRatio
related #543

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-27 20:44:18 +08:00
bggRGjQaUbCoE
a15b932a69 opt: query dm
Closes #544

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-27 20:03:41 +08:00
bggRGjQaUbCoE
3a6b6614a4 opt: video tool bar
Closes #528

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-27 15:43:11 +08:00
bggRGjQaUbCoE
6ff83e34f3 opt: sort fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-27 14:47:40 +08:00
bggRGjQaUbCoE
e4cadc5a40 chore: update dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-27 13:50:33 +08:00
bggRGjQaUbCoE
907d37dd4c mod: whisper: show article_card
ref orz12/main

Closes #540

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-27 11:10:40 +08:00
bggRGjQaUbCoE
00d30313af opt: video widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-27 10:59:16 +08:00
bggRGjQaUbCoE
950dd82e3c fix: sort fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-26 23:40:03 +08:00
bggRGjQaUbCoE
c53c3a387c fix: #539
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-26 23:32:57 +08:00
bggRGjQaUbCoE
5d0b2dc8e3 feat: custom app font weight
Closes #533

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-26 21:42:57 +08:00
bggRGjQaUbCoE
f5e9375917 mod: update qa when switching
Closes #437

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-26 20:40:38 +08:00
bggRGjQaUbCoE
cdfab7a7db chore: update dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-26 19:02:24 +08:00
bggRGjQaUbCoE
f3e6a59e4f chore: upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-26 17:21:41 +08:00
bggRGjQaUbCoE
239c9ca2a7 mod: top or refresh debounce
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-26 16:39:52 +08:00
bggRGjQaUbCoE
becb566ca8 feat: sort fav
Closes #530

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-26 16:21:28 +08:00
bggRGjQaUbCoE
aa378d924b opt: post segments
Closes #531

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-26 12:10:55 +08:00
bggRGjQaUbCoE
812f351ddd opt: intro: queryUserStat
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-26 12:10:55 +08:00
My-Responsitories
b9adf26ee0 opt: model & feat: filter play (#529)
* opt: model

* opt: model

* feat: filter play
2025-03-25 14:45:30 +00:00
bggRGjQaUbCoE
018cd058ca opt: set playback speed
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 21:19:11 +08:00
bggRGjQaUbCoE
cd3385be63 opt: only play audio
Closes #519

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 18:25:12 +08:00
bggRGjQaUbCoE
d4e4813c78 feat: static dm duration
Closes #479

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 17:47:11 +08:00
My-Responsitories
8030912087 Revert "fix: mixinKeyEncTab" (#527)
This reverts commit 0f78669faf.
2025-03-25 17:03:15 +08:00
bggRGjQaUbCoE
4879701008 opt: up panel
Closes #513

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 16:49:27 +08:00
bggRGjQaUbCoE
6ab8e5925e mod: remove maxConnectionsPerHost
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 15:36:52 +08:00
bggRGjQaUbCoE
52dda9964c opt: queryFollowUp
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 15:15:19 +08:00
bggRGjQaUbCoE
1825329236 fix: SetSwitchItem
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 14:52:11 +08:00
bggRGjQaUbCoE
60a650f798 fix: #526
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 14:38:12 +08:00
bggRGjQaUbCoE
0f78669faf fix: mixinKeyEncTab
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 14:13:09 +08:00
bggRGjQaUbCoE
015309b3dc opt: main: anim to top
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 14:06:19 +08:00
My-Responsitories
a74edd22c1 feat: refresh on top (#524)
* feat: refresh on top

* check ctr client

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

---------

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-25 13:46:38 +08:00
My-Responsitories
7a6085e923 refa: video model (#523) 2025-03-25 02:12:44 +00:00
bggRGjQaUbCoE
bf464994df opt: video progressbar
Closes #507
Closes #514

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 21:29:32 +08:00
bggRGjQaUbCoE
a611a88f69 opt: post segment
Closes #483

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 21:10:10 +08:00
bggRGjQaUbCoE
025b5c8e6d mod: search history
Closes #488

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 21:10:10 +08:00
bggRGjQaUbCoE
d37685f7cf opt: member page: show uname
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 21:10:10 +08:00
My-Responsitories
10a22b5186 opt: stat widget (#520) 2025-03-24 13:09:11 +00:00
bggRGjQaUbCoE
d9a74c43dc fix: del history
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 20:06:59 +08:00
bggRGjQaUbCoE
90c8aeb05d mod: history: full type
Closes #473

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 19:10:02 +08:00
bggRGjQaUbCoE
34f63612a4 fix: #517
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 14:29:54 +08:00
bggRGjQaUbCoE
edfa9a8dd1 mod: live: only play audio
Closes #465

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 14:07:42 +08:00
bggRGjQaUbCoE
95fa19f121 opt: dm widget
Closes #160
Closes #515

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 13:44:54 +08:00
bggRGjQaUbCoE
79d0d314f5 fix: #516
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 13:12:36 +08:00
bggRGjQaUbCoE
ddbf168c87 chore: update dm dep
Closes #511

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 11:37:02 +08:00
bggRGjQaUbCoE
0eee8bbac2 mod: def hwdec
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 10:50:51 +08:00
bggRGjQaUbCoE
92f02b5943 fix: search
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 10:20:43 +08:00
bggRGjQaUbCoE
f110c2a55f fix: fav search
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 10:16:01 +08:00
bggRGjQaUbCoE
8ddf42fff1 opt: switch btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-24 00:19:25 +08:00
My-Responsitories
d2c34d64c3 fix #510 2025-03-23 23:17:18 +08:00
bggRGjQaUbCoE
2341027972 mod: show switch btn when playall
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-23 22:19:13 +08:00
bggRGjQaUbCoE
006c9301d9 fix: follow staff
related #481

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-23 21:53:26 +08:00
bggRGjQaUbCoE
1f8955d0b3 opt: prev/next play
Closes #506

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-23 21:47:12 +08:00
bggRGjQaUbCoE
5a758ebb3a opt: play all
Closes #503

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-23 20:49:29 +08:00
bggRGjQaUbCoE
97bef56006 mod: live qa
Closes #464

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-23 20:35:16 +08:00
bggRGjQaUbCoE
96ba36ed67 fix: def subtitle stroke
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-23 20:34:59 +08:00
My-Responsitories
edf84fcc8f opt: blacklist (#501) 2025-03-23 11:13:07 +00:00
bggRGjQaUbCoE
a8428e52d2 opt: save subtitle
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-23 17:03:56 +08:00
bggRGjQaUbCoE
fd8559228e feat: save subtitle
Closes #495

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-23 16:27:38 +08:00
My-Responsitories
d6587cf3b6 feat: logout (#497)
* feat: logout

* update api type
2025-03-23 13:46:26 +08:00
My-Responsitories
7c3e3cb1f8 some fixes (#498)
* nologin reject headtbeat

* fix: change anonymity

* use account as key
2025-03-23 13:46:04 +08:00
bggRGjQaUbCoE
9d0ac30fad feat: follow staff
Closes #481

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-23 13:40:44 +08:00
bggRGjQaUbCoE
68d11d7638 feat: custom subtitle fontweight
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-23 12:19:25 +08:00
My-Responsitories
3881b3dc74 feat: retry before sending (#489)
* feat: retry before sending

* reduce idleTimeout
2025-03-23 12:09:11 +08:00
My-Responsitories
99b14d0f0e opt: danmaku filter (#486) 2025-03-23 12:07:57 +08:00
My-Responsitories
066f3d4132 some fix (#480)
* fix dislikeVideo

* fix profile update

* show login toast
2025-03-23 12:06:34 +08:00
My-Responsitories
b15fdfa2ff feat: account manager (#468)
* feat: account manager

* remove dep

* some fixes

* migrate accounts

* reimplement clearCookie
2025-03-19 13:19:32 +08:00
bggRGjQaUbCoE
94fa0652ac fix: #470
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-17 18:34:49 +08:00
bggRGjQaUbCoE
5c54e131ba opt: loadingwidget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-17 14:00:11 +08:00
bggRGjQaUbCoE
1fc85fd618 mod: medialist: del btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-17 13:59:47 +08:00
bggRGjQaUbCoE
e1c561b613 fix: #467
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-17 00:45:35 +08:00
bggRGjQaUbCoE
950620bf9e opt: dyn panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-16 21:26:37 +08:00
bggRGjQaUbCoE
ae7a1e2373 mod: dyn: show medialist type
Closes #462

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-16 20:50:04 +08:00
bggRGjQaUbCoE
bddeb72d9b fix: medialist: del btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-16 18:44:32 +08:00
bggRGjQaUbCoE
b99cf4f629 opt: playall
Closes #450

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-16 18:19:22 +08:00
bggRGjQaUbCoE
b07cf62bdd mod: medialist: show del btn
Closes #451

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-16 16:36:43 +08:00
bggRGjQaUbCoE
57d2d3f5d9 fix: video page theme
mod: popscope

related #459

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-16 15:33:55 +08:00
My-Responsitories
7854c5e6b9 split report dialog (#460) 2025-03-16 13:34:04 +08:00
bggRGjQaUbCoE
0b8e95477c mod: handle show viewpoints btn
Closes #457

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-16 11:22:20 +08:00
dom
c2e9a7deb3 Update 功能请求.yml 2025-03-16 10:38:47 +08:00
bggRGjQaUbCoE
361a6a4c1d fix: get args
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-15 19:55:47 +08:00
bggRGjQaUbCoE
8eca9a6644 opt: live: msg bg
Closes #448

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-15 16:19:09 +08:00
bggRGjQaUbCoE
0770f325ab feat: subtitle stroke
Closes #446

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-15 15:46:07 +08:00
bggRGjQaUbCoE
98d52760b3 mod: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-15 15:27:16 +08:00
bggRGjQaUbCoE
0ebe976b8a mod: custom enableSlideVolumeBrightness
Closes #439

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-14 19:47:22 +08:00
bggRGjQaUbCoE
21fe0ef288 mod: search from tag
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-14 12:19:30 +08:00
bggRGjQaUbCoE
28ef1890d1 fix: #438
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-13 22:08:06 +08:00
bggRGjQaUbCoE
d6f238c720 fix: get sortField
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-13 22:07:32 +08:00
bggRGjQaUbCoE
44bf9dd9e1 opt: dyn panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-13 13:49:39 +08:00
bggRGjQaUbCoE
e357da5162 opt: dyn panel
Closes #436

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-13 12:27:36 +08:00
bggRGjQaUbCoE
c296aa036a fix: img preview quality
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-13 11:54:22 +08:00
bggRGjQaUbCoE
12c46f938d mod: split dm/sub settings, add reset btn
Closes #223
Closes #361

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-12 18:27:08 +08:00
bggRGjQaUbCoE
b4412f5b37 opt: some widgets
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-12 16:45:21 +08:00
bggRGjQaUbCoE
5f2ac0d59b fix: ctr tag
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-12 09:37:10 +08:00
bggRGjQaUbCoE
2a73725455 opt: live room
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-11 22:25:13 +08:00
bggRGjQaUbCoE
a8725e64ee opt: live room
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-11 21:51:16 +08:00
bggRGjQaUbCoE
727ae8cd2b opt: live room
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-11 21:35:47 +08:00
bggRGjQaUbCoE
714f288170 opt: live room
Closes #427

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-11 21:10:37 +08:00
bggRGjQaUbCoE
3da64d2641 fix: share video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-11 14:29:05 +08:00
My-Responsitories
2556290a6e fix #424 2025-03-11 14:11:13 +08:00
bggRGjQaUbCoE
66b547a904 fix: #424
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-11 13:59:58 +08:00
bggRGjQaUbCoE
c1ab273478 opt: video: long press
Closes #423

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-11 13:54:31 +08:00
bggRGjQaUbCoE
4aa3d5f273 opt: video sheet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-11 13:44:24 +08:00
bggRGjQaUbCoE
a6a1de169b fix: video theme, view later
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-11 11:54:44 +08:00
bggRGjQaUbCoE
af6188be77 opt: live room bg
Closes #422

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-11 11:54:17 +08:00
bggRGjQaUbCoE
bd39de2109 opt: video dialog
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-10 23:56:51 +08:00
bggRGjQaUbCoE
33375aeb7d feat: dark video page
Closes #420

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-10 23:33:33 +08:00
bggRGjQaUbCoE
fafe6c1e91 opt: video page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-10 21:40:59 +08:00
bggRGjQaUbCoE
68b072bf44 mod: remove unused import
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-10 20:19:43 +08:00
My-Responsitories
99cdec62a1 view later (#419) 2025-03-10 18:46:07 +08:00
bggRGjQaUbCoE
59797a2f5f mod: jump from invalid fav item
Closes #416

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-10 16:52:54 +08:00
bggRGjQaUbCoE
5cc661e314 opt: video width
Closes #417

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-10 15:54:38 +08:00
bggRGjQaUbCoE
2ce79d21b5 opt: view invalid user space
Closes #414

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-10 15:14:36 +08:00
bggRGjQaUbCoE
b75fda3596 mod: common ctr
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-10 11:12:52 +08:00
bggRGjQaUbCoE
2efa6f4ace mod: jump from cvid
Closes #405

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-09 21:42:47 +08:00
bggRGjQaUbCoE
95e50e436b mod: option show dyn actionbar
Closes #412

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-09 21:22:19 +08:00
bggRGjQaUbCoE
35a53bc8ac mod: option shrink video
Closes #410

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-09 21:00:24 +08:00
bggRGjQaUbCoE
58c16ef52e chore: add episode info api
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-09 20:49:53 +08:00
bggRGjQaUbCoE
847ac80d5f opt: viewpoints page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-09 10:09:54 +08:00
bggRGjQaUbCoE
0408b27ca5 mod: check reply manually
Closes #407

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-09 09:50:06 +08:00
My-Responsitories
2949adbbfd fix: msg_feed insert type error (#411)
* fix: msg_feed insert type error

* jump to dyn secondary reply
2025-03-09 09:49:16 +08:00
dom
2f616ba237 Update and rename 功能请求.md to 功能请求.yml 2025-03-08 20:01:16 +08:00
dom
b50ead327c Update and rename bug-反馈.md to bug-反馈.yml 2025-03-08 19:56:06 +08:00
bggRGjQaUbCoE
2fe0f43cb6 fix: #404
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-08 08:33:03 +08:00
bggRGjQaUbCoE
b85413be9b opt: video bg
Closes #397

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-07 15:04:16 +08:00
bggRGjQaUbCoE
c88776c4a0 mod: try-catch nextplay
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-07 11:51:20 +08:00
My-Responsitories
b7cb977f2b remove duplicated code (#396)
* remove duplicated code

* partical revert

---------

Co-authored-by: dom <githubaccount56556@proton.me>
2025-03-07 11:49:46 +08:00
bggRGjQaUbCoE
3048e36d2f mod: requery dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-06 16:27:51 +08:00
bggRGjQaUbCoE
64f37fa743 opt: triple
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-06 13:52:37 +08:00
bggRGjQaUbCoE
737b7d0507 fix: numformat
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-06 13:32:44 +08:00
bggRGjQaUbCoE
973dad4176 fix: video header
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-06 12:26:34 +08:00
bggRGjQaUbCoE
46110adb8f fix: #391
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-06 12:06:00 +08:00
bggRGjQaUbCoE
b5c7ed1c34 opt: video: check title width
Closes #206

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-06 11:58:39 +08:00
bggRGjQaUbCoE
86678ec15a opt: triple
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-06 11:03:58 +08:00
bggRGjQaUbCoE
893fb63a72 mod: video appbar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-06 10:48:37 +08:00
bggRGjQaUbCoE
3b717cfc58 fix: video minScale
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-06 10:11:10 +08:00
bggRGjQaUbCoE
ed40a91a52 opt: fullscreen triple
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 23:49:03 +08:00
bggRGjQaUbCoE
8b1bec6ed2 mod: blacklist item
Closes #390

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 23:02:32 +08:00
bggRGjQaUbCoE
a6a3476cb2 mod: video minScale
Closes #388

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 22:48:01 +08:00
bggRGjQaUbCoE
fac3c19d3f mod: show fullscreen action item
Closes #367

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 21:50:26 +08:00
bggRGjQaUbCoE
dc1451c3af opt: at me item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 17:57:51 +08:00
bggRGjQaUbCoE
08b0a93064 mod: msg top: show time
Closes #387

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 17:39:09 +08:00
bggRGjQaUbCoE
72dd0b9e81 opt: report panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 17:04:16 +08:00
bggRGjQaUbCoE
8236b93717 mod: msg top item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 17:01:22 +08:00
My-Responsitories
c4c5eee2eb fix banUid (#386) 2025-03-05 17:01:08 +08:00
My-Responsitories
5cc9c59c76 report panel (#385) 2025-03-05 15:42:04 +08:00
bggRGjQaUbCoE
bf4ecc85dd mod: article req
Closes #305

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 14:25:54 +08:00
bggRGjQaUbCoE
da5c2148ad opt: horizontal image view
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 13:42:35 +08:00
bggRGjQaUbCoE
bfcea11320 opt: dyndetail/html ctr listener
related #305

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 13:22:06 +08:00
bggRGjQaUbCoE
ed19e13630 fix: video appbar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 11:23:51 +08:00
bggRGjQaUbCoE
6497fb6cd0 mod: common slide page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 10:45:02 +08:00
bggRGjQaUbCoE
9c21f03df8 opt: tabbar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-05 00:04:38 +08:00
bggRGjQaUbCoE
7667e73d9d fix: #383
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-04 23:38:38 +08:00
bggRGjQaUbCoE
ff2ed0421c mod: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-04 22:32:09 +08:00
bggRGjQaUbCoE
56c5ad360a mod: more slide dismiss pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-04 20:35:15 +08:00
bggRGjQaUbCoE
ef644d2837 fix: #381
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-04 20:34:45 +08:00
bggRGjQaUbCoE
4642eda98d refa: msg top page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-04 14:37:58 +08:00
bggRGjQaUbCoE
8ef163dd38 mod: refresh
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-04 12:06:50 +08:00
bggRGjQaUbCoE
5986add7dd feat: show video note list
Closes #376

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-04 11:42:41 +08:00
bggRGjQaUbCoE
c990cf1660 mod: progressbar
Closes #379

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-04 09:37:10 +08:00
bggRGjQaUbCoE
76c16c035e mod: show dm merged count
related #359

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-03 21:38:02 +08:00
My-Responsitories
d5a244ce7f update msg api (#375) 2025-03-03 17:23:19 +08:00
bggRGjQaUbCoE
432c5133e6 mod: show clear search history dialog
related #359

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-03 11:40:39 +08:00
bggRGjQaUbCoE
a9f9b324a9 opt: slide gesture
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-02 13:59:45 +08:00
bggRGjQaUbCoE
4735297285 opt: seek indicator
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-02 12:13:48 +08:00
bggRGjQaUbCoE
3abff4b9da opt: string ext
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-02 12:04:13 +08:00
bggRGjQaUbCoE
c32b98fa7f fix: thumbnail image url
Closes #360

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-02 11:53:51 +08:00
bggRGjQaUbCoE
a605c0fcfb mod: reenable multi seek
Closes #365

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-02 10:23:45 +08:00
bggRGjQaUbCoE
1e83b4557f mod: extra jump to reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-02 00:47:02 +08:00
bggRGjQaUbCoE
c3d729fc77 mod: scheme: jump to reply
Closes #362

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-02 00:39:39 +08:00
bggRGjQaUbCoE
00ea891784 fix: clear audio noti
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-01 17:53:24 +08:00
bggRGjQaUbCoE
c98dbccbd7 fix: #359
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-01 16:40:40 +08:00
bggRGjQaUbCoE
4a68122c31 opt: live bottom control
Closes #349

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-01 13:52:26 +08:00
bggRGjQaUbCoE
9c4a52de87 opt: video toolbar icon
Closes #329

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-01 13:32:54 +08:00
bggRGjQaUbCoE
6c11140f43 fix: #352
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-01 09:36:41 +08:00
bggRGjQaUbCoE
11398ca64b opt: handle response
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-01 09:36:41 +08:00
bggRGjQaUbCoE
312ce6e639 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 21:54:53 +08:00
bggRGjQaUbCoE
139b48c457 opt: dm widget
Closes #350

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 21:47:42 +08:00
bggRGjQaUbCoE
f1f478e193 fix: live: show statusbar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 21:47:42 +08:00
bggRGjQaUbCoE
1abaf3db3f fix: #343
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 21:47:42 +08:00
bggRGjQaUbCoE
f6bfbc3ed6 mod: video sheet panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 20:20:39 +08:00
bggRGjQaUbCoE
d2890d72e5 fix: #341 #343 #339
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 17:48:04 +08:00
bggRGjQaUbCoE
5c029c8f64 Revert "opt: video page"
This reverts commit 82030b8d06.
2025-02-28 17:30:50 +08:00
bggRGjQaUbCoE
c37a631df2 fix: #345
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 15:57:31 +08:00
bggRGjQaUbCoE
82030b8d06 opt: video page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 15:36:39 +08:00
bggRGjQaUbCoE
e362f75dac opt: slide gesture
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 13:10:14 +08:00
bggRGjQaUbCoE
3fecf7c0a4 fix: #339
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 11:26:03 +08:00
bggRGjQaUbCoE
801043468d fix: getPlayerKey
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 10:16:02 +08:00
bggRGjQaUbCoE
1b4f588671 fix: #339 #341
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 10:05:27 +08:00
bggRGjQaUbCoE
7ad48570f0 mod: fullscreen debounce
Closes #340

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-28 00:37:57 +08:00
bggRGjQaUbCoE
5b8c68303f fix: #337
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 23:52:54 +08:00
bggRGjQaUbCoE
87d3d0ca14 fix: video page v
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 21:28:14 +08:00
bggRGjQaUbCoE
b330440371 fix: video page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 21:00:49 +08:00
bggRGjQaUbCoE
2a173ef804 fix: #334
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 20:06:46 +08:00
bggRGjQaUbCoE
bceabae06f fix: video play
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 19:05:57 +08:00
bggRGjQaUbCoE
69667c135d feat: slide to dismiss subreply page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 18:05:34 +08:00
bggRGjQaUbCoE
587870ad71 fix: player key
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 16:52:33 +08:00
bggRGjQaUbCoE
609fab345a fix: video page v
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 16:26:54 +08:00
bggRGjQaUbCoE
29c47cee78 fix: #333
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 15:51:03 +08:00
bggRGjQaUbCoE
6a9795f561 opt: video page v
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 11:38:44 +08:00
bggRGjQaUbCoE
72e7f0aa9f Revert "fix: #333"
This reverts commit acfa384c0c.
2025-02-27 11:38:43 +08:00
bggRGjQaUbCoE
acfa384c0c fix: #333
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 09:51:21 +08:00
bggRGjQaUbCoE
c2d27ddd04 opt: video page v
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-27 09:28:43 +08:00
My-Responsitories
0a6950e34a enable http2 (#331) 2025-02-26 22:02:19 +08:00
My-Responsitories
1c3d77b95d opt: wbiSign (#332) 2025-02-26 22:01:38 +08:00
bggRGjQaUbCoE
fb11208bbe fix: video toolbar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-26 20:23:26 +08:00
bggRGjQaUbCoE
94f05127b6 fix: #330
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-26 19:53:06 +08:00
bggRGjQaUbCoE
25a3046c3c fix: video page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-26 18:53:31 +08:00
bggRGjQaUbCoE
f479fc37ba mod: follow tabbarview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-26 18:36:25 +08:00
bggRGjQaUbCoE
3ee19a8f08 mod: show followed user verif
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-26 18:27:50 +08:00
bggRGjQaUbCoE
b8d2ad68dd fix: video page v
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-26 17:43:35 +08:00
dom
8434c488da refa: vertical video page (#328)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-26 17:21:24 +08:00
bggRGjQaUbCoE
41f251ad50 fix: dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-26 11:39:56 +08:00
bggRGjQaUbCoE
8e99ff1173 mod: search: show user verf
Closes #322

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-25 18:27:15 +08:00
bggRGjQaUbCoE
a921b983f5 opt: cancel seek
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-25 18:03:26 +08:00
bggRGjQaUbCoE
81eeda0a68 mod: video: cancellable slide seek
Closes #319

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-25 16:53:35 +08:00
bggRGjQaUbCoE
1a54f61355 mod: player: enable long press feedback
Closes #318

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-25 16:53:01 +08:00
bggRGjQaUbCoE
382cd5b73d mod: error toast
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-25 15:20:52 +08:00
bggRGjQaUbCoE
e236485bc7 mod: listener
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-25 15:20:52 +08:00
bggRGjQaUbCoE
0e69e23606 mod: video tabbarview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-24 22:25:37 +08:00
bggRGjQaUbCoE
0ef85f2551 fix: search ctr
related #306

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-24 21:04:28 +08:00
My-Responsitories
8d3990124e revert genTraceId (#314) 2025-02-24 17:13:15 +08:00
bggRGjQaUbCoE
7f912a1781 mod: refresh
related #306

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-24 17:10:43 +08:00
bggRGjQaUbCoE
d9ae1dd97a fix: handle grpc response
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-24 16:31:04 +08:00
bggRGjQaUbCoE
307db51aec mod: filter goods reposted dyn
Closes #309

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-24 15:00:54 +08:00
My-Responsitories
347a704b54 replace grpc to dio (#313)
* replace grpc to dio

* load danmaku from grpc
2025-02-24 14:55:28 +08:00
My-Responsitories
9e242fb902 remove duplicate code (#312)
* remove duplicate code

* Update login.dart
2025-02-24 14:55:08 +08:00
bggRGjQaUbCoE
192cd60a4f fix: get video progress
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-24 10:31:14 +08:00
bggRGjQaUbCoE
a98d8511d6 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-23 21:03:05 +08:00
bggRGjQaUbCoE
811b79610c fix: live room
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-23 18:10:51 +08:00
bggRGjQaUbCoE
14129e8f21 mod: horizontal live room
Closes #62

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-23 17:15:25 +08:00
bggRGjQaUbCoE
16de044d3d mod: triple
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-23 15:10:31 +08:00
bggRGjQaUbCoE
e573a8a9c0 mod: fs
related #306

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-23 14:58:12 +08:00
bggRGjQaUbCoE
108648cabf mod: seek from url
related #208

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-23 11:28:13 +08:00
bggRGjQaUbCoE
8e4ce07d19 opt: parse scheme
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-23 11:05:50 +08:00
bggRGjQaUbCoE
09cebd70ae mod: seek from dm
Closes #208

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-23 10:43:35 +08:00
bggRGjQaUbCoE
6a615c408b opt: nav icon color
ref orz12/main
2025-02-22 20:37:51 +08:00
bggRGjQaUbCoE
9ebc054c8c opt: spring
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-22 20:17:38 +08:00
My-Responsitories
b2c520bd91 feat: custom spring (#304) 2025-02-22 17:56:36 +08:00
bggRGjQaUbCoE
6506afa732 mod: add expand dyn live panel option
Closes #302

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-22 16:35:25 +08:00
bggRGjQaUbCoE
d1c74b9389 Revert "mod: set user-agent"
This reverts commit 4c56fcd6a8.
2025-02-22 16:10:11 +08:00
bggRGjQaUbCoE
61ca7bc1cb opt: horizontal preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-22 12:08:36 +08:00
bggRGjQaUbCoE
f94cb2a4b5 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-21 21:27:19 +08:00
bggRGjQaUbCoE
4c56fcd6a8 mod: set user-agent
Closes #299

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-21 20:56:15 +08:00
bggRGjQaUbCoE
d5bb2ec165 opt: del fav folder
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-21 18:20:08 +08:00
bggRGjQaUbCoE
27bc68f264 opt: follow page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-21 18:18:16 +08:00
bggRGjQaUbCoE
516eed76b7 mod: video sheet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-21 18:04:31 +08:00
bggRGjQaUbCoE
4190c17cdc fix: #295
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-21 17:43:50 +08:00
bggRGjQaUbCoE
3d0fedfb61 mod: convert forEach
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-21 11:39:32 +08:00
bggRGjQaUbCoE
9d57deffb4 fix: filter dm midhash
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-21 11:17:54 +08:00
My-Responsitories
cc1951c721 fix regex & use set in uid (#296)
* fix regex count & use set in uid

* fix regex
2025-02-21 10:42:28 +08:00
bggRGjQaUbCoE
1cd8d4913d fix: #294
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-20 23:15:06 +08:00
bggRGjQaUbCoE
19890e29e9 mod: restore video duration
Closes #293

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-20 21:09:53 +08:00
bggRGjQaUbCoE
f759dba7da opt: filter danmaku
related #283

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-20 18:09:51 +08:00
bggRGjQaUbCoE
fb6f92a70b opt: #284
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-19 16:18:21 +08:00
bggRGjQaUbCoE
f22cad42d7 opt: filter data
Closes #283

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-19 15:42:24 +08:00
My-Responsitories
cfb6c674ea skip mcdn on parsing (#281) 2025-02-19 14:55:02 +08:00
bggRGjQaUbCoE
415c68a570 fix: typo
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-19 12:16:20 +08:00
bggRGjQaUbCoE
15b949bb9c mod: webview jump
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-19 11:42:05 +08:00
bggRGjQaUbCoE
316a9809e4 mod: delay checking dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-18 22:42:39 +08:00
bggRGjQaUbCoE
3f5aa03056 mod: insert dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-18 22:03:59 +08:00
bggRGjQaUbCoE
6bc33795a3 feat: create dyn antifraud
Closes #278

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-18 21:51:09 +08:00
bggRGjQaUbCoE
3191ae27a5 mod: repost panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-18 18:54:46 +08:00
bggRGjQaUbCoE
b25de52b9e feat: repost video
Closes #279

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-18 18:28:59 +08:00
bggRGjQaUbCoE
a08b4648d5 mod: try-catch biliSendCommAntifraud
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-18 17:07:00 +08:00
bggRGjQaUbCoE
e7a7c945de fix: biliSendCommAntifraud
related #275

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-18 15:48:39 +08:00
bggRGjQaUbCoE
571f358280 mod: user search widget
Closes #280

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-17 23:11:16 +08:00
bggRGjQaUbCoE
7ddc3adfaa feat: bili comm antifraud
Closes #275

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-17 21:31:55 +08:00
bggRGjQaUbCoE
957c326148 feat: anti goods reply
Closes #276

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-17 18:49:20 +08:00
bggRGjQaUbCoE
0b246d03a6 feat: anti goods dyn
Closes #277

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-17 18:49:03 +08:00
bggRGjQaUbCoE
5dd3ff32b6 fix: view forwarded dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 22:00:55 +08:00
bggRGjQaUbCoE
a48d262637 mod: show member coin/like archives(web)
Closes #265

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 20:55:41 +08:00
bggRGjQaUbCoE
b5d17b5161 mod: pay coins page
related #245

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 20:34:06 +08:00
bggRGjQaUbCoE
980733ba22 fix: member contribute page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 19:49:39 +08:00
bggRGjQaUbCoE
7043fdc35d mod: debug logout
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 15:09:53 +08:00
bggRGjQaUbCoE
81713a6bc4 mod: article: add action panel
related #235

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 15:00:49 +08:00
bggRGjQaUbCoE
959bcfaa30 mod: keep pgc index page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 13:37:34 +08:00
bggRGjQaUbCoE
fa465f792d opt: video width
Closes #267

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 13:37:34 +08:00
bggRGjQaUbCoE
74bf78b9cd feat: pgc index page
Closes #216

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 13:15:00 +08:00
bggRGjQaUbCoE
8c408e59f6 opt: post segment panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 09:25:33 +08:00
bggRGjQaUbCoE
25d27e42ed fix: #263
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-16 09:22:09 +08:00
bggRGjQaUbCoE
0f2b0cc5f2 Revert "fix: #263"
This reverts commit 84ed34f3a7.
2025-02-16 01:01:01 +08:00
bggRGjQaUbCoE
00ea34f45d opt: video bs
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 23:42:02 +08:00
bggRGjQaUbCoE
ec936c1821 opt: video bs
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 23:00:47 +08:00
bggRGjQaUbCoE
2ff84857e7 refa: video bottom sheet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 22:19:43 +08:00
bggRGjQaUbCoE
84ed34f3a7 fix: #263
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 21:07:56 +08:00
bggRGjQaUbCoE
f0508e1bc2 mod: disable version check when debug
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 20:40:39 +08:00
bggRGjQaUbCoE
8ea7bf36d7 fix: dyn detail: repost btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 20:22:47 +08:00
bggRGjQaUbCoE
8819461eed mod: dyn detail: add action panel
Closes #235

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 18:04:43 +08:00
bggRGjQaUbCoE
7c30668c87 fix: #261
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 16:41:49 +08:00
bggRGjQaUbCoE
a3424950ca fix: push dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 14:33:58 +08:00
bggRGjQaUbCoE
ebc42eb05e fix: logout
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 14:25:10 +08:00
bggRGjQaUbCoE
fc6ff44471 fix: ai conclusion
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 14:23:15 +08:00
bggRGjQaUbCoE
be03377449 fix: emote
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 13:52:12 +08:00
bggRGjQaUbCoE
e52934093a opt: member tab
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 13:30:50 +08:00
bggRGjQaUbCoE
ebfd98488e mod: show staff verf
Closes #259

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 13:26:08 +08:00
bggRGjQaUbCoE
6a68af77dc mod: member tab
Closes #260

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 13:14:25 +08:00
bggRGjQaUbCoE
e5c0fb7cb2 fix: in-app webview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 12:57:04 +08:00
bggRGjQaUbCoE
d9611cce80 opt: in-app webview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 12:26:44 +08:00
bggRGjQaUbCoE
4b48aba2ae opt: data parse
related #258

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 12:03:43 +08:00
bggRGjQaUbCoE
47fbb6cd0e opt: whisper: msg preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-15 00:06:35 +08:00
bggRGjQaUbCoE
dae71d427c feat: whisper: revoke msg
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-14 23:28:48 +08:00
bggRGjQaUbCoE
46bc2ceb78 mod: scheme match
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-14 21:52:57 +08:00
bggRGjQaUbCoE
6f98200179 mod: whisper pic type
Closes #253

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-14 21:35:15 +08:00
bggRGjQaUbCoE
a57b4c56a5 fix: #257
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-14 21:07:56 +08:00
bggRGjQaUbCoE
6c3062ba2d feat: pure black theme
Closes #254

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-14 16:10:22 +08:00
bggRGjQaUbCoE
064c8a9dfe mod: page observer
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-14 11:15:50 +08:00
bggRGjQaUbCoE
7dd47736fb opt: better url pattern
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-13 22:27:16 +08:00
bggRGjQaUbCoE
84cc65489f mod: scheme match
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-13 17:51:52 +08:00
bggRGjQaUbCoE
2b9cb54d91 opt: view from playlist
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-13 17:15:48 +08:00
dom
54c7fef217 opt: jump url (#246)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-13 16:53:40 +08:00
bggRGjQaUbCoE
ba74cb8c01 opt: video bottom control
Closes #244

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-13 15:04:44 +08:00
bggRGjQaUbCoE
675932aa69 mod: try-catch some requests
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 22:17:01 +08:00
bggRGjQaUbCoE
d996e0a7dd fix: #240
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 21:52:27 +08:00
bggRGjQaUbCoE
b6279f702a fix: #239
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 20:48:14 +08:00
bggRGjQaUbCoE
695a89b91a opt: view pgc section
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 16:14:59 +08:00
bggRGjQaUbCoE
09753b6bbd fix: #226
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 14:20:16 +08:00
bggRGjQaUbCoE
6502b97388 mod: pgc coin
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 13:24:46 +08:00
bggRGjQaUbCoE
95d84647b7 opt: coin page checkbox
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 12:59:30 +08:00
bggRGjQaUbCoE
8f5065332e fix: intro up verify
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 12:59:23 +08:00
bggRGjQaUbCoE
71c8cbb8da fix: #232
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 12:39:31 +08:00
bggRGjQaUbCoE
3217731486 mod: coin with like
Closes #231

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 12:20:06 +08:00
bggRGjQaUbCoE
a4e63fe0e8 mod: video intro: show detailed owner info
Closes #229

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-12 12:27:29 +08:00
bggRGjQaUbCoE
cdb8f6845c mod: history card menu
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-11 21:50:59 +08:00
bggRGjQaUbCoE
0a7d286c47 mod: reply2reply header
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-11 21:14:35 +08:00
bggRGjQaUbCoE
e17fd0071d mod: forwarded live dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-11 21:14:35 +08:00
bggRGjQaUbCoE
a9ba30b9b9 mod: show dyn gif emote
mod: emote tabbar

opt: video progress indicator

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-11 18:30:01 +08:00
bggRGjQaUbCoE
4267a3b8e0 mod: member archive: show progress
Closes #225

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-11 17:21:06 +08:00
bggRGjQaUbCoE
50022ae635 fix: whisper: null check
related #217

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-11 15:20:52 +08:00
bggRGjQaUbCoE
0991621152 mod: dyn action
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-11 14:53:17 +08:00
bggRGjQaUbCoE
192f8924c8 fix: #217
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-11 14:07:43 +08:00
bggRGjQaUbCoE
51a12d7266 mod: minor tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-11 12:05:21 +08:00
bggRGjQaUbCoE
1417fcda6e fix: seek anim
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 21:33:57 +08:00
bggRGjQaUbCoE
6114e6f033 opt: restore scale btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 19:58:44 +08:00
bggRGjQaUbCoE
bc2dbc59ce mod: video scale set
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 18:51:41 +08:00
bggRGjQaUbCoE
7c5075413e mod: add restore video scale button
related #222

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 18:31:22 +08:00
bggRGjQaUbCoE
52175b0b69 mod: show reply gif emote
Closes #212

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 16:22:48 +08:00
bggRGjQaUbCoE
f0a3515279 opt: search aid/bvid
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 13:46:41 +08:00
bggRGjQaUbCoE
3c2ccf7d40 mod: check av/bv search
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 13:31:50 +08:00
bggRGjQaUbCoE
abd01e1a27 fix: reply cv jump
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 13:14:40 +08:00
bggRGjQaUbCoE
0f63976a00 mod: reply jump
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 12:04:13 +08:00
bggRGjQaUbCoE
6817eb6e56 fix: reply jump url
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 11:13:55 +08:00
bggRGjQaUbCoE
a951d42623 mod: web down
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-10 11:13:54 +08:00
bggRGjQaUbCoE
8f5c2bf3ba mod: dyn article: show block type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-09 22:47:31 +08:00
bggRGjQaUbCoE
7744217d17 mod: grpc reply: jump to vote
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-09 20:59:58 +08:00
bggRGjQaUbCoE
a84c153bdd fix: later request
log #214

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-09 18:35:01 +08:00
bggRGjQaUbCoE
31a0a90ba4 mod: reply2relpy header
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-09 15:43:57 +08:00
bggRGjQaUbCoE
383ce777e3 mod: webview: handle download request
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-09 10:44:46 +08:00
bggRGjQaUbCoE
e7ac88ffb1 opt: reply2reply header
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-09 09:36:26 +08:00
bggRGjQaUbCoE
9657c77999 mod: push article
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-09 09:36:26 +08:00
bggRGjQaUbCoE
afd508f28b opt: persistent header
Closes #211

ref pilipala

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-09 00:25:25 +08:00
bggRGjQaUbCoE
634612c1a2 fix: article
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-08 23:50:17 +08:00
bggRGjQaUbCoE
76545397d4 mod: video push
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-08 23:22:25 +08:00
bggRGjQaUbCoE
d2f586a7f1 fix: push bangumi
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-08 23:08:58 +08:00
bggRGjQaUbCoE
7cfebcb6ed opt: webview to video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-08 22:56:43 +08:00
bggRGjQaUbCoE
9a3766e7b7 opt: webview to video
Closes #209

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-08 21:41:19 +08:00
bggRGjQaUbCoE
588a06bece opt: article content
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-07 17:40:45 +08:00
bggRGjQaUbCoE
e45a126862 fix: handleWebview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-07 16:39:33 +08:00
bggRGjQaUbCoE
a581945c9e feat: interactive video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-07 15:38:33 +08:00
bggRGjQaUbCoE
331fd0d619 mod: intro panel
opt: pgc page

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-07 15:24:03 +08:00
bggRGjQaUbCoE
c6e229d571 fix: replay
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-06 12:09:29 +08:00
bggRGjQaUbCoE
b2c3b1ff95 fix: #199
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-06 12:09:08 +08:00
bggRGjQaUbCoE
3fc12fcc09 mod: widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-05 16:55:10 +08:00
bggRGjQaUbCoE
e098631553 mod: dyn square type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-05 13:49:37 +08:00
bggRGjQaUbCoE
0fcd55755e mod: handleWebview
Closes #194

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-05 13:36:44 +08:00
bggRGjQaUbCoE
65e7c0c4f4 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-05 12:19:45 +08:00
bggRGjQaUbCoE
70aecd1e38 mod: view point
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-04 14:38:06 +08:00
bggRGjQaUbCoE
a40c773491 fix: interceptor
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-04 13:09:52 +08:00
bggRGjQaUbCoE
b4abb58a41 mod: seg bar, dyn decorate
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-04 11:33:23 +08:00
bggRGjQaUbCoE
e368436bc6 feat: reply: sync to dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-03 11:43:42 +08:00
bggRGjQaUbCoE
6c96b3a7f5 fix: check reply url
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-03 10:49:25 +08:00
bggRGjQaUbCoE
149f0c082d fix: reply2reply mode
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-03 10:20:30 +08:00
bggRGjQaUbCoE
994199b5a2 fix: check reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 23:44:47 +08:00
bggRGjQaUbCoE
8db3d80151 fix: onVideoDetailChange
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 22:29:25 +08:00
bggRGjQaUbCoE
93af1e7c44 opt: reply check
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 22:29:10 +08:00
dom
54e90bd986 feat: comment antifraud (#193)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 21:24:07 +08:00
bggRGjQaUbCoE
ca16551917 mod: dm chart height
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 17:18:21 +08:00
bggRGjQaUbCoE
f4977d2855 mod: def hardwareDecoding
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-01 18:04:16 +08:00
bggRGjQaUbCoE
bd91fb7c6d mod: show volume when hiding sysui for ios
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-01 17:59:22 +08:00
bggRGjQaUbCoE
e1805896f4 Revert "opt: dm chart"
This reverts commit 31a639400e.
2025-01-31 20:40:18 +08:00
bggRGjQaUbCoE
31a639400e opt: dm chart
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-31 16:20:59 +08:00
bggRGjQaUbCoE
d6b24561fa fix: dm chart x
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-31 13:32:07 +08:00
dom
7ba9646d38 feat: danmaku chart (#192)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-31 11:36:05 +08:00
bggRGjQaUbCoE
58a7cf1e75 fix: image preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 18:03:15 +08:00
bggRGjQaUbCoE
1a327198f7 opt: video: onreset
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 15:52:59 +08:00
bggRGjQaUbCoE
e4fe91ef92 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 14:22:20 +08:00
bggRGjQaUbCoE
afcf817c4f fix: video duration
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 13:57:21 +08:00
bggRGjQaUbCoE
21550815db fix: seek preview image
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 12:11:44 +08:00
bggRGjQaUbCoE
02af3a18ff opt: video seek preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 10:08:15 +08:00
bggRGjQaUbCoE
a5a13b45cf fix: seek preview index
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-29 23:45:10 +08:00
bggRGjQaUbCoE
0fd232ab3a feat: video seek preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-29 21:20:58 +08:00
bggRGjQaUbCoE
8d83143ca6 opt: fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-29 15:29:20 +08:00
bggRGjQaUbCoE
74452cd622 mod: save as livephoto for ios
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-29 12:07:00 +08:00
bggRGjQaUbCoE
cf2e8cec54 fix: horizontal preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-28 23:27:46 +08:00
bggRGjQaUbCoE
5231faf254 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-28 21:57:57 +08:00
bggRGjQaUbCoE
959d4de78a opt: image preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-28 18:33:40 +08:00
bggRGjQaUbCoE
f5d7dc6b6a feat: live photo
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-28 15:40:19 +08:00
bggRGjQaUbCoE
b761c35d10 mod: show pendant/decorate
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-28 10:18:57 +08:00
bggRGjQaUbCoE
7f3f7f6bdd mod: dyn author panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 21:50:35 +08:00
bggRGjQaUbCoE
c5877b7c5e feat: custom show dyn decorate
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 21:21:28 +08:00
bggRGjQaUbCoE
9e4187ef17 mod: fetch only-fans dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 18:01:32 +08:00
bggRGjQaUbCoE
bf7ce3e5a2 mod: delay reloading fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 17:04:25 +08:00
bggRGjQaUbCoE
2c55314491 fix: dialog title
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 16:21:00 +08:00
bggRGjQaUbCoE
d28efef672 feat: copy/move toview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 15:17:11 +08:00
bggRGjQaUbCoE
49b631d560 feat: copy/move fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 14:59:00 +08:00
bggRGjQaUbCoE
896510f852 mod: fav sheet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 13:39:33 +08:00
bggRGjQaUbCoE
1d8e469a46 feat: clean fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 13:26:31 +08:00
bggRGjQaUbCoE
caee40a5d9 mod: sr desc
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 11:52:45 +08:00
bggRGjQaUbCoE
7de051e6bb fix: skip listener
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-26 20:55:29 +08:00
463 changed files with 36632 additions and 24801 deletions

View File

@@ -1,23 +0,0 @@
---
name: Bug 反馈
about: 描述你所遇到的bug
title: "[Bug] "
labels: bug
assignees: ''
---
### 问题描述
请提供一个清晰而简明的问题描述。
### 复现步骤
请提供复现该问题所需的具体步骤。
### 预期行为
请描述你期望的正确行为或结果。
### 错误日志
请提供设置->关于->错误日志中的内容粘贴在下方代码框中。如果没有请提供您的app版本号、系统版本、设备型号等相关信息。
### 相关信息
请补充截图、录屏、BV号等其他有助于解决问题的信息。

55
.github/ISSUE_TEMPLATE/bug-反馈.yml vendored Normal file
View 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号等其他有助于解决问题的信息。

View File

@@ -1,20 +0,0 @@
---
name: 功能请求
about: 对于功能的一些建议
title: "[FR] "
labels: enhancement
assignees: ''
---
### 功能描述
请提供对所请求功能的清晰描述。
### 目标
请描述你希望通过这个功能实现的目标。
### 解决方案
如果你有任何关于如何实现这个功能的想法或建议,请在这里提供。
### 其他
请提供已实现该功能或类似功能的应用

43
.github/ISSUE_TEMPLATE/功能请求.yml vendored Normal file
View 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: 请提供已实现该功能或类似功能的应用

View File

@@ -47,6 +47,27 @@
## feat
- [x] 直播弹幕发送表情
- [x] 收藏夹排序
- [x] 稍后再看`未看`/`未看完`/`已看完`分类
- [x] WebDAV 备份/恢复设置
- [x] 保存评论/动态
- [x] 高级弹幕 by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] 取消/置顶评论
- [x] 记笔记
- [x] 多账号支持 by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] 屏蔽带货动态/评论
- [x] 互动视频
- [x] 发评/动态反诈
- [x] 高能进度条
- [x] 滑动跳转预览视频缩略图
- [x] Live Photo
- [x] 复制/移动/排序收藏夹/稍后再看视频
- [x] 超分辨率
- [x] 合并弹幕
- [x] 会员彩色弹幕
- [x] 播放全部/继续播放/倒序播放
- [x] Cookie登录
- [x] 显示视频分段信息
- [x] 调节字幕大小
- [x] 调节全屏弹幕大小
@@ -59,7 +80,6 @@
- [x] 评论楼中楼定位点击查看的评论
- [x] 评论楼中楼按热度/时间排序
- [x] 评论点踩
- [x] 显示ops专栏
- [x] 私信发图
- [x] 投币动画
- [x] 取消/追番,更新追番状态
@@ -73,8 +93,8 @@
- [x] 筛选搜索
- [x] 转发动态
- [x] 合集图片
- [x] 删除/置顶私信
- [x] 举报用户/评论/视频
- [x] 删除/置顶/撤回私信
- [x] 举报用户/评论/视频/动态
- [x] 删除/发布文本/图片动态
- [x] 其他
@@ -144,7 +164,6 @@
- [x] 音质选择(视视频而定)
- [x] 解码格式选择(视视频而定)
- [x] 弹幕
- [ ] 直播弹幕
- [x] 字幕
- [x] 记忆播放
- [x] 视频比例:高度/宽度适应、填充、包含等

View File

@@ -4,10 +4,13 @@ import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
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
@@ -21,6 +24,55 @@ class MainActivity : AudioServiceActivity() {
methodChannel.setMethodCallHandler { call, result ->
if (call.method == "back") {
back()
} else if (call.method == "biliSendCommAntifraud") {
try {
val action = call.argument<Int>("action") ?: 0
val oid = call.argument<Number>("oid") ?: 0L
val type = call.argument<Int>("type") ?: 0
val rpid = call.argument<Number>("rpid") ?: 0L
val root = call.argument<Number>("root") ?: 0L
val parent = call.argument<Number>("parent") ?: 0L
val ctime = call.argument<Number>("ctime") ?: 0L
val commentText = call.argument<String>("comment_text") ?: ""
val pictures = call.argument<String?>("pictures")
val sourceId = call.argument<String>("source_id") ?: ""
val uid = call.argument<Number>("uid") ?: 0L
val cookies = call.argument<List<String>>("cookies") ?: emptyList<String>()
val intent = Intent().apply {
component = ComponentName("icu.freedomIntrovert.biliSendCommAntifraud", "icu.freedomIntrovert.biliSendCommAntifraud.ByXposedLaunchedActivity")
putExtra("action", action)
putExtra("oid", oid.toLong())
putExtra("type", type)
putExtra("rpid", rpid.toLong())
putExtra("root", root.toLong())
putExtra("parent", parent.toLong())
putExtra("ctime", ctime.toLong())
putExtra("comment_text", commentText)
if(pictures != null)
putExtra("pictures", pictures)
putExtra("source_id", sourceId)
putExtra("uid", uid.toLong())
putStringArrayListExtra("cookies", ArrayList(cookies))
}
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()
}

View File

@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp"
android:width="108dp"
android:height="24dp"
android:width="24dp"
android:viewportWidth="108.0"
android:viewportHeight="108.0">
<path
android:fillColor="#FF5CB67B"
android:pathData="M56,54L39.78,54l2.22,-10.94h14c3.02,0 5.47,2.45 5.47,5.47 0,3.02 -2.45,5.47 -5.47,5.47zM56,35.77h-9.62l-7.13,36.45h7.51L48.92,61.29h7.08c7.05,0 12.76,-5.71 12.76,-12.76 0,-7.05 -5.71,-12.76 -12.76,-12.76z"
android:pathData="M57.54,54L28.82,54l3.93,-19.36h24.78c5.35,0 9.68,4.33 9.68,9.68 0,5.35 -4.33,9.68 -9.68,9.68zM57.54,21.73L40.5,21.73L27.88,86.27h13.3l3.83,-19.36h12.54c12.48,0 22.59,-10.11 22.59,-22.59 0,-12.48 -10.11,-22.59 -22.59,-22.59z"
android:strokeWidth="0.252073"
android:fillType="evenOdd" />
</vector>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/*" />

View File

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 B

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/images/lv/lv6_s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -23,246 +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 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 const urlPattern =
r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]';
static get goodsUrlPrefix => "https://gaoneng.bilibili.com/tetris";
// 超分辨率滤镜
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"}
];
}

View File

@@ -87,6 +87,7 @@ class DynamicCardSkeleton extends StatelessWidget {
],
),
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [

View File

@@ -39,51 +39,55 @@ class VideoCardHSkeleton extends StatelessWidget {
),
// VideoContent(videoItem: videoItem)
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 200,
height: 11,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 150,
height: 13,
),
const Spacer(),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 100,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Row(
children: [
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
margin: const EdgeInsets.only(right: 8),
),
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
),
],
)
],
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color:
Theme.of(context).colorScheme.onInverseSurface,
width: 200,
height: 11,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color:
Theme.of(context).colorScheme.onInverseSurface,
width: 150,
height: 13,
),
const Spacer(),
Container(
color:
Theme.of(context).colorScheme.onInverseSurface,
width: 100,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Row(
children: [
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
margin: const EdgeInsets.only(right: 8),
),
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
),
],
)
],
),
),
)),
),
],
),
);

View File

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

View File

@@ -1,3 +1,5 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/dynamics/article_content_model.dart';
import 'package:PiliPlus/utils/extension.dart';
@@ -9,7 +11,9 @@ Widget articleContent({
required BuildContext context,
required List<ArticleContentModel> list,
Function(List<String>, int)? callback,
required double maxWidth,
}) {
debugPrint('articleContent');
List<String>? imgList = list
.where((item) => item.pic != null)
.toList()
@@ -55,30 +59,28 @@ Widget articleContent({
),
);
} else if (item.pic != null) {
return LayoutBuilder(
builder: (context, constraints) => Hero(
tag: item.pic!.pics!.first.url!,
child: GestureDetector(
onTap: () {
if (callback != null) {
callback(
imgList,
imgList.indexOf(item.pic!.pics!.first.url!),
);
} else {
context.imageView(
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
imgList: imgList,
);
}
},
child: NetworkImgLayer(
width: constraints.maxWidth,
height: constraints.maxWidth *
item.pic!.pics!.first.height! /
item.pic!.pics!.first.width!,
src: item.pic!.pics!.first.url,
),
return Hero(
tag: item.pic!.pics!.first.url!,
child: GestureDetector(
onTap: () {
if (callback != null) {
callback(
imgList,
imgList.indexOf(item.pic!.pics!.first.url!),
);
} else {
context.imageView(
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
imgList: imgList.map((url) => SourceModel(url: url)).toList(),
);
}
},
child: NetworkImgLayer(
width: maxWidth,
height: maxWidth *
item.pic!.pics!.first.height! /
item.pic!.pics!.first.width!,
src: item.pic!.pics!.first.url,
),
),
);

View File

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

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
class PBadge extends StatelessWidget {
@@ -12,10 +13,12 @@ class PBadge extends StatelessWidget {
final double? fs;
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,
@@ -26,10 +29,16 @@ class PBadge extends StatelessWidget {
this.fs = 11,
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;
@@ -38,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);
@@ -63,14 +73,17 @@ 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,
style: TextStyle(
height: 1,
fontSize: fs ?? fontSize,

View File

@@ -19,8 +19,16 @@ class CustomSliverPersistentHeaderDelegate
//创建child子组件
//shrinkOffsetchild偏移值minExtent~maxExtent
//overlapsContentSliverPersistentHeader覆盖其他子组件返回true否则返回false
return ColoredBox(
color: bgColor,
return DecoratedBox(
decoration: BoxDecoration(
color: bgColor,
boxShadow: [
BoxShadow(
color: bgColor,
offset: const Offset(0, -2),
),
],
),
child: child,
);
}

View File

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

View File

@@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void showConfirmDialog({
required BuildContext context,
required String title,
String? content,
required VoidCallback onConfirm,
}) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(title),
content: content == null ? null : Text(content),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () {
Get.back();
onConfirm();
},
child: Text('确认'),
),
],
);
},
);
}
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);
},
)
],
),
));
}

View File

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

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

View File

@@ -1,4 +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';
@@ -11,6 +15,7 @@ Widget htmlRender({
required double constrainedWidth,
Function(List<String>, int)? callback,
}) {
debugPrint('htmlRender');
return SelectionArea(
child: Html(
data: htmlContent,
@@ -25,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 ||
@@ -46,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(
@@ -54,7 +65,7 @@ Widget htmlRender({
callback([imgUrl], 0);
} else {
context.imageView(
imgList: [imgUrl],
imgList: [SourceModel(url: imgUrl)],
);
}
},

View File

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

View File

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

View File

@@ -2,9 +2,12 @@ import 'dart:math';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel, SourceType;
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/nine_grid_view.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
class ImageModel {
@@ -12,19 +15,23 @@ class ImageModel {
required this.width,
required this.height,
required this.url,
this.liveUrl,
});
dynamic width;
dynamic height;
String url;
String? liveUrl;
bool? _isLongPic;
bool? _isLivePhoto;
dynamic get safeWidth => width ?? 1;
dynamic get safeHeight => height ?? 1;
bool get isLongPic => _isLongPic ??= (safeHeight / safeWidth) > (22 / 9);
bool get isLivePhoto => _isLivePhoto ??= liveUrl?.isNotEmpty == true;
}
Widget imageview(
Widget imageView(
double maxWidth,
List<ImageModel> picArr, {
VoidCallback? onViewImage,
@@ -83,6 +90,17 @@ Widget imageview(
);
}
late final enableLivePhoto = GStorage.enableLivePhoto;
int parseSize(size) {
return switch (size) {
int() => size,
double() => size.round(),
String() => int.tryParse(size) ?? 1,
_ => 1,
};
}
return NineGridView(
type: NineGridType.weiBo,
margin: const EdgeInsets.only(top: 6),
@@ -102,7 +120,19 @@ Widget imageview(
onViewImage?.call();
context.imageView(
initialPage: index,
imgList: picArr.map((item) => item.url).toList(),
imgList: picArr.map(
(item) {
bool isLive = item.isLivePhoto && enableLivePhoto;
return SourceModel(
sourceType:
isLive ? SourceType.livePhoto : SourceType.networkImage,
url: item.url,
liveUrl: isLive ? item.liveUrl : null,
width: isLive ? parseSize(item.width) : null,
height: isLive ? parseSize(item.height) : null,
);
},
).toList(),
onDismissed: onDismissed,
);
}
@@ -143,7 +173,14 @@ Widget imageview(
},
),
),
if (picArr[index].isLongPic)
if (picArr[index].isLivePhoto)
const PBadge(
text: 'Live',
right: 8,
bottom: 8,
type: 'gray',
)
else if (picArr[index].isLongPic)
const PBadge(
text: '长图',
right: 8,

View File

@@ -1,18 +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:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:status_bar_control/status_bar_control.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'interactive_viewer_boundary.dart';
import 'interactive_viewer.dart' as custom;
@@ -33,6 +31,24 @@ typedef IndexedFocusedWidgetBuilder = Widget Function(
typedef IndexedTagStringBuilder = String Function(int index);
enum SourceType { fileImage, networkImage, livePhoto }
class SourceModel {
final SourceType sourceType;
final String url;
final String? liveUrl;
final int? width;
final int? height;
const SourceModel({
this.sourceType = SourceType.networkImage,
required this.url,
this.liveUrl,
this.width,
this.height,
});
}
class InteractiveviewerGallery<T> extends StatefulWidget {
const InteractiveviewerGallery({
super.key,
@@ -45,17 +61,14 @@ class InteractiveviewerGallery<T> extends StatefulWidget {
this.onDismissed,
this.setStatusBar,
this.onClose,
this.isFile,
});
final bool? isFile;
final VoidCallback? onClose;
final ValueChanged? onClose;
final bool? setStatusBar;
/// The sources to show.
final List<String> sources;
final List<SourceModel> sources;
/// The index of the first source in [sources] to show.
final int initIndex;
@@ -92,17 +105,14 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
late Offset _doubleTapLocalPosition;
int? currentIndex;
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();
@@ -110,38 +120,51 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
)..addListener(() {
_transformationController!.value =
_animation?.value ?? Matrix4.identity();
});
)..addListener(listener);
currentIndex = widget.initIndex;
if (widget.setStatusBar != false) {
setStatusBar();
}
if (widget.sources[currentIndex.value].sourceType == SourceType.livePhoto) {
_onPlay(currentIndex.value);
}
}
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,
);
}
}
if (widget.isFile != true) {
for (int index = 0; index < widget.sources.length; index++) {
for (int index = 0; index < widget.sources.length; index++) {
if (widget.sources[index].sourceType == SourceType.networkImage) {
CachedNetworkImageProvider(_getActualUrl(index)).evict();
}
}
@@ -201,14 +224,22 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
}
void _onPlay(int index) {
_player ??= Player();
_videoController ??= VideoController(_player!);
_player!.open(Media(widget.sources[index].liveUrl!));
}
/// When the page view changed its page, the source will animate back into the
/// original scale if it was scaled up.
///
/// Additionally the swipe up / down to dismiss gets enabled.
void _onPageChanged(int page) {
setState(() {
currentIndex = page;
});
_player?.pause();
currentIndex.value = page;
if (widget.sources[page].sourceType == SourceType.livePhoto) {
_onPlay(page);
}
widget.onPageChanged?.call(page);
if (_transformationController!.value != Matrix4.identity()) {
// animate the reset for the transformation of the interactive viewer
@@ -224,19 +255,24 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
}
String _getActualUrl(int index) => _thumbList[index] && _quality != 100
? '${widget.sources[index]}@${_quality}q.webp'.http2https
: widget.sources[index].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) {
widget.onClose!();
widget.onClose!(false);
} else {
Get.back();
widget.onDismissed?.call(_pageController!.page!.floor());
}
}
Player? _player;
VideoController? _videoController;
@override
Widget build(BuildContext context) {
return Stack(
@@ -272,12 +308,15 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_doubleTapLocalPosition = details.localPosition;
},
onDoubleTap: onDoubleTap,
onLongPress: widget.isFile == true ? null : onLongPress,
onLongPress:
widget.sources[index].sourceType == SourceType.fileImage
? null
: onLongPress,
child: widget.itemBuilder != null
? widget.itemBuilder!(
context,
index,
index == currentIndex,
index == currentIndex.value,
_enablePageView,
)
: _itemBuilder(index),
@@ -321,51 +360,70 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
if (widget.sources.length > 1)
Align(
alignment: Alignment.center,
child: Text(
"${currentIndex! + 1}/${widget.sources.length}",
style: const TextStyle(color: Colors.white),
child: Obx(
() => Text(
"${currentIndex.value + 1}/${widget.sources.length}",
style: const TextStyle(color: Colors.white),
),
),
),
if (widget.isFile != true)
if (widget.sources[currentIndex.value].sourceType !=
SourceType.fileImage)
Align(
alignment: Alignment.centerRight,
child: PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem(
value: 0,
onTap: () =>
onShareImg(widget.sources[currentIndex!]),
onTap: () => DownloadUtils.onShareImg(
widget.sources[currentIndex.value].url),
child: const Text("分享图片"),
),
PopupMenuItem(
value: 1,
onTap: () {
Utils.copyText(widget.sources[currentIndex!]);
Utils.copyText(
widget.sources[currentIndex.value].url);
},
child: const Text("复制链接"),
),
PopupMenuItem(
value: 2,
onTap: () {
DownloadUtils.downloadImg(
context,
[widget.sources[currentIndex!]],
this.context,
[widget.sources[currentIndex.value].url],
);
},
child: const Text("保存图片"),
),
if (widget.sources.length > 1)
PopupMenuItem(
value: 3,
onTap: () {
DownloadUtils.downloadImg(
context,
widget.sources,
this.context,
widget.sources
.map((item) => item.url)
.toList(),
);
},
child: const Text("保存全部图片"),
),
if (widget.sources[currentIndex.value].sourceType ==
SourceType.livePhoto)
PopupMenuItem(
onTap: () {
DownloadUtils.downloadLivePhoto(
context: this.context,
url: widget.sources[currentIndex.value].url,
liveUrl: widget
.sources[currentIndex.value].liveUrl!,
width:
widget.sources[currentIndex.value].width!,
height: widget
.sources[currentIndex.value].height!,
);
},
child: const Text("保存 Live Photo"),
),
];
},
child: const Icon(Icons.more_horiz, color: Colors.white),
@@ -379,51 +437,54 @@ 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(
tag: widget.sources[index],
child: widget.isFile == true
? Image(
filterQuality: FilterQuality.low,
image: FileImage(File(widget.sources[index])),
)
: CachedNetworkImage(
fadeInDuration: const Duration(milliseconds: 0),
fadeOutDuration: const Duration(milliseconds: 0),
imageUrl: _getActualUrl(index),
// 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(() {
// _thumbList[index] = false;
// });
// });
// },
),
tag: widget.sources[index].url,
child: switch (widget.sources[index].sourceType) {
SourceType.fileImage => Image(
filterQuality: FilterQuality.low,
image: FileImage(File(widget.sources[index].url)),
),
SourceType.networkImage => CachedNetworkImage(
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
imageUrl: _getActualUrl(index),
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(() {
// _thumbList[index] = false;
// });
// });
// },
),
SourceType.livePhoto => Obx(() => currentIndex.value == index
? IgnorePointer(
child: Video(
controller: _videoController!,
fill: Colors.transparent,
),
)
: const SizedBox.shrink()),
},
),
);
}
@@ -487,7 +548,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
children: [
ListTile(
onTap: () {
onShareImg(widget.sources[currentIndex!]);
DownloadUtils.onShareImg(
widget.sources[currentIndex.value].url);
Get.back();
},
dense: true,
@@ -496,7 +558,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
ListTile(
onTap: () {
Get.back();
Utils.copyText(widget.sources[currentIndex!]);
Utils.copyText(widget.sources[currentIndex.value].url);
},
dense: true,
title: const Text('复制链接', style: TextStyle(fontSize: 14)),
@@ -505,8 +567,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
onTap: () {
Get.back();
DownloadUtils.downloadImg(
context,
[widget.sources[currentIndex!]],
this.context,
[widget.sources[currentIndex.value].url],
);
},
dense: true,
@@ -517,13 +579,32 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
onTap: () {
Get.back();
DownloadUtils.downloadImg(
context,
widget.sources,
this.context,
widget.sources.map((item) => item.url).toList(),
);
},
dense: true,
title: const Text('保存全部图片', style: TextStyle(fontSize: 14)),
),
if (widget.sources[currentIndex.value].sourceType ==
SourceType.livePhoto)
ListTile(
onTap: () {
Get.back();
DownloadUtils.downloadLivePhoto(
context: this.context,
url: widget.sources[currentIndex.value].url,
liveUrl: widget.sources[currentIndex.value].liveUrl!,
width: widget.sources[currentIndex.value].width!,
height: widget.sources[currentIndex.value].height!,
);
},
dense: true,
title: const Text(
'保存 Live Photo',
style: TextStyle(fontSize: 14),
),
),
],
),
);

View 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;
}

View File

@@ -1,493 +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 Container(
height: Utils.getSheetHeight(context),
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
Container(
height: 45,
padding: EdgeInsets.symmetric(
horizontal: widget.showTitle != false ? 14 : 6),
child: Row(
children: [
if (widget.showTitle != false)
Text(
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
style: Theme.of(context).textTheme.titleMedium,
),
StreamBuilder(
stream: _favStream?.stream,
builder: (context, snapshot) => snapshot.hasData
? mediumButton(
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
icon: _seasonFav == 1
? Icons.notifications_off_outlined
: Icons.notifications_active_outlined,
onPressed: () async {
dynamic result = await VideoHttp.seasonFav(
isFav: _seasonFav == 1,
seasonId: widget.season.id,
);
if (result['status']) {
SmartDialog.showToast(
'${_seasonFav == 1 ? '取消' : ''}订阅成功');
_seasonFav = _seasonFav == 1 ? 0 : 1;
_favStream?.add(_seasonFav);
} else {
SmartDialog.showToast(result['msg']);
}
},
)
: const SizedBox.shrink(),
),
mediumButton(
tooltip: '跳至顶部',
icon: Icons.vertical_align_top,
onPressed: () {
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0]
? 0
: _isList
? widget.season.sections[_ctr?.index].episodes
.length -
1
: episodes.length - 1,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
mediumButton(
tooltip: '跳至底部',
icon: Icons.vertical_align_bottom,
onPressed: () {
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0]
? _isList
? widget.season.sections[_ctr?.index].episodes
.length -
1
: episodes.length - 1
: 0,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
mediumButton(
tooltip: '跳至当前',
icon: Icons.my_location,
onPressed: () async {
if (_ctr != null && _ctr?.index != (_index)) {
_ctr?.animateTo(_index);
await Future.delayed(const Duration(milliseconds: 225));
}
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: currentIndex,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
if (widget.isSupportReverse == true)
if (!_isList)
_reverseButton
else
StreamBuilder(
stream: _indexStream?.stream,
initialData: _index,
builder: (context, snapshot) {
return snapshot.data == _index
? _reverseButton
: const SizedBox.shrink();
},
),
const Spacer(),
StreamBuilder(
stream: _indexStream?.stream,
initialData: _index,
builder: (context, snapshot) => mediumButton(
tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
icon: !reverse[snapshot.data]
? MdiIcons.sortNumericAscending
: MdiIcons.sortNumericDescending,
onPressed: () {
setState(() {
reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0];
});
},
),
),
if (widget.onClose != null)
mediumButton(
tooltip: '关闭',
icon: Icons.close,
onPressed: widget.onClose,
),
],
),
),
Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
if (_isList)
Material(
child: 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
? TabBarView(
controller: _ctr,
children: List.generate(
widget.season.sections.length,
(index) => _buildBody(
index, widget.season.sections[index].episodes),
),
)
: _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) => Material(
child: 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),
),
),
);
}

View File

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

View File

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

View File

@@ -410,7 +410,7 @@ class _NineGridViewState extends State<NineGridView> {
@override
Widget build(BuildContext context) {
Widget? child = Container();
Widget? child;
double? realWidth = widget.width;
double? realHeight = widget.height;
switch (widget.type) {

View File

@@ -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;
}

View File

@@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
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(),
),
),
],
);
}
}

View 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: '其他',
},
};
}

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

View 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;
}

View File

@@ -22,7 +22,7 @@ class Segment {
class SegmentProgressBar extends CustomPainter {
final List<Segment> segmentColors;
late double _defHeight;
double? _defHeight;
SegmentProgressBar({
required this.segmentColors,
@@ -42,6 +42,18 @@ class SegmentProgressBar extends CustomPainter {
if (segmentColors[i].title != null) {
double fontSize = 10;
_defHeight ??= (TextPainter(
text: TextSpan(
text: segmentColors[i].title,
style: TextStyle(
fontSize: fontSize,
),
),
textDirection: TextDirection.ltr,
)..layout())
.height +
2;
TextPainter getTextPainter() => TextPainter(
text: TextSpan(
text: segmentColors[i].title,
@@ -51,14 +63,12 @@ class SegmentProgressBar extends CustomPainter {
height: 1,
),
),
strutStyle: StrutStyle(height: 1, leading: 0),
strutStyle:
StrutStyle(leading: 0, height: 1, fontSize: fontSize),
textDirection: TextDirection.ltr,
)..layout();
TextPainter textPainter = getTextPainter();
if (i == 0) {
_defHeight = textPainter.height;
}
late double prevStart;
if (i != 0) {
@@ -75,7 +85,7 @@ class SegmentProgressBar extends CustomPainter {
canvas.drawRect(
Rect.fromLTRB(
0,
-_defHeight - 2,
-_defHeight!,
size.width,
0,
),
@@ -86,9 +96,9 @@ class SegmentProgressBar extends CustomPainter {
canvas.drawRect(
Rect.fromLTWH(
segmentStart,
-_defHeight - 2,
-_defHeight!,
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
size.height + _defHeight + 2,
size.height + _defHeight!,
),
paint,
);
@@ -98,7 +108,7 @@ class SegmentProgressBar extends CustomPainter {
: (segmentStart - prevStart - textPainter.width) / 2 +
prevStart +
1;
double textY = (-_defHeight - textPainter.height) / 2 - 1;
double textY = (-_defHeight! - textPainter.height) / 2;
textPainter.paint(canvas, Offset(textX, textY));
} else {
canvas.drawRect(

View File

@@ -39,7 +39,13 @@ class _SelfSizedHorizontalListState extends State<SelfSizedHorizontalList> {
WidgetsBinding.instance.addPostFrameCallback((v) => setState(() {}));
}
if (widget.itemCount == 0) return const SizedBox();
if (isInit) return Container(key: infoKey, child: widget.childBuilder(0));
if (isInit) {
return Container(
key: infoKey,
padding: widget.padding,
child: widget.childBuilder(0),
);
}
return SizedBox(
height: height,

View File

@@ -1,35 +0,0 @@
import 'package:flutter/material.dart';
import 'package:PiliPlus/utils/utils.dart';
Widget statDanMu({
required BuildContext context,
String? theme,
dynamic danmu,
String? size,
}) {
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 = 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!)}条弹幕',
)
],
);
}

View 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)}条弹幕';
}

View File

@@ -1,39 +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,
}) {
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 = 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" ? "浏览" : "播放"}',
),
],
);
}

View File

@@ -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,250 +35,286 @@ 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!)) {
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),
],
);
},
),
// 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;
}
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,
),
videoContent(context)
],
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),
],
),
),
),
),
),
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')
: '';
if (pubdate != '') pubdate += ' ';
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (videoItem.title is String)
Expanded(
child: Text(
videoItem.title as String,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
)
else
Expanded(
child: RichText(
overflow: TextOverflow.ellipsis,
maxLines: 2,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan(
children: [
for (final i in videoItem.title) ...[
TextSpan(
text: i['text'] as String,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.bodyMedium!
.fontSize,
letterSpacing: 0.3,
color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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,
),
]
],
),
),
],
),
),
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate)
Expanded(
flex: 0,
child: Text(
"$pubdate ${showOwner ? videoItem.owner.name : ''}",
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
)
else
Expanded(
child: Text(
videoItem.title,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 3),
Row(
children: [
if (showView) ...[
statView(
context: context,
theme: 'gray',
view: videoItem.stat.view as int,
),
const SizedBox(width: 8),
],
if (showDanmaku)
statDanMu(
context: context,
theme: 'gray',
danmu: videoItem.stat.danmu as int,
),
const Spacer(),
if (source == 'normal') const SizedBox(width: 24),
],
),
],
),
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate)
Expanded(
flex: 0,
child: Text(
"$pubdate ${showOwner ? videoItem.owner.name : ''}",
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
),
const SizedBox(height: 3),
Row(
children: [
if (showView) ...[
StatView(
context: context,
theme: 'gray',
value: videoItem.stat.viewStr,
),
const SizedBox(width: 8),
],
if (showDanmaku)
StatDanMu(
context: context,
theme: 'gray',
value: videoItem.stat.danmuStr,
),
const Spacer(),
if (source == 'normal') const SizedBox(width: 24),
],
),
],
),
);
}

View File

@@ -58,7 +58,7 @@ class VideoCardHGrpc extends StatelessWidget {
return;
}
try {
PiliScheme.routePush(Uri.parse(videoItem.smallCoverV5.base.uri));
PiliScheme.routePushFromUrl(videoItem.smallCoverV5.base.uri);
} catch (err) {
SmartDialog.showToast(err.toString());
}
@@ -111,7 +111,8 @@ class VideoCardHGrpc extends StatelessWidget {
},
),
),
videoContent(context)
const SizedBox(width: 10),
videoContent(context),
],
);
},
@@ -133,58 +134,43 @@ class VideoCardHGrpc extends StatelessWidget {
Widget videoContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...[
Expanded(
child: Text(
videoItem.smallCoverV5.base.title,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
videoItem.smallCoverV5.base.title,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
],
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate)
Text(
videoItem.smallCoverV5.rightDesc1,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const SizedBox(height: 3),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate)
Text(
videoItem.smallCoverV5.rightDesc2,
videoItem.smallCoverV5.rightDesc1,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
@@ -193,26 +179,36 @@ class VideoCardHGrpc extends StatelessWidget {
overflow: TextOverflow.clip,
),
),
// Row(
// children: [
// if (showView) ...[
// StatView(
// theme: 'gray',
// view: videoItem.stat.view as int,
// ),
// const SizedBox(width: 8),
// ],
// if (showDanmaku)
// StatDanMu(
// theme: 'gray',
// danmu: videoItem.stat.danmu as int,
// ),
// const Spacer(),
// if (source == 'normal') const SizedBox(width: 24),
// ],
// ),
],
),
const SizedBox(height: 3),
Text(
videoItem.smallCoverV5.rightDesc2,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
// Row(
// children: [
// if (showView) ...[
// StatView(
// theme: 'gray',
// view: videoItem.stat.view as int,
// ),
// const SizedBox(width: 8),
// ],
// if (showDanmaku)
// StatDanMu(
// theme: 'gray',
// danmu: videoItem.stat.danmu as int,
// ),
// const Spacer(),
// if (source == 'normal') const SizedBox(width: 24),
// ],
// ),
],
),
);
}

View File

@@ -1,11 +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';
@@ -18,10 +17,12 @@ class VideoCardHMemberVideo extends StatelessWidget {
required this.videoItem,
this.onTap,
this.bvid,
this.fromViewAid,
});
final Item videoItem;
final VoidCallback? onTap;
final dynamic bvid;
final String? fromViewAid;
@override
Widget build(BuildContext context) {
@@ -43,12 +44,12 @@ class VideoCardHMemberVideo extends StatelessWidget {
return;
}
}
if (videoItem.bvid == null || videoItem.firstCid == null) {
if (videoItem.bvid == null || videoItem.cid == null) {
return;
}
try {
Get.toNamed(
'/video?bvid=${videoItem.bvid}&cid=${videoItem.firstCid}',
Utils.toViewPage(
'bvid=${videoItem.bvid}&cid=${videoItem.cid}',
arguments: {
'heroTag': Utils.makeHeroTag(videoItem.bvid),
},
@@ -83,6 +84,25 @@ class VideoCardHMemberVideo extends StatelessWidget {
width: maxWidth,
height: maxHeight,
),
if (fromViewAid == videoItem.param)
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black54,
),
child: Center(
child: const Text(
'上次观看',
style: TextStyle(
color: Colors.white,
fontSize: 15,
letterSpacing: 1.5,
),
),
),
),
),
if (videoItem.badges?.isNotEmpty == true)
PBadge(
text: videoItem.badges!
@@ -90,8 +110,48 @@ class VideoCardHMemberVideo extends StatelessWidget {
.join('|'),
right: 6.0,
top: 6.0,
type: videoItem.badges!.first.text == '充电专属'
? 'error'
: 'primary',
),
if (videoItem.duration != null)
if (videoItem.history != null) ...[
Builder(builder: (context) {
try {
return Positioned(
left: 0,
right: 0,
bottom: 0,
child: videoProgressIndicator(
videoItem.history!['progress'] /
videoItem.history!['duration'],
),
);
} catch (_) {
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,
@@ -103,7 +163,8 @@ class VideoCardHMemberVideo extends StatelessWidget {
},
),
),
videoContent(context)
const SizedBox(width: 10),
videoContent(context),
],
);
},
@@ -125,66 +186,61 @@ class VideoCardHMemberVideo extends StatelessWidget {
Widget videoContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...[
Expanded(
child: Text(
// videoItem.season?['title'] ?? videoItem.title ?? '',
videoItem.title ?? '',
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: videoItem.bvid != null && videoItem.bvid == bvid
? FontWeight.bold
: null,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: videoItem.bvid != null && videoItem.bvid == bvid
? Theme.of(context).colorScheme.primary
: null,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
// videoItem.season?['title'] ?? videoItem.title ?? '',
videoItem.title,
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: videoItem.bvid != null && videoItem.bvid == bvid
? FontWeight.bold
: null,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: videoItem.bvid != null && videoItem.bvid == bvid
? Theme.of(context).colorScheme.primary
: null,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Text(
videoItem.season != null
? Utils.dateFormat(videoItem.season?['mtime'])
: videoItem.publishTimeText ?? '',
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const SizedBox(height: 3),
Row(
children: [
StatView(
context: context,
theme: 'gray',
// view: videoItem.season?['view_content'] ??
// videoItem.viewContent,
value: videoItem.stat.viewStr,
),
const SizedBox(width: 8),
StatDanMu(
context: context,
theme: 'gray',
// danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku,
value: videoItem.stat.danmuStr,
),
],
Text(
videoItem.season != null
? Utils.dateFormat(videoItem.season?['mtime'])
: videoItem.publishTimeText ?? '',
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const SizedBox(height: 3),
Row(
children: [
statView(
context: context,
theme: 'gray',
// view: videoItem.season?['view_content'] ??
// videoItem.viewContent,
view: videoItem.viewContent,
),
const SizedBox(width: 8),
statDanMu(
context: context,
theme: 'gray',
// danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku,
danmu: videoItem.danmaku,
),
],
),
],
),
),
],
),
);
}

View File

@@ -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,56 +30,19 @@ class VideoCardV extends StatelessWidget {
}
void onPushDetail(heroTag) async {
String goto = videoItem.goto;
String goto = videoItem.goto!;
switch (goto) {
case 'bangumi':
// if (videoItem.bangumiBadge == '电影') {
// SmartDialog.showToast('暂不支持电影观看');
// return;
// }
Utils.viewBangumi(epId: videoItem.param);
// SmartDialog.showLoading(msg: '资源获取中');
// var result = await SearchHttp.bangumiInfo(seasonId: null, epId: epId);
// SmartDialog.dismiss();
// if (result['status']) {
// var bangumiDetail = result['data'];
// EpisodeItem episode = result['data'].episodes.first;
// int? epId = result['data'].userStatus?.progress?.lastEpId;
// if (epId == null) {
// epId = episode.epId;
// } else {
// for (var item in result['data'].episodes) {
// if (item.epId == epId) {
// episode = item;
// break;
// }
// }
// }
// String bvid = episode.bvid!;
// int cid = episode.cid!;
// String pic = episode.cover!;
// String seasonId = bangumiDetail.seasonId;
// dynamic heroTag = Utils.makeHeroTag(cid);
// Get.toNamed(
// '/video?bvid=$bvid&cid=$cid&seasonId=$seasonId&epId=$epId',
// arguments: {
// 'pic': pic,
// 'heroTag': heroTag,
// 'videoType': SearchType.media_bangumi,
// },
// );
// } else {
// SmartDialog.showToast(result['msg']);
// }
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,
@@ -93,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;
}
}
@@ -133,8 +83,8 @@ class VideoCardV extends StatelessWidget {
}
break;
default:
SmartDialog.showToast(videoItem.goto);
Utils.handleWebview(videoItem.uri);
SmartDialog.showToast(goto);
Utils.handleWebview(videoItem.uri!);
}
}
@@ -152,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,
@@ -217,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,
@@ -261,7 +211,7 @@ class VideoCardV extends StatelessWidget {
),
const SizedBox(width: 2),
],
if (videoItem.isFollowed == 1) ...[
if (videoItem.isFollowed) ...[
const PBadge(
text: '已关注',
stack: 'normal',
@@ -297,27 +247,26 @@ 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(),
Expanded(
flex: 0,
child: RichText(
child: Text.rich(
maxLines: 1,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan(
TextSpan(
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
@@ -333,14 +282,13 @@ class VideoCardV extends StatelessWidget {
],
if (videoItem is RecVideoItemAppModel &&
videoItem.desc != null &&
videoItem.desc.contains(' · ')) ...<Widget>[
videoItem.desc!.contains(' · ')) ...<Widget>[
const Spacer(),
Expanded(
flex: 0,
child: RichText(
child: Text.rich(
maxLines: 1,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan(
TextSpan(
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
@@ -350,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),
]

View File

@@ -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';
@@ -21,56 +22,23 @@ class VideoCardVMemberHome extends StatelessWidget {
String goto = videoItem.goto ?? '';
switch (goto) {
case 'bangumi':
// if (videoItem.bangumiBadge == '电影') {
// SmartDialog.showToast('暂不支持电影观看');
// return;
// }
// int epId = videoItem.param;
Utils.viewBangumi(epId: videoItem.param);
// SmartDialog.showLoading(msg: '资源获取中');
// var result = await SearchHttp.bangumiInfo(seasonId: null, epId: epId);
// SmartDialog.dismiss();
// if (result['status']) {
// var bangumiDetail = result['data'];
// EpisodeItem episode = result['data'].episodes.first;
// int? epId = result['data'].userStatus?.progress?.lastEpId;
// if (epId == null) {
// epId = episode.epId;
// } else {
// for (var item in result['data'].episodes) {
// if (item.epId == epId) {
// episode = item;
// break;
// }
// }
// }
// String bvid = episode.bvid!;
// int cid = episode.cid!;
// String pic = episode.cover!;
// String seasonId = bangumiDetail.seasonId;
// dynamic heroTag = Utils.makeHeroTag(cid);
// Get.toNamed(
// '/video?bvid=$bvid&cid=$cid&seasonId=$seasonId&epId=$epId',
// arguments: {
// 'pic': pic,
// 'heroTag': heroTag,
// 'videoType': SearchType.media_bangumi,
// },
// );
// } else {
// SmartDialog.showToast(result['msg']);
// }
// break;
break;
case 'av':
if (videoItem.isPgc == true && videoItem.uri?.isNotEmpty == true) {
if (Utils.viewPgcFromUri(videoItem.uri!)) {
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,

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
@@ -21,16 +22,16 @@ class VideoCustomAction {
}
class VideoCustomActions {
dynamic videoItem;
BaseSimpleVideoItemModel videoItem;
BuildContext context;
late List<VideoCustomAction> actions;
VoidCallback? onRemove;
VideoCustomActions(this.videoItem, this.context, [this.onRemove]) {
actions = [
if ((videoItem.bvid as String?)?.isNotEmpty == true) ...[
if (videoItem.bvid?.isNotEmpty == true) ...[
VideoCustomAction(
videoItem.bvid,
videoItem.bvid!,
'copy',
Stack(
children: [
@@ -39,7 +40,7 @@ class VideoCustomActions {
],
),
() {
Utils.copyText(videoItem.bvid);
Utils.copyText(videoItem.bvid!);
},
),
VideoCustomAction(
@@ -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'] ?? '成功');
},

View File

@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
Widget videoProgressIndicator(double progress) => ClipRect(
clipper: ProgressClipper(),
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
child: LinearProgressIndicator(
minHeight: 10,
value: progress,
),
),
);
class ProgressClipper extends CustomClipper<Rect> {
@override
Rect getClip(Size size) {
return Rect.fromLTWH(0, 6, size.width, size.height - 6);
}
@override
bool shouldReclip(CustomClipper<Rect> oldClipper) {
return false;
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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-\u9fa5UP]");
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);
}
}

View File

@@ -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
@@ -121,13 +143,19 @@ class Api {
// up_mid num 目标用户mid 必要
// type num 目标内容属性 非必要 默认为全部 0全部 2视频稿件
// rid num 目标 视频稿件avid
static const String videoInFolder = '/x/v3/fav/folder/created/list-all';
static const String favFolder = '/x/v3/fav/folder/created/list-all';
static const String copyToview = '/x/v2/history/toview/copy';
static const String moveToview = '/x/v2/history/toview/move';
// 视频详情页 相关视频
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';
@@ -182,18 +210,6 @@ class Api {
static const String deleteFolder = '/x/v3/fav/folder/del';
/// 收藏夹 详情
/// 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';
@@ -209,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';
@@ -247,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)
@@ -255,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
@@ -264,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';
@@ -311,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';
@@ -349,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';
@@ -358,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';
@@ -393,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&copyright=-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';
@@ -409,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
@@ -458,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
@@ -609,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';
@@ -629,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';
@@ -652,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';
@@ -663,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';
@@ -684,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';
@@ -698,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
@@ -708,4 +726,42 @@ class Api {
/// 我的关注 - 正在直播
static const String getFollowingLive =
'${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following';
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';
}

View File

@@ -1,10 +1,58 @@
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';
import 'index.dart';
class BangumiHttp {
static Future<LoadingState> bangumiList({
static Future<LoadingState> pgcIndexResult({
required int page,
required Map<String, dynamic> params,
seasonType,
type,
indexType,
}) async {
dynamic res = await Request().get(
Api.pgcIndexResult,
queryParameters: {
...params,
if (seasonType != null) 'season_type': seasonType,
if (type != null) 'type': type,
if (indexType != null) 'index_type': indexType,
'page': page,
'pagesize': 21,
},
);
if (res.data['code'] == 0) {
return LoadingState.success(res.data['data']);
} else {
return LoadingState.error(res.data['message']);
}
}
static Future<LoadingState> pgcIndexCondition({
seasonType,
type,
indexType,
}) async {
dynamic res = await Request().get(
Api.pgcIndexCondition,
queryParameters: {
if (seasonType != null) 'season_type': seasonType,
if (type != null) 'type': type,
if (indexType != null) 'index_type': indexType,
},
);
if (res.data['code'] == 0) {
return LoadingState.success(Condition.fromJson(res.data['data']));
} else {
return LoadingState.error(res.data['message']);
}
}
static Future<LoadingState<List<BangumiListItemModel>?>> bangumiList({
int? page,
int? indexType,
}) async {
@@ -21,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']);
}

View File

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

View File

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

View File

@@ -1,34 +1,49 @@
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';
class DanmakaHttp {
class DanmakuHttp {
// 获取视频弹幕
static Future queryDanmaku({
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 DanmakaHttp {
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 DanmakaHttp {
//'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 DanmakaHttp {
if (response.statusCode != 200) {
return {
'status': false,
'data': [],
'msg': '弹幕发送失败,状态码:${response.statusCode}',
};
}
@@ -94,7 +108,6 @@ class DanmakaHttp {
} else {
return {
'status': false,
'data': [],
'msg': "${response.data['code']}: ${response.data['message']}",
};
}

View File

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

View File

@@ -1,11 +1,14 @@
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';
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,
@@ -14,7 +17,7 @@ class DynamicsHttp {
'type': type ?? 'all',
'timezone_offset': '-480',
'offset': offset,
'features': 'itemOpusStyle'
'features': 'itemOpusStyle,listOnlyfans'
};
if (mid != -1) {
data['host_mid'] = mid;
@@ -24,6 +27,15 @@ class DynamicsHttp {
if (res.data['code'] == 0) {
try {
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
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) {
return LoadingState.error(err.toString());
@@ -41,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']};
}
}
@@ -68,23 +76,29 @@ class DynamicsHttp {
'data': res.data['data'],
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
return {'status': false, 'msg': res.data['message']};
}
}
//
static Future dynamicDetail({
String? id,
dynamic id,
dynamic rid,
dynamic type,
bool clearCookie = false,
}) async {
var res = await Request().get(Api.dynamicDetail, queryParameters: {
'timezone_offset': -480,
'id': id,
'features': 'itemOpusStyle',
});
var res = await Request().get(
Api.dynamicDetail,
queryParameters: {
'timezone_offset': -480,
if (id != null) 'id': id,
if (rid != null) 'rid': rid,
if (type != null) 'type': type,
'features': 'itemOpusStyle,listOnlyfans',
},
options:
clearCookie ? Options(extra: {'account': AnonymousAccount()}) : null,
);
if (res.data['code'] == 0) {
try {
return {
@@ -94,14 +108,12 @@ class DynamicsHttp {
} catch (err) {
return {
'status': false,
'data': [],
'msg': err.toString(),
};
}
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}

View File

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

View File

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

View File

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

View File

@@ -1,128 +1,101 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:math' show Random;
import 'package:cookie_jar/cookie_jar.dart';
import 'package:PiliPlus/build_config.dart';
import 'package:PiliPlus/http/retry_interceptor.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/accounts/account_manager/account_mgr.dart';
import 'package:PiliPlus/utils/global_data.dart';
import 'package:archive/archive.dart';
import 'package:brotli/brotli.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:dio_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 'interceptor_anonymity.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(AnonymityInterceptor());
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,65 +103,89 @@ 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.interceptors.add(ApiInterceptor());
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));
}
// 日志拦截器 输出请求、响应内容
dio.interceptors.add(LogInterceptor(
request: false,
requestHeader: false,
responseHeader: false,
));
if (BuildConfig.isDebug) {
dio.interceptors.add(LogInterceptor(
request: false,
requestHeader: false,
responseHeader: false,
));
}
dio.transformer = BackgroundTransformer();
dio.options.validateStatus = (int? status) {
return status! >= 200 && status < 300 ||
HttpString.validateStatusCodes.contains(status);
return status! >= 200 && status < 300;
};
}
@@ -217,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(),
@@ -246,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(),
@@ -258,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,20 +290,26 @@ class Request {
token.cancel("cancelled");
}
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';
static String headerUa({type = 'mob'}) {
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;
}
}

View File

@@ -1,89 +0,0 @@
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 {
// @override
// void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// debugPrint("请求之前");
// // 在请求之前添加头部或认证信息
// options.headers['Authorization'] = 'Bearer token';
// options.headers['Content-Type'] = 'application/json';
// 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];
}

View File

@@ -1,51 +0,0 @@
// ignore_for_file: avoid_print
import 'dart:io';
import 'package:dio/dio.dart';
// import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../pages/mine/controller.dart';
import 'api.dart';
class AnonymityInterceptor 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) {
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}');
options.headers[HttpHeaders.cookieHeader] = "";
if (options.data != null && options.data.csrf != null) {
options.data.csrf = "";
}
break;
}
}
handler.next(options);
}
}

View File

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

View File

@@ -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;
}

View File

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

View File

@@ -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,41 +309,38 @@ 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 memberCardInfo({int? mid}) async {
var res = await Request()
.get(Api.memberCardInfo, queryParameters: {'mid': mid, 'photo': true});
var res = await Request().get(
Api.memberCardInfo,
queryParameters: {
'mid': mid,
'photo': false,
},
);
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,
@@ -367,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,
@@ -378,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 {
@@ -397,14 +382,17 @@ class MemberHttp {
}
// 用户动态
static Future<LoadingState> memberDynamic({String? offset, int? mid}) async {
static Future<LoadingState<DynamicsDataModel>> memberDynamic({
String? offset,
int? mid,
}) 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',
'features': 'itemOpusStyle',
'features': 'itemOpusStyle,listOnlyfans',
'platform': 'web',
'web_location': '333.999',
'dm_img_list': '[]',
@@ -416,7 +404,15 @@ class MemberHttp {
});
var res = await Request().get(Api.memberDynamic, queryParameters: params);
if (res.data['code'] == 0) {
return LoadingState.success(DynamicsDataModel.fromJson(res.data['data']));
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
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);
} else {
Map errMap = {
-352: '风控校验失败,请检查登录状态',
@@ -428,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 {
@@ -465,11 +464,7 @@ class MemberHttp {
.toList()
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
return {'status': false, 'msg': res.data['message']};
}
}
@@ -512,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']};
}
}
@@ -544,11 +535,7 @@ class MemberHttp {
.toList()
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
return {'status': false, 'msg': res.data['message']};
}
}
@@ -563,11 +550,7 @@ class MemberHttp {
.toList()
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
return {'status': false, 'msg': res.data['message']};
}
}
@@ -584,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 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,
@@ -610,24 +590,19 @@ class MemberHttp {
},
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': 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 {
'status': false,
'data': [],
'msg': res.data['message'],
};
return LoadingState.error(res.data['message']);
}
}
// 最近点赞
static Future 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,
@@ -643,16 +618,12 @@ class MemberHttp {
},
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
};
List<MemberCoinsDataModel>? list = (res.data['data']?['list'] as List?)
?.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
.toList();
return LoadingState.success(list);
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
return LoadingState.error(res.data['message']);
}
}
@@ -684,11 +655,7 @@ class MemberHttp {
debugPrint(err.toString());
}
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
return {'status': false, 'msg': res.data['message']};
}
}
@@ -699,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']};
}
}
@@ -724,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'],

View File

@@ -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,21 +110,19 @@ class MsgHttp {
'data': res.data['data'],
};
} else {
return {
'status': false,
'date': [],
'msg': res.data['message'],
};
return {'status': false, 'msg': res.data['message']};
}
}
static Future createDynamic({
dynamic mid,
dynamic dynIdStr, // repost
dynamic dynIdStr, // repost dyn
dynamic rid, // repost video
dynamic dynType,
dynamic rawText,
List? pics,
int? publishTime,
ReplyOption replyOption = ReplyOption.allow,
ReplyOption? replyOption,
int? privatePub,
}) async {
String csrf = await Request.getCsrf();
@@ -171,17 +145,21 @@ class MsgHttp {
}
]
},
if (dynIdStr == null)
if (replyOption != null || publishTime != null)
"option": {
if (publishTime != null) "timer_pub_time": publishTime,
if (replyOption == ReplyOption.close) "close_comment": 1,
if (replyOption == ReplyOption.choose) "up_choose_comment": 1,
if (replyOption == ReplyOption.close)
"close_comment": 1
else if (replyOption == ReplyOption.choose)
"up_choose_comment": 1,
},
"scene": dynIdStr != null
? 4
: pics != null
? 2
: 1,
"scene": rid != null
? 5
: dynIdStr != null
? 4
: pics != null
? 2
: 1,
if (privatePub != null)
'create_option': {
'private_pub': privatePub,
@@ -189,16 +167,27 @@ class MsgHttp {
if (pics != null) 'pics': pics,
"attach_card": null,
"upload_id":
"${mid}_${DateTime.now().millisecondsSinceEpoch ~/ 1000}_${Random().nextInt(9000) + 1000}",
"${rid != null ? 0 : mid}_${DateTime.now().millisecondsSinceEpoch ~/ 1000}_${Random().nextInt(9000) + 1000}",
"meta": {
"app_meta": {"from": "create.dynamic.web", "mobi_app": "web"}
}
},
if (dynIdStr != null) "web_repost_src": {"dyn_id_str": dynIdStr}
if (dynIdStr != null || rid != null)
"web_repost_src": {
if (dynIdStr != null) "dyn_id_str": dynIdStr,
if (rid != null)
"revs_id": {
"dyn_type": dynType,
"rid": rid,
}
}
},
);
if (res.data['code'] == 0) {
return {'status': true};
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
@@ -238,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 {
@@ -267,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,
@@ -293,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,
@@ -316,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,
@@ -336,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,
@@ -369,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,
@@ -404,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']};
}
}
@@ -446,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,
@@ -476,11 +484,7 @@ class MsgHttp {
debugPrint(err.toString());
}
} else {
return {
'status': false,
'date': [],
'msg': res.data['message'],
};
return {'status': false, 'msg': res.data['message']};
}
}
@@ -490,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,
@@ -508,7 +512,6 @@ class MsgHttp {
} else {
return {
'status': false,
'date': [],
'msg': "message: ${res.data['message']},"
" msg: ${res.data['msg']},"
" code: ${res.data['code']}",
@@ -541,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]'],
@@ -557,47 +560,42 @@ class MsgHttp {
'data': res.data['data'],
};
} else {
return {
'status': false,
'date': [],
'msg': "message: ${res.data['message']},"
" msg: ${res.data['msg']},"
" code: ${res.data['code']}",
};
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();
}
}

View File

@@ -1,8 +1,9 @@
import 'dart:io';
import 'package:PiliPlus/common/constants.dart';
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';
@@ -10,10 +11,16 @@ import 'package:dio/dio.dart';
import '../models/video/reply/data.dart';
import '../models/video/reply/emote.dart';
import 'api.dart';
import 'constants.dart';
import 'init.dart';
class ReplyHttp {
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,
@@ -21,15 +28,12 @@ class ReplyHttp {
required int type,
required int page,
int sort = 1,
required String banWordForReply,
required bool antiGoodsReply,
bool? enableFilter,
}) async {
Options? options = !isLogin
? Options(
headers: {HttpHeaders.cookieHeader: "buvid3= ; b_nut= ; sid= "})
: null;
var res = !isLogin
? await Request().get(
'${HttpString.apiBaseUrl}${Api.replyList}/main',
'${Api.replyList}/main',
queryParameters: {
'oid': oid,
'type': type,
@@ -37,10 +41,10 @@ class ReplyHttp {
'{"offset":"${nextOffset.replaceAll('"', '\\"')}"}',
'mode': sort + 2, //2:按时间排序3按热度排序
},
options: options,
options: isLogin.not ? _options : null,
)
: await Request().get(
'${HttpString.apiBaseUrl}${Api.replyList}',
Api.replyList,
queryParameters: {
'oid': oid,
'type': type,
@@ -48,22 +52,20 @@ class ReplyHttp {
'pn': page,
'ps': 20,
},
options: options,
options: isLogin.not ? _options : null,
);
if (res.data['code'] == 0) {
ReplyData replyData = ReplyData.fromJson(res.data['data']);
if (banWordForReply.isNotEmpty) {
if (enableFilter != false && replyRegExp.pattern.isNotEmpty) {
// topReplies
if (replyData.topReplies?.isNotEmpty == true) {
replyData.topReplies!.removeWhere((item) {
bool hasMatch = RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content?.message ?? '');
bool hasMatch = replyRegExp.hasMatch(item.content?.message ?? '');
// remove subreplies
if (hasMatch.not) {
if (item.replies?.isNotEmpty == true) {
item.replies!.removeWhere((item) =>
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content?.message ?? ''));
replyRegExp.hasMatch(item.content?.message ?? ''));
}
}
return hasMatch;
@@ -73,14 +75,43 @@ class ReplyHttp {
// replies
if (replyData.replies?.isNotEmpty == true) {
replyData.replies!.removeWhere((item) {
bool hasMatch = RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content?.message ?? '');
bool hasMatch = replyRegExp.hasMatch(item.content?.message ?? '');
// remove subreplies
if (hasMatch.not) {
if (item.replies?.isNotEmpty == true) {
item.replies!.removeWhere((item) =>
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content?.message ?? ''));
replyRegExp.hasMatch(item.content?.message ?? ''));
}
}
return hasMatch;
});
}
}
// antiGoodsReply
if (antiGoodsReply) {
// topReplies
if (replyData.topReplies?.isNotEmpty == true) {
replyData.topReplies!.removeWhere((item) {
bool hasMatch = needRemove(item);
// remove subreplies
if (hasMatch.not) {
if (item.replies?.isNotEmpty == true) {
item.replies!.removeWhere(needRemove);
}
}
return hasMatch;
});
}
// replies
if (replyData.replies?.isNotEmpty == true) {
replyData.replies!.removeWhere((item) {
bool hasMatch = needRemove(item);
// remove subreplies
if (hasMatch.not) {
if (item.replies?.isNotEmpty == true) {
item.replies!.removeWhere(needRemove);
}
}
return hasMatch;
@@ -93,34 +124,54 @@ class ReplyHttp {
}
}
static Future<LoadingState> replyListGrpc({
static Future<LoadingState<MainListReply>> replyListGrpc({
int type = 1,
required int oid,
required CursorReq cursor,
required String banWordForReply,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.mainList(type: type, oid: oid, cursor: cursor);
if (res['status']) {
MainListReply mainListReply = res['data'];
if (banWordForReply.isNotEmpty) {
// keyword filter
if (replyRegExp.pattern.isNotEmpty) {
// upTop
if (mainListReply.hasUpTop() &&
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(mainListReply.upTop.content.message)) {
replyRegExp.hasMatch(mainListReply.upTop.content.message)) {
mainListReply.clearUpTop();
}
// replies
if (mainListReply.replies.isNotEmpty) {
mainListReply.replies.removeWhere((item) {
bool hasMatch = RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content.message);
bool hasMatch = replyRegExp.hasMatch(item.content.message);
// remove subreplies
if (hasMatch.not) {
if (item.replies.isNotEmpty) {
item.replies.removeWhere((item) =>
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content.message));
item.replies.removeWhere(
(item) => replyRegExp.hasMatch(item.content.message));
}
}
return hasMatch;
});
}
}
// antiGoodsReply
if (antiGoodsReply) {
// upTop
if (mainListReply.hasUpTop() && needRemoveGrpc(mainListReply.upTop)) {
mainListReply.clearUpTop();
}
// replies
if (mainListReply.replies.isNotEmpty) {
mainListReply.replies.removeWhere((item) {
bool hasMatch = needRemoveGrpc(item);
// remove subreplies
if (hasMatch.not) {
if (item.replies.isNotEmpty) {
item.replies.removeWhere(needRemoveGrpc);
}
}
return hasMatch;
@@ -133,42 +184,80 @@ class ReplyHttp {
}
}
// ref BiliRoamingX
static bool needRemoveGrpc(ReplyInfo reply) {
if ((reply.content.url.isNotEmpty &&
reply.content.url.values.any((url) {
return url.hasExtra() &&
(url.extra.goodsCmControl == 1 ||
url.extra.goodsItemId != 0 ||
url.extra.goodsPrefetchedCache.isNotEmpty);
})) ||
reply.content.message.contains(Constants.goodsUrlPrefix)) {
return true;
}
return false;
}
static bool needRemove(ReplyItemModel reply) {
try {
if ((reply.content?.jumpUrl?.isNotEmpty == true &&
reply.content!.jumpUrl!.values.any((url) {
return url['extra'] != null &&
(url['extra']['goods_cm_control'] == 1 ||
url['extra']['goods_item_id'] != 0 ||
url['extra']['goods_prefetched_cache'].isNotEmpty);
})) ||
reply.content?.message?.contains(Constants.goodsUrlPrefix) == true) {
return true;
}
} catch (_) {}
return false;
}
@Deprecated('Use replyReplyListGrpc instead')
static Future<LoadingState> replyReplyList({
required bool isLogin,
required int oid,
required int root,
required int pageNum,
required int type,
int sort = 1,
required String banWordForReply,
required bool antiGoodsReply,
bool? isCheck,
bool? filterBanWord,
}) async {
Options? options = GStorage.userInfo.get('userInfoCache') == null
? Options(
headers: {HttpHeaders.cookieHeader: "buvid3= ; b_nut= ; sid= "})
: null;
var res = await Request().get(
'${HttpString.apiBaseUrl}${Api.replyReplyList}',
Api.replyReplyList,
queryParameters: {
'oid': oid,
'root': root,
'pn': pageNum,
'type': type,
'sort': 1,
'csrf': await Request.getCsrf(),
if (isLogin) 'csrf': await Request.getCsrf(),
},
options: options,
options: isLogin.not ? _options : null,
);
if (res.data['code'] == 0) {
ReplyReplyData replyData = ReplyReplyData.fromJson(res.data['data']);
if (banWordForReply.isNotEmpty) {
if (filterBanWord != false && replyRegExp.pattern.isNotEmpty) {
if (replyData.replies?.isNotEmpty == true) {
replyData.replies!.removeWhere((item) =>
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content?.message ?? ''));
replyData.replies!.removeWhere(
(item) => replyRegExp.hasMatch(item.content?.message ?? ''));
}
}
if (antiGoodsReply) {
if (replyData.replies?.isNotEmpty == true) {
replyData.replies!.removeWhere(needRemove);
}
}
return LoadingState.success(replyData);
} else {
return LoadingState.error(res.data['message']);
return LoadingState.error(
isCheck == true
? '${res.data['code']}${res.data['message']}'
: res.data['message'],
);
}
}
@@ -178,7 +267,7 @@ class ReplyHttp {
required int root,
required int rpid,
required CursorReq cursor,
required String banWordForReply,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.detailList(
type: type,
@@ -189,11 +278,15 @@ class ReplyHttp {
);
if (res['status']) {
DetailListReply detailListReply = res['data'];
if (banWordForReply.isNotEmpty) {
if (replyRegExp.pattern.isNotEmpty) {
if (detailListReply.root.replies.isNotEmpty) {
detailListReply.root.replies.removeWhere((item) =>
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content.message));
detailListReply.root.replies.removeWhere(
(item) => replyRegExp.hasMatch(item.content.message));
}
}
if (antiGoodsReply) {
if (detailListReply.root.replies.isNotEmpty) {
detailListReply.root.replies.removeWhere(needRemoveGrpc);
}
}
return LoadingState.success(detailListReply);
@@ -208,7 +301,7 @@ class ReplyHttp {
required int root,
required int rpid,
required CursorReq cursor,
required String banWordForReply,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.dialogList(
type: type,
@@ -219,11 +312,15 @@ class ReplyHttp {
);
if (res['status']) {
DialogListReply dialogListReply = res['data'];
if (banWordForReply.isNotEmpty) {
if (replyRegExp.pattern.isNotEmpty) {
if (dialogListReply.replies.isNotEmpty) {
dialogListReply.replies.removeWhere((item) =>
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content.message));
dialogListReply.replies.removeWhere(
(item) => replyRegExp.hasMatch(item.content.message));
}
}
if (antiGoodsReply) {
if (dialogListReply.replies.isNotEmpty) {
dialogListReply.replies.removeWhere(needRemoveGrpc);
}
}
return LoadingState.success(dialogListReply);
@@ -254,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']};
}
}
@@ -282,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',
@@ -302,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']};
}
}
}

View 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);
}
}
}

View File

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

View File

@@ -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,73 @@ 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 {
var res = await Request().post(
Api.cleanFav,
data: {
'media_id': mediaId,
'platform': 'web',
'csrf': await Request.getCsrf(),
},
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 deleteFolder({
required List<dynamic> mediaIds,
}) async {
@@ -124,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,
@@ -149,15 +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([]);
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,
@@ -169,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,
@@ -206,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']};
}
}
@@ -242,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,
@@ -281,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(),
},
);
@@ -298,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: {
@@ -319,7 +406,7 @@ class UserHttp {
static Future hasFollow(int mid) async {
var res = await Request().get(
Api.hasFollow,
Api.relation,
queryParameters: {
'fid': mid,
},
@@ -332,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',
@@ -374,7 +461,7 @@ class UserHttp {
}
// 我的订阅
static Future<LoadingState> userSubFolder({
static Future<LoadingState<List<SubFolderItemData>?>> userSubFolder({
required int mid,
required int pn,
required int ps,
@@ -416,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,
@@ -474,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,
@@ -545,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']};
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,9 @@
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';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
@@ -14,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';
@@ -30,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
@@ -52,15 +53,11 @@ void main() async {
],
);
}
if (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}
@@ -118,6 +115,8 @@ class MyApp extends StatelessWidget {
Box get setting => GStorage.setting;
static ThemeData? darkThemeData;
@override
Widget build(BuildContext context) {
// 主题色
@@ -177,17 +176,18 @@ class MyApp extends StatelessWidget {
// tones: FlexTones.soft(Brightness.dark),
);
}
// 图片缓存
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
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,
@@ -204,86 +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,
}) {
return 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,
),
);
}
}
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;
}
}

View File

@@ -175,6 +175,7 @@ class EpisodeItem {
this.subtitle,
this.title,
this.vid,
this.showTitle,
});
int? aid;
@@ -205,6 +206,7 @@ class EpisodeItem {
String? subtitle;
String? title;
String? vid;
String? showTitle;
EpisodeItem.fromJson(Map<String, dynamic> json) {
aid = json['aid'];
@@ -235,6 +237,7 @@ class EpisodeItem {
subtitle = json['subtitle'];
title = json['title'];
vid = json['vid'];
showTitle = json['show_title'];
}
}

View File

@@ -1,3 +1,6 @@
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
class BangumiListDataModel {
BangumiListDataModel({
this.hasNext,
@@ -8,25 +11,23 @@ class BangumiListDataModel {
});
int? hasNext;
List? list;
List<BangumiListItemModel>? list;
int? num;
int? size;
int? total;
BangumiListDataModel.fromJson(Map<String, dynamic> json) {
hasNext = json['has_next'];
list = json['list'] != null
? json['list']
.map<BangumiListItemModel>((e) => BangumiListItemModel.fromJson(e))
.toList()
: [];
list = (json['list'] as List?)
?.map<BangumiListItemModel>((e) => BangumiListItemModel.fromJson(e))
.toList();
num = json['num'];
size = json['size'];
total = json['total'];
}
}
class BangumiListItemModel {
class BangumiListItemModel with MultiSelectData {
BangumiListItemModel({
this.badge,
this.badgeType,
@@ -46,6 +47,7 @@ class BangumiListItemModel {
this.title,
this.titleIcon,
this.progress,
this.newEp,
});
String? badge;
@@ -64,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'];
@@ -84,7 +88,7 @@ class BangumiListItemModel {
subTitle = json['sub_title'];
title = json['title'];
titleIcon = json['title_icon'];
newEp = json['new_ep'];
progress = json['progress'];
}
}

View File

@@ -0,0 +1,70 @@
class Condition {
List<Filter>? filter;
List<Order>? order;
Condition({
this.filter,
this.order,
});
Condition.fromJson(Map json) {
filter = (json['filter'] as List?)
?.map((item) => Filter.fromJson(item))
.toList();
order =
(json['order'] as List?)?.map((item) => Order.fromJson(item)).toList();
}
}
class Order {
String? field;
String? name;
String? sort;
Order({
this.field,
this.name,
this.sort,
});
Order.fromJson(Map json) {
field = json['field'];
name = json['name'];
sort = json['sort'];
}
}
class Filter {
String? field;
String? name;
List<Values>? values;
Filter({
this.field,
this.name,
this.values,
});
Filter.fromJson(Map json) {
field = json['field'];
name = json['name'];
values = (json['values'] as List?)
?.map((item) => Values.fromJson(item))
.toList();
}
}
class Values {
String? keyword;
String? name;
Values({
this.keyword,
this.name,
});
Values.fromJson(Map json) {
keyword = json['keyword'];
name = json['name'];
}
}

View 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,
};
}

View 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,
};
}

View 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(),
};
}

View 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,
};
}

View File

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

View File

@@ -8,7 +8,7 @@ class PostSegmentModel {
required this.category,
required this.actionType,
});
Pair<int, int> segment;
Pair<double, double> segment;
SegmentType category;
ActionType actionType;
}

Some files were not shown because too many files have changed in this diff Show More