Compare commits

...

269 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
56350b181f opt: findClosestNumber
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 20:36:54 +08:00
bggRGjQaUbCoE
5982fd312b fix: filter reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 20:20:18 +08:00
bggRGjQaUbCoE
4d35dfe2f0 mod: mine page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 20:12:56 +08:00
bggRGjQaUbCoE
0eac1b2c69 opt: findClosestNumber
Closes #120

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 20:09:08 +08:00
bggRGjQaUbCoE
89050c7ca8 chore: release 1.1.1
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 19:30:47 +08:00
bggRGjQaUbCoE
ae16771b5e feat: filter reply
Closes #118

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 19:28:52 +08:00
bggRGjQaUbCoE
847f42fee3 opt: reply2reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 19:28:48 +08:00
bggRGjQaUbCoE
8d4294ba75 feat: custom horizontal preview
Closes #117

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 19:28:48 +08:00
bggRGjQaUbCoE
0b9d4d970a opt: cdn test desc
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 19:28:48 +08:00
bggRGjQaUbCoE
34c024239d opt: mine page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 18:45:28 +08:00
bggRGjQaUbCoE
71daa6df29 opt: member info widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 18:27:24 +08:00
bggRGjQaUbCoE
20c1112a10 Update android.yml
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 14:23:21 +08:00
bggRGjQaUbCoE
31e8c36653 Update ios.yml 2025-01-08 14:19:42 +08:00
bggRGjQaUbCoE
e06a3d8f22 opt: login/logout
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 13:57:36 +08:00
bggRGjQaUbCoE
c77ceea262 mod: update request onerror
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 11:58:14 +08:00
bggRGjQaUbCoE
28b6b769b2 mod: add check update
Closes #112

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-08 11:28:32 +08:00
bggRGjQaUbCoE
57722eb579 opt: main page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 20:59:37 +08:00
bggRGjQaUbCoE
d4e381380a opt: msg badge
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 20:31:10 +08:00
bggRGjQaUbCoE
21fdcdb2bb opt: reply error widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 18:20:19 +08:00
bggRGjQaUbCoE
1a30e542a9 opt: unread badge
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 17:39:36 +08:00
bggRGjQaUbCoE
c1ce704e4e feat: home: show unread badge
Closes #107

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 17:05:38 +08:00
bggRGjQaUbCoE
30a5889215 mod: rank: filter like ratio
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 12:21:18 +08:00
bggRGjQaUbCoE
75a242de2a mod: hot: filter like ratio
Closes #108

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 12:18:13 +08:00
bggRGjQaUbCoE
a0afbb2615 mod: tmply disable grpc reply
Closes #114

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 12:00:38 +08:00
bggRGjQaUbCoE
da3c087ade fix: #115
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 11:41:35 +08:00
bggRGjQaUbCoE
4dc0389624 chore: rename to PiliPlus
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 11:20:44 +08:00
bggRGjQaUbCoE
488cb58b85 opt: speed test
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 11:03:24 +08:00
bggRGjQaUbCoE
f5b50ffcb0 feat: cdn speed test
Closes #105

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-07 10:26:29 +08:00
bggRGjQaUbCoE
d9474a79c1 opt: videopage: didpop
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 21:09:41 +08:00
bggRGjQaUbCoE
3a15353bc4 opt: multi del
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 18:03:09 +08:00
bggRGjQaUbCoE
b239737498 fix: vttSubtitlesIndex
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 17:55:17 +08:00
bggRGjQaUbCoE
5001f3b6d2 mod: sync flip/onlyPlayAudio from orz12/main
Closes #100

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 16:17:07 +08:00
bggRGjQaUbCoE
3d803cce9f opt: init play
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 14:32:15 +08:00
bggRGjQaUbCoE
d0046d0faf mod: partial revert
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 13:26:13 +08:00
bggRGjQaUbCoE
d59c364ba6 fix: auto play
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 12:13:31 +08:00
bggRGjQaUbCoE
fee161e99b mod: intro: author info widget
Closes #103

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 11:38:17 +08:00
bggRGjQaUbCoE
5a481dbaaf opt: query sponsorblock
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 11:16:51 +08:00
bggRGjQaUbCoE
f3279b4177 opt: reply item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 09:48:40 +08:00
bggRGjQaUbCoE
242fde92f6 opt: history item menu
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 09:26:48 +08:00
bggRGjQaUbCoE
a9c542ac4e fix: video title
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 09:08:52 +08:00
bggRGjQaUbCoE
4aebc0aac5 feat: sponsorblock: show video label
Closes #102

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 08:59:55 +08:00
bggRGjQaUbCoE
51bf59e329 opt: intro action
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 08:59:36 +08:00
bggRGjQaUbCoE
39716cc1d4 opt: requery video url
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 08:59:15 +08:00
bggRGjQaUbCoE
50cf99720b opt: listsheet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 08:58:56 +08:00
bggRGjQaUbCoE
214239a6f8 opt: heartbeat
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 08:58:24 +08:00
bggRGjQaUbCoE
0d63d6102f chore: update sponsorblock title
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 00:04:43 +08:00
bggRGjQaUbCoE
47e79ee7d8 opt: player
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-06 00:04:43 +08:00
bggRGjQaUbCoE
22e6e19500 mod: update def settings
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 23:27:44 +08:00
bggRGjQaUbCoE
8ae92b859f opt: make heartbeat when changing episode
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 21:04:35 +08:00
bggRGjQaUbCoE
78180a1dd1 opt: login
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 15:40:49 +08:00
bggRGjQaUbCoE
f47c500c5b fix: pgclabel data
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 15:25:47 +08:00
bggRGjQaUbCoE
2e65b65b1d opt: media page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 15:11:59 +08:00
bggRGjQaUbCoE
88578393c2 opt: query bangumi url
Closes #101

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 14:56:30 +08:00
bggRGjQaUbCoE
1643db4656 opt: multi select
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 14:45:20 +08:00
bggRGjQaUbCoE
e4b8dfcada opt: view to-view video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 14:37:01 +08:00
bggRGjQaUbCoE
1a3f5414c6 opt: send danmaku
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 13:37:34 +08:00
bggRGjQaUbCoE
789d8a77dd mod: image view
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 13:19:41 +08:00
bggRGjQaUbCoE
5efbdda107 mod: seek
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 13:19:33 +08:00
bggRGjQaUbCoE
2aa109b089 Revert "feat: custom subtitle bg stroke"
This reverts commit 168bb22670.
2025-01-05 12:15:42 +08:00
bggRGjQaUbCoE
22abc4488b opt: send danmaku panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 12:12:41 +08:00
bggRGjQaUbCoE
0d41731681 mod: update danmaku dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 11:44:25 +08:00
bggRGjQaUbCoE
f467532f9d opt: whisper data
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 11:24:12 +08:00
bggRGjQaUbCoE
daf01df5aa fix: #99
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-05 11:11:28 +08:00
bggRGjQaUbCoE
738c057304 fix: add sent danmaku
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 22:54:14 +08:00
bggRGjQaUbCoE
cf76cb6f63 fix: add sent danmaku
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 22:37:17 +08:00
bggRGjQaUbCoE
27e39d4de5 feat: new send danmaku panel
Closes #98

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 21:57:49 +08:00
bggRGjQaUbCoE
58fd373e8c fix: search settings item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 17:43:54 +08:00
bggRGjQaUbCoE
76b37437d3 opt: reply item
Closes #95

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 16:51:09 +08:00
bggRGjQaUbCoE
8186307f98 opt: manul skip
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 16:04:41 +08:00
bggRGjQaUbCoE
be42ce97f8 feat: sponsorblock: manual skip
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 15:39:33 +08:00
bggRGjQaUbCoE
5f6dcc9569 mod: update bufferSize
related #93

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 15:39:03 +08:00
bggRGjQaUbCoE
4539e0e5c5 opt: rcmd settings
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 13:24:23 +08:00
bggRGjQaUbCoE
d066262cdd opt: toast text color
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 13:12:15 +08:00
bggRGjQaUbCoE
7ac4a32468 chore: update release version
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 12:36:14 +08:00
bggRGjQaUbCoE
9cf74c0db6 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 10:44:21 +08:00
bggRGjQaUbCoE
14f2c34d21 opt: continuePlayingPart
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 09:15:47 +08:00
bggRGjQaUbCoE
b7b4432d71 feat: continue playing part
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-04 08:51:00 +08:00
bggRGjQaUbCoE
0be609db3d fix: #92
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-03 21:52:51 +08:00
bggRGjQaUbCoE
321b7933d7 opt: code
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-03 21:23:55 +08:00
bggRGjQaUbCoE
1d51db0a62 fix: settings
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-03 18:18:06 +08:00
bggRGjQaUbCoE
18ee1d4e18 feat: search settings item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-03 17:47:13 +08:00
bggRGjQaUbCoE
413a49bcb1 fix: search suggest text color
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-03 13:56:37 +08:00
bggRGjQaUbCoE
fd1bb0af30 mod: SliverHeaderDelegate
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-03 13:28:12 +08:00
bggRGjQaUbCoE
f808012ec2 opt: style settings
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-03 13:12:43 +08:00
bggRGjQaUbCoE
51e436faed opt: bangumi card
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-03 11:31:56 +08:00
bggRGjQaUbCoE
168bb22670 feat: custom subtitle bg stroke
Closes #90

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-03 11:20:42 +08:00
bggRGjQaUbCoE
1232116d22 opt: copy log
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-02 22:04:58 +08:00
bggRGjQaUbCoE
621239551f opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-02 21:44:38 +08:00
bggRGjQaUbCoE
f1a10a786d feat: custom disable ssl cert verf
Closes #88

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-02 14:13:16 +08:00
bggRGjQaUbCoE
d0ef75bce7 fix: #87
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-02 13:25:48 +08:00
bggRGjQaUbCoE
3919e42b59 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-02 13:01:06 +08:00
bggRGjQaUbCoE
eafaa1b045 fix: bangumi next play index
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-02 12:09:53 +08:00
bggRGjQaUbCoE
6e08735421 fix: fav: video params
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-02 11:57:30 +08:00
bggRGjQaUbCoE
a5823e1e90 fix: bangumi watch progress
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-02 11:40:28 +08:00
bggRGjQaUbCoE
665f5cdeef feat: get bangumi last play time
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-02 11:26:30 +08:00
bggRGjQaUbCoE
28c2323ef1 opt: view bangumi
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-02 10:10:51 +08:00
bggRGjQaUbCoE
d30dd96bbd fix: bangumi heartbeat
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 20:08:31 +08:00
bggRGjQaUbCoE
1026fc79e1 fix: medialist desc
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 18:19:09 +08:00
bggRGjQaUbCoE
1073d82008 opt: dynamic state num
related #85

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 18:07:19 +08:00
bggRGjQaUbCoE
30f3440b90 mod: live: remove cookie when anonymous
related #86

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 17:46:03 +08:00
bggRGjQaUbCoE
45e1282a0e opt: reply/like num
Closes #85

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 16:24:07 +08:00
bggRGjQaUbCoE
2e480518b7 fix: subtitle padding
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 13:39:56 +08:00
bggRGjQaUbCoE
1e7ff89341 feat: custom subtitle bg opacity
Closes #78

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 13:26:49 +08:00
bggRGjQaUbCoE
269fb033e0 opt: shortenChineseDateString
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 12:54:47 +08:00
bggRGjQaUbCoE
dbc93883e8 feat: custom subtitle padding
Closes #77

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 12:26:08 +08:00
bggRGjQaUbCoE
144a9b604a mod: home: show anonymous icon
Closes #81

opt: SliverPersistentHeaderDelegate

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 11:50:23 +08:00
bggRGjQaUbCoE
dda0fc15c7 mod: remove disliked rcmd
Closes #80

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 11:49:45 +08:00
bggRGjQaUbCoE
1dd7b9ed0a opt: numFormat
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 10:27:34 +08:00
bggRGjQaUbCoE
b7768e5886 mod: update danmaku dep
Closes #84

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 10:01:28 +08:00
bggRGjQaUbCoE
7df4c5c4c7 fix: #82
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-01 08:57:58 +08:00
bggRGjQaUbCoE
952fd5fc38 fix: intro panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-31 22:36:42 +08:00
bggRGjQaUbCoE
cde0ea244b feat: medialist: continue playing #70
Closes #70

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-31 20:55:08 +08:00
bggRGjQaUbCoE
098e2220cc feat: medialist: reverse play #70
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-31 18:20:03 +08:00
bggRGjQaUbCoE
df41729d74 fix: reverse play
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-31 17:04:37 +08:00
bggRGjQaUbCoE
273e5649c3 fix: #79
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-31 12:30:03 +08:00
bggRGjQaUbCoE
de3edcfa13 feat: part: reverse play #70
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-31 11:59:46 +08:00
bggRGjQaUbCoE
1215d126cc mod: add reverse from first option
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-31 12:16:04 +08:00
bggRGjQaUbCoE
20a89fbccb fix: reverse play
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-30 23:23:20 +08:00
bggRGjQaUbCoE
cbe814fdd6 fix: list sheet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-30 22:22:18 +08:00
bggRGjQaUbCoE
04583e92b7 feat: season: reverse play #70
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-30 21:58:26 +08:00
bggRGjQaUbCoE
ae6c6431f3 fix: #73 2024-12-30 18:22:21 +08:00
bggRGjQaUbCoE
2973299e29 opt: danmaku bottomsheet
Closes #74

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-30 18:15:31 +08:00
bggRGjQaUbCoE
52f9b0f83c mod: delete dynamic on dynamic page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-30 13:54:30 +08:00
bggRGjQaUbCoE
2a1849d24c opt: login dialog
Closes #72

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-30 13:45:07 +08:00
bggRGjQaUbCoE
991ae8518a opt: create dynamic panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-30 12:49:24 +08:00
bggRGjQaUbCoE
bef7a28229 opt: member info widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-30 12:01:49 +08:00
bggRGjQaUbCoE
753fdeea03 opt: dynamic panel 2024-12-30 12:01:49 +08:00
bggRGjQaUbCoE
ef8d57ddfd fix: #68
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 20:46:57 +08:00
bggRGjQaUbCoE
582574a605 mod: buildconfig
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 20:18:18 +08:00
bggRGjQaUbCoE
43583be6da opt: video boxfit option
Closes #69

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 18:57:59 +08:00
bggRGjQaUbCoE
836f1a9b06 mod: add buildconfig
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 18:39:55 +08:00
bggRGjQaUbCoE
90176a4787 opt: fav: validate ownership
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 16:53:46 +08:00
bggRGjQaUbCoE
a6cb49fd02 mod: handle http2https
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 15:36:20 +08:00
bggRGjQaUbCoE
85733e071b opt: image save dialog
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 15:04:03 +08:00
bggRGjQaUbCoE
bdd927e7e3 opt: get dynamicDetailRatio
Closes #66

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 14:32:01 +08:00
bggRGjQaUbCoE
e2f8cb89a9 fix: #65
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 14:06:00 +08:00
bggRGjQaUbCoE
8fd51da8da feat: custom show argue msg
Closes #63

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 12:07:26 +08:00
bggRGjQaUbCoE
0edb7f44a7 mod: save more covers
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 11:20:47 +08:00
bggRGjQaUbCoE
882f16bdab fix: view documentary
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 11:11:40 +08:00
bggRGjQaUbCoE
b6217f6e6e opt: regTitle
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 10:57:44 +08:00
bggRGjQaUbCoE
e9945ab63c mod: image save dialog
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 10:41:09 +08:00
bggRGjQaUbCoE
eca69f3d6d opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 10:05:06 +08:00
bggRGjQaUbCoE
f854e949cd mod: disable focus highlight for android
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-29 10:04:44 +08:00
bggRGjQaUbCoE
e34fce6d0e mod: save image: dissable dialog
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 21:41:02 +08:00
bggRGjQaUbCoE
b00708b498 fix: live room
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 21:30:25 +08:00
bggRGjQaUbCoE
d6ed1edc6f opt: dynamic up panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 21:09:03 +08:00
bggRGjQaUbCoE
93560a6fb2 opt: dynamicDetail/html page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 20:25:04 +08:00
bggRGjQaUbCoE
07307a666c fix: play all after multi select
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 20:13:44 +08:00
bggRGjQaUbCoE
0e253ecb83 opt: findClosestNumber
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 18:52:48 +08:00
bggRGjQaUbCoE
8545a3cbe6 fix: typo
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 18:12:09 +08:00
bggRGjQaUbCoE
dbd8b80507 opt: searchword: check page
log #57

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 14:55:39 +08:00
bggRGjQaUbCoE
6260809e40 opt: handleState
weird

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 14:31:25 +08:00
bggRGjQaUbCoE
820c7aa324 opt: staff widget
Closes #61

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 14:03:18 +08:00
bggRGjQaUbCoE
ec8c010c96 opt: dynamicDetail/html page
Closes #60

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 14:03:12 +08:00
bggRGjQaUbCoE
de91bdff74 refactor: subscription
opt: pages

Closes #58

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 11:32:04 +08:00
bggRGjQaUbCoE
51f87cc49c Revert "Reapply "chore: bump flutter version""
Closes #59

This reverts commit f6406f47a6.
2024-12-28 09:46:46 +08:00
bggRGjQaUbCoE
821a6ad4b2 mod: pip
Closes #57

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 09:43:44 +08:00
bggRGjQaUbCoE
cbf0d050f8 mod: intro
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-28 09:43:44 +08:00
bggRGjQaUbCoE
7fab59acd2 mod: home: try-catch ctr
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-27 20:23:55 +08:00
bggRGjQaUbCoE
fedb67c809 fix: media list desc
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-27 15:20:41 +08:00
bggRGjQaUbCoE
0e8502b087 fix: media type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-27 14:33:37 +08:00
bggRGjQaUbCoE
64672dbdf9 feat: play all member archives
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-27 13:38:24 +08:00
bggRGjQaUbCoE
329eb31387 fix: play all
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-27 11:36:31 +08:00
bggRGjQaUbCoE
8e8dc273aa mod: danmaku
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-27 10:50:42 +08:00
bggRGjQaUbCoE
91fc383723 mod: update danmaku dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-27 10:40:44 +08:00
bggRGjQaUbCoE
e4f4a088ce opt: check cellular
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 22:04:31 +08:00
bggRGjQaUbCoE
45a965135e feat: custom danmaku line height
Closes #56

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 22:01:53 +08:00
bggRGjQaUbCoE
81a23ea59d feat: custom reply length limit
Closes #55

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 20:47:07 +08:00
bggRGjQaUbCoE
79da08b285 fix: danmaku block
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 19:07:57 +08:00
bggRGjQaUbCoE
d3c7b3830f mod: update settings
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 14:19:42 +08:00
bggRGjQaUbCoE
5e0a46f268 fix: intro: check mid
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 13:55:18 +08:00
bggRGjQaUbCoE
5d1c1494dd feat: cellular video/audio qa
Closes #52

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 13:36:00 +08:00
bggRGjQaUbCoE
ed3036cc43 opt: horizontal member page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 12:22:17 +08:00
bggRGjQaUbCoE
5410a5cecc mod: remove w400 fw
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 12:22:17 +08:00
bggRGjQaUbCoE
65be638b66 opt: def searchword
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 12:22:11 +08:00
bggRGjQaUbCoE
563edbb07c fix: video tabbar index
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 11:28:09 +08:00
bggRGjQaUbCoE
5664447e15 mod: try-catch itemscrollctr jump
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-26 11:28:09 +08:00
bggRGjQaUbCoE
eee7eda1a2 feat: custom horizontal member page
Closes #51

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-25 23:54:42 +08:00
bggRGjQaUbCoE
513a3d2175 opt: media list page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-25 20:27:08 +08:00
bggRGjQaUbCoE
11dde3a887 opt: play all
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-25 19:13:24 +08:00
bggRGjQaUbCoE
234017cc8a fix: video tabbar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-25 17:19:31 +08:00
bggRGjQaUbCoE
f6406f47a6 Reapply "chore: bump flutter version"
mod: disable impeller, ref Kazumi

This reverts commit fe2b4f6735.
2024-12-25 16:38:38 +08:00
bggRGjQaUbCoE
a7fb8f6007 opt: video tabbar, settings icon
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-25 16:21:06 +08:00
bggRGjQaUbCoE
6810aaeba1 fix: autoExitFullscreen
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-25 15:29:52 +08:00
bggRGjQaUbCoE
6acba93c2c mod: close listener on dispose
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-25 15:11:45 +08:00
bggRGjQaUbCoE
169ae7d562 feat: custom horizontal season panel
Closes #50

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-25 14:15:11 +08:00
bggRGjQaUbCoE
c371d74a0c opt: login page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-25 12:04:57 +08:00
bggRGjQaUbCoE
00681e95b5 fix: defaultRcmdType
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 20:35:56 +08:00
bggRGjQaUbCoE
5eed75e353 feat: cookie login
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 20:09:27 +08:00
bggRGjQaUbCoE
9223f40f6d opt: expand ctr
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 13:57:21 +08:00
bggRGjQaUbCoE
34bceeea39 opt: import dialog
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 13:44:43 +08:00
bggRGjQaUbCoE
36ee59c7da fix: after login
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 13:39:56 +08:00
bggRGjQaUbCoE
c23f15b195 feat: import/export login info
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 12:56:37 +08:00
bggRGjQaUbCoE
94c077a4fe mod: long press to clear logs
avoid being unable to clear logs when stuck in logspage

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 12:56:37 +08:00
bggRGjQaUbCoE
23ba9ad8c1 opt: expand intro panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 12:03:53 +08:00
bggRGjQaUbCoE
f29e49dc4c opt: report position
Closes #48

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 11:51:48 +08:00
bggRGjQaUbCoE
7603a72101 mod: update danmaku dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 00:14:54 +08:00
bggRGjQaUbCoE
569cf6b4a3 mod: auto expand intro panel
Closes #47

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 00:09:32 +08:00
bggRGjQaUbCoE
e2b30200bf mod: update danmaku dep
Closes #46

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 00:07:51 +08:00
bggRGjQaUbCoE
07d8504f91 mod: reply2reply: recheck jump index
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 23:42:43 +08:00
bggRGjQaUbCoE
952f1429eb fix: video tabbar length
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 22:56:58 +08:00
bggRGjQaUbCoE
c79364cef2 mod: playall: auto play next
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 21:11:38 +08:00
bggRGjQaUbCoE
3ee1c9fdcd mod: update danmaku dep
Closes #45

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 21:10:22 +08:00
bggRGjQaUbCoE
385ebd01cc feat: custom show reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 19:44:00 +08:00
bggRGjQaUbCoE
a8d40b4aea feat: custom expand intro panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 19:27:39 +08:00
bggRGjQaUbCoE
dffea51223 fix: whisper page: pass none null mid
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 17:43:21 +08:00
bggRGjQaUbCoE
812170ce38 feat: custom show related video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 17:43:16 +08:00
bggRGjQaUbCoE
c8e89653ed fix: media list
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 15:40:34 +08:00
bggRGjQaUbCoE
521c24f762 opt: video page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 15:16:24 +08:00
bggRGjQaUbCoE
47641eeb28 opt: later page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 15:07:11 +08:00
bggRGjQaUbCoE
ff8f6da0bb opt: member info
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 14:58:53 +08:00
bggRGjQaUbCoE
9536b5db6f fix: dynamic appbar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 14:58:53 +08:00
bggRGjQaUbCoE
a9e4f2081d feat: custom schedule duration
opt: bottom sheet

Closes #44

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 12:04:17 +08:00
guozhigq
9e8d34e0dc feat: 稍后再看&收藏夹播放全部
Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 12:04:12 +08:00
bggRGjQaUbCoE
47241897de Update main.yml
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-22 22:56:10 +08:00
bggRGjQaUbCoE
aed3b12b09 revert: replyitem: prefixicon
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-22 20:21:52 +08:00
bggRGjQaUbCoE
0fde99dc68 mod: login page: sync orz12/main
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-22 19:27:25 +08:00
bggRGjQaUbCoE
0ae2665c56 fix: #42
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-22 17:09:42 +08:00
bggRGjQaUbCoE
0b311d37c8 opt: video page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-22 11:10:06 +08:00
bggRGjQaUbCoE
a01d54cd80 Revert "mod: color alpha"
This reverts commit a7ffc3b05f.
2024-12-22 11:10:02 +08:00
bggRGjQaUbCoE
fe2b4f6735 Revert "chore: bump flutter version"
This reverts commit 47fd90e4a5.
2024-12-22 10:48:50 +08:00
bggRGjQaUbCoE
6f5bd626b4 feat: filter hot/rank video title
Closes #38

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-19 21:32:30 +08:00
bggRGjQaUbCoE
a7ffc3b05f mod: color alpha
`withOpacity` -> `withValues`

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-19 20:22:55 +08:00
bggRGjQaUbCoE
45b4f9570b fix: ci
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-19 19:47:10 +08:00
bggRGjQaUbCoE
1d9f7f052d feat: custom rcmd filter duration
Closes #41

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-19 19:27:33 +08:00
bggRGjQaUbCoE
47fd90e4a5 chore: bump flutter version
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-19 18:51:17 +08:00
bggRGjQaUbCoE
ed4d2685b4 opt: dynamic card theme
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-16 12:24:40 +08:00
bggRGjQaUbCoE
b9aa968a2e Update main.yml
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-16 11:20:15 +08:00
bggRGjQaUbCoE
3852e21571 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-16 11:09:26 +08:00
bggRGjQaUbCoE
5a69e6abb0 opt: member info card
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-15 17:11:23 +08:00
bggRGjQaUbCoE
726fd0b338 mod: check future builder data
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-15 17:07:07 +08:00
bggRGjQaUbCoE
4aadc9b050 Update ios.yml
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-15 13:25:37 +08:00
bggRGjQaUbCoE
41c9367c42 mod: set flutter version
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-15 13:25:36 +08:00
bggRGjQaUbCoE
52f888167f opt: image view
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-15 13:25:36 +08:00
bggRGjQaUbCoE
fee1ad56f7 feat: use canvas_danmaku
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-15 13:25:36 +08:00
bggRGjQaUbCoE
4e7cf0a1bd opt: color scheme
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-14 14:39:50 +08:00
bggRGjQaUbCoE
bc0914e146 opt: color
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-14 12:38:51 +08:00
bggRGjQaUbCoE
b898a78e62 mod: color scheme
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-14 11:53:40 +08:00
bggRGjQaUbCoE
1b71fd4ca6 mod: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-13 18:26:49 +08:00
bggRGjQaUbCoE
94d055610e opt: code
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-13 10:54:53 +08:00
bggRGjQaUbCoE
be371e002a opt: image view
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-13 00:56:56 +08:00
bggRGjQaUbCoE
7905f51067 opt: image view
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-12 17:03:18 +08:00
bggRGjQaUbCoE
c7fef4e998 opt: replyitem: seek time
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-12 17:03:12 +08:00
bggRGjQaUbCoE
5d8b42a928 feat: use interactiveviewer gallery
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-12 13:18:52 +08:00
bggRGjQaUbCoE
22f668245d opt: image preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-08 19:03:35 +08:00
bggRGjQaUbCoE
ce89a5fdb9 opt: search member archive
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-08 18:11:47 +08:00
bggRGjQaUbCoE
63a12ba6ed opt: code
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-08 14:08:29 +08:00
bggRGjQaUbCoE
7cdfe26a26 opt: v/b status
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-08 13:55:19 +08:00
bggRGjQaUbCoE
dfd67219e3 fix(ios): live room danmaku
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-08 11:22:21 +08:00
bggRGjQaUbCoE
979df1585e opt: video page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-04 10:38:59 +08:00
bggRGjQaUbCoE
731a7dd3e5 mod: uppercase up
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-03 21:37:46 +08:00
bggRGjQaUbCoE
4ec7a628a6 mod: video report button
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-03 21:07:09 +08:00
bggRGjQaUbCoE
48d4e3ed34 opt: get video subtitle
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-03 21:00:29 +08:00
bggRGjQaUbCoE
c8a4be00ce opt: viewpoints panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-03 12:30:13 +08:00
bggRGjQaUbCoE
98158c4f0c opt: SegmentProgressBar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-03 11:59:32 +08:00
bggRGjQaUbCoE
49fe27176d opt: video page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-03 10:55:37 +08:00
bggRGjQaUbCoE
3d7583e010 fix: reset subtitle, viewpoints
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-02 15:26:05 +08:00
bggRGjQaUbCoE
64ff4e0d5c fix: fav search item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-02 15:01:27 +08:00
bggRGjQaUbCoE
84ee106ddf opt: blackMidsList
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-02 13:57:48 +08:00
bggRGjQaUbCoE
cbdd8e77db opt: video subtitle
avoid refetching subtitle
fix stuck when parsing large subtitle body

opt: viewpoints

Update README.md

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-02 13:48:43 +08:00
bggRGjQaUbCoE
a0b1e23727 opt: viewpoints
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-01 19:40:08 +08:00
bggRGjQaUbCoE
aa05ae3f32 fix: refresh member video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-01 18:29:25 +08:00
bggRGjQaUbCoE
7d7ae3f130 opt: viewpoints
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-01 17:38:30 +08:00
bggRGjQaUbCoE
f9ed31c65a feat: progressbar: show viewpoints #28
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-01 16:18:57 +08:00
bggRGjQaUbCoE
43977c737b fix: loadingState cast
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-01 13:59:02 +08:00
bggRGjQaUbCoE
62a1768307 fix: refresh rcmd
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-01 13:16:09 +08:00
bggRGjQaUbCoE
26e8553d9e opt: dynamic detail/html page
Closes #26

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-01 12:39:13 +08:00
bggRGjQaUbCoE
018424d5bd feat: custom subtitle fontscale
Closes #28

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-01 10:37:02 +08:00
bggRGjQaUbCoE
a6f5bd8d7d opt: action item gesture
Closes #29

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-01 09:04:22 +08:00
362 changed files with 20359 additions and 12866 deletions

View File

@@ -1,223 +0,0 @@
name: CI
on:
workflow_dispatch:
# push:
# branches:
# - 'main'
# paths-ignore:
# - '**.md'
# - '**.txt'
# - '.github/**'
# - '.idea/**'
# - '!.github/workflows/CI.yml'
jobs:
update_version:
name: Read and update version
runs-on: ubuntu-latest
outputs:
# 定义输出变量 version以便在其他job中引用
new_version: ${{ steps.version.outputs.new_version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
#- name: 获取first parent commit次数
# id: get-first-parent-commit-count
# run: |
# version=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
# recent_release_tag=$(git tag -l | grep $version | egrep -v "[-|+]" || true)
# if [[ "x$recent_release_tag" == "x" ]]; then
# echo "当前版本tag不存在请手动生成tag."
# exit 1
# fi
# git log --oneline HEAD
# first_parent_commit_count=$(git rev-list --first-parent --count $recent_release_tag..HEAD)
# echo "count=$first_parent_commit_count" >> $GITHUB_OUTPUT
- name: 从tag获取之前的version_code与beta版本号
id: get-previous-codes
run: |
version=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
last_tag=$(git tag --sort=committerdate | tail -1)
if (echo $last_tag | grep -v "+"); then
echo "Tag格式不正确"
exit 1
elif (echo $last_tag | grep -v $version); then
echo "当前版本tag不存在请手动添加tag."
exit 1
fi
version_code=$(echo $last_tag | cut -d "+" -f 2)
beta_code=$(echo $last_tag | cut -d "+" -f 1 | cut -d "." -f 4)
beta_code=${beta_code:-0}
echo "beta-code=$beta_code" >> $GITHUB_OUTPUT
echo "version-code=$version_code" >> $GITHUB_OUTPUT
- name: 更新版本号
id: version
run: |
# 读取版本号
version_name=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
let beta_code=${{ steps.get-previous-codes.outputs.beta-code }}+1
let version_code=${{ steps.get-previous-codes.outputs.version-code }}+1
# 构建新版本号
NEW_VERSION=${version_name}-beta.${beta_code}+${version_code}
# 输出新版本号
echo "New version: $NEW_VERSION"
# 设置新版本号为输出变量
echo "new_version=$NEW_VERSION" >>$GITHUB_OUTPUT
android:
name: Build CI (Android)
needs: update_version
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: 构建Java环境
uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "17"
token: ${{secrets.GIT_TOKEN}}
- name: 检查缓存
uses: actions/cache@v2
id: cache-flutter
with:
path: /root/flutter-sdk
key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
- name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2
with:
flutter-version: 3.24.0
channel: any
- name: 下载项目依赖
run: flutter pub get
- name: 解码生成 jks
run: echo $KEYSTORE_BASE64 | base64 -di > android/app/vvex.jks
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
- name: 更新版本号
id: version
run: |
# 更新pubspec.yaml文件中的版本号
sed -i "s/version: .*/version: ${{ needs.update_version.outputs.new_version }}/g" pubspec.yaml
- name: flutter build apk
run: flutter build apk --release --split-per-abi
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
- name: flutter build apk
run: |
sed -i "s/version: .*/version: ${{ needs.update_version.outputs.new_version }}0/g" pubspec.yaml
flutter build apk --release
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
- name: 重命名应用
run: |
version_name=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
for file in build/app/outputs/flutter-apk/app-*.apk; do
if [[ $file =~ app-(.?*)release.apk ]]; then
new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}${version_name}.apk"
mv "$file" "$new_file_name"
fi
done
- name: 上传
uses: actions/upload-artifact@v3
with:
name: Pilipala-CI
path: |
build/app/outputs/flutter-apk/Pili-*.apk
iOS:
name: Build CI (iOS)
needs: update_version
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2.10.0
with:
cache: true
flutter-version: 3.24.0
- name: 更新版本号
id: version
run: |
# 更新pubspec.yaml文件中的版本号
sed -i "" "s/version: .*/version: ${{ needs.update_version.outputs.new_version }}/g" pubspec.yaml
- name: flutter build ipa
run: |
flutter build ios --release --no-codesign
ln -sf ./build/ios/iphoneos Payload
zip -r9 app.ipa Payload/runner.app
- name: 重命名应用
run: |
version_name=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
for file in app.ipa; do
new_file_name="build/Pili-${version_name}.ipa"
mv "$file" "$new_file_name"
done
- name: 上传
uses: actions/upload-artifact@v3
with:
if-no-files-found: error
name: Pilipala-CI
path: |
build/Pili-*.ipa
upload:
runs-on: ubuntu-latest
needs:
- update_version
- android
- iOS
steps:
- uses: actions/download-artifact@v3
with:
name: Pilipala-CI
path: ./Pilipala-CI
- name: Upload Pre-release
uses: ncipollo/release-action@v1
with:
name: ${{ needs.update_version.outputs.new_version }}
token: ${{ secrets.GIT_TOKEN }}
commit: main
tag: ${{ needs.update_version.outputs.new_version }}
prerelease: true
allowUpdates: true
artifacts: Pilipala-CI/*

View File

@@ -10,6 +10,8 @@ jobs:
steps:
- name: 代码迁出
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: 构建Java环境
uses: actions/setup-java@v4
@@ -28,12 +30,30 @@ jobs:
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2
with:
flutter-version: 3.24.0
channel: any
channel: stable
flutter-version-file: pubspec.yaml
- name: 修复3.24的stable显示中文不正确问题 // from orz12
run: |
version=$(grep -m 1 'flutter:' pubspec.yaml | awk '{print $2}')
if [ "$(echo "$version < 3.27.0" | awk '{print ($1 < $2)}')" -eq 1 ]; then
cd $FLUTTER_ROOT
git config --global user.name "orz12"
git config --global user.email "orz12@test.com"
git cherry-pick d4124bd --strategy-option theirs
# flutter precache
flutter --version
cd -
fi
- name: 下载项目依赖
run: flutter pub get
- name: 更新版本号
run: |
version_name=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
sed -i "s/version: .*/version: $version_name-$(git rev-parse --short HEAD)+$(git rev-list --count HEAD)/g" pubspec.yaml
- name: Write key
run: |
if [ ! -z "${{ secrets.SIGN_KEYSTORE_BASE64 }}" ]; then
@@ -45,17 +65,10 @@ jobs:
fi
- name: flutter build apk
run: flutter build apk --release --target-platform=android-arm64
- name: flutter build apk
run: flutter build apk --release --split-per-abi
- name: 上传
uses: actions/upload-artifact@v4
with:
name: app-release
path: |
build/app/outputs/flutter-apk/app-release.apk
run: |
chmod +x lib/scripts/build.sh
lib/scripts/build.sh
flutter build apk --release --split-per-abi
- name: 上传
uses: actions/upload-artifact@v4

View File

@@ -1,130 +0,0 @@
name: Build iOS
on:
workflow_dispatch:
push:
branches:
- 'build-ios'
paths-ignore:
- '**.md'
- '**.txt'
- '.github/**'
- '.idea/**'
- '!.github/workflows/build-ios.yml'
jobs:
update_version:
name: Read latest version
runs-on: ubuntu-latest
outputs:
# 定义输出变量 version以便在其他job中引用
new_version: ${{ steps.get-last-tag.outputs.tag}}
last_commit: ${{ steps.get-last-commit.outputs.last_commit }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: 获取最后一次提交
id: get-last-commit
run: |
last_commit=$(git log -1 --pretty="%h %s" --first-parent)
echo "last_commit=$last_commit" >> $GITHUB_OUTPUT
- name: 获取最后一个tag
id: get-last-tag
run: |
version=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
last_tag=$(git tag --sort=committerdate | tail -1)
if (echo $last_tag | grep -v "+"); then
echo "Illegal tag!"
exit 1
elif (echo $last_tag | grep -v $version); then
echo "No tags for current version in the repo, please add one manually."
exit 1
fi
echo "tag=$last_tag" >> $GITHUB_OUTPUT
iOS:
name: Build CI (iOS)
needs: update_version
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
- name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2.10.0
with:
cache: true
flutter-version: 3.24.0
- name: 更新版本号
id: version
run: |
# 更新pubspec.yaml文件中的版本号
sed -i "" "s/version: .*/version: ${{ needs.update_version.outputs.new_version }}/g" pubspec.yaml
- name: flutter build ipa
run: |
flutter build ios --release --no-codesign
ln -sf ./build/ios/iphoneos Payload
zip -r9 app.ipa Payload/runner.app
- name: 重命名应用
run: |
for file in app.ipa; do
new_file_name="build/Pili-${{ needs.update_version.outputs.new_version }}.ipa"
mv "$file" "$new_file_name"
done
- name: 上传
uses: actions/upload-artifact@v3
with:
if-no-files-found: error
name: PiliPalaX-iOS
path: |
build/Pili-*.ipa
upload:
runs-on: ubuntu-latest
needs:
- update_version
- iOS
steps:
- uses: actions/download-artifact@v3
with:
name: PiliPalaX-iOS
path: ./PiliPalaX-iOS
# - name: Upload Pre-release
# uses: ncipollo/release-action@v1
# with:
# name: ${{ needs.update_version.outputs.new_version }}
# token: ${{ secrets.GIT_TOKEN }}
# commit: main
# tag: ${{ needs.update_version.outputs.new_version }}
# prerelease: true
# allowUpdates: true
# artifacts: Pilipala-CI/*
- name: 发送到Telegram频道
uses: xireiki/channel-post@v1.0.7
with:
bot_token: ${{ secrets.BOT_TOKEN }}
chat_id: ${{ secrets.CHAT_ID }}
large_file: false
method: sendFile
path: PiliPalaX-iOS/*
parse_mode: Markdown
context: "*v${{ needs.update_version.outputs.new_version }}*\n${{ needs.update_version.outputs.last_commit }}"

View File

@@ -16,17 +16,23 @@ jobs:
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch }}
fetch-depth: 0
- name: Setup flutter
uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version-file: pubspec.yaml
- name: Set up xcode
uses: BoundfoxStudios/action-xcode-select@v1
- name: 更新版本号
run: |
version_name=$(yq e '.version' pubspec.yaml | cut -d "+" -f 1)
sed -i '' "s/version: .*/version: $version_name+$(git rev-list --count HEAD)/" pubspec.yaml
- name: Build iOS
run: |
chmod +x lib/scripts/build.sh
lib/scripts/build.sh
flutter build ios --release --no-codesign
ln -sf ./build/ios/iphoneos Payload
zip -r9 ios-release-no-sign.ipa Payload/runner.app

4
.gitignore vendored
View File

@@ -133,4 +133,6 @@ app.*.symbols
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
!.vscode/settings.json
!.vscode/settings.json
/lib/build_config.dart

6
.vscode/launch.json vendored
View File

@@ -5,18 +5,18 @@
"version": "0.2.0",
"configurations": [
{
"name": "pilipala",
"name": "piliplus",
"request": "launch",
"type": "dart"
},
{
"name": "pilipala (profile mode)",
"name": "piliplus (profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "pilipala (release mode)",
"name": "piliplus (release mode)",
"request": "launch",
"type": "dart",
"flutterMode": "release"

View File

@@ -1,24 +1,24 @@
<div align="center">
<img width="200" height="200" src="https://github.com/orz12/pilipala/blob/main/assets/images/logo/logo_android.png">
<img width="200" height="200" src="https://github.com/bggRGjQaUbCoE/PiliPlus/blob/main/assets/images/logo/logo_android.png">
</div>
<div align="center">
<h1>PiliPalaX</h1>
<h1>PiliPlus</h1>
<div align="center">
![GitHub repo size](https://img.shields.io/github/repo-size/bggRGjQaUbCoE/PiliPalaX)
![GitHub Repo stars](https://img.shields.io/github/stars/bggRGjQaUbCoE/PiliPalaX)
![GitHub all releases](https://img.shields.io/github/downloads/bggRGjQaUbCoE/PiliPalaX/total)
![GitHub repo size](https://img.shields.io/github/repo-size/bggRGjQaUbCoE/PiliPlus)
![GitHub Repo stars](https://img.shields.io/github/stars/bggRGjQaUbCoE/PiliPlus)
![GitHub all releases](https://img.shields.io/github/downloads/bggRGjQaUbCoE/PiliPlus/total)
</div>
<p>使用Flutter开发的BiliBili第三方客户端</p>
<img src="https://github.com/orz12/pilipala/blob/main/assets/screenshots/510shots_so.png" width="32%" alt="home" />
<img src="https://github.com/orz12/pilipala/blob/main/assets/screenshots/174shots_so.png" width="32%" alt="home" />
<img src="https://github.com/orz12/pilipala/blob/main/assets/screenshots/850shots_so.png" width="32%" alt="home" />
<img src="https://github.com/bggRGjQaUbCoE/PiliPlus/blob/main/assets/screenshots/510shots_so.png" width="32%" alt="home" />
<img src="https://github.com/bggRGjQaUbCoE/PiliPlus/blob/main/assets/screenshots/174shots_so.png" width="32%" alt="home" />
<img src="https://github.com/bggRGjQaUbCoE/PiliPlus/blob/main/assets/screenshots/850shots_so.png" width="32%" alt="home" />
<br/>
<img src="https://github.com/orz12/pilipala/blob/main/assets/screenshots/main_screen.png" width="96%" alt="home" />
<img src="https://github.com/bggRGjQaUbCoE/PiliPlus/blob/main/assets/screenshots/main_screen.png" width="96%" alt="home" />
<br/>
</div>
@@ -47,6 +47,10 @@
## feat
- [x] 显示视频分段信息
- [x] 调节字幕大小
- [x] 调节全屏弹幕大小
- [x] 收藏夹/稍后再看多选删除
- [x] 搜索用户动态
- [x] 直播弹幕
- [x] 修改头像/用户名/签名/性别/生日
@@ -182,9 +186,10 @@
## 声明
此项目PiliPalaX)是个人为了兴趣而开发, 仅用于学习和测试请于下载后24小时内删除。
此项目PiliPlus)是个人为了兴趣而开发, 仅用于学习和测试请于下载后24小时内删除。
所用API皆从官方网站收集, 不提供任何破解内容。
在此致敬原作者:[guozhigq/pilipala](https://github.com/guozhigq/pilipala)
在此致敬上游作者:[orz12/PiliPalaX](https://github.com/orz12/PiliPalaX)
本仓库做了更激进的修改,感谢原作者的开源精神。
感谢使用

View File

@@ -35,7 +35,7 @@ def _keyAlias = System.getenv("KEY_ALIAS") ?: keystoreProperties["keyAlias"]
def _keyPassword = System.getenv("KEY_PASSWORD") ?: keystoreProperties["keyPassword"]
android {
compileSdkVersion 34
compileSdkVersion flutter.compileSdkVersion
namespace 'com.example.pilipalax'
ndkVersion flutter.ndkVersion
@@ -61,7 +61,7 @@ android {
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
minSdkVersion 21
minSdkVersion flutter.minSdkVersion
multiDexEnabled true
}

View File

@@ -11,7 +11,7 @@
/>
<application
android:label="PiliPalaX Debug"
android:label="PiliPlus Debug"
tools:replace="android:label">
<activity
android:name=".MainActivity"
@@ -36,7 +36,7 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter android:label="PiliPalaX Debug">
<intent-filter android:label="PiliPlus Debug">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
@@ -60,7 +60,7 @@
<!--<data android:host="bangumi.bilibili.com"/>-->
<!--<data android:host="space.bilibili.com"/>-->
</intent-filter>
<intent-filter android:label="PiliPalaX Debug">
<intent-filter android:label="PiliPlus Debug">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />

View File

@@ -36,7 +36,7 @@
</queries>
<application
android:label="PiliPalaX"
android:label="PiliPlus"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
xmlns:tools="http://schemas.android.com/tools"
@@ -67,7 +67,7 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter android:label="PiliPalaX+">
<intent-filter android:label="PiliPlus">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
@@ -91,7 +91,7 @@
<!--<data android:host="bangumi.bilibili.com"/>-->
<!--<data android:host="space.bilibili.com"/>-->
</intent-filter>
<intent-filter android:label="PiliPalaX+">
<intent-filter android:label="PiliPlus">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />

View File

@@ -16,7 +16,8 @@ class MainActivity : AudioServiceActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
methodChannel = MethodChannel(flutterEngine!!.getDartExecutor()!!.getBinaryMessenger(), CHANNEL)
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "PiliPlus")
methodChannel.setMethodCallHandler { call, result ->
if (call.method == "back") {
back()
@@ -53,10 +54,6 @@ class MainActivity : AudioServiceActivity() {
methodChannel.invokeMethod("onUserLeaveHint", null)
}
companion object {
private const val CHANNEL = "onUserLeaveHint"
}
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
MethodChannel(

View File

@@ -7,6 +7,7 @@
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#212121</item>
<item name="android:defaultFocusHighlightEnabled">false</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@@ -9,6 +9,7 @@
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:defaultFocusHighlightEnabled">false</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@@ -7,6 +7,7 @@
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#ffffff</item>
<item name="android:defaultFocusHighlightEnabled">false</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@@ -9,6 +9,7 @@
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:defaultFocusHighlightEnabled">false</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
android.enableJetifier=true

View File

@@ -1,5 +1,5 @@
PiliPalaX is a third-party Bilibili client developed in Flutter,
fork from PiliPala (https://github.com/guozhigq/pilipala).
PiliPlus is a third-party Bilibili client developed in Flutter,
fork from PiliPalaX (https://github.com/orz12/PiliPalaX).
Top Features:

View File

@@ -1 +1 @@
PiliPalaX
PiliPlus

View File

@@ -1,5 +1,5 @@
PiliPalaX 是使用 Flutter 开发的 BiliBili 第三方客户端,
是由PiliPala仓库fork并进行了差异化开发的版本
PiliPlus 是使用 Flutter 开发的 BiliBili 第三方客户端,
是由PiliPalaX仓库fork并进行了差异化开发的版本
主要功能:

View File

@@ -1 +1 @@
PiliPalaX
PiliPlus

View File

@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>PiliPalaX</string>
<string>PiliPlus</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -13,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>PiliPalaX</string>
<string>PiliPlus</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
@@ -42,7 +42,7 @@
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>

View File

@@ -21,13 +21,10 @@ class Constants {
static const String traceId =
'11111111111111111111111111111111:1111111111111111:0:0';
static const String userAgent =
'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android build/1462100 channel/bili innerVer/1462100 osVer/14 network/2';
'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';
// jsonEncode(
// {"appId": 5, "platform": 3, "version": "1.46.2", "abtest": ""});
// Uri.encodeComponent(
// '{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
//Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
//内容来自 https://passport.bilibili.com/web/generic/country/list
static const List<Map<String, dynamic>> internationalDialingPrefix = [

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPlus/common/constants.dart';
import 'skeleton.dart';

View File

@@ -1,4 +1,4 @@
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:flutter/material.dart';
import 'skeleton.dart';
@@ -8,79 +8,88 @@ class VideoCardHSkeleton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Skeleton(
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
return Container(
decoration: BoxDecoration(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: 5,
),
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
return Container(
decoration: BoxDecoration(
color:
Theme.of(context).colorScheme.onInverseSurface,
borderRadius:
BorderRadius.circular(StyleString.imgRadius.x),
),
);
},
),
),
// 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,
borderRadius:
BorderRadius.circular(StyleString.imgRadius.x),
width: 200,
height: 11,
margin: const EdgeInsets.only(bottom: 5),
),
);
},
),
),
// 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,
),
],
)
],
),
)),
],
),
);
},
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,4 +1,4 @@
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:flutter/material.dart';
import 'skeleton.dart';
@@ -45,6 +45,17 @@ class VideoCardVSkeleton extends StatelessWidget {
margin: const EdgeInsets.only(bottom: 12),
color: Theme.of(context).colorScheme.onInverseSurface,
),
Container(
width: 110,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
color: Theme.of(context).colorScheme.onInverseSurface,
),
Container(
width: 75,
height: 13,
color: Theme.of(context).colorScheme.onInverseSurface,
),
],
),
),

View File

@@ -1,5 +1,5 @@
import 'package:PiliPalaX/common/widgets/no_splash_factory.dart';
import 'package:PiliPalaX/common/widgets/overlay_pop.dart';
import 'package:PiliPlus/common/widgets/no_splash_factory.dart';
import 'package:PiliPlus/common/widgets/overlay_pop.dart';
import 'package:flutter/material.dart';
class AnimatedDialog extends StatefulWidget {

View File

@@ -1,7 +1,6 @@
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/models/dynamics/article_content_model.dart';
import 'package:PiliPalaX/pages/preview/view.dart';
import 'package:PiliPalaX/utils/extension.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/dynamics/article_content_model.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
@@ -9,6 +8,7 @@ import 'package:flutter_html/flutter_html.dart';
Widget articleContent({
required BuildContext context,
required List<ArticleContentModel> list,
Function(List<String>, int)? callback,
}) {
List<String>? imgList = list
.where((item) => item.pic != null)
@@ -17,7 +17,7 @@ Widget articleContent({
.toList();
return SliverList.separated(
itemCount: list.length,
itemBuilder: (_, index) {
itemBuilder: (context, index) {
ArticleContentModel item = list[index];
if (item.text != null) {
List<InlineSpan> spanList = [];
@@ -26,7 +26,7 @@ Widget articleContent({
text: item.word?.words,
style: TextStyle(
letterSpacing: 0.3,
fontSize: FontSize.large.value,
fontSize: 17,
height: LineHeight.percent(125).size,
fontStyle:
item.word?.style?.italic == true ? FontStyle.italic : null,
@@ -56,25 +56,29 @@ Widget articleContent({
);
} else if (item.pic != null) {
return LayoutBuilder(
builder: (_, constraints) => GestureDetector(
onTap: () {
showDialog(
useSafeArea: false,
context: context,
builder: (context) {
return ImagePreview(
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,
}
},
child: NetworkImgLayer(
width: constraints.maxWidth,
height: constraints.maxWidth *
item.pic!.pics!.first.height! /
item.pic!.pics!.first.width!,
src: item.pic!.pics!.first.url,
),
),
),
);

View File

@@ -42,8 +42,8 @@ class PBadge extends StatelessWidget {
color = Colors.white;
}
if (type == 'color') {
bgColor = t.primaryContainer.withOpacity(0.5);
color = t.primary;
bgColor = t.secondaryContainer.withOpacity(0.5);
color = t.onSecondaryContainer;
}
if (type == 'line') {
bgColor = Colors.transparent;

View File

@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
class CustomSliverPersistentHeaderDelegate
extends SliverPersistentHeaderDelegate {
CustomSliverPersistentHeaderDelegate({
required this.child,
required this.bgColor,
double extent = 45,
}) : _minExtent = extent,
_maxExtent = extent;
final double _minExtent;
final double _maxExtent;
final Widget child;
final Color bgColor;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
//创建child子组件
//shrinkOffsetchild偏移值minExtent~maxExtent
//overlapsContentSliverPersistentHeader覆盖其他子组件返回true否则返回false
return ColoredBox(
color: bgColor,
child: child,
);
}
//SliverPersistentHeader最大高度
@override
double get maxExtent => _maxExtent;
//SliverPersistentHeader最小高度
@override
double get minExtent => _minExtent;
@override
bool shouldRebuild(
covariant CustomSliverPersistentHeaderDelegate oldDelegate) {
return oldDelegate.bgColor != bgColor;
}
}

View File

@@ -1,8 +1,5 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:PiliPalaX/utils/storage.dart';
Box<dynamic> setting = GStorage.setting;
import 'package:PiliPlus/utils/storage.dart';
class CustomToast extends StatelessWidget {
const CustomToast({super.key, required this.msg});
@@ -11,8 +8,8 @@ class CustomToast extends StatelessWidget {
@override
Widget build(BuildContext context) {
final double toastOpacity =
setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0) as double;
final double toastOpacity = GStorage.setting
.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0) as double;
return Container(
margin:
EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 30),
@@ -28,7 +25,7 @@ class CustomToast extends StatelessWidget {
msg,
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
);

View File

@@ -133,32 +133,9 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
}
if (_height == 0) {
return SliverToBoxAdapter(
child: Stack(
children: [
Padding(
// Padding which centers the flexible space within the app bar
padding: EdgeInsets.symmetric(
vertical: MediaQuery.paddingOf(context).top / 2),
child: Container(
key: _childKey,
child:
widget.flexibleSpace ?? SizedBox(height: kToolbarHeight)),
),
Positioned.fill(
// 10 is the magic number which the app bar is pushed down within the sliver app bar. Couldnt find exactly where this number
// comes from and found it through trial and error.
top: 10,
child: Align(
alignment: Alignment.topCenter,
child: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: widget.leading,
actions: widget.actions,
),
),
)
],
child: Container(
key: _childKey,
child: widget.flexibleSpace ?? SizedBox(height: kToolbarHeight),
),
);
}

View File

@@ -1,4 +1,4 @@
import 'package:PiliPalaX/pages/preview/view.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'network_img_layer.dart';
@@ -9,6 +9,7 @@ Widget htmlRender({
int? imgCount,
List<String>? imgList,
required double constrainedWidth,
Function(List<String>, int)? callback,
}) {
return SelectionArea(
child: Html(
@@ -40,29 +41,29 @@ Widget htmlRender({
// extensionContext.element!.previousElementSibling == null ||
// extensionContext.element!.nextElementSibling == null;
// imgUrl = Utils().imageUrl(imgUrl!);
// return Image.network(
// imgUrl,
// return CachedNetworkImage(
// imageUrl: imgUrl,
// width: isEmote ? 22 : null,
// height: isEmote ? 22 : null,
// );
return GestureDetector(
onTap: () {
showDialog(
useSafeArea: false,
context: context,
builder: (context) {
return ImagePreview(
initialPage: 0,
return Hero(
tag: imgUrl,
child: GestureDetector(
onTap: () {
if (callback != null) {
callback([imgUrl], 0);
} else {
context.imageView(
imgList: [imgUrl],
);
},
);
},
child: NetworkImgLayer(
width: isEmote ? 22 : constrainedWidth,
height: isEmote ? 22 : 200,
src: imgUrl,
ignoreHeight: !isEmote,
}
},
child: NetworkImgLayer(
width: isEmote ? 22 : constrainedWidth,
height: isEmote ? 22 : 200,
src: imgUrl,
ignoreHeight: !isEmote,
),
),
);
} catch (err) {
@@ -73,7 +74,7 @@ Widget htmlRender({
],
style: {
'html': Style(
fontSize: FontSize.large,
fontSize: FontSize(16),
lineHeight: LineHeight.percent(160),
letterSpacing: 0.3,
),
@@ -91,7 +92,7 @@ Widget htmlRender({
// margin: Margins.zero,
),
'span': Style(
fontSize: FontSize.medium,
fontSize: FontSize.large,
height: Height(1.8),
),
'div': Style(height: Height.auto()),
@@ -109,12 +110,12 @@ Widget htmlRender({
margin: Margins.only(bottom: 8),
),
'h3,h4,h5': Style(
fontSize: FontSize.large,
fontSize: FontSize(16),
fontWeight: FontWeight.bold,
margin: Margins.only(bottom: 4),
),
'figcaption': Style(
fontSize: FontSize.medium,
fontSize: FontSize.large,
textAlign: TextAlign.center,
// margin: Margins.only(top: 4),
),

View File

@@ -7,6 +7,7 @@ class HttpError extends StatelessWidget {
this.errMsg,
this.callback,
this.btnText,
this.extraWidget,
super.key,
});
@@ -14,6 +15,7 @@ class HttpError extends StatelessWidget {
final String? errMsg;
final Function()? callback;
final String? btnText;
final Widget? extraWidget;
@override
Widget build(BuildContext context) {
@@ -44,8 +46,13 @@ class HttpError extends StatelessWidget {
style: Theme.of(context).textTheme.titleSmall,
),
),
if (extraWidget != null) ...[
const SizedBox(height: 10),
extraWidget!,
const SizedBox(height: 5),
],
if (callback != null) ...[
const SizedBox(height: 20),
if (extraWidget == null) const SizedBox(height: 20),
FilledButton.tonal(
onPressed: callback,
style: ButtonStyle(

View File

@@ -6,6 +6,7 @@ Widget iconButton({
required IconData icon,
required VoidCallback? onPressed,
double size = 36,
double? iconSize,
Color? bgColor,
Color? iconColor,
}) {
@@ -17,7 +18,7 @@ Widget iconButton({
onPressed: onPressed,
icon: Icon(
icon,
size: size / 2,
size: iconSize ?? size / 2,
color: iconColor ?? Theme.of(context).colorScheme.onSecondaryContainer,
),
style: IconButton.styleFrom(
@@ -28,3 +29,22 @@ Widget iconButton({
),
);
}
Widget mediumButton({
String? tooltip,
IconData? icon,
VoidCallback? onPressed,
}) {
return SizedBox(
width: 34,
height: 34,
child: IconButton(
tooltip: tooltip,
icon: Icon(icon),
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: onPressed,
),
);
}

View File

@@ -0,0 +1,96 @@
import 'dart:math';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/utils/download.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
void imageSaveDialog({
required BuildContext context,
required String? title,
required String? cover,
}) {
final double imgWidth = min(Get.width, Get.height) - 8 * 2;
SmartDialog.show(
animationType: SmartAnimationType.centerScale_otherSlide,
builder: (context) => Container(
width: imgWidth,
margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(10.0),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Stack(
children: [
GestureDetector(
onTap: SmartDialog.dismiss,
child: NetworkImgLayer(
width: imgWidth,
height: imgWidth / StyleString.aspectRatio,
src: cover,
quality: 100,
),
),
Positioned(
right: 8,
top: 8,
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
shape: BoxShape.circle,
),
child: IconButton(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: SmartDialog.dismiss,
icon: const Icon(
Icons.close,
size: 18,
color: Colors.white,
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 8, 10),
child: Row(
children: [
Expanded(
child: SelectableText(
title ?? '',
style: Theme.of(context).textTheme.titleSmall,
),
),
const SizedBox(width: 4),
IconButton(
tooltip: '保存封面图',
onPressed: () async {
bool saveStatus = await DownloadUtils.downloadImg(
context,
[cover ?? ''],
);
// 保存成功,自动关闭弹窗
if (saveStatus) {
SmartDialog.dismiss();
}
},
icon: const Icon(Icons.download, size: 20),
)
],
),
),
],
),
),
);
}

View File

@@ -1,9 +1,9 @@
import 'dart:math';
import 'package:PiliPalaX/common/widgets/badge.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/common/widgets/nine_grid_view.dart';
import 'package:PiliPalaX/pages/preview/view.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
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:flutter/material.dart';
class ImageModel {
@@ -23,10 +23,13 @@ class ImageModel {
bool get isLongPic => _isLongPic ??= (safeHeight / safeWidth) > (22 / 9);
}
Widget image(
Widget imageview(
double maxWidth,
List<ImageModel> picArr,
) {
List<ImageModel> picArr, {
VoidCallback? onViewImage,
ValueChanged<int>? onDismissed,
Function(List<String>, int)? callback,
}) {
double imageWidth = (maxWidth - 2 * 5) / 3;
double imageHeight = imageWidth;
if (picArr.length == 1) {
@@ -53,39 +56,43 @@ Widget image(
height: picArr.length == 1 ? imageHeight : null,
width: picArr.length == 1 ? imageWidth : maxWidth,
itemCount: picArr.length,
itemBuilder: (context, index) => GestureDetector(
onTap: () {
showDialog(
useSafeArea: false,
context: context,
builder: (context) {
return ImagePreview(
itemBuilder: (context, index) => Hero(
tag: picArr[index].url,
child: GestureDetector(
onTap: () {
if (callback != null) {
callback(picArr.map((item) => item.url).toList(), index);
} else {
onViewImage?.call();
context.imageView(
initialPage: index,
imgList: picArr.map((item) => item.url).toList(),
onDismissed: onDismissed,
);
},
);
},
child: Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: NetworkImgLayer(
src: picArr[index].url,
width: imageWidth,
height: imageHeight,
isLongPic: () => picArr[index].isLongPic,
callback: () =>
picArr[index].safeWidth <= picArr[index].safeHeight,
}
},
child: Stack(
alignment: Alignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: NetworkImgLayer(
src: picArr[index].url,
width: imageWidth,
height: imageHeight,
isLongPic: () => picArr[index].isLongPic,
callback: () =>
picArr[index].safeWidth <= picArr[index].safeHeight,
),
),
),
if (picArr[index].isLongPic)
const PBadge(
text: '长图',
right: 8,
bottom: 8,
),
],
if (picArr[index].isLongPic)
const PBadge(
text: '长图',
right: 8,
bottom: 8,
),
],
),
),
),
);

View File

@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
/// https://github.com/qq326646683/interactiveviewer_gallery
/// A [PageRoute] with a semi transparent background.
///
/// Similar to calling [showDialog] except it can be used with a [Navigator] to
/// show a [Hero] animation.
class HeroDialogRoute<T> extends PageRoute<T> {
HeroDialogRoute({
required this.builder,
});
final WidgetBuilder builder;
@override
bool get opaque => false;
@override
bool get barrierDismissible => true;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
@override
bool get maintainState => true;
@override
Color? get barrierColor => null;
@override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return FadeTransition(
opacity: CurvedAnimation(parent: animation, curve: Curves.easeOut),
child: child,
);
}
@override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
final Widget child = builder(context);
final Widget result = Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: child,
);
return result;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,240 @@
import 'interactive_viewer.dart' as custom;
import 'package:flutter/material.dart';
/// https://github.com/qq326646683/interactiveviewer_gallery
/// A callback for the [InteractiveViewerBoundary] that is called when the scale
/// changed.
typedef ScaleChanged = void Function(double scale);
/// Builds an [InteractiveViewer] and provides callbacks that are called when a
/// horizontal boundary has been hit.
///
/// The callbacks are called when an interaction ends by listening to the
/// [InteractiveViewer.onInteractionEnd] callback.
class InteractiveViewerBoundary extends StatefulWidget {
const InteractiveViewerBoundary({
super.key,
required this.child,
required this.boundaryWidth,
this.controller,
this.onScaleChanged,
this.onLeftBoundaryHit,
this.onRightBoundaryHit,
this.onNoBoundaryHit,
required this.maxScale,
required this.minScale,
this.onDismissed,
this.onReset,
this.dismissThreshold = 0.2,
});
final VoidCallback? onReset;
final double dismissThreshold;
final VoidCallback? onDismissed;
final Widget child;
/// The max width this widget can have.
///
/// If the [InteractiveViewer] can take up the entire screen width, this
/// should be set to `MediaQuery.of(context).size.width`.
final double boundaryWidth;
/// The [TransformationController] for the [InteractiveViewer].
final custom.TransformationController? controller;
/// Called when the scale changed after an interaction ended.
final ScaleChanged? onScaleChanged;
/// Called when the left boundary has been hit after an interaction ended.
final VoidCallback? onLeftBoundaryHit;
/// Called when the right boundary has been hit after an interaction ended.
final VoidCallback? onRightBoundaryHit;
/// Called when no boundary has been hit after an interaction ended.
final VoidCallback? onNoBoundaryHit;
final double maxScale;
final double minScale;
@override
InteractiveViewerBoundaryState createState() =>
InteractiveViewerBoundaryState();
}
class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
with SingleTickerProviderStateMixin {
custom.TransformationController? _controller;
double? _scale;
late AnimationController _animateController;
late Animation<Offset> _slideAnimation;
late Animation<double> _scaleAnimation;
late Animation<Decoration> _opacityAnimation;
Offset _offset = Offset.zero;
bool _dragging = false;
bool get _isActive => _dragging || _animateController.isAnimating;
@override
void initState() {
super.initState();
_controller = widget.controller ?? custom.TransformationController();
_animateController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_updateMoveAnimation();
}
@override
void dispose() {
_controller!.dispose();
_animateController.dispose();
super.dispose();
}
void _updateMoveAnimation() {
final double endX = _offset.dx.sign * (_offset.dx.abs() / _offset.dy.abs());
final double endY = _offset.dy.sign;
_slideAnimation = _animateController.drive(
Tween<Offset>(
begin: Offset.zero,
end: Offset(endX, endY),
),
);
_scaleAnimation = _animateController.drive(
Tween<double>(
begin: 1,
end: 0.25,
),
);
_opacityAnimation = _animateController.drive(
DecorationTween(
begin: const BoxDecoration(
color: Colors.black,
),
end: const BoxDecoration(
color: Colors.transparent,
),
),
);
}
void _handleDragStart(ScaleStartDetails details) {
_dragging = true;
if (_animateController.isAnimating) {
_animateController.stop();
} else {
_offset = Offset.zero;
_animateController.value = 0.0;
}
setState(_updateMoveAnimation);
}
void _handleDragUpdate(ScaleUpdateDetails details) {
if (!_isActive || _animateController.isAnimating) {
return;
}
_offset += details.focalPointDelta;
setState(_updateMoveAnimation);
if (!_animateController.isAnimating) {
_animateController.value = _offset.dy.abs() / context.size!.height;
}
}
void _handleDragEnd(ScaleEndDetails details) {
if (!_isActive || _animateController.isAnimating) {
return;
}
_dragging = false;
if (_animateController.isCompleted) {
return;
}
if (!_animateController.isDismissed) {
// if the dragged value exceeded the dismissThreshold, call onDismissed
// else animate back to initial position.
if (_animateController.value > widget.dismissThreshold) {
widget.onDismissed?.call();
} else {
_animateController.reverse();
}
}
}
void _updateBoundaryDetection() {
final double scale = _controller!.value.row0[0];
if (_scale != scale) {
// the scale changed
_scale = scale;
widget.onScaleChanged?.call(scale);
}
if (scale <= 1.01) {
// cant hit any boundaries when the child is not scaled
return;
}
final double xOffset = _controller!.value.row0[3];
final double boundaryWidth = widget.boundaryWidth;
final double boundaryEnd = boundaryWidth * scale;
final double xPos = boundaryEnd + xOffset;
if (boundaryEnd.round() == xPos.round()) {
// left boundary hit
widget.onLeftBoundaryHit?.call();
} else if (boundaryWidth.round() == xPos.round()) {
// right boundary hit
widget.onRightBoundaryHit?.call();
} else {
widget.onNoBoundaryHit?.call();
}
}
Widget get content => DecoratedBoxTransition(
decoration: _opacityAnimation,
child: SlideTransition(
position: _slideAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: widget.child,
),
),
);
@override
Widget build(BuildContext context) {
return custom.InteractiveViewer(
maxScale: widget.maxScale,
minScale: widget.minScale,
transformationController: _controller,
onInteractionEnd: (_) => _updateBoundaryDetection(),
onPanStart: _handleDragStart,
onPanUpdate: _handleDragUpdate,
onPanEnd: _handleDragEnd,
onReset: widget.onReset,
isAnimating: () => _animateController.value != 0,
child: content,
);
}
}

View File

@@ -0,0 +1,507 @@
import 'dart:io';
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:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.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 'interactive_viewer_boundary.dart';
import 'interactive_viewer.dart' as custom;
/// https://github.com/qq326646683/interactiveviewer_gallery
/// Builds a carousel controlled by a [PageView] for the tweet media sources.
///
/// Used for showing a full screen view of the [TweetMedia] sources.
///
/// The sources can be panned and zoomed interactively using an
/// [InteractiveViewer].
/// An [InteractiveViewerBoundary] is used to detect when the boundary of the
/// source is hit after zooming in to disable or enable the swiping gesture of
/// the [PageView].
///
typedef IndexedFocusedWidgetBuilder = Widget Function(
BuildContext context, int index, bool isFocus, bool enablePageView);
typedef IndexedTagStringBuilder = String Function(int index);
class InteractiveviewerGallery<T> extends StatefulWidget {
const InteractiveviewerGallery({
super.key,
required this.sources,
required this.initIndex,
this.itemBuilder,
this.maxScale = 8,
this.minScale = 1.0,
this.onPageChanged,
this.onDismissed,
this.setStatusBar,
});
final bool? setStatusBar;
/// The sources to show.
final List<String> sources;
/// The index of the first source in [sources] to show.
final int initIndex;
/// The item content
final IndexedFocusedWidgetBuilder? itemBuilder;
final double maxScale;
final double minScale;
final ValueChanged<int>? onPageChanged;
final ValueChanged<int>? onDismissed;
@override
State<InteractiveviewerGallery> createState() =>
_InteractiveviewerGalleryState();
}
class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
with SingleTickerProviderStateMixin {
PageController? _pageController;
custom.TransformationController? _transformationController;
/// The controller to animate the transformation value of the
/// [InteractiveViewer] when it should reset.
late AnimationController _animationController;
Animation<Matrix4>? _animation;
/// `true` when an source is zoomed in and not at the at a horizontal boundary
/// to disable the [PageView].
bool _enablePageView = true;
late Offset _doubleTapLocalPosition;
int? currentIndex;
late List<bool> _thumbList;
late int _quality;
@override
void initState() {
super.initState();
_quality =
GStorage.setting.get(SettingBoxKey.previewQuality, defaultValue: 80);
_thumbList = List.generate(widget.sources.length, (_) => true);
_pageController = PageController(initialPage: widget.initIndex);
_transformationController = custom.TransformationController();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
)..addListener(() {
_transformationController!.value =
_animation?.value ?? Matrix4.identity();
});
currentIndex = widget.initIndex;
if (widget.setStatusBar != false) {
setStatusBar();
}
}
setStatusBar() async {
if (Platform.isIOS || Platform.isAndroid) {
await StatusBarControl.setHidden(
true,
animation: StatusBarAnimation.FADE,
);
}
}
@override
void dispose() async {
_pageController?.dispose();
_animationController.removeListener(() {});
_animationController.dispose();
if (widget.setStatusBar != false) {
if (Platform.isIOS || Platform.isAndroid) {
StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE);
}
}
for (int index = 0; index < widget.sources.length; index++) {
CachedNetworkImageProvider(_getActualUrl(index)).evict();
}
super.dispose();
}
/// When the source gets scaled up, the swipe up / down to dismiss gets
/// disabled.
///
/// When the scale resets, the dismiss and the page view swiping gets enabled.
void _onScaleChanged(double scale) {
final bool initialScale = scale <= widget.minScale;
if (initialScale) {
if (!_enablePageView) {
setState(() {
_enablePageView = true;
});
}
} else {
if (_enablePageView) {
setState(() {
_enablePageView = false;
});
}
}
}
/// When the left boundary has been hit after scaling up the source, the page
/// view swiping gets enabled if it has a page to swipe to.
void _onLeftBoundaryHit() {
if (!_enablePageView && _pageController!.page!.floor() > 0) {
setState(() {
_enablePageView = true;
});
}
}
/// When the right boundary has been hit after scaling up the source, the page
/// view swiping gets enabled if it has a page to swipe to.
void _onRightBoundaryHit() {
if (!_enablePageView &&
_pageController!.page!.floor() < widget.sources.length - 1) {
setState(() {
_enablePageView = true;
});
}
}
/// When the source has been scaled up and no horizontal boundary has been hit,
/// the page view swiping gets disabled.
void _onNoBoundaryHit() {
if (_enablePageView) {
setState(() {
_enablePageView = false;
});
}
}
/// 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;
});
widget.onPageChanged?.call(page);
if (_transformationController!.value != Matrix4.identity()) {
// animate the reset for the transformation of the interactive viewer
_animation = Matrix4Tween(
begin: _transformationController!.value,
end: Matrix4.identity(),
).animate(
CurveTween(curve: Curves.easeOut).animate(_animationController),
);
_animationController.forward(from: 0);
}
}
String _getActualUrl(int index) => _thumbList[index] && _quality != 100
? '${widget.sources[index]}@${_quality}q.webp'.http2https
: widget.sources[index].http2https;
@override
Widget build(BuildContext context) {
return Stack(
children: [
InteractiveViewerBoundary(
controller: _transformationController,
boundaryWidth: MediaQuery.of(context).size.width,
onScaleChanged: _onScaleChanged,
onLeftBoundaryHit: _onLeftBoundaryHit,
onRightBoundaryHit: _onRightBoundaryHit,
onNoBoundaryHit: _onNoBoundaryHit,
maxScale: widget.maxScale,
minScale: widget.minScale,
onDismissed: () {
Get.back();
widget.onDismissed?.call(_pageController!.page!.floor());
},
onReset: () {
if (!_enablePageView) {
setState(() {
_enablePageView = true;
});
}
},
child: PageView.builder(
onPageChanged: _onPageChanged,
controller: _pageController,
physics:
_enablePageView ? null : const NeverScrollableScrollPhysics(),
itemCount: widget.sources.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: Get.back,
onDoubleTapDown: (TapDownDetails details) {
_doubleTapLocalPosition = details.localPosition;
},
onDoubleTap: onDoubleTap,
onLongPress: onLongPress,
child: widget.itemBuilder != null
? widget.itemBuilder!(
context,
index,
index == currentIndex,
_enablePageView,
)
: _itemBuilder(index),
);
},
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
padding: EdgeInsets.fromLTRB(
12,
8,
20,
MediaQuery.of(context).padding.bottom + 8,
),
decoration: _enablePageView
? BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black.withOpacity(0.3)
],
),
)
: null,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: () {
Get.back();
widget.onDismissed?.call(_pageController!.page!.floor());
},
),
widget.sources.length > 1
? Text(
"${currentIndex! + 1}/${widget.sources.length}",
style: const TextStyle(color: Colors.white),
)
: const SizedBox(),
PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem(
value: 0,
onTap: () => onShareImg(widget.sources[currentIndex!]),
child: const Text("分享图片"),
),
PopupMenuItem(
value: 1,
onTap: () {
Utils.copyText(widget.sources[currentIndex!]);
},
child: const Text("复制链接"),
),
PopupMenuItem(
value: 2,
onTap: () {
DownloadUtils.downloadImg(
context,
[widget.sources[currentIndex!]],
);
},
child: const Text("保存图片"),
),
if (widget.sources.length > 1)
PopupMenuItem(
value: 3,
onTap: () {
DownloadUtils.downloadImg(
context,
widget.sources as List<String>,
);
},
child: const Text("保存全部图片"),
),
];
},
child: const Icon(Icons.more_horiz, color: Colors.white),
),
],
),
),
),
],
);
}
// 图片分享
void onShareImg(String imgUrl) async {
SmartDialog.showLoading();
var response = await Dio()
.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: 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;
// });
// });
// },
),
),
);
}
onDoubleTap() {
Matrix4 matrix = _transformationController!.value.clone();
double currentScale = matrix.row0.x;
double targetScale = widget.minScale;
if (currentScale <= widget.minScale) {
targetScale = widget.maxScale * 0.4;
}
double offSetX = targetScale == 1.0
? 0.0
: -_doubleTapLocalPosition.dx * (targetScale - 1);
double offSetY = targetScale == 1.0
? 0.0
: -_doubleTapLocalPosition.dy * (targetScale - 1);
matrix = Matrix4.fromList([
targetScale,
matrix.row1.x,
matrix.row2.x,
matrix.row3.x,
matrix.row0.y,
targetScale,
matrix.row2.y,
matrix.row3.y,
matrix.row0.z,
matrix.row1.z,
targetScale,
matrix.row3.z,
offSetX,
offSetY,
matrix.row2.w,
matrix.row3.w
]);
_animation = Matrix4Tween(
begin: _transformationController!.value,
end: matrix,
).animate(
CurveTween(curve: Curves.easeOut).animate(_animationController),
);
_animationController
.forward(from: 0)
.whenComplete(() => _onScaleChanged(targetScale));
}
onLongPress() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
onTap: () {
onShareImg(widget.sources[currentIndex!]);
Get.back();
},
dense: true,
title: const Text('分享', style: TextStyle(fontSize: 14)),
),
ListTile(
onTap: () {
Get.back();
Utils.copyText(widget.sources[currentIndex!]);
},
dense: true,
title: const Text('复制链接', style: TextStyle(fontSize: 14)),
),
ListTile(
onTap: () {
Get.back();
DownloadUtils.downloadImg(
context,
[widget.sources[currentIndex!]],
);
},
dense: true,
title: const Text('保存图片', style: TextStyle(fontSize: 14)),
),
if (widget.sources.length > 1)
ListTile(
onTap: () {
Get.back();
DownloadUtils.downloadImg(
context,
widget.sources as List<String>,
);
},
dense: true,
title: const Text('保存全部图片', style: TextStyle(fontSize: 14)),
),
],
),
);
},
);
}
}

View File

@@ -1,10 +1,12 @@
import 'dart:async';
import 'dart:math';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/http/video.dart';
import 'package:PiliPalaX/models/bangumi/info.dart' as bangumi;
import 'package:PiliPalaX/models/video_detail_res.dart' as video;
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';
@@ -16,14 +18,18 @@ import '../../utils/utils.dart';
class ListSheetContent extends StatefulWidget {
const ListSheetContent({
super.key,
this.index,
this.index, // tab index
this.season,
required this.episodes,
this.episodes,
this.bvid,
this.aid,
required this.currentCid,
required this.changeFucCall,
required this.onClose,
this.onClose,
this.onReverse,
this.showTitle,
this.isSupportReverse,
this.isReversed,
});
final dynamic index;
@@ -34,6 +40,10 @@ class ListSheetContent extends StatefulWidget {
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();
@@ -42,26 +52,63 @@ class ListSheetContent extends StatefulWidget {
class _ListSheetContentState extends State<ListSheetContent>
with TickerProviderStateMixin {
late List<ItemScrollController> itemScrollController = [];
late final int currentIndex =
widget.episodes!.indexWhere((dynamic e) => e.cid == widget.currentCid) ??
0;
late int currentIndex = _currentIndex;
late List<bool> reverse;
int get _index => widget.index ?? 0;
bool get _isList =>
widget.season != null &&
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>();
_indexStream ??= StreamController<int>.broadcast();
_ctr = TabController(
vsync: this,
length: widget.season.sections.length,
@@ -77,11 +124,8 @@ class _ListSheetContentState extends State<ListSheetContent>
reverse = _isList
? List.generate(widget.season.sections.length, (_) => false)
: [false];
WidgetsBinding.instance.addPostFrameCallback((_) {
itemScrollController[_index].jumpTo(index: currentIndex);
});
if (widget.bvid != null && widget.season != null) {
_favStream = StreamController<int>();
_favStream ??= StreamController<int>();
() async {
dynamic result = await VideoHttp.videoRelation(bvid: widget.bvid);
if (result['status']) {
@@ -90,12 +134,19 @@ class _ListSheetContentState extends State<ListSheetContent>
}
}();
}
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();
@@ -138,6 +189,7 @@ class _ListSheetContentState extends State<ListSheetContent>
}
SmartDialog.showToast('切换到:$title');
widget.onClose?.call();
currentIndex = index;
widget.changeFucCall(
episode is bangumi.EpisodeItem ? episode.epId : null,
episode.runtimeType.toString() == "EpisodeItem"
@@ -171,7 +223,7 @@ class _ListSheetContentState extends State<ListSheetContent>
)
: null,
child: LayoutBuilder(
builder: (_, constraints) => NetworkImgLayer(
builder: (context, constraints) => NetworkImgLayer(
radius: 6,
src: episode is video.EpisodeItem
? episode.arc?.pic
@@ -230,17 +282,19 @@ class _ListSheetContentState extends State<ListSheetContent>
children: [
Container(
height: 45,
padding: const EdgeInsets.only(left: 14, right: 14),
padding: EdgeInsets.symmetric(
horizontal: widget.showTitle != false ? 14 : 6),
child: Row(
children: [
Text(
'合集(${_isList ? widget.season.epCount : widget.episodes!.length}',
style: Theme.of(context).textTheme.titleMedium,
),
if (widget.showTitle != false)
Text(
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
style: Theme.of(context).textTheme.titleMedium,
),
StreamBuilder(
stream: _favStream?.stream,
builder: (_, snapshot) => snapshot.hasData
? _mediumButton(
builder: (context, snapshot) => snapshot.hasData
? mediumButton(
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
icon: _seasonFav == 1
? Icons.notifications_off_outlined
@@ -262,39 +316,43 @@ class _ListSheetContentState extends State<ListSheetContent>
)
: const SizedBox.shrink(),
),
_mediumButton(
mediumButton(
tooltip: '跳至顶部',
icon: Icons.vertical_align_top,
onPressed: () {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0]
? 0
: _isList
? widget.season.sections[_ctr?.index].episodes
.length -
1
: widget.episodes.length - 1,
duration: const Duration(milliseconds: 200),
);
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(
mediumButton(
tooltip: '跳至底部',
icon: Icons.vertical_align_bottom,
onPressed: () {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0]
? _isList
? widget.season.sections[_ctr?.index].episodes
.length -
1
: widget.episodes.length - 1
: 0,
duration: const Duration(milliseconds: 200),
);
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(
mediumButton(
tooltip: '跳至当前',
icon: Icons.my_location,
onPressed: () async {
@@ -302,21 +360,36 @@ class _ListSheetContentState extends State<ListSheetContent>
_ctr?.animateTo(_index);
await Future.delayed(const Duration(milliseconds: 225));
}
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: currentIndex,
duration: const Duration(milliseconds: 200),
);
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: 0,
builder: (_, snapshot) => _mediumButton(
tooltip: reverse[snapshot.data] ? '' : '',
initialData: _index,
builder: (context, snapshot) => mediumButton(
tooltip: reverse[snapshot.data] ? '' : '',
icon: !reverse[snapshot.data]
? MdiIcons.sortAscending
: MdiIcons.sortDescending,
? MdiIcons.sortNumericAscending
: MdiIcons.sortNumericDescending,
onPressed: () {
setState(() {
reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0];
@@ -324,11 +397,12 @@ class _ListSheetContentState extends State<ListSheetContent>
},
),
),
_mediumButton(
tooltip: '关闭',
icon: Icons.close,
onPressed: widget.onClose,
),
if (widget.onClose != null)
mediumButton(
tooltip: '关闭',
icon: Icons.close,
onPressed: widget.onClose,
),
],
),
),
@@ -359,36 +433,41 @@ class _ListSheetContentState extends State<ListSheetContent>
index, widget.season.sections[index].episodes),
),
)
: _buildBody(null, widget.episodes),
: _buildBody(null, episodes),
),
],
),
);
}
Widget _mediumButton({
String? tooltip,
IconData? icon,
VoidCallback? onPressed,
}) {
return SizedBox(
width: 34,
height: 34,
child: IconButton(
tooltip: tooltip,
icon: Icon(icon),
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: onPressed,
),
);
}
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 + 20,
bottom: MediaQuery.of(context).padding.bottom + 80,
),
reverse: reverse[i ?? 0],
itemCount: episodes.length,
@@ -405,7 +484,7 @@ class _ListSheetContentState extends State<ListSheetContent>
);
},
itemScrollController: itemScrollController[i ?? 0],
separatorBuilder: (_, index) => Divider(
separatorBuilder: (context, index) => Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),

View File

@@ -80,7 +80,7 @@ class LiveCard extends StatelessWidget {
Text(
liveItem.title as String,
textAlign: TextAlign.start,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w400),
style: const TextStyle(fontSize: 13),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),

View File

@@ -1,4 +1,4 @@
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:flutter/material.dart';
Widget get loadingWidget => Center(child: CircularProgressIndicator());

View File

@@ -1,13 +1,9 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:PiliPalaX/utils/extension.dart';
import 'package:PiliPalaX/utils/global_data.dart';
import '../../utils/storage.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/global_data.dart';
import '../constants.dart';
Box<dynamic> setting = GStorage.setting;
class NetworkImgLayer extends StatelessWidget {
const NetworkImgLayer({
super.key,
@@ -67,7 +63,7 @@ class NetworkImgLayer extends StatelessWidget {
child: Builder(
builder: (context) => CachedNetworkImage(
imageUrl:
'${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}',
'${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${type != 'emote' && thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}',
width: width,
height: ignoreHeight == null || ignoreHeight == false
? height
@@ -83,17 +79,17 @@ class NetworkImgLayer extends StatelessWidget {
fadeInDuration:
fadeInDuration ?? const Duration(milliseconds: 120),
filterQuality: FilterQuality.low,
errorWidget: (BuildContext context, String url, Object error) =>
placeholder(context),
// errorWidget: (BuildContext context, String url, Object error) =>
// placeholder(context),
placeholder: (BuildContext context, String url) =>
placeholder(context),
imageBuilder: imageBuilder,
errorListener: (value) {
thumbnail = false;
if (context.mounted) {
(context as Element).markNeedsBuild();
}
},
// errorListener: (value) {
// thumbnail = false;
// if (context.mounted) {
// (context as Element).markNeedsBuild();
// }
// },
),
),
)

View File

@@ -1,7 +1,6 @@
import 'dart:math';
import 'package:PiliPalaX/grpc/app/card/v1/card.pb.dart' as card;
import 'package:PiliPalaX/utils/extension.dart';
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../utils/download.dart';
@@ -83,14 +82,8 @@ class OverlayPop extends StatelessWidget {
context,
[
videoItem is card.Card
? (videoItem as card.Card)
.smallCoverV5
.base
.cover
.http2https
: (videoItem.pic != null
? (videoItem.pic as String).http2https
: (videoItem.cover as String).http2https)
? (videoItem as card.Card).smallCoverV5.base.cover
: videoItem.pic ?? videoItem.cover
],
);
closeFn?.call();

View File

@@ -1,133 +0,0 @@
// ignore_for_file: depend_on_referenced_packages
import 'dart:math';
import 'dart:ui' as ui show Image;
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart';
double get maxDragOffset => 100;
double hideHeight = maxDragOffset / 2.3;
double refreshHeight = maxDragOffset / 1.5;
class PullToRefreshHeader extends StatelessWidget {
const PullToRefreshHeader(
this.info,
this.lastRefreshTime, {
this.color,
super.key,
});
final PullToRefreshScrollNotificationInfo? info;
final DateTime? lastRefreshTime;
final Color? color;
@override
Widget build(BuildContext context) {
final PullToRefreshScrollNotificationInfo? infos = info;
if (infos == null) {
return const SizedBox();
}
String text = '';
if (infos.mode == PullToRefreshIndicatorMode.armed) {
text = 'Release to refresh';
} else if (infos.mode == PullToRefreshIndicatorMode.refresh ||
infos.mode == PullToRefreshIndicatorMode.snap) {
text = 'Loading...';
} else if (infos.mode == PullToRefreshIndicatorMode.done) {
text = 'Refresh completed.';
} else if (infos.mode == PullToRefreshIndicatorMode.drag) {
text = 'Pull to refresh';
} else if (infos.mode == PullToRefreshIndicatorMode.canceled) {
text = 'Cancel refresh';
}
final TextStyle ts = const TextStyle(
color: Colors.grey,
).copyWith(fontSize: 14);
final double dragOffset = info?.dragOffset ?? 0.0;
final DateTime time = lastRefreshTime ?? DateTime.now();
final double top = -hideHeight + dragOffset;
return Container(
height: dragOffset,
color: color ?? Colors.transparent,
// padding: EdgeInsets.only(top: dragOffset / 3),
// padding: EdgeInsets.only(bottom: 5.0),
child: Stack(
children: <Widget>[
Positioned(
left: 0.0,
right: 0.0,
top: top,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Container(
alignment: Alignment.centerRight,
margin: const EdgeInsets.only(right: 12.0),
child: RefreshImage(top: top),
),
),
Column(
children: <Widget>[
Text(text, style: ts),
Text(
'Last updated:${DateFormat('yyyy-MM-dd hh:mm').format(time)}',
style: ts.copyWith(fontSize: 14),
)
],
),
const Spacer(),
],
),
)
],
),
);
}
}
class RefreshImage extends StatelessWidget {
const RefreshImage({
super.key,
required this.top,
});
final double top;
@override
Widget build(BuildContext context) {
const double imageSize = 30;
return ExtendedImage.asset(
'assets/flutterCandies_grey.png',
width: imageSize,
height: imageSize,
afterPaintImage: (Canvas canvas, Rect rect, ui.Image image, Paint paint) {
final double imageHeight = image.height.toDouble();
final double imageWidth = image.width.toDouble();
final Size size = rect.size;
final double y =
(1 - min(top / (refreshHeight - hideHeight), 1)) * imageHeight;
canvas.drawImageRect(
image,
Rect.fromLTWH(0.0, y, imageWidth, imageHeight - y),
Rect.fromLTWH(rect.left, rect.top + y / imageHeight * size.height,
size.width, (imageHeight - y) / imageHeight * size.height),
Paint()
..colorFilter =
const ColorFilter.mode(Color(0xFFea5504), BlendMode.srcIn)
..isAntiAlias = false
..filterQuality = FilterQuality.low,
);
//canvas.restore();
},
);
}
}

View File

@@ -4,16 +4,27 @@ class Segment {
final double start;
final double end;
final Color color;
final String? title;
final String? url;
final int? from;
final int? to;
Segment(this.start, this.end, this.color);
Segment(
this.start,
this.end,
this.color, [
this.title,
this.url,
this.from,
this.to,
]);
}
class SegmentProgressBar extends CustomPainter {
final double progress;
final List<Segment> segmentColors;
late double _defHeight;
SegmentProgressBar({
required this.progress,
required this.segmentColors,
});
@@ -21,29 +32,89 @@ class SegmentProgressBar extends CustomPainter {
void paint(Canvas canvas, Size size) {
final paint = Paint()..style = PaintingStyle.fill;
for (var segment in segmentColors) {
paint.color = segment.color;
final segmentStart = segment.start * size.width;
final segmentEnd = segment.end * size.width;
final progressEnd = progress * size.width;
for (int i = 0; i < segmentColors.length; i++) {
paint.color = segmentColors[i].color;
final segmentStart = segmentColors[i].start * size.width;
final segmentEnd = segmentColors[i].end * size.width;
if (progressEnd < segmentStart) {
break;
}
if (segmentEnd > segmentStart ||
(segmentEnd == segmentStart && segmentStart > 0)) {
if (segmentColors[i].title != null) {
double fontSize = 8;
final segmentWidth =
(progressEnd < segmentEnd ? progressEnd : segmentEnd) - segmentStart;
if (segmentWidth > 0) {
canvas.drawRect(
Rect.fromLTWH(segmentStart, 0, segmentWidth, size.height),
paint,
);
TextPainter getTextPainter() => TextPainter(
text: TextSpan(
text: segmentColors[i].title,
style: TextStyle(
color: Colors.white,
fontSize: fontSize,
),
),
textDirection: TextDirection.ltr,
)..layout();
TextPainter textPainter = getTextPainter();
if (i == 0) {
_defHeight = textPainter.height;
}
late double prevStart;
if (i != 0) {
prevStart = segmentColors[i - 1].start * size.width;
}
double width = i == 0 ? segmentStart : segmentStart - prevStart;
while (textPainter.width > width - 2 && fontSize >= 2) {
fontSize -= 1;
textPainter = getTextPainter();
}
if (i == 0) {
canvas.drawRect(
Rect.fromLTRB(
0,
-_defHeight,
size.width,
0,
),
Paint()..color = Colors.grey[600]!.withOpacity(0.45),
);
}
canvas.drawRect(
Rect.fromLTWH(
segmentStart,
-_defHeight,
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
size.height + _defHeight,
),
paint,
);
double textX = i == 0
? (segmentStart - textPainter.width) / 2
: (segmentStart - prevStart - textPainter.width) / 2 +
prevStart +
1;
double textY = (-_defHeight - textPainter.height) / 2;
textPainter.paint(canvas, Offset(textX, textY));
} else {
canvas.drawRect(
Rect.fromLTWH(
segmentStart,
0,
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
size.height,
),
paint,
);
}
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
return false;
}
}

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:PiliPlus/utils/utils.dart';
Widget statDanMu({
required BuildContext context,
@@ -24,7 +24,6 @@ Widget statDanMu({
Text(
Utils.numFormat(danmu!),
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: size == 'medium' ? 12 : 11,
color: color,
),

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:PiliPlus/utils/utils.dart';
Widget statView({
required BuildContext context,
@@ -27,7 +27,6 @@ Widget statView({
Text(
Utils.numFormat(view!),
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: size == 'medium' ? 12 : 11,
color: color,
),

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/models/model_hot_video_item.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 '../../http/search.dart';
@@ -16,24 +17,22 @@ class VideoCardH extends StatelessWidget {
const VideoCardH({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
this.showDanmaku = true,
this.showPubdate = false,
this.onTap,
this.onLongPress,
});
final dynamic videoItem;
final Function()? longPress;
final Function()? longPressEnd;
final String source;
final bool showOwner;
final bool showView;
final bool showDanmaku;
final bool showPubdate;
final GestureTapCallback? onTap;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
@override
Widget build(BuildContext context) {
@@ -43,21 +42,33 @@ class VideoCardH extends StatelessWidget {
try {
type = videoItem.type;
} catch (_) {}
List<VideoCustomAction> actions =
VideoCustomActions(videoItem, context).actions;
final String heroTag = Utils.makeHeroTag(aid);
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!,
},
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
// for (var item in actions)
// CustomSemanticsAction(
// label: item.title.isEmpty ? 'label' : item.title): item.onTap!,
// },
child: InkWell(
borderRadius: BorderRadius.circular(12),
onLongPress: longPress,
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();
@@ -67,76 +78,102 @@ class VideoCardH extends StatelessWidget {
SmartDialog.showToast('课堂视频暂不支持播放');
return;
}
if (videoItem is HotVideoItemModel &&
videoItem.pgcLabel?.isNotEmpty == true &&
videoItem.redirectUrl?.isNotEmpty == true) {
String? id = RegExp(r'(ep|ss)\d+')
.firstMatch(videoItem.redirectUrl)
?.group(0);
if (id != null) {
Utils.viewBangumi(
seasonId:
id.startsWith('ss') ? id.replaceFirst('ss', '') : null,
epId: id.startsWith('ep') ? id.replaceFirst('ep', '') : null,
);
}
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': heroTag});
Get.toNamed(
'/video?bvid=$bvid&cid=$cid',
arguments: {
'videoItem': videoItem,
'heroTag': Utils.makeHeroTag(aid)
},
);
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
return 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: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.pic as String,
width: maxWidth,
height: maxHeight,
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: 5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder:
(BuildContext context, BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
NetworkImgLayer(
src: videoItem.pic as String,
width: maxWidth,
height: maxHeight,
),
if (videoItem is HotVideoItemModel &&
videoItem.pgcLabel?.isNotEmpty == true)
PBadge(
text: videoItem.pgcLabel,
top: 6.0,
right: 6.0,
),
if (videoItem.duration != 0)
PBadge(
text: Utils.timeFormat(videoItem.duration!),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: 'primary',
),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
// 6.0, 6.0, null, null),
],
);
},
),
if (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),
],
);
},
),
videoContent(context)
],
);
},
),
videoContent(context)
],
),
),
),
),
if (source == 'normal')
Positioned(
bottom: 0,
right: 0,
right: 12,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
actions: actions,
videoItem: videoItem,
),
),
]);
@@ -153,13 +190,12 @@ class VideoCardH extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (videoItem.title is String) ...[
if (videoItem.title is String)
Expanded(
child: Text(
videoItem.title as String,
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
@@ -167,19 +203,19 @@ class VideoCardH extends StatelessWidget {
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
] else ...[
)
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(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context)
.textTheme
.bodyMedium!
@@ -195,7 +231,6 @@ class VideoCardH extends StatelessWidget {
),
),
),
],
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
@@ -221,7 +256,6 @@ class VideoCardH extends StatelessWidget {
"$pubdate ${showOwner ? videoItem.owner.name : ''}",
maxLines: 1,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,

View File

@@ -1,5 +1,6 @@
import 'package:PiliPalaX/grpc/app/card/v1/card.pb.dart' as card;
import 'package:PiliPalaX/utils/app_scheme.dart';
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../../utils/utils.dart';
@@ -12,8 +13,6 @@ class VideoCardHGrpc extends StatelessWidget {
const VideoCardHGrpc({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
@@ -21,8 +20,6 @@ class VideoCardHGrpc extends StatelessWidget {
this.showPubdate = false,
});
final card.Card videoItem;
final Function()? longPress;
final Function()? longPressEnd;
final String source;
final bool showOwner;
final bool showView;
@@ -50,7 +47,11 @@ class VideoCardHGrpc extends StatelessWidget {
// },
child: InkWell(
borderRadius: BorderRadius.circular(12),
onLongPress: longPress,
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.smallCoverV5.base.title,
cover: videoItem.smallCoverV5.base.cover,
),
onTap: () async {
if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放');
@@ -58,13 +59,6 @@ class VideoCardHGrpc extends StatelessWidget {
}
try {
PiliScheme.routePush(Uri.parse(videoItem.smallCoverV5.base.uri));
// final int cid =
// videoItem.smallCoverV5.base.playerArgs.cid.toInt() ??
// await SearchHttp.ab2c(aid: aid, bvid: bvid);
// Get.toNamed('/video?bvid=$bvid&cid=$cid',
// arguments: {'heroTag': heroTag});
// Get.toNamed('/video?bvid=$bvid&cid=$cid',
// arguments: {'videoItem': videoItem, 'heroTag': heroTag});
} catch (err) {
SmartDialog.showToast(err.toString());
}
@@ -150,7 +144,6 @@ class VideoCardHGrpc extends StatelessWidget {
videoItem.smallCoverV5.base.title,
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
@@ -183,7 +176,6 @@ class VideoCardHGrpc extends StatelessWidget {
videoItem.smallCoverV5.rightDesc1,
maxLines: 1,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
@@ -195,7 +187,6 @@ class VideoCardHGrpc extends StatelessWidget {
videoItem.smallCoverV5.rightDesc2,
maxLines: 1,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,

View File

@@ -1,7 +1,8 @@
import 'package:PiliPalaX/common/widgets/stat/danmu.dart';
import 'package:PiliPalaX/common/widgets/stat/view.dart';
import 'package:PiliPalaX/common/widgets/video_popup_menu.dart';
import 'package:PiliPalaX/models/space_archive/item.dart';
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/video_popup_menu.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';
@@ -15,90 +16,105 @@ class VideoCardHMemberVideo extends StatelessWidget {
const VideoCardHMemberVideo({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
this.onTap,
this.bvid,
});
final Item videoItem;
final Function()? longPress;
final Function()? longPressEnd;
final VoidCallback? onTap;
final dynamic bvid;
@override
Widget build(BuildContext context) {
final int aid = int.tryParse(videoItem.param ?? '') ?? -1;
final String bvid = videoItem.bvid ?? '';
final String heroTag = Utils.makeHeroTag(aid);
return Stack(children: [
InkWell(
borderRadius: BorderRadius.circular(12),
onLongPress: longPress,
onTap: () async {
try {
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.firstCid}',
arguments: {'heroTag': heroTag});
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
return 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: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.cover,
// videoItem.season?['cover'] ?? videoItem.cover,
width: maxWidth,
height: maxHeight,
),
),
// if (videoItem.season != null)
// PBadge(
// text: '合集: ${videoItem.season?['count']}',
// right: 6.0,
// bottom: 6.0,
// type: 'gray',
// )
// else
if (videoItem.duration != null)
PBadge(
text: Utils.timeFormat(videoItem.duration),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
],
);
},
),
),
videoContent(context)
],
);
return Stack(
children: [
InkWell(
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.cover,
),
onTap: () async {
if (onTap != null) {
onTap!();
return;
}
try {
Get.toNamed(
'/video?bvid=$bvid&cid=${videoItem.firstCid}',
arguments: {
'heroTag': Utils.makeHeroTag(aid),
},
);
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: 5,
),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
return 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.cover,
// videoItem.season?['cover'] ?? videoItem.cover,
width: maxWidth,
height: maxHeight,
),
// if (videoItem.season != null)
// PBadge(
// text: '合集: ${videoItem.season?['count']}',
// right: 6.0,
// bottom: 6.0,
// type: 'gray',
// )
// else
if (videoItem.duration != null)
PBadge(
text: Utils.timeFormat(videoItem.duration),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
],
);
},
),
),
videoContent(context)
],
);
},
),
),
),
),
Positioned(
bottom: 0,
right: 0,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
actions: VideoCustomActions(videoItem, context).actions,
Positioned(
bottom: 0,
right: 12,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
videoItem: videoItem,
),
),
),
]);
],
);
}
Widget videoContent(context) {
@@ -115,10 +131,13 @@ class VideoCardHMemberVideo extends StatelessWidget {
videoItem.title ?? '',
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: FontWeight.w400,
fontWeight: videoItem.bvid == bvid ? FontWeight.bold : null,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: videoItem.bvid == bvid
? Theme.of(context).colorScheme.primary
: null,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
@@ -131,7 +150,6 @@ class VideoCardHMemberVideo extends StatelessWidget {
: videoItem.publishTimeText ?? '',
maxLines: 1,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,

View File

@@ -1,6 +1,6 @@
import 'package:PiliPalaX/http/search.dart';
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/http/search.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 '../../models/home/rcmd/result.dart';
@@ -18,14 +18,12 @@ import 'video_popup_menu.dart';
// 视频卡片 - 垂直布局
class VideoCardV extends StatelessWidget {
final dynamic videoItem;
final Function()? longPress;
final Function()? longPressEnd;
final VoidCallback? onRemove;
const VideoCardV({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
this.onRemove,
});
bool isStringNumeric(String str) {
@@ -83,11 +81,14 @@ class VideoCardV extends StatelessWidget {
if (cid == -1) {
cid = await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid);
}
Get.toNamed('/video?bvid=$bvid&cid=$cid', arguments: {
// 'videoItem': videoItem,
'pic': videoItem.pic,
'heroTag': heroTag,
});
Get.toNamed(
'/video?bvid=$bvid&cid=$cid',
arguments: {
// 'videoItem': videoItem,
'pic': videoItem.pic,
'heroTag': heroTag,
},
);
break;
// 动态
case 'picture':
@@ -135,7 +136,7 @@ class VideoCardV extends StatelessWidget {
default:
SmartDialog.showToast(videoItem.goto);
Get.toNamed(
'/webviewnew',
'/webview',
parameters: {
'url': videoItem.uri,
'type': 'url',
@@ -147,23 +148,24 @@ class VideoCardV extends StatelessWidget {
@override
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(videoItem.id);
List<VideoCustomAction> actions =
VideoCustomActions(videoItem, context).actions;
return Stack(children: [
Semantics(
label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true,
customSemanticsActions: <CustomSemanticsAction, void Function()>{
for (var item in actions)
CustomSemanticsAction(label: item.title): item.onTap!,
},
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
// for (var item in actions)
// CustomSemanticsAction(label: item.title): item.onTap!,
// },
child: Card(
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: InkWell(
onTap: () async => onPushDetail(heroTag),
onLongPress: longPress,
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.id)),
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.pic,
),
child: Column(
children: [
AspectRatio(
@@ -173,13 +175,10 @@ class VideoCardV extends StatelessWidget {
double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
),
NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
),
if (videoItem.duration > 0)
PBadge(
@@ -203,13 +202,15 @@ class VideoCardV extends StatelessWidget {
),
if (videoItem.goto == 'av')
Positioned(
right: -5,
bottom: -2,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
actions: actions,
)),
right: -5,
bottom: -2,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
videoItem: videoItem,
onRemove: onRemove,
),
),
]);
}
@@ -229,7 +230,6 @@ class VideoCardV extends StatelessWidget {
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w400,
height: 1.38,
)),
),
@@ -247,7 +247,8 @@ class VideoCardV extends StatelessWidget {
size: 'small',
type: 'line',
fs: 9,
)
),
const SizedBox(width: 2),
],
if (videoItem.rcmdReason != null) ...[
PBadge(
@@ -255,7 +256,8 @@ class VideoCardV extends StatelessWidget {
stack: 'normal',
size: 'small',
type: 'color',
)
),
const SizedBox(width: 2),
],
if (videoItem.goto == 'picture') ...[
const PBadge(
@@ -264,7 +266,8 @@ class VideoCardV extends StatelessWidget {
size: 'small',
type: 'line',
fs: 9,
)
),
const SizedBox(width: 2),
],
if (videoItem.isFollowed == 1) ...[
const PBadge(
@@ -272,7 +275,8 @@ class VideoCardV extends StatelessWidget {
stack: 'normal',
size: 'small',
type: 'color',
)
),
const SizedBox(width: 2),
],
Expanded(
flex: 1,
@@ -307,7 +311,7 @@ class VideoCardV extends StatelessWidget {
view: videoItem.stat.view,
goto: videoItem.goto,
),
const SizedBox(width: 6),
const SizedBox(width: 4),
if (videoItem.goto != 'picture')
statDanMu(
context: context,
@@ -320,6 +324,7 @@ class VideoCardV extends StatelessWidget {
flex: 0,
child: RichText(
maxLines: 1,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan(
style: TextStyle(
fontSize:
@@ -342,6 +347,7 @@ class VideoCardV extends StatelessWidget {
flex: 0,
child: RichText(
maxLines: 1,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan(
style: TextStyle(
fontSize:

View File

@@ -1,4 +1,5 @@
import 'package:PiliPalaX/models/space/item.dart';
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/models/space/item.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -10,14 +11,10 @@ import 'network_img_layer.dart';
// 视频卡片 - 垂直布局
class VideoCardVMemberHome extends StatelessWidget {
final Item videoItem;
final Function()? longPress;
final Function()? longPressEnd;
const VideoCardVMemberHome({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
});
void onPushDetail(heroTag) async {
@@ -67,11 +64,14 @@ class VideoCardVMemberHome extends StatelessWidget {
// break;
case 'av':
String bvid = videoItem.bvid ?? '';
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.firstCid}', arguments: {
// 'videoItem': videoItem,
'pic': videoItem.cover,
'heroTag': heroTag,
});
Get.toNamed(
'/video?bvid=$bvid&cid=${videoItem.firstCid}',
arguments: {
// 'videoItem': videoItem,
'pic': videoItem.cover,
'heroTag': heroTag,
},
);
break;
// 动态
// case 'picture':
@@ -119,7 +119,7 @@ class VideoCardVMemberHome extends StatelessWidget {
default:
SmartDialog.showToast(goto);
Get.toNamed(
'/webviewnew',
'/webview',
parameters: {
'url': videoItem.uri ?? '',
'type': 'url',
@@ -131,7 +131,6 @@ class VideoCardVMemberHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(videoItem.bvid);
// List<VideoCustomAction> actions =
// VideoCustomActions(videoItem, context).actions;
return Stack(children: [
@@ -146,8 +145,12 @@ class VideoCardVMemberHome extends StatelessWidget {
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: InkWell(
onTap: () async => onPushDetail(heroTag),
onLongPress: longPress,
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.bvid)),
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.cover,
),
child: Column(
children: [
AspectRatio(
@@ -157,13 +160,10 @@ class VideoCardVMemberHome extends StatelessWidget {
double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.cover,
width: maxWidth,
height: maxHeight,
),
NetworkImgLayer(
src: videoItem.cover,
width: maxWidth,
height: maxHeight,
),
if ((videoItem.duration ?? -1) > 0)
PBadge(
@@ -214,70 +214,69 @@ Widget videoContent(BuildContext context, Item videoItem) {
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w400,
height: 1.38,
)),
),
],
),
const Spacer(),
// const Spacer(),
// const SizedBox(height: 2),
// VideoStat(
// videoItem: videoItem,
// ),
Row(
children: [
// if (videoItem.goto == 'bangumi') ...[
// PBadge(
// text: videoItem.bangumiBadge,
// stack: 'normal',
// size: 'small',
// type: 'line',
// fs: 9,
// )
// ],
// if (videoItem.rcmdReason != null) ...[
// PBadge(
// text: videoItem.rcmdReason,
// stack: 'normal',
// size: 'small',
// type: 'color',
// )
// ],
if (videoItem.goto == 'picture') ...[
const PBadge(
text: '动态',
stack: 'normal',
size: 'small',
type: 'line',
fs: 9,
)
],
// if (videoItem.isFollowed == 1) ...[
// const PBadge(
// text: '已关注',
// stack: 'normal',
// size: 'small',
// type: 'color',
// )
// ],
Expanded(
flex: 1,
child: Text(
videoItem.author ?? '',
// semanticsLabel: "Up主${videoItem.owner.name}",
maxLines: 1,
overflow: TextOverflow.clip,
style: TextStyle(
height: 1.5,
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
),
if (videoItem.goto == 'av') const SizedBox(width: 10)
],
),
// Row(
// children: [
// if (videoItem.goto == 'bangumi') ...[
// PBadge(
// text: videoItem.bangumiBadge,
// stack: 'normal',
// size: 'small',
// type: 'line',
// fs: 9,
// )
// ],
// if (videoItem.rcmdReason != null) ...[
// PBadge(
// text: videoItem.rcmdReason,
// stack: 'normal',
// size: 'small',
// type: 'color',
// )
// ],
// if (videoItem.goto == 'picture') ...[
// const PBadge(
// text: '动态',
// stack: 'normal',
// size: 'small',
// type: 'line',
// fs: 9,
// )
// ],
// if (videoItem.isFollowed == 1) ...[
// const PBadge(
// text: '已关注',
// stack: 'normal',
// size: 'small',
// type: 'color',
// )
// ],
// Expanded(
// flex: 1,
// child: Text(
// videoItem.author ?? '',
// // semanticsLabel: "Up主${videoItem.owner.name}",
// maxLines: 1,
// overflow: TextOverflow.clip,
// style: TextStyle(
// height: 1.5,
// fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
// color: Theme.of(context).colorScheme.outline,
// ),
// ),
// ),
// if (videoItem.goto == 'av') const SizedBox(width: 10)
// ],
// ),
],
),
),

View File

@@ -1,4 +1,5 @@
import 'package:PiliPalaX/utils/utils.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.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';
@@ -9,7 +10,7 @@ import '../../http/video.dart';
import '../../models/home/rcmd/result.dart';
import '../../pages/mine/controller.dart';
import '../../utils/storage.dart';
import 'package:PiliPalaX/models/space_archive/item.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
class VideoCustomAction {
String title;
@@ -23,7 +24,9 @@ class VideoCustomActions {
dynamic videoItem;
BuildContext context;
late List<VideoCustomAction> actions;
VideoCustomActions(this.videoItem, this.context) {
VoidCallback? onRemove;
VideoCustomActions(this.videoItem, this.context, [this.onRemove]) {
actions = [
if ((videoItem.bvid as String?)?.isNotEmpty == true)
VideoCustomAction(
@@ -82,12 +85,10 @@ class VideoCustomActions {
return;
}
Widget actionButton(DislikeReason? r, FeedbackReason? f) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12.0, vertical: 0.0),
),
onPressed: () async {
return SearchText(
text: r?.name ?? f?.name ?? '未知',
onTap: (_) async {
Get.back();
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.feedDislike(
reasonId: r?.id,
@@ -97,10 +98,12 @@ class VideoCustomActions {
);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? (r?.toast ?? f?.toast) : res['msg']);
Get.back();
res['status'] ? (r?.toast ?? f?.toast) : res['msg'],
);
if (res['status']) {
onRemove?.call();
}
},
child: Text(r?.name ?? f?.name ?? '未知'),
);
}
@@ -108,53 +111,57 @@ class VideoCustomActions {
context: context,
builder: (context) {
return AlertDialog(
title: const Text('请选择'),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (tp.dislikeReasons != null)
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Text('我不想看'),
),
if (tp.dislikeReasons != null)
if (tp.dislikeReasons != null) ...[
Text('我不想看'),
const SizedBox(height: 5),
Wrap(
spacing: 5.0,
runSpacing: 2.0,
spacing: 8.0,
runSpacing: 8.0,
children: tp.dislikeReasons!.map((item) {
return actionButton(item, null);
}).toList(),
),
if (tp.feedbacks != null)
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Text('反馈'),
),
if (tp.feedbacks != null)
],
if (tp.feedbacks != null) ...[
const SizedBox(height: 5),
Text('反馈'),
const SizedBox(height: 5),
Wrap(
spacing: 5.0,
runSpacing: 2.0,
spacing: 8.0,
runSpacing: 8.0,
children: tp.feedbacks!.map((item) {
return actionButton(null, item);
}).toList(),
),
//分割线
],
const Divider(),
ElevatedButton(
onPressed: () async {
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.feedDislikeCancel(
// reasonId: r?.id,
// feedbackId: f?.id,
id: v.param!,
goto: v.goto!,
);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? "成功" : res['msg']);
Get.back();
},
child: const Text("撤销"),
Center(
child: FilledButton.tonal(
onPressed: () async {
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.feedDislikeCancel(
// reasonId: r?.id,
// feedbackId: f?.id,
id: v.param!,
goto: v.goto!,
);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? "成功" : res['msg']);
Get.back();
},
style: FilledButton.styleFrom(
visualDensity: VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: const Text("撤销"),
),
),
],
),
@@ -167,7 +174,6 @@ class VideoCustomActions {
context: context,
builder: (context) {
return AlertDialog(
title: const Text('点踩该视频?'),
content: SingleChildScrollView(
child: Column(
children: [
@@ -178,20 +184,28 @@ class VideoCustomActions {
spacing: 5.0,
runSpacing: 2.0,
children: [
ElevatedButton(
FilledButton.tonal(
onPressed: () async {
Get.back();
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.dislikeVideo(
bvid: videoItem.bvid as String, type: true);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? "点踩成功" : res['msg']);
Get.back();
res['status'] ? "点踩成功" : res['msg'],
);
},
style: FilledButton.styleFrom(
visualDensity: VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: const Text("点踩"),
),
ElevatedButton(
FilledButton.tonal(
onPressed: () async {
Get.back();
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.dislikeVideo(
bvid: videoItem.bvid as String,
@@ -199,8 +213,13 @@ class VideoCustomActions {
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? "取消踩" : res['msg']);
Get.back();
},
style: FilledButton.styleFrom(
visualDensity: VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: const Text("撤销"),
),
],
@@ -240,13 +259,9 @@ class VideoCustomActions {
act: 5,
reSrc: 11,
);
List<int> blackMidsList = GStorage.localCache
.get(LocalCacheKey.blackMidsList, defaultValue: [-1])
.map<int>((i) => i as int)
.toList();
List<int> blackMidsList = GStorage.blackMidsList;
blackMidsList.insert(0, videoItem.owner.mid);
GStorage.localCache
.put(LocalCacheKey.blackMidsList, blackMidsList);
GStorage.setBlackMidsList(blackMidsList);
Get.back();
SmartDialog.showToast(res['msg'] ?? '成功');
},
@@ -258,10 +273,10 @@ class VideoCustomActions {
);
}),
VideoCustomAction(
"${MineController.anonymity ? '退出' : '进入'}无痕模式",
"${MineController.anonymity.value ? '退出' : '进入'}无痕模式",
'anonymity',
Icon(
MineController.anonymity
MineController.anonymity.value
? MdiIcons.incognitoOff
: MdiIcons.incognito,
size: 16,
@@ -274,14 +289,16 @@ class VideoCustomActions {
class VideoPopupMenu extends StatelessWidget {
final double? size;
final double? iconSize;
final List<VideoCustomAction> actions;
final double menuItemHeight = 45;
final dynamic videoItem;
final VoidCallback? onRemove;
const VideoPopupMenu({
super.key,
required this.size,
required this.iconSize,
required this.actions,
required this.videoItem,
this.onRemove,
});
@override
@@ -299,7 +316,8 @@ class VideoPopupMenu extends StatelessWidget {
),
position: PopupMenuPosition.under,
onSelected: (String type) {},
itemBuilder: (BuildContext context) => actions.map((e) {
itemBuilder: (BuildContext context) =>
VideoCustomActions(videoItem, context, onRemove).actions.map((e) {
return PopupMenuItem<String>(
value: e.value,
height: menuItemHeight,

View File

@@ -1,8 +1,8 @@
import 'package:PiliPalaX/grpc/app/dynamic/v1/dynamic.pbgrpc.dart' as v1;
import 'package:PiliPalaX/grpc/app/dynamic/v2/dynamic.pbgrpc.dart' as v2;
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pbgrpc.dart';
import 'package:PiliPalaX/grpc/app/playeronline/v1/playeronline.pbgrpc.dart';
import 'package:PiliPalaX/grpc/app/show/popular/v1/popular.pbgrpc.dart';
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 {

View File

@@ -1,21 +1,21 @@
import 'dart:convert';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/grpc/app/dynamic/v1/dynamic.pb.dart';
import 'package:PiliPalaX/grpc/app/dynamic/v2/dynamic.pb.dart';
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPalaX/grpc/app/playeronline/v1/playeronline.pbgrpc.dart';
import 'package:PiliPalaX/grpc/app/show/popular/v1/popular.pb.dart';
import 'package:PiliPalaX/grpc/device/device.pb.dart';
import 'package:PiliPalaX/grpc/fawkes/fawkes.pb.dart';
import 'package:PiliPalaX/grpc/grpc_client.dart';
import 'package:PiliPalaX/grpc/locale/locale.pb.dart';
import 'package:PiliPalaX/grpc/metadata/metadata.pb.dart';
import 'package:PiliPalaX/grpc/network/network.pb.dart' as network;
import 'package:PiliPalaX/grpc/restriction/restriction.pb.dart';
import 'package:PiliPalaX/utils/login.dart';
import 'package:PiliPalaX/utils/storage.dart';
import 'package:PiliPalaX/utils/utils.dart';
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/show/popular/v1/popular.pb.dart';
import 'package:PiliPlus/grpc/device/device.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/utils/login.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:fixnum/src/int64.dart';
import 'package:flutter/material.dart';
import 'package:grpc/grpc.dart';
@@ -112,8 +112,8 @@ class GrpcRepo {
e.details?.firstOrNull?.getFieldOrNull(2),
allowMalformed: true,
);
msg = msg.replaceAll(
RegExp(r"[^a-zA-Z0-9\u4e00-\u9fa5,.;!?,。;!?]"), '');
msg =
msg.replaceAll(RegExp(r"[^a-zA-Z0-9\u4e00-\u9fa5,.;?,。;!?]"), '');
if (msg.isNotEmpty) {
return {'status': false, 'msg': msg};
} else {

View File

@@ -19,8 +19,8 @@ class Api {
static const String videoUrl = '/x/player/wbi/playurl';
// 番剧视频流
// https://api.bilibili.com/pgc/player/web/playurl?cid=104236640&bvid=BV13t411n7ex
static const String bangumiVideoUrl = '/pgc/player/web/playurl';
// https://api.bilibili.com/pgc/player/web/v2/playurl?cid=104236640&bvid=BV13t411n7ex
static const String bangumiVideoUrl = '/pgc/player/web/v2/playurl';
// 字幕
// aid, cid
@@ -247,6 +247,8 @@ 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 mediaListHistory = '/x/v1/medialist/history';
// 查询视频分P列表 (avid/bvid转cid)
static const String ab2c = '/x/player/pagelist';
@@ -400,7 +402,7 @@ class Api {
// github 获取最新版
static const String latestApp =
'https://api.github.com/repos/orz12/pilipala/releases';
'https://api.github.com/repos/bggRGjQaUbCoE/PiliPlus/releases';
// 多少人在看
// https://api.bilibili.com/x/player/online/total?aid=913663681&cid=1203559746&bvid=BV1MM4y1s7NZ&ts=56427838
@@ -442,6 +444,11 @@ class Api {
// 获取指定分组下的up
static const String followUpGroup = '/x/relation/tag';
// 获取未读私信数
// https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread
static const String msgUnread =
'${HttpString.tUrl}/session_svr/v1/session_svr/single_unread';
// 获取消息中心未读信息
static const String msgFeedUnread = '/x/msgfeed/unread';
//https://api.bilibili.com/x/msgfeed/reply?platform=web&build=0&mobi_app=web
@@ -587,6 +594,9 @@ class Api {
static const String safeCenterSmsVerify =
'${HttpString.passBaseUrl}/x/safecenter/login/tel/verify';
static const String oauth2AccessToken =
'${HttpString.passBaseUrl}/x/passport-login/oauth2/access_token';
/// 密码加密密钥
/// disable_rcmd
/// local_id
@@ -690,4 +700,7 @@ class Api {
static const String videoRelation = '/x/web-interface/archive/relation';
static const String seasonFav = '/x/v3/fav/season/'; // + fav unfav
/// 稍后再看&收藏夹视频列表
static const String mediaList = '/x/v2/medialist/resource/list';
}

View File

@@ -1,11 +1,12 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPlus/http/loading_state.dart';
import '../models/bangumi/list.dart';
import 'index.dart';
class BangumiHttp {
static Future<LoadingState> bangumiList({int? page}) async {
var res = await Request().get(Api.bangumiList, data: {'page': page});
var res =
await Request().get(Api.bangumiList, queryParameters: {'page': page});
if (res.data['code'] == 0) {
BangumiListDataModel data =
BangumiListDataModel.fromJson(res.data['data']);
@@ -16,7 +17,8 @@ class BangumiHttp {
}
static Future<LoadingState> bangumiFollow({int? mid}) async {
var res = await Request().get(Api.bangumiFollow, data: {'vmid': mid});
var res =
await Request().get(Api.bangumiFollow, queryParameters: {'vmid': mid});
if (res.data['code'] == 0) {
BangumiListDataModel data =
BangumiListDataModel.fromJson(res.data['data']);

View File

@@ -1,11 +1,11 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPlus/http/loading_state.dart';
import '../models/user/black.dart';
import 'index.dart';
class BlackHttp {
static Future<LoadingState> blackList({required int pn, int? ps}) async {
var res = await Request().get(Api.blackLst, data: {
var res = await Request().get(Api.blackLst, queryParameters: {
'pn': pn,
'ps': ps ?? 50,
're_version': 0,

View File

@@ -2,7 +2,7 @@ import 'index.dart';
class CommonHttp {
static Future unReadDynamic() async {
var res = await Request().get(Api.getUnreadDynamic, data: {
var res = await Request().get(Api.getUnreadDynamic, queryParameters: {
'alltype_offset': 0,
'video_offset': 0,
'article_offset': 0,

View File

@@ -16,7 +16,7 @@ class DanmakaHttp {
};
var response = await Request().get(
Api.webDanmaku,
data: params,
queryParameters: params,
options: Options(responseType: ResponseType.bytes),
);
if (response.statusCode != 200 || response.data == null) {
@@ -29,8 +29,8 @@ class DanmakaHttp {
int type = 1, //弹幕类选择(1视频弹幕 2漫画弹幕)
required int oid, // 视频cid
required String msg, //弹幕文本(长度小于 100 字符)
int mode =
1, // 弹幕类型(1滚动弹幕 4底端弹幕 5顶端弹幕 6逆向弹幕(不能使用) 7高级弹幕 8代码弹幕不能使用 9BAS弹幕pool必须为2)
// 弹幕类型(1滚动弹幕 4底端弹幕 5顶端弹幕 6逆向弹幕(不能使用) 7高级弹幕 8代码弹幕不能使用 9BAS弹幕pool必须为2)
int mode = 1,
// String? aid,// 稿件avid
// String? bvid,// bvid与aid必须有一个
required String bvid,
@@ -47,7 +47,6 @@ class DanmakaHttp {
// 构建参数对象
// assert(aid != null || bvid != null);
// assert(csrf != null || access_key != null);
assert(msg.length < 100);
// 构建参数对象
var params = <String, dynamic>{
'type': type,

View File

@@ -1,4 +1,4 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPlus/http/loading_state.dart';
import '../models/dynamics/result.dart';
import '../models/dynamics/up.dart';
@@ -20,7 +20,7 @@ class DynamicsHttp {
data['host_mid'] = mid;
data.remove('timezone_offset');
}
var res = await Request().get(Api.followDynamic, data: data);
var res = await Request().get(Api.followDynamic, queryParameters: data);
if (res.data['code'] == 0) {
try {
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
@@ -80,7 +80,7 @@ class DynamicsHttp {
static Future dynamicDetail({
String? id,
}) async {
var res = await Request().get(Api.dynamicDetail, data: {
var res = await Request().get(Api.dynamicDetail, queryParameters: {
'timezone_offset': -480,
'id': id,
'features': 'itemOpusStyle',

View File

@@ -1,4 +1,4 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPlus/http/loading_state.dart';
import '../models/fans/result.dart';
import 'index.dart';
@@ -6,7 +6,7 @@ import 'index.dart';
class FanHttp {
static Future<LoadingState> fans(
{int? vmid, int? pn, int? ps, String? orderType}) async {
var res = await Request().get(Api.fans, data: {
var res = await Request().get(Api.fans, queryParameters: {
'vmid': vmid,
'pn': pn,
'ps': ps,

View File

@@ -4,7 +4,7 @@ import 'index.dart';
class FollowHttp {
static Future followings(
{int? vmid, int? pn, int? ps, String? orderType}) async {
var res = await Request().get(Api.followings, data: {
var res = await Request().get(Api.followings, queryParameters: {
'vmid': vmid,
'pn': pn,
'ps': ps,

View File

@@ -1,6 +1,6 @@
import 'dart:convert';
import 'package:PiliPalaX/models/dynamics/article_content_model.dart';
import 'package:PiliPlus/models/dynamics/article_content_model.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:html/dom.dart' as dom;

View File

@@ -1,4 +1,3 @@
// ignore_for_file: avoid_print
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
@@ -9,23 +8,20 @@ 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:dio_http2_adapter/dio_http2_adapter.dart';
import 'package:hive/hive.dart';
import 'package:PiliPalaX/utils/id_utils.dart';
import 'package:PiliPlus/utils/id_utils.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 final Request _instance = Request._internal();
static late CookieManager cookieManager;
static late final Dio dio;
factory Request() => _instance;
Box setting = GStorage.setting;
static Box localCache = GStorage.localCache;
late bool enableSystemProxy;
late String systemProxyHost;
late String systemProxyPort;
@@ -34,7 +30,6 @@ class Request {
/// 设置cookie
static setCookie() async {
Box userInfoCache = GStorage.userInfo;
final String cookiePath = await Utils.getCookiePath();
final PersistCookieJar cookieJar = PersistCookieJar(
ignoreExpires: true,
@@ -43,9 +38,20 @@ class Request {
cookieManager = CookieManager(cookieJar);
dio.interceptors.add(cookieManager);
dio.interceptors.add(AnonymityInterceptor());
// final List<Cookie> cookie = await cookieManager.cookieJar
// .loadForRequest(Uri.parse(HttpString.baseUrl));
final userInfo = userInfoCache.get('userInfoCache');
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));
@@ -65,7 +71,7 @@ class Request {
log("setCookie, ${e.toString()}");
}
// final String cookieString = cookie
// final String cookieString = cookies
// .map((Cookie cookie) => '${cookie.name}=${cookie.value}')
// .join('; ');
// dio.options.headers['cookie'] = cookieString;
@@ -134,12 +140,12 @@ class Request {
headers: {},
);
enableSystemProxy = setting.get(SettingBoxKey.enableSystemProxy,
defaultValue: false) as bool;
enableSystemProxy = GStorage.setting
.get(SettingBoxKey.enableSystemProxy, defaultValue: false) as bool;
systemProxyHost =
setting.get(SettingBoxKey.systemProxyHost, defaultValue: '');
GStorage.setting.get(SettingBoxKey.systemProxyHost, defaultValue: '');
systemProxyPort =
setting.get(SettingBoxKey.systemProxyPort, defaultValue: '');
GStorage.setting.get(SettingBoxKey.systemProxyPort, defaultValue: '');
dio = Dio(options);
@@ -147,7 +153,7 @@ class Request {
// ..httpClientAdapter = Http2Adapter(
// ConnectionManager(
// idleTimeout: const Duration(milliseconds: 10000),
// onClientCreate: (_, ClientSetting config) =>
// onClientCreate: (context, ClientSetting config) =>
// config.onBadCertificate = (_) => true,
// ),
// );
@@ -189,7 +195,8 @@ class Request {
/*
* get请求
*/
Future<Response> get(url, {data, options, cancelToken, extra}) async {
Future<Response> get(url,
{queryParameters, options, cancelToken, extra}) async {
Response response;
if (extra != null) {
if (extra['ua'] != null) {
@@ -202,7 +209,7 @@ class Request {
try {
response = await dio.get(
url,
queryParameters: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);

View File

@@ -1,5 +1,3 @@
// ignore_for_file: avoid_print
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
@@ -27,8 +25,7 @@ class ApiInterceptor extends Interceptor {
// final String? accessKey = uri.queryParameters['access_key'];
// final String? mid = uri.queryParameters['mid'];
// try {
// Box localCache = GStorage.localCache;
// localCache.put(LocalCacheKey.accessKey,
// GStorage.localCache.put(LocalCacheKey.accessKey,
// <String, String?>{'mid': mid, 'value': accessKey});
// } catch (_) {}
// }
@@ -50,7 +47,9 @@ class ApiInterceptor extends Interceptor {
// 屏蔽弹幕、心跳、人数请求的错误提示
if (!url.contains('heartbeat') &&
!url.contains('seg.so') &&
!url.contains('online/total')) {
!url.contains('online/total') &&
!url.contains('github') &&
(!url.contains('skipSegments') && err.requestOptions.method != 'GET')) {
SmartDialog.showToast(
await dioError(err) + url,
displayType: SmartToastType.onlyRefresh,
@@ -77,29 +76,13 @@ class ApiInterceptor extends Interceptor {
case DioExceptionType.sendTimeout:
return '发送请求超时,请检查网络设置';
case DioExceptionType.unknown:
final String res = await checkConnect();
final String res =
(await Connectivity().checkConnectivity()).first.title;
return '$res网络异常 ${error.error}';
}
}
static Future<String> checkConnect() async {
final List<ConnectivityResult> connectivityResult =
await Connectivity().checkConnectivity();
switch (connectivityResult.first) {
case ConnectivityResult.mobile:
return '流量';
case ConnectivityResult.wifi:
return 'Wi-Fi';
case ConnectivityResult.ethernet:
return '局域';
case ConnectivityResult.vpn:
return '代理';
case ConnectivityResult.other:
return '其他';
case ConnectivityResult.none:
return '';
default:
return '';
}
}
}
extension _ConnectivityResultExt on ConnectivityResult {
String get title => ['蓝牙', 'Wi-Fi', '局域', '流量', '', '代理', '其他'][index];
}

View File

@@ -24,12 +24,13 @@ class AnonymityInterceptor extends Interceptor {
Api.dynamicDetail,
Api.aiConclusion,
Api.getSeasonDetailApi,
Api.liveRoomDmToken,
Api.liveRoomDmPrefetch,
];
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (MineController.anonymity) {
if (MineController.anonymity.value) {
String uri = options.uri.toString();
for (var i in anonymityList) {
// 如果请求的url包含无痕列表中的url则清空cookie
@@ -39,7 +40,7 @@ class AnonymityInterceptor extends Interceptor {
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) {
if (options.data != null && options.data.csrf != null) {
options.data.csrf = "";
}
break;

View File

@@ -1,6 +1,6 @@
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/models/live/danmu_info.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/live/danmu_info.dart';
import 'package:dio/dio.dart';
import '../models/live/item.dart';
import '../models/live/room_info.dart';
@@ -12,7 +12,7 @@ class LiveHttp {
static Future<LoadingState> liveList(
{int? vmid, int? pn, int? ps, String? orderType}) async {
var res = await Request().get(Api.liveList,
data: {'page': pn, 'page_size': 30, 'platform': 'web'});
queryParameters: {'page': pn, 'page_size': 30, 'platform': 'web'});
if (res.data['code'] == 0) {
List<LiveItemModel> list = res.data['data']['list']
.map<LiveItemModel>((e) => LiveItemModel.fromJson(e))
@@ -67,7 +67,7 @@ class LiveHttp {
}
static Future liveRoomInfo({roomId, qn}) async {
var res = await Request().get(Api.liveRoomInfo, data: {
var res = await Request().get(Api.liveRoomInfo, queryParameters: {
'room_id': roomId,
'protocol': '0, 1',
'format': '0, 1, 2',
@@ -90,7 +90,7 @@ class LiveHttp {
}
static Future liveRoomInfoH5({roomId, qn}) async {
var res = await Request().get(Api.liveRoomInfoH5, data: {
var res = await Request().get(Api.liveRoomInfoH5, queryParameters: {
'room_id': roomId,
});
if (res.data['code'] == 0) {
@@ -108,7 +108,7 @@ class LiveHttp {
}
static Future liveRoomDanmaPrefetch({roomId}) async {
var res = await Request().get(Api.liveRoomDmPrefetch, data: {
var res = await Request().get(Api.liveRoomDmPrefetch, queryParameters: {
'roomid': roomId,
});
if (res.data['code'] == 0) {
@@ -123,7 +123,7 @@ class LiveHttp {
}
static Future liveRoomGetDanmakuToken({roomId}) async {
var res = await Request().get(Api.liveRoomDmToken, data: {
var res = await Request().get(Api.liveRoomDmToken, queryParameters: {
'id': roomId,
});
if (res.data['code'] == 0) {

View File

@@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import 'package:encrypt/encrypt.dart';
import 'package:flutter/material.dart';
import '../common/constants.dart';
import '../models/login/index.dart';
import '../utils/login.dart';
@@ -43,6 +42,7 @@ class LoginHttp {
);
var res = await Request()
.post(Api.getTVCode, queryParameters: {...params, 'sign': sign});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -107,9 +107,10 @@ class LoginHttp {
int timestamp = DateTime.now().millisecondsSinceEpoch;
var data = {
'appkey': Constants.appKey,
'build': '1462100',
'build': '2001100',
'buvid': buvid,
'c_locale': 'zh_CN',
'channel': 'yingyongbao',
'cid': cid,
// if (deviceTouristId != null) 'device_tourist_id': deviceTouristId,
'disable_rcmd': '0',
@@ -142,6 +143,7 @@ class LoginHttp {
headers: headers,
),
);
if (res.data['code'] == 0 && res.data['data']['recaptcha_url'] == "") {
return {'status': true, 'data': res.data['data']};
} else {
@@ -158,7 +160,7 @@ class LoginHttp {
// dynamic publicKey = RSAKeyParser().parse(key);
// var params = {
// 'appkey': Constants.appKey,
// 'build': '1462100',
// 'build': '2001100',
// 'buvid': buvid,
// 'c_locale': 'zh_CN',
// 'channel': 'yingyongbao',
@@ -185,7 +187,7 @@ class LoginHttp {
// contentType: Headers.formUrlEncodedContentType,
// headers: headers,
// ));
// debugPrint("getGuestId: $res");
// print("getGuestId: $res");
// if (res.data['code'] == 0) {
// return {'status': true, 'data': res.data['data']};
// } else {
@@ -211,7 +213,7 @@ class LoginHttp {
Map<String, String> data = {
'appkey': Constants.appKey,
'bili_local_id': deviceId,
'build': '1462100',
'build': '2001100',
'buvid': buvid,
'c_locale': 'zh_CN',
'channel': 'yingyongbao',
@@ -247,7 +249,6 @@ class LoginHttp {
);
data['sign'] = sign;
data.map((key, value) {
debugPrint('$key: $value');
return MapEntry<String, dynamic>(key, value);
});
var res = await Request().post(
@@ -259,6 +260,7 @@ class LoginHttp {
//responseType: ResponseType.plain
),
);
if (res.data['code'] == 0) {
return {
'status': true,
@@ -287,7 +289,7 @@ class LoginHttp {
Map<String, String> data = {
'appkey': Constants.appKey,
'bili_local_id': deviceId,
'build': '1462100',
'build': '2001100',
'buvid': buvid,
'c_locale': 'zh_CN',
'captcha_key': captchaKey,
@@ -321,7 +323,6 @@ class LoginHttp {
);
data['sign'] = sign;
data.map((key, value) {
debugPrint('$key: $value');
return MapEntry<String, dynamic>(key, value);
});
var res = await Request().post(
@@ -333,6 +334,7 @@ class LoginHttp {
//responseType: ResponseType.plain
),
);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -349,9 +351,12 @@ class LoginHttp {
static Future safeCenterGetInfo({
required String tmpCode,
}) async {
var res = await Request().get(Api.safeCenterGetInfo, data: {
'tmp_code': tmpCode,
});
var res = await Request().get(
Api.safeCenterGetInfo,
queryParameters: {
'tmp_code': tmpCode,
},
);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -364,9 +369,10 @@ class LoginHttp {
}
}
// 风控验证手机前的验证码
// 风控验证手机前的极验验证码
static Future preCapture() async {
var res = await Request().post(Api.preCapture);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -379,23 +385,40 @@ class LoginHttp {
}
}
// 风控验证手机
// 风控验证手机:发送短信验证码
static Future safeCenterSmsCode({
String? smsType,
required String tmpCode,
required String geeChallenge,
required String geeSeccode,
required String geeValidate,
required String recaptchaToken,
String? geeChallenge,
String? geeSeccode,
String? geeValidate,
String? recaptchaToken,
required String refererUrl,
}) async {
var res = await Request().post(Api.safeCenterSmsCode, data: {
Map<String, String> data = {
'disable_rcmd': '0',
'sms_type': smsType ?? 'loginTelCheck',
'tmp_code': tmpCode,
'gee_challenge': geeChallenge,
'gee_seccode': geeSeccode,
'gee_validate': geeValidate,
'recaptcha_token': recaptchaToken,
});
if (geeChallenge != null) 'gee_challenge': geeChallenge,
if (geeSeccode != null) 'gee_seccode': geeSeccode,
if (geeValidate != null) 'gee_validate': geeValidate,
if (recaptchaToken != null) 'recaptcha_token': recaptchaToken,
};
String sign = Utils.appSign(
data,
Constants.appKey,
Constants.appSec,
);
data['sign'] = sign;
var res = await Request().post(
Api.safeCenterSmsCode,
data: data,
options:
Options(contentType: Headers.formUrlEncodedContentType, headers: {
"Referer": refererUrl,
}),
);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -408,21 +431,93 @@ class LoginHttp {
}
}
static Future safeCenterSmsVerify(
{String? type,
required String code,
required String tmpCode,
required String requestId,
required String source,
required String captchaKey}) async {
var res = await Request().post(Api.safeCenterSmsVerify, data: {
// 风控验证手机:提交短信验证码
static Future safeCenterSmsVerify({
String? type,
required String code,
required String tmpCode,
required String requestId,
required String source,
required String captchaKey,
required String refererUrl,
}) async {
Map<String, String> data = {
'type': type ?? 'loginTelCheck',
'code': code,
'tmp_code': tmpCode,
'request_id': requestId,
'source': source,
'captcha_key': captchaKey,
};
String sign = Utils.appSign(
data,
Constants.appKey,
Constants.appSec,
);
data['sign'] = sign;
var res = await Request().post(
Api.safeCenterSmsVerify,
data: data,
options:
Options(contentType: Headers.formUrlEncodedContentType, headers: {
"Referer": refererUrl,
}),
);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {
'status': false,
'code': res.data['code'],
'msg': res.data['message'],
'data': res.data['data']
};
}
}
// 风控验证手机用oauthCode换回accessToken
static Future oauth2AccessToken({
required String code,
}) async {
Map<String, String> data = {
'appkey': Constants.appKey,
'build': '2001100',
'buvid': buvid,
// 'c_locale': 'zh_CN',
// 'channel': 'yingyongbao',
'code': code,
// 'device': 'phone',
// 'device_id': deviceId,
// 'device_name': 'vivo',
// 'device_platform': 'Android14vivo',
'disable_rcmd': '0',
'grant_type': 'authorization_code',
'local_id': buvid,
'mobi_app': 'android_hd',
'platform': 'android',
// 's_locale': 'zh_CN',
// '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);
});
var res = await Request().post(
Api.oauth2AccessToken,
data: data,
options: Options(
contentType: Headers.formUrlEncodedContentType,
headers: headers,
),
);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {

View File

@@ -1,14 +1,14 @@
import 'dart:convert';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/grpc/grpc_repo.dart';
import 'package:PiliPalaX/http/constants.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/models/space/data.dart';
import 'package:PiliPalaX/models/space_fav/space_fav.dart';
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute.dart'
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_fav/space_fav.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
show ContributeType;
import 'package:PiliPalaX/utils/storage.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
@@ -91,7 +91,7 @@ class MemberHttp {
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
dynamic res = await Request().get(
Api.spaceArticle,
data: data,
queryParameters: data,
options: Options(
headers: {
'env': 'prod',
@@ -136,7 +136,7 @@ class MemberHttp {
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
dynamic res = await Request().get(
Api.spaceFav,
data: data,
queryParameters: data,
options: Options(
headers: {
'env': 'prod',
@@ -206,7 +206,7 @@ class MemberHttp {
: type == ContributeType.series
? Api.spaceSeries
: Api.spaceBangumi,
data: data,
queryParameters: data,
options: Options(
headers: {
'env': 'prod',
@@ -251,7 +251,7 @@ class MemberHttp {
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
dynamic res = await Request().get(
Api.space,
data: data,
queryParameters: data,
options: Options(
headers: {
'env': 'prod',
@@ -284,7 +284,7 @@ class MemberHttp {
});
var res = await Request().get(
Api.memberInfo,
data: params,
queryParameters: params,
extra: {'ua': 'pc'},
);
if (res.data['code'] == 0) {
@@ -302,7 +302,7 @@ class MemberHttp {
}
static Future memberStat({int? mid}) async {
var res = await Request().get(Api.userStat, data: {'vmid': mid});
var res = await Request().get(Api.userStat, queryParameters: {'vmid': mid});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -316,7 +316,7 @@ class MemberHttp {
static Future memberCardInfo({int? mid}) async {
var res = await Request()
.get(Api.memberCardInfo, data: {'mid': mid, 'photo': true});
.get(Api.memberCardInfo, queryParameters: {'mid': mid, 'photo': true});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -336,6 +336,7 @@ class MemberHttp {
String? keyword,
String order = 'pubdate',
bool orderAvoided = true,
dynamic wwebid,
}) async {
String dmImgStr = Utils.base64EncodeRandomString(16, 64);
String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
@@ -353,10 +354,11 @@ class MemberHttp {
'dm_img_str': dmImgStr,
'dm_cover_img_str': dmCoverImgStr,
'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
'w_webid': wwebid,
});
var res = await Request().get(
Api.memberArchive,
data: params,
queryParameters: params,
extra: {'ua': 'Mozilla/5.0'},
);
if (res.data['code'] == 0) {
@@ -393,7 +395,7 @@ class MemberHttp {
'x-bili-device-req-json': jsonEncode({"platform": "web", "device": "pc"}),
'x-bili-web-req-json': jsonEncode({"spm_id": "333.999"}),
});
var res = await Request().get(Api.memberDynamic, data: params);
var res = await Request().get(Api.memberDynamic, queryParameters: params);
if (res.data['code'] == 0) {
return LoadingState.success(DynamicsDataModel.fromJson(res.data['data']));
} else {
@@ -412,7 +414,7 @@ class MemberHttp {
int? mid,
required String keyword,
}) async {
var res = await Request().get(Api.memberDynamicSearch, data: {
var res = await Request().get(Api.memberDynamicSearch, queryParameters: {
'keyword': keyword,
'mid': mid,
'pn': pn,
@@ -508,7 +510,7 @@ class MemberHttp {
int? pn,
int? ps,
) async {
var res = await Request().get(Api.followUpGroup, data: {
var res = await Request().get(Api.followUpGroup, queryParameters: {
'mid': mid,
'tagid': tagid,
'pn': pn,
@@ -552,7 +554,7 @@ class MemberHttp {
// 获取uo专栏
static Future getMemberSeasons(int? mid, int? pn, int? ps) async {
var res = await Request().get(Api.getMemberSeasonsApi, data: {
var res = await Request().get(Api.getMemberSeasonsApi, queryParameters: {
'mid': mid,
'page_num': pn,
'page_size': ps,
@@ -580,7 +582,7 @@ class MemberHttp {
});
var res = await Request().get(
Api.getRecentCoinVideoApi,
data: {
queryParameters: {
'vmid': mid,
'gaia_source': 'main_web',
'web_location': 333.999,
@@ -613,7 +615,7 @@ class MemberHttp {
});
var res = await Request().get(
Api.getRecentLikeVideoApi,
data: {
queryParameters: {
'vmid': mid,
'gaia_source': 'main_web',
'web_location': 333.999,
@@ -645,7 +647,7 @@ class MemberHttp {
}) async {
var res = await Request().get(
Api.getSeasonDetailApi,
data: {
queryParameters: {
'mid': mid,
'season_id': seasonId,
'sort_reverse': sortReverse,
@@ -673,7 +675,8 @@ class MemberHttp {
// 获取up播放数、点赞数
static Future memberView({required int mid}) async {
var res = await Request().get(Api.getMemberViewApi, data: {'mid': mid});
var res = await Request()
.get(Api.getMemberViewApi, queryParameters: {'mid': mid});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -703,7 +706,7 @@ class MemberHttp {
'web_location': 333.999,
};
Map params = await WbiSign().makSign(data);
var res = await Request().get(Api.followSearch, data: {
var res = await Request().get(Api.followSearch, queryParameters: {
...data,
'w_rid': params['w_rid'],
'wts': params['wts'],

View File

@@ -1,6 +1,6 @@
import 'dart:math';
import 'package:PiliPalaX/http/constants.dart';
import 'package:PiliPalaX/pages/dynamics/view.dart' show ReplyOption;
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/pages/dynamics/view.dart' show ReplyOption;
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
@@ -12,7 +12,7 @@ import 'init.dart';
class MsgHttp {
static Future msgFeedReplyMe({int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedReply, data: {
var res = await Request().get(Api.msgFeedReply, queryParameters: {
'id': cursor == -1 ? null : cursor,
'reply_time': cursorTime == -1 ? null : cursorTime,
});
@@ -31,7 +31,7 @@ class MsgHttp {
}
static Future msgFeedAtMe({int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedAt, data: {
var res = await Request().get(Api.msgFeedAt, queryParameters: {
'id': cursor == -1 ? null : cursor,
'at_time': cursorTime == -1 ? null : cursorTime,
});
@@ -50,7 +50,7 @@ class MsgHttp {
}
static Future msgFeedLikeMe({int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedLike, data: {
var res = await Request().get(Api.msgFeedLike, queryParameters: {
'id': cursor == -1 ? null : cursor,
'like_time': cursorTime == -1 ? null : cursorTime,
});
@@ -70,7 +70,7 @@ class MsgHttp {
static Future msgFeedSysUserNotify() async {
String csrf = await Request.getCsrf();
var res = await Request().get(Api.msgSysUserNotify, data: {
var res = await Request().get(Api.msgSysUserNotify, queryParameters: {
'csrf': csrf,
'page_size': 20,
});
@@ -90,7 +90,7 @@ class MsgHttp {
static Future msgFeedSysUnifiedNotify() async {
String csrf = await Request.getCsrf();
var res = await Request().get(Api.msgSysUnifiedNotify, data: {
var res = await Request().get(Api.msgSysUnifiedNotify, queryParameters: {
'csrf': csrf,
'page_size': 10,
});
@@ -110,7 +110,7 @@ class MsgHttp {
static Future msgSysUpdateCursor(int cursor) async {
String csrf = await Request.getCsrf();
var res = await Request().get(Api.msgSysUpdateCursor, data: {
var res = await Request().get(Api.msgSysUpdateCursor, queryParameters: {
'csrf': csrf,
'cursor': cursor,
});
@@ -149,6 +149,7 @@ class MsgHttp {
List? pics,
int? publishTime,
ReplyOption replyOption = ReplyOption.allow,
int? privatePub,
}) async {
String csrf = await Request.getCsrf();
var res = await Request().post(
@@ -181,6 +182,10 @@ class MsgHttp {
: pics != null
? 2
: 1,
if (privatePub != null)
'create_option': {
'private_pub': privatePub,
},
if (pics != null) 'pics': pics,
"attach_card": null,
"upload_id":
@@ -400,7 +405,7 @@ class MsgHttp {
}
Map signParams = await WbiSign().makSign(params);
var res = await Request().get(Api.sessionList, data: signParams);
var res = await Request().get(Api.sessionList, queryParameters: signParams);
if (res.data['code'] == 0) {
try {
return {
@@ -424,7 +429,7 @@ class MsgHttp {
}
static Future accountList(uids) async {
var res = await Request().get(Api.sessionAccountList, data: {
var res = await Request().get(Api.sessionAccountList, queryParameters: {
'uids': uids,
'build': 0,
'mobi_app': 'web',
@@ -460,7 +465,7 @@ class MsgHttp {
'build': 0,
'mobi_app': 'web',
});
var res = await Request().get(Api.sessionMsg, data: params);
var res = await Request().get(Api.sessionMsg, queryParameters: params);
if (res.data['code'] == 0) {
try {
return {
@@ -494,7 +499,7 @@ class MsgHttp {
'csrf_token': csrf,
'csrf': csrf
});
var res = await Request().get(Api.ackSessionMsg, data: params);
var res = await Request().get(Api.ackSessionMsg, queryParameters: params);
if (res.data['code'] == 0) {
return {
'status': true,

View File

@@ -1,9 +1,10 @@
import 'dart:io';
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPalaX/grpc/grpc_repo.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/utils/storage.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/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart';
import '../models/video/reply/data.dart';
@@ -28,7 +29,7 @@ class ReplyHttp {
var res = !isLogin
? await Request().get(
'${HttpString.apiBaseUrl}${Api.replyList}/main',
data: {
queryParameters: {
'oid': oid,
'type': type,
'pagination_str':
@@ -39,7 +40,7 @@ class ReplyHttp {
)
: await Request().get(
'${HttpString.apiBaseUrl}${Api.replyList}',
data: {
queryParameters: {
'oid': oid,
'type': type,
'sort': sort,
@@ -49,7 +50,44 @@ class ReplyHttp {
options: options,
);
if (res.data['code'] == 0) {
return LoadingState.success(ReplyData.fromJson(res.data['data']));
ReplyData replyData = ReplyData.fromJson(res.data['data']);
String banWordForReply = GStorage.banWordForReply;
if (banWordForReply.isNotEmpty) {
// topReplies
if (replyData.topReplies?.isNotEmpty == true) {
replyData.topReplies!.removeWhere((item) {
bool hasMatch = RegExp(banWordForReply, caseSensitive: false)
.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 ?? ''));
}
}
return hasMatch;
});
}
// replies
if (replyData.replies?.isNotEmpty == true) {
replyData.replies!.removeWhere((item) {
bool hasMatch = RegExp(banWordForReply, caseSensitive: false)
.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 ?? ''));
}
}
return hasMatch;
});
}
}
return LoadingState.success(replyData);
} else {
return LoadingState.error(res.data['message']);
}
@@ -62,7 +100,34 @@ class ReplyHttp {
}) async {
dynamic res = await GrpcRepo.mainList(type: type, oid: oid, cursor: cursor);
if (res['status']) {
return LoadingState.success(res['data']);
MainListReply mainListReply = res['data'];
String banWordForReply = GStorage.banWordForReply;
if (banWordForReply.isNotEmpty) {
// upTop
if (mainListReply.hasUpTop() &&
RegExp(banWordForReply, caseSensitive: false)
.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);
// remove subreplies
if (hasMatch.not) {
if (item.replies.isNotEmpty) {
item.replies.removeWhere((item) =>
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content.message));
}
}
return hasMatch;
});
}
}
return LoadingState.success(mainListReply);
} else {
return LoadingState.error(
'${res['msg'].startsWith('gRPC Error') ? '如无法加载评论:\n关闭代理\n或设置中关闭使用gRPC加载评论\n\n' : ''}${res['msg']}');
@@ -82,7 +147,7 @@ class ReplyHttp {
: null;
var res = await Request().get(
'${HttpString.apiBaseUrl}${Api.replyReplyList}',
data: {
queryParameters: {
'oid': oid,
'root': root,
'pn': pageNum,
@@ -93,33 +158,21 @@ class ReplyHttp {
options: options,
);
if (res.data['code'] == 0) {
return LoadingState.success(ReplyReplyData.fromJson(res.data['data']));
ReplyReplyData replyData = ReplyReplyData.fromJson(res.data['data']);
String banWordForReply = GStorage.banWordForReply;
if (banWordForReply.isNotEmpty) {
if (replyData.replies?.isNotEmpty == true) {
replyData.replies!.removeWhere((item) =>
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content?.message ?? ''));
}
}
return LoadingState.success(replyData);
} else {
return LoadingState.error(res.data['message']);
}
}
static Future<LoadingState> dialogListGrpc({
int type = 1,
required int oid,
required int root,
required int rpid,
required CursorReq cursor,
}) async {
dynamic res = await GrpcRepo.dialogList(
type: type,
oid: oid,
root: root,
rpid: rpid,
cursor: cursor,
);
if (res['status']) {
return LoadingState.success(res['data']);
} else {
return LoadingState.error(res['msg']);
}
}
static Future<LoadingState> replyReplyListGrpc({
int type = 1,
required int oid,
@@ -135,7 +188,46 @@ class ReplyHttp {
cursor: cursor,
);
if (res['status']) {
return LoadingState.success(res['data']);
DetailListReply detailListReply = res['data'];
String banWordForReply = GStorage.banWordForReply;
if (banWordForReply.isNotEmpty) {
if (detailListReply.root.replies.isNotEmpty) {
detailListReply.root.replies.removeWhere((item) =>
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content.message));
}
}
return LoadingState.success(detailListReply);
} else {
return LoadingState.error(res['msg']);
}
}
static Future<LoadingState> dialogListGrpc({
int type = 1,
required int oid,
required int root,
required int rpid,
required CursorReq cursor,
}) async {
dynamic res = await GrpcRepo.dialogList(
type: type,
oid: oid,
root: root,
rpid: rpid,
cursor: cursor,
);
if (res['status']) {
DialogListReply dialogListReply = res['data'];
String banWordForReply = GStorage.banWordForReply;
if (banWordForReply.isNotEmpty) {
if (dialogListReply.replies.isNotEmpty) {
dialogListReply.replies.removeWhere((item) =>
RegExp(banWordForReply, caseSensitive: false)
.hasMatch(item.content.message));
}
}
return LoadingState.success(dialogListReply);
} else {
return LoadingState.error(res['msg']);
}
@@ -200,7 +292,7 @@ class ReplyHttp {
}
static Future<LoadingState> getEmoteList({String? business}) async {
var res = await Request().get(Api.myEmote, data: {
var res = await Request().get(Api.myEmote, queryParameters: {
'business': business ?? 'reply',
'web_location': '333.1245',
});

View File

@@ -1,8 +1,7 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:hive/hive.dart';
import 'package:PiliPlus/http/loading_state.dart';
import '../models/bangumi/info.dart';
import '../models/common/search_type.dart';
import '../models/search/hot.dart';
@@ -12,7 +11,6 @@ import '../utils/storage.dart';
import 'index.dart';
class SearchHttp {
static Box localCache = GStorage.localCache;
static Future hotSearchList() async {
var res = await Request().get(Api.hotSearchList);
if (res.data is String) {
@@ -40,7 +38,7 @@ class SearchHttp {
// 获取搜索建议
static Future searchSuggest({required term}) async {
var res = await Request().get(Api.searchSuggest,
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
queryParameters: {'term': term, 'main_ver': 'v1', 'highlight': term});
if (res.data is String) {
Map<String, dynamic> resultMap = json.decode(res.data);
if (resultMap['code'] == 0) {
@@ -98,14 +96,13 @@ class SearchHttp {
if (pubBegin != null) 'pubtime_begin_s': pubBegin,
if (pubEnd != null) 'pubtime_end_s': pubEnd,
};
var res = await Request().get(Api.searchByType, data: reqData);
if (res.data['code'] == 0) {
var res = await Request().get(Api.searchByType, queryParameters: reqData);
if (res.data['code'] is int && res.data['code'] == 0) {
dynamic data;
try {
switch (searchType) {
case SearchType.video:
List<int> blackMidsList = localCache
.get(LocalCacheKey.blackMidsList, defaultValue: <int>[]);
List<int> blackMidsList = GStorage.blackMidsList;
if (res.data['data']['result'] != null) {
for (var i in res.data['data']['result']) {
// 屏蔽推广和拉黑用户
@@ -140,15 +137,15 @@ class SearchHttp {
}
}
static Future<int> ab2c({int? aid, String? bvid}) async {
static Future<int> ab2c({dynamic aid, dynamic bvid}) 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, data: <String, dynamic>{...data});
final dynamic res = await Request()
.get(Api.ab2c, queryParameters: <String, dynamic>{...data});
if (res.data['code'] == 0) {
return res.data['data'].first['cid'];
} else {
@@ -160,7 +157,7 @@ class SearchHttp {
static Future<LoadingState> bangumiInfoNew({int? seasonId, int? epId}) async {
final dynamic res = await Request().get(
Api.bangumiInfo,
data: {
queryParameters: {
if (seasonId != null) 'season_id': seasonId,
if (epId != null) 'ep_id': epId,
},
@@ -183,8 +180,8 @@ class SearchHttp {
} else if (epId != null) {
data['ep_id'] = epId;
}
final dynamic res =
await Request().get(Api.bangumiInfo, data: <String, dynamic>{...data});
final dynamic res = await Request()
.get(Api.bangumiInfo, queryParameters: <String, dynamic>{...data});
if (res.data['code'] == 0) {
return {
'status': true,

View File

@@ -1,4 +1,5 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/video/later.dart';
import 'package:dio/dio.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../common/constants.dart';
@@ -15,7 +16,7 @@ import 'init.dart';
class UserHttp {
static Future<dynamic> userStat({required int mid}) async {
var res = await Request().get(Api.userStat, data: {'vmid': mid});
var res = await Request().get(Api.userStat, queryParameters: {'vmid': mid});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -47,9 +48,9 @@ class UserHttp {
static Future<LoadingState> userfavFolder({
required int pn,
required int ps,
required int mid,
required dynamic mid,
}) async {
var res = await Request().get(Api.userFavFolder, data: {
var res = await Request().get(Api.userFavFolder, queryParameters: {
'pn': pn,
'ps': ps,
'up_mid': mid,
@@ -113,7 +114,7 @@ class UserHttp {
static Future folderInfo({
dynamic mediaId,
}) async {
var res = await Request().get(Api.folderInfo, data: {
var res = await Request().get(Api.folderInfo, queryParameters: {
'media_id': mediaId,
});
if (res.data['code'] == 0) {
@@ -130,7 +131,7 @@ class UserHttp {
String keyword = '',
String order = 'mtime',
int type = 0}) async {
var res = await Request().get(Api.userFavFolderDetail, data: {
var res = await Request().get(Api.userFavFolderDetail, queryParameters: {
'media_id': mediaId,
'pn': pn,
'ps': ps,
@@ -172,7 +173,7 @@ class UserHttp {
int? max,
int? viewAt,
}) async {
var res = await Request().get(Api.historyList, data: {
var res = await Request().get(Api.historyList, queryParameters: {
'type': 'all',
'ps': 20,
'max': max ?? 0,
@@ -265,7 +266,7 @@ class UserHttp {
static Future thirdLogin() async {
var res = await Request().get(
'https://passport.bilibili.com/login/app/third',
data: {
queryParameters: {
'appkey': Constants.appKey,
'api': Constants.thirdApi,
'sign': Constants.thirdSign,
@@ -319,7 +320,7 @@ class UserHttp {
static Future hasFollow(int mid) async {
var res = await Request().get(
Api.hasFollow,
data: {
queryParameters: {
'fid': mid,
},
);
@@ -359,7 +360,7 @@ class UserHttp {
{required int pn, required String keyword}) async {
var res = await Request().get(
Api.searchHistory,
data: {
queryParameters: {
'pn': pn,
'keyword': keyword,
'business': 'all',
@@ -373,24 +374,25 @@ class UserHttp {
}
// 我的订阅
static Future userSubFolder({
static Future<LoadingState> userSubFolder({
required int mid,
required int pn,
required int ps,
}) async {
var res = await Request().get(Api.userSubFolder, data: {
'up_mid': mid,
'ps': ps,
'pn': pn,
'platform': 'web',
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': SubFolderModelData.fromJson(res.data['data'])
};
var res = await Request().get(
Api.userSubFolder,
queryParameters: {
'up_mid': mid,
'ps': ps,
'pn': pn,
'platform': 'web',
},
);
if (res.data['code'] == 0 && res.data['data'] is Map) {
return LoadingState.success(
SubFolderModelData.fromJson(res.data['data']).list);
} else {
return {'status': false, 'msg': res.data['message']};
return LoadingState.error(res.data['message']);
}
}
@@ -399,7 +401,7 @@ class UserHttp {
required int pn,
required int ps,
}) async {
var res = await Request().get(Api.favSeasonList, data: {
var res = await Request().get(Api.favSeasonList, queryParameters: {
'season_id': id,
'ps': ps,
'pn': pn,
@@ -419,7 +421,7 @@ class UserHttp {
required int pn,
required int ps,
}) async {
var res = await Request().get(Api.favResourceList, data: {
var res = await Request().get(Api.favResourceList, queryParameters: {
'media_id': id,
'ps': ps,
'pn': pn,
@@ -463,11 +465,118 @@ class UserHttp {
}
static videoTags({required String bvid}) async {
var res = await Request().get(Api.videoTags, data: {'bvid': bvid});
var res =
await Request().get(Api.videoTags, queryParameters: {'bvid': bvid});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {'status': false};
}
}
// 稍后再看播放全部
// 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,
required int bizId,
required int ps,
dynamic oid,
int? otype,
bool withCurrent = false,
bool desc = true,
dynamic sortField = 1,
bool direction = false,
}) async {
var res = await Request().get(
Api.mediaList,
queryParameters: {
'mobi_app': 'web',
'type': type,
'biz_id': bizId,
if (oid != null) 'oid': oid,
if (otype != null) 'otype': otype, // video:2 // bangumi: 24
'ps': ps,
'direction': direction,
'desc': desc,
'sort_field': sortField,
'tid': 0,
'with_current': withCurrent,
},
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data']['media_list'] != null
? res.data['data']['media_list']
.map<MediaVideoItemModel>(
(e) => MediaVideoItemModel.fromJson(e))
.toList()
: []
};
} 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()
// };
// }
}

View File

@@ -1,12 +1,10 @@
import 'dart:convert';
import 'dart:developer';
import 'package:PiliPalaX/grpc/app/card/v1/card.pb.dart' as card;
import 'package:PiliPalaX/grpc/grpc_repo.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import 'package:flutter/foundation.dart';
import '../common/constants.dart';
import '../models/common/reply_type.dart';
import '../models/home/rcmd/result.dart';
@@ -31,18 +29,15 @@ import 'login.dart';
/// 返回{'status': bool, 'data': List}
/// view层根据 status 判断渲染逻辑
class VideoHttp {
static Box localCache = GStorage.localCache;
static Box setting = GStorage.setting;
static bool enableRcmdDynamic =
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
static Box userInfoCache = GStorage.userInfo;
GStorage.setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
// 首页推荐视频
static Future<LoadingState> rcmdVideoList(
{required int ps, required int freshIdx}) async {
var res = await Request().get(
Api.recommendListWeb,
data: {
queryParameters: {
'version': 1,
'feed_version': 'V8',
'homepage_ver': 1,
@@ -54,8 +49,7 @@ class VideoHttp {
);
if (res.data['code'] == 0) {
List<RecVideoItemModel> list = [];
List<int> blackMidsList =
localCache.get(LocalCacheKey.blackMidsList, defaultValue: <int>[]);
List<int> blackMidsList = GStorage.blackMidsList;
for (var i in res.data['data']['item']) {
//过滤掉live与ad以及拉黑用户
if (i['goto'] == 'av' &&
@@ -78,7 +72,7 @@ class VideoHttp {
{bool loginStatus = true, required int freshIdx}) async {
Map<String, String> data = {
'access_key': loginStatus
? (localCache
? (GStorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
'')
: '',
@@ -121,7 +115,7 @@ class VideoHttp {
var res = await Request().get(
Api.recommendListApp,
data: data,
queryParameters: data,
options: Options(headers: {
'Host': 'app.bilibili.com',
'buvid': LoginHttp.buvid,
@@ -141,8 +135,7 @@ class VideoHttp {
);
if (res.data['code'] == 0) {
List<RecVideoItemAppModel> list = [];
List<int> blackMidsList =
localCache.get(LocalCacheKey.blackMidsList, defaultValue: <int>[]);
List<int> blackMidsList = GStorage.blackMidsList;
for (var i in res.data['data']['items']) {
// 屏蔽推广和拉黑用户
if (i['card_goto'] != 'ad_av' &&
@@ -168,16 +161,16 @@ class VideoHttp {
{required int pn, required int ps}) async {
var res = await Request().get(
Api.hotList,
data: {'pn': pn, 'ps': ps},
queryParameters: {'pn': pn, 'ps': ps},
);
if (res.data['code'] == 0) {
List<HotVideoItemModel> list = [];
List<int> blackMidsList = localCache
.get(LocalCacheKey.blackMidsList, defaultValue: [-1])
.map<int>((e) => e as int)
.toList();
List<int> blackMidsList = GStorage.blackMidsList;
for (var i in res.data['data']['list']) {
if (!blackMidsList.contains(i['owner']['mid'])) {
if (!blackMidsList.contains(i['owner']['mid']) &&
!RecommendFilter.filterTitle(i['title']) &&
!RecommendFilter.filterLikeRatio(
i['stat']['like'], i['stat']['view'])) {
list.add(HotVideoItemModel.fromJson(i));
}
}
@@ -191,8 +184,7 @@ class VideoHttp {
dynamic res = await GrpcRepo.popular(idx);
if (res['status']) {
List<card.Card> list = [];
List<int> blackMidsList =
localCache.get(LocalCacheKey.blackMidsList, defaultValue: <int>[]);
List<int> blackMidsList = GStorage.blackMidsList;
for (card.Card item in res['data']) {
if (!blackMidsList.contains(item.smallCoverV5.up.id.toInt())) {
list.add(item);
@@ -205,25 +197,29 @@ class VideoHttp {
}
// 视频流
static Future videoUrl(
{int? avid, String? bvid, required int cid, int? qn}) async {
static Future videoUrl({
int? avid,
String? bvid,
required int cid,
int? qn,
dynamic epid,
dynamic seasonId,
}) async {
Map<String, dynamic> data = {
if (avid != null) 'avid': avid,
if (bvid != null) 'bvid': bvid,
if (epid != null) 'ep_id': epid,
if (seasonId != null) 'season_id': seasonId,
'cid': cid,
'qn': qn ?? 80,
// 获取所有格式的视频
'fnval': 4048,
};
if (avid != null) {
data['avid'] = avid;
}
if (bvid != null) {
data['bvid'] = bvid;
}
// 免登录查看1080p
if ((userInfoCache.get('userInfoCache') == null ||
MineController.anonymity) &&
setting.get(SettingBoxKey.p1080, defaultValue: true)) {
if ((GStorage.userInfo.get('userInfoCache') == null ||
MineController.anonymity.value) &&
GStorage.setting.get(SettingBoxKey.p1080, defaultValue: true)) {
data['try_look'] = 1;
}
@@ -235,12 +231,24 @@ class VideoHttp {
'web_location': 1550101,
});
late final isLogin = GStorage.isLogin;
try {
var res = await Request().get(Api.videoUrl, data: params);
var res = await Request().get(
epid != null && isLogin ? Api.bangumiVideoUrl : Api.videoUrl,
queryParameters: params);
if (res.data['code'] == 0) {
late PlayUrlModel data;
if (epid != null && isLogin) {
data = PlayUrlModel.fromJson(res.data['result']['video_info'])
..lastPlayTime = res.data['result']['play_view_business_info']
['user_status']['watch_progress']['current_watch_progress'];
} else {
data = PlayUrlModel.fromJson(res.data['data']);
}
return {
'status': true,
'data': PlayUrlModel.fromJson(res.data['data'])
'data': data,
};
} else {
return {
@@ -257,7 +265,8 @@ class VideoHttp {
// 视频信息 标题、简介
static Future videoIntro({required String bvid}) async {
var res = await Request().get(Api.videoIntro, data: {'bvid': bvid});
var res =
await Request().get(Api.videoIntro, queryParameters: {'bvid': bvid});
VideoDetailResponse result = VideoDetailResponse.fromJson(res.data);
if (result.code == 0) {
return {
@@ -312,7 +321,7 @@ class VideoHttp {
static Future videoRelation({required dynamic bvid}) async {
var res = await Request().get(
Api.videoRelation,
data: {
queryParameters: {
'aid': IdUtils.bv2av(bvid),
'bvid': bvid,
},
@@ -332,7 +341,8 @@ class VideoHttp {
// 相关视频
static Future<LoadingState> relatedVideoList({required String bvid}) async {
var res = await Request().get(Api.relatedList, data: {'bvid': bvid});
var res =
await Request().get(Api.relatedList, queryParameters: {'bvid': bvid});
if (res.data['code'] == 0) {
List<HotVideoItemModel> list = [];
for (var i in res.data['data']) {
@@ -351,7 +361,7 @@ class VideoHttp {
static Future bangumiLikeCoinFav({dynamic epId}) async {
var res = await Request().get(
Api.bangumiLikeCoinFav,
data: {'ep_id': epId},
queryParameters: {'ep_id': epId},
);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
@@ -362,7 +372,8 @@ class VideoHttp {
// 获取点赞状态
static Future hasLikeVideo({required String bvid}) async {
var res = await Request().get(Api.hasLikeVideo, data: {'bvid': bvid});
var res =
await Request().get(Api.hasLikeVideo, queryParameters: {'bvid': bvid});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -372,7 +383,8 @@ class VideoHttp {
// 获取投币状态
static Future hasCoinVideo({required String bvid}) async {
var res = await Request().get(Api.hasCoinVideo, data: {'bvid': bvid});
var res =
await Request().get(Api.hasCoinVideo, queryParameters: {'bvid': bvid});
debugPrint('res: $res');
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
@@ -404,7 +416,8 @@ class VideoHttp {
// 获取收藏状态
static Future hasFavVideo({required int aid}) async {
var res = await Request().get(Api.hasFavVideo, data: {'aid': aid});
var res =
await Request().get(Api.hasFavVideo, queryParameters: {'aid': aid});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -514,7 +527,7 @@ class VideoHttp {
return {'status': false, 'msg': "请退出账号后重新登录"};
}
assert((reasonId != null) ^ (feedbackId != null));
var res = await Request().get(Api.feedDislike, data: {
var res = await Request().get(Api.feedDislike, queryParameters: {
'goto': goto,
'id': id,
// 'mid': mid,
@@ -544,7 +557,7 @@ class VideoHttp {
return {'status': false, 'msg': "请退出账号后重新登录"};
}
// assert ((reasonId != null) ^ (feedbackId != null));
var res = await Request().get(Api.feedDislikeCancel, data: {
var res = await Request().get(Api.feedDislikeCancel, queryParameters: {
'goto': goto,
'id': id,
// 'mid': mid,
@@ -644,7 +657,7 @@ class VideoHttp {
}) async {
var res = await Request().get(
Api.videoInFolder,
data: {
queryParameters: {
'up_mid': mid,
'rid': rid,
if (type != null) 'type': type,
@@ -719,7 +732,7 @@ class VideoHttp {
// 查询是否关注up
static Future hasFollow({required int mid}) async {
var res = await Request().get(Api.hasFollow, data: {'fid': mid});
var res = await Request().get(Api.hasFollow, queryParameters: {'fid': mid});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -744,18 +757,36 @@ class VideoHttp {
}
// 视频播放进度
static Future heartBeat({bvid, cid, progress, realtime}) async {
static Future heartBeat({
bvid,
cid,
progress,
epid,
seasonId,
subType,
}) async {
await Request().post(Api.heartBeat, queryParameters: {
// 'aid': aid,
'bvid': bvid,
'cid': cid,
// 'epid': '',
// 'sid': '',
// 'mid': '',
if (epid != null) 'epid': epid,
if (seasonId != null) 'sid': seasonId,
if (epid != null) 'type': 4,
if (subType != null) 'sub_type': subType,
'played_time': progress,
// 'realtime': realtime,
// 'type': '',
// 'sub_type': '',
'csrf': await Request.getCsrf(),
});
}
static Future medialistHistory({
required int desc,
required dynamic oid,
required dynamic upperMid,
}) async {
await Request().post(Api.mediaListHistory, queryParameters: {
'desc': desc,
'oid': oid,
'upper_mid': upperMid,
'csrf': await Request.getCsrf(),
});
}
@@ -825,7 +856,7 @@ class VideoHttp {
// 查看视频同时在看人数
static Future onlineTotal({int? aid, String? bvid, int? cid}) async {
var res = await Request().get(Api.onlineTotal, data: {
var res = await Request().get(Api.onlineTotal, queryParameters: {
'aid': aid,
'bvid': bvid,
'cid': cid,
@@ -847,7 +878,7 @@ class VideoHttp {
'cid': cid,
'up_mid': upMid,
});
var res = await Request().get(Api.aiConclusion, data: params);
var res = await Request().get(Api.aiConclusion, queryParameters: params);
if (res.data['code'] == 0 && res.data['data']['code'] == 0) {
return {
'status': true,
@@ -861,11 +892,14 @@ class VideoHttp {
static Future subtitlesJson(
{String? aid, String? bvid, required int cid}) async {
assert(aid != null || bvid != null);
var res = await Request().get(Api.subtitleUrl, data: {
if (aid != null) 'aid': aid,
if (bvid != null) 'bvid': bvid,
'cid': cid,
});
var res = await Request().get(
Api.subtitleUrl,
queryParameters: {
if (aid != null) 'aid': aid,
if (bvid != null) 'bvid': bvid,
'cid': cid,
},
);
if (res.data['code'] == 0) {
dynamic data = res.data['data'];
List subtitlesJson = data['subtitle']['subtitles'];
@@ -887,6 +921,9 @@ class VideoHttp {
return {
'status': true,
'data': subtitlesJson,
'view_points': data['view_points'],
// 'last_play_time': data['last_play_time'],
'last_play_cid': data['last_play_cid'],
};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
@@ -910,6 +947,12 @@ class VideoHttp {
return "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}.${ms.toString().padLeft(3, '0')}";
}
String processList(List list) {
return list.fold('WEBVTT\n\n', (previous, item) {
return '$previous${item?['sid'] ?? 0}\n${subtitleTimecode(item['from'])} --> ${subtitleTimecode(item['to'])}\n${item['content'].trim()}\n\n';
});
}
for (var i in subtitlesJson) {
var res =
await Request().get("https://${i['subtitle_url'].split('//')[1]}");
@@ -944,21 +987,16 @@ class VideoHttp {
]
}
*/
if (res.data != null) {
String vttData = "WEBVTT\n\n";
for (var item in res.data['body']) {
vttData += "${item['sid'] ?? 0}\n";
vttData +=
"${subtitleTimecode(item['from'])} --> ${subtitleTimecode(item['to'])}\n";
vttData += "${item['content'].trim()}\n\n";
}
if (res.data != null && res.data?['body'] is List) {
String vttData = await compute(processList, res.data['body'] as List);
subtitlesVtt.add({
'language': i['lan'],
'title': i['lan_doc'],
'text': vttData,
});
} else {
SmartDialog.showToast("字幕${i['lan_doc']}加载失败, ${res.data['message']}");
// SmartDialog.showToast("字幕${i['lan_doc']}加载失败, ${res.data['message']}");
debugPrint('字幕${i['lan_doc']}加载失败, ${res.data['message']}');
}
}
if (subtitlesVtt.isNotEmpty) {
@@ -973,10 +1011,12 @@ class VideoHttp {
var res = await Request().get(rankApi);
if (res.data['code'] == 0) {
List<HotVideoItemModel> list = [];
List<int> blackMidsList =
localCache.get(LocalCacheKey.blackMidsList, defaultValue: <int>[]);
List<int> blackMidsList = GStorage.blackMidsList;
for (var i in res.data['data']['list']) {
if (!blackMidsList.contains(i['owner']['mid'])) {
if (!blackMidsList.contains(i['owner']['mid']) &&
!RecommendFilter.filterTitle(i['title']) &&
!RecommendFilter.filterLikeRatio(
i['stat']['like'], i['stat']['view'])) {
list.add(HotVideoItemModel.fromJson(i));
}
}

View File

@@ -1,6 +1,8 @@
import 'dart:io';
import 'package:PiliPalaX/utils/cache_manage.dart';
import 'package:PiliPlus/build_config.dart';
import 'package:PiliPlus/utils/cache_manage.dart';
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
@@ -9,18 +11,18 @@ import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:hive/hive.dart';
import 'package:PiliPalaX/common/widgets/custom_toast.dart';
import 'package:PiliPalaX/http/init.dart';
import 'package:PiliPalaX/models/common/color_type.dart';
import 'package:PiliPalaX/pages/video/detail/index.dart';
import 'package:PiliPalaX/router/app_pages.dart';
import 'package:PiliPalaX/pages/main/view.dart';
import 'package:PiliPalaX/services/service_locator.dart';
import 'package:PiliPalaX/utils/app_scheme.dart';
import 'package:PiliPalaX/utils/data.dart';
import 'package:PiliPalaX/utils/storage.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';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/data.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
import 'package:PiliPalaX/utils/recommend_filter.dart';
import 'package:PiliPlus/utils/recommend_filter.dart';
import 'package:catcher_2/catcher_2.dart';
import './services/loggeer.dart';
@@ -50,13 +52,19 @@ void main() async {
],
);
}
if (GStorage.badCertificateCallback) {
HttpOverrides.global = _CustomHttpOverrides();
}
await setupServiceLocator();
Request();
await Request.setCookie();
RecommendFilter();
SmartDialog.config.toast =
SmartConfigToast(displayType: SmartToastType.normal);
SmartDialog.config.loading =
SmartConfigLoading(backType: SmartBackType.normal);
// 异常捕获 logo记录
final String buildConfig = '''\n
Build Time: ${BuildConfig.buildTime}
Commit Hash: ${BuildConfig.commitHash}''';
final Catcher2Options debugConfig = Catcher2Options(
SilentReportMode(),
[
@@ -64,13 +72,25 @@ void main() async {
ConsoleHandler(
enableDeviceParameters: false,
enableApplicationParameters: false,
enableCustomParameters: true,
)
],
customParameters: {
'BuildConfig': buildConfig,
},
);
final Catcher2Options releaseConfig = Catcher2Options(
SilentReportMode(),
[FileHandler(await getLogsPath())],
[
FileHandler(await getLogsPath()),
ConsoleHandler(
enableCustomParameters: true,
)
],
customParameters: {
'BuildConfig': buildConfig,
},
);
Catcher2(
@@ -96,9 +116,10 @@ void main() async {
class MyApp extends StatelessWidget {
const MyApp({super.key});
Box get setting => GStorage.setting;
@override
Widget build(BuildContext context) {
Box setting = GStorage.setting;
// 主题色
Color defaultColor =
colorThemeTypes[setting.get(SettingBoxKey.customColor, defaultValue: 0)]
@@ -110,8 +131,10 @@ class MyApp extends StatelessWidget {
// 字体缩放大小
double textScale =
setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
DynamicSchemeVariant dynamicSchemeVariant =
DynamicSchemeVariant.values[GStorage.schemeVariant];
// DynamicSchemeVariant dynamicSchemeVariant =
// DynamicSchemeVariant.values[GStorage.schemeVariant];
FlexSchemeVariant variant =
FlexSchemeVariant.values[GStorage.schemeVariant];
// 强制设置高帧率
if (Platform.isAndroid) {
@@ -139,29 +162,36 @@ class MyApp extends StatelessWidget {
darkColorScheme = darkDynamic.harmonized();
} else {
// dynamic取色失败采用品牌色
lightColorScheme = ColorScheme.fromSeed(
seedColor: brandColor,
lightColorScheme = SeedColorScheme.fromSeeds(
primaryKey: brandColor,
brightness: Brightness.light,
dynamicSchemeVariant: dynamicSchemeVariant,
variant: variant,
// dynamicSchemeVariant: dynamicSchemeVariant,
// tones: FlexTones.soft(Brightness.light),
);
darkColorScheme = ColorScheme.fromSeed(
seedColor: brandColor,
darkColorScheme = SeedColorScheme.fromSeeds(
primaryKey: brandColor,
brightness: Brightness.dark,
dynamicSchemeVariant: dynamicSchemeVariant,
variant: variant,
// dynamicSchemeVariant: dynamicSchemeVariant,
// tones: FlexTones.soft(Brightness.dark),
);
}
// 图片缓存
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
return GetMaterialApp(
// showSemanticsDebugger: true,
title: 'PiliPalaX',
title: 'PiliPlus',
theme: _getThemeData(
colorScheme: lightColorScheme,
isDynamic: lightDynamic != null && isDynamicColor,
variant: variant,
),
darkTheme: _getThemeData(
colorScheme: darkColorScheme,
isDynamic: darkDynamic != null && isDynamicColor,
isDark: true,
variant: variant,
),
themeMode: GStorage.themeMode,
localizationsDelegates: const [
@@ -196,9 +226,9 @@ class MyApp extends StatelessWidget {
ThemeData _getThemeData({
required ColorScheme colorScheme,
required bool isDynamic,
bool isDark = false,
required FlexSchemeVariant variant,
}) {
Color surfaceTintColor =
isDynamic ? colorScheme.surfaceTint : colorScheme.surfaceContainer;
return ThemeData(
colorScheme: colorScheme,
useMaterial3: true,
@@ -207,19 +237,17 @@ class MyApp extends StatelessWidget {
titleSpacing: 0,
centerTitle: false,
scrolledUnderElevation: 0,
backgroundColor: Platform.isIOS ? colorScheme.surface : null,
backgroundColor: isDynamic ? null : colorScheme.surface,
titleTextStyle: TextStyle(fontSize: 16, color: colorScheme.onSurface),
),
navigationBarTheme: NavigationBarThemeData(
surfaceTintColor: surfaceTintColor,
surfaceTintColor: isDynamic ? colorScheme.onSurfaceVariant : null,
),
snackBarTheme: SnackBarThemeData(
actionTextColor: colorScheme.primary,
backgroundColor: colorScheme.secondaryContainer,
closeIconColor: colorScheme.secondary,
contentTextStyle: TextStyle(
color: colorScheme.secondary,
),
contentTextStyle: TextStyle(color: colorScheme.secondary),
elevation: 20,
),
pageTransitionsTheme: const PageTransitionsTheme(
@@ -230,19 +258,32 @@ class MyApp extends StatelessWidget {
},
),
popupMenuTheme: PopupMenuThemeData(
surfaceTintColor: surfaceTintColor,
surfaceTintColor: isDynamic ? colorScheme.onSurfaceVariant : null,
),
cardTheme: CardTheme(
elevation: 1,
surfaceTintColor: surfaceTintColor,
surfaceTintColor: isDynamic
? colorScheme.onSurfaceVariant
: isDark
? colorScheme.onSurfaceVariant
: null,
shadowColor: Colors.transparent,
),
dialogTheme: DialogTheme(
surfaceTintColor: surfaceTintColor,
),
// dialogTheme: DialogTheme(
// surfaceTintColor: isDark ? colorScheme.onSurfaceVariant : null,
// ),
progressIndicatorTheme: ProgressIndicatorThemeData(
refreshBackgroundColor: colorScheme.onSecondary,
),
);
}
}
class _CustomHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
}
}

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
final List<Map<String, dynamic>> colorThemeTypes = [
{'color': const Color.fromARGB(255, 92, 182, 123), 'label': '默认绿'},
{'color': const Color.fromARGB(255, 251, 114, 153), 'label': '粉红色'},
{'color': const Color(0xFF5CB67B), 'label': '默认绿'},
{'color': const Color(0xFFFF7299), 'label': '粉红色'},
{'color': Colors.red, 'label': '红色'},
{'color': Colors.orange, 'label': '橙色'},
{'color': Colors.amber, 'label': '琥珀色'},

View File

@@ -4,6 +4,8 @@ extension DynamicBadgeModeDesc on DynamicBadgeMode {
String get description => ['隐藏', '红点', '数字'][index];
}
extension DynamicBadgeModeCode on DynamicBadgeMode {
int get code => [0, 1, 2][index];
enum MsgUnReadType { pm, reply, at, like, sysMsg, all }
extension MsgUnReadTypeExt on MsgUnReadType {
String get title => ['私信', '回复我的', '@我', '收到的赞', '系统通知', '全部'][index];
}

View File

@@ -11,35 +11,35 @@ extension BusinessTypeExtension on DynamicsType {
String get labels => ['全部', '投稿', '番剧', '专栏', 'UP'][index];
}
List tabsConfig = [
{
'tag': 'all',
'value': DynamicsType.all,
'label': '全部',
'enabled': true,
},
{
'tag': 'video',
'value': DynamicsType.video,
'label': '投稿',
'enabled': true,
},
{
'tag': 'pgc',
'value': DynamicsType.pgc,
'label': '番剧',
'enabled': true,
},
{
'tag': 'article',
'value': DynamicsType.article,
'label': '专栏',
'enabled': true,
},
{
'tag': 'up',
'value': DynamicsType.up,
'label': 'UP',
'enabled': true,
},
];
List get tabsConfig => [
{
'tag': 'all',
'value': DynamicsType.all,
'label': '全部',
'enabled': true,
},
{
'tag': 'video',
'value': DynamicsType.video,
'label': '投稿',
'enabled': true,
},
{
'tag': 'pgc',
'value': DynamicsType.pgc,
'label': '番剧',
'enabled': true,
},
{
'tag': 'article',
'value': DynamicsType.article,
'label': '专栏',
'enabled': true,
},
{
'tag': 'up',
'value': DynamicsType.up,
'label': 'UP',
'enabled': true,
},
];

View File

@@ -1,43 +1,43 @@
import 'package:flutter/material.dart';
List defaultNavigationBars = [
{
'id': 0,
'icon': const Icon(
Icons.home_outlined,
size: 23,
),
'selectIcon': const Icon(
Icons.home,
size: 23,
),
'label': "首页",
'count': 0,
},
{
'id': 1,
'icon': const Icon(
Icons.motion_photos_on_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.motion_photos_on,
size: 21,
),
'label': "动态",
'count': 0,
},
{
'id': 2,
'icon': const Icon(
Icons.video_collection_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.video_collection,
size: 21,
),
'label': "媒体库",
'count': 0,
}
];
List get defaultNavigationBars => [
{
'id': 0,
'icon': const Icon(
Icons.home_outlined,
size: 23,
),
'selectIcon': const Icon(
Icons.home,
size: 23,
),
'label': "首页",
'count': 0,
},
{
'id': 1,
'icon': const Icon(
Icons.motion_photos_on_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.motion_photos_on,
size: 21,
),
'label': "动态",
'count': 0,
},
{
'id': 2,
'icon': const Icon(
Icons.video_collection_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.video_collection,
size: 21,
),
'label': "媒体库",
'count': 0,
}
];

View File

@@ -1,10 +1,10 @@
import 'package:PiliPalaX/pages/rank/index.dart';
import 'package:PiliPlus/pages/rank/index.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/pages/bangumi/index.dart';
import 'package:PiliPalaX/pages/hot/index.dart';
import 'package:PiliPalaX/pages/live/index.dart';
import 'package:PiliPalaX/pages/rcmd/index.dart';
import 'package:PiliPlus/pages/bangumi/index.dart';
import 'package:PiliPlus/pages/hot/index.dart';
import 'package:PiliPlus/pages/live/index.dart';
import 'package:PiliPlus/pages/rcmd/index.dart';
enum TabType { live, rcmd, hot, rank, bangumi }
@@ -13,55 +13,55 @@ extension TabTypeDesc on TabType {
String get id => ['live', 'rcmd', 'hot', 'rank', 'bangumi'][index];
}
List tabsConfig = [
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '直播',
'type': TabType.live,
'ctr': Get.find<LiveController>,
'page': const RcmdPage(tabType: TabType.live),
},
{
'icon': const Icon(
Icons.thumb_up_off_alt_outlined,
size: 15,
),
'label': '推荐',
'type': TabType.rcmd,
'ctr': Get.find<RcmdController>,
'page': const RcmdPage(tabType: TabType.rcmd),
},
{
'icon': const Icon(
Icons.whatshot_outlined,
size: 15,
),
'label': '热门',
'type': TabType.hot,
'ctr': Get.find<HotController>,
'page': const HotPage(),
},
{
'icon': const Icon(
Icons.category_outlined,
size: 15,
),
'label': '分区',
'type': TabType.rank,
'ctr': Get.find<RankController>,
'page': const RankPage(),
},
{
'icon': const Icon(
Icons.play_circle_outlined,
size: 15,
),
'label': '番剧',
'type': TabType.bangumi,
'ctr': Get.find<BangumiController>,
'page': const BangumiPage(),
},
];
List get tabsConfig => [
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '直播',
'type': TabType.live,
'ctr': Get.find<LiveController>,
'page': const RcmdPage(tabType: TabType.live),
},
{
'icon': const Icon(
Icons.thumb_up_off_alt_outlined,
size: 15,
),
'label': '推荐',
'type': TabType.rcmd,
'ctr': Get.find<RcmdController>,
'page': const RcmdPage(tabType: TabType.rcmd),
},
{
'icon': const Icon(
Icons.whatshot_outlined,
size: 15,
),
'label': '热门',
'type': TabType.hot,
'ctr': Get.find<HotController>,
'page': const HotPage(),
},
{
'icon': const Icon(
Icons.category_outlined,
size: 15,
),
'label': '分区',
'type': TabType.rank,
'ctr': Get.find<RankController>,
'page': const RankPage(),
},
{
'icon': const Icon(
Icons.play_circle_outlined,
size: 15,
),
'label': '番剧',
'type': TabType.bangumi,
'ctr': Get.find<BangumiController>,
'page': const BangumiPage(),
},
];

View File

@@ -416,6 +416,8 @@ class DynamicMajorModel {
this.none,
this.type,
this.courses,
this.common,
this.music,
});
DynamicArchiveModel? archive;
@@ -431,6 +433,8 @@ class DynamicMajorModel {
// MAJOR_TYPE_OPUS 图文/文章
String? type;
Map? courses;
Map? common;
Map? music;
DynamicMajorModel.fromJson(Map<String, dynamic> json) {
archive = json['archive'] != null
@@ -454,6 +458,8 @@ class DynamicMajorModel {
json['none'] != null ? DynamicNoneModel.fromJson(json['none']) : null;
type = json['type'];
courses = json['courses'] ?? {};
common = json['common'] ?? {};
music = json['music'] ?? {};
}
}

View File

@@ -4,6 +4,7 @@ class FollowUpModel {
this.upList,
});
String? errMsg;
LiveUsers? liveUsers;
List<UpItem>? upList;

View File

@@ -1,4 +1,4 @@
import 'package:PiliPalaX/utils/id_utils.dart';
import 'package:PiliPlus/utils/id_utils.dart';
class RecVideoItemAppModel {
RecVideoItemAppModel({

View File

@@ -27,7 +27,9 @@ class HotVideoItemModel {
this.seasontype,
this.isOgv,
this.rcmdReason,
required this.checked,
this.checked,
this.pgcLabel,
this.redirectUrl,
});
int? aid;
@@ -55,7 +57,9 @@ class HotVideoItemModel {
int? seasontype;
bool? isOgv;
RcmdReason? rcmdReason;
late bool checked;
bool? checked;
String? pgcLabel;
String? redirectUrl;
HotVideoItemModel.fromJson(Map<String, dynamic> json) {
aid = json["aid"];
@@ -85,7 +89,8 @@ class HotVideoItemModel {
rcmdReason = json['rcmd_reason'] != '' && json['rcmd_reason'] != null
? RcmdReason.fromJson(json['rcmd_reason'])
: null;
checked = false;
pgcLabel = json['pgc_label'];
redirectUrl = json['redirect_url'];
}
}

View File

@@ -55,7 +55,7 @@ class AccountListModel {
mid = json['mid'];
name = json['name'] ?? '';
sex = json['sex'];
face = json['face'];
face = json['face'] ?? json['pic_url'];
sign = json['sign'];
rank = json['rank'];
level = json['level'];

View File

@@ -1,6 +1,6 @@
import 'dart:convert';
import 'package:PiliPalaX/models/msg/account.dart';
import 'package:PiliPlus/models/msg/account.dart';
class SessionDataModel {
SessionDataModel({
@@ -47,6 +47,7 @@ class SessionList {
this.liveStatus,
this.bizMsgUnreadCount,
// this.userLabel,
this.accountInfo,
});
int? talkerId;
@@ -105,6 +106,9 @@ class SessionList {
liveStatus = json["live_status"];
bizMsgUnreadCount = json["biz_msg_unread_count"];
// userLabel = json["user_label"];
accountInfo = json['account_info'] == null
? null
: AccountListModel.fromJson(json['account_info']);
}
}

View File

@@ -1,5 +1,5 @@
import 'package:PiliPalaX/utils/em.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:PiliPlus/utils/em.dart';
import 'package:PiliPlus/utils/utils.dart';
class SearchVideoModel {
SearchVideoModel({

View File

@@ -29,16 +29,16 @@ class SearchSuggestItem {
String? value;
String? term;
int? spid;
Widget? textRich;
dynamic textRich;
SearchSuggestItem.fromJson(Map<String, dynamic> json, String inputTerm) {
value = json['value'];
term = json['term'];
textRich = highlightText(json['name']);
textRich = json['name'];
}
}
Widget highlightText(String str) {
Widget highlightText(BuildContext context, String str) {
// 创建正则表达式,匹配 <em class="suggest_high_light">...</em> 格式的文本
RegExp regex = RegExp(r'<em class="suggest_high_light">(.*?)<\/em>');
@@ -72,7 +72,7 @@ Widget highlightText(String str) {
text: highlightedText,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(Get.context!).colorScheme.primary),
color: Theme.of(context).colorScheme.primary),
));
// 更新当前索引位置
@@ -86,7 +86,7 @@ Widget highlightText(String str) {
// 将剩余的普通文本部分添加到 children 列表中
children.add(TextSpan(
text: remainingText,
style: DefaultTextStyle.of(Get.context!).style,
style: DefaultTextStyle.of(context).style,
));
}

View File

@@ -1,4 +1,4 @@
import 'package:PiliPalaX/models/space/space_tag_bottom.dart';
import 'package:PiliPlus/models/space/space_tag_bottom.dart';
import 'package:json_annotation/json_annotation.dart';
import 'achieve.dart';

View File

@@ -13,7 +13,7 @@ import 'series.dart';
import 'setting.dart';
import 'tab.dart';
import 'tab2.dart';
import 'package:PiliPalaX/models/space_article/data.dart' as space;
import 'package:PiliPlus/models/space_article/data.dart' as space;
part 'data.g.dart';

View File

@@ -1,4 +1,4 @@
import 'package:PiliPalaX/models/space_archive/item.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:json_annotation/json_annotation.dart';
part 'season.g.dart';

View File

@@ -1,5 +1,5 @@
import 'package:PiliPalaX/models/model_owner.dart';
import 'package:PiliPalaX/models/user/fav_folder.dart';
import 'package:PiliPlus/models/model_owner.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
class FavDetailData {
FavDetailData({
@@ -47,7 +47,7 @@ class FavDetailItemData {
this.stat,
this.cid,
this.epId,
required this.checked,
this.checked,
});
int? id;
@@ -70,7 +70,7 @@ class FavDetailItemData {
Stat? stat;
int? cid;
String? epId;
late bool checked;
bool? checked;
FavDetailItemData.fromJson(Map<String, dynamic> json) {
id = json['id'];
@@ -95,7 +95,6 @@ class FavDetailItemData {
if (json['link'] != null && json['link'].contains('/bangumi')) {
epId = resolveEpId(json['link']);
}
checked = false;
}
String resolveEpId(url) {

View File

@@ -85,7 +85,7 @@ class HisListItem {
this.kid,
this.tagName,
this.liveStatus,
required this.checked,
this.checked,
});
String? title;
@@ -112,7 +112,7 @@ class HisListItem {
int? kid;
String? tagName;
int? liveStatus;
late bool checked;
bool? checked;
void isFullScreen;
HisListItem.fromJson(Map<String, dynamic> json) {
@@ -140,7 +140,6 @@ class HisListItem {
kid = json['kid'];
tagName = json['tag_name'];
liveStatus = json['live_status'];
checked = false;
}
}

276
lib/models/video/later.dart Normal file
View File

@@ -0,0 +1,276 @@
class MediaVideoItemModel {
MediaVideoItemModel({
this.id,
this.aid,
this.bvid,
this.cid,
this.offset,
this.index,
this.intro,
this.attr,
this.tid,
this.copyRight,
this.cntInfo,
this.cover,
this.duration,
this.pubtime,
this.likeState,
this.favState,
this.page,
this.pages,
this.title,
this.type,
this.upper,
this.link,
this.shortLink,
this.rights,
this.elecInfo,
this.coin,
this.progressPercent,
this.badge,
this.forbidFav,
this.moreType,
this.businessOid,
});
int? id;
int? aid;
String? bvid;
int? cid;
int? offset;
int? index;
String? intro;
int? attr;
int? tid;
int? copyRight;
Map? cntInfo;
String? cover;
int? duration;
int? pubtime;
int? likeState;
int? favState;
int? page;
List<Page>? pages;
String? title;
int? type;
Upper? upper;
String? link;
String? shortLink;
Rights? rights;
dynamic elecInfo;
Coin? coin;
double? progressPercent;
dynamic badge;
bool? forbidFav;
int? moreType;
int? businessOid;
factory MediaVideoItemModel.fromJson(Map<String, dynamic> json) =>
MediaVideoItemModel(
id: json["id"],
aid: json["id"],
bvid: json["bv_id"],
cid: json["pages"] == null ? -1 : json["pages"].first['id'],
offset: json["offset"],
index: json["index"],
intro: json["intro"],
attr: json["attr"],
tid: json["tid"],
copyRight: json["copy_right"],
cntInfo: json["cnt_info"],
cover: json["cover"],
duration: json["duration"],
pubtime: json["pubtime"],
likeState: json["like_state"],
favState: json["fav_state"],
page: json["page"],
// json["pages"] 可能为null
pages: json["pages"] == null
? []
: List<Page>.from(json["pages"].map((x) => Page.fromJson(x))),
title: json["title"],
type: json["type"],
upper: Upper.fromJson(json["upper"]),
link: json["link"],
shortLink: json["short_link"],
rights: Rights.fromJson(json["rights"]),
elecInfo: json["elec_info"],
coin: Coin.fromJson(json["coin"]),
progressPercent: json["progress_percent"].toDouble(),
badge: json["badge"],
forbidFav: json["forbid_fav"],
moreType: json["more_type"],
businessOid: json["business_oid"],
);
}
class Coin {
Coin({
this.maxNum,
this.coinNumber,
});
int? maxNum;
int? coinNumber;
factory Coin.fromJson(Map<String, dynamic> json) => Coin(
maxNum: json["max_num"],
coinNumber: json["coin_number"],
);
}
class Page {
Page({
this.id,
this.title,
this.intro,
this.duration,
this.link,
this.page,
this.metas,
this.from,
this.dimension,
});
int? id;
String? title;
String? intro;
int? duration;
String? link;
int? page;
List<Meta>? metas;
String? from;
Dimension? dimension;
factory Page.fromJson(Map<String, dynamic> json) => Page(
id: json["id"],
title: json["title"],
intro: json["intro"],
duration: json["duration"],
link: json["link"],
page: json["page"],
metas: List<Meta>.from(json["metas"].map((x) => Meta.fromJson(x))),
from: json["from"],
dimension: Dimension.fromJson(json["dimension"]),
);
}
class Dimension {
Dimension({
this.width,
this.height,
this.rotate,
});
int? width;
int? height;
int? rotate;
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
width: json["width"],
height: json["height"],
rotate: json["rotate"],
);
}
class Meta {
Meta({
this.quality,
this.size,
});
int? quality;
int? size;
factory Meta.fromJson(Map<String, dynamic> json) => Meta(
quality: json["quality"],
size: json["size"],
);
}
class Rights {
Rights({
this.bp,
this.elec,
this.download,
this.movie,
this.pay,
this.ugcPay,
this.hd5,
this.noReprint,
this.autoplay,
this.noBackground,
});
int? bp;
int? elec;
int? download;
int? movie;
int? pay;
int? ugcPay;
int? hd5;
int? noReprint;
int? autoplay;
int? noBackground;
factory Rights.fromJson(Map<String, dynamic> json) => Rights(
bp: json["bp"],
elec: json["elec"],
download: json["download"],
movie: json["movie"],
pay: json["pay"],
ugcPay: json["ugc_pay"],
hd5: json["hd5"],
noReprint: json["no_reprint"],
autoplay: json["autoplay"],
noBackground: json["no_background"],
);
}
class Upper {
Upper({
this.mid,
this.name,
this.face,
this.followed,
this.fans,
this.vipType,
this.vipStatue,
this.vipDueDate,
this.vipPayType,
this.officialRole,
this.officialTitle,
this.officialDesc,
this.displayName,
});
int? mid;
String? name;
String? face;
int? followed;
int? fans;
int? vipType;
int? vipStatue;
int? vipDueDate;
int? vipPayType;
int? officialRole;
String? officialTitle;
String? officialDesc;
String? displayName;
factory Upper.fromJson(Map<String, dynamic> json) => Upper(
mid: json["mid"],
name: json["name"],
face: json["face"],
followed: json["followed"],
fans: json["fans"],
vipType: json["vip_type"],
vipStatue: json["vip_statue"],
vipDueDate: json["vip_due_date"],
vipPayType: json["vip_pay_type"],
officialRole: json["official_role"],
officialTitle: json["official_title"],
officialDesc: json["official_desc"],
displayName: json["display_name"],
);
}

View File

@@ -1,4 +1,4 @@
import 'package:PiliPalaX/models/video/play/quality.dart';
import 'package:PiliPlus/models/video/play/quality.dart';
class PlayUrlModel {
PlayUrlModel({

View File

@@ -1,4 +1,4 @@
import 'package:PiliPalaX/models/video/reply/item.dart';
import 'package:PiliPlus/models/video/reply/item.dart';
import 'config.dart';
import 'page.dart';

View File

@@ -49,6 +49,7 @@ class VideoDetailData {
Map<String, int>? rights;
Owner? owner;
Stat? stat;
String? argueMsg;
String? videoDynamic;
int? cid;
Dimension? dimension;
@@ -68,6 +69,8 @@ class VideoDetailData {
bool? needJumpBv;
String? epId;
List<Staff>? staff;
late bool isPageReversed;
late bool isSeasonReversed;
VideoDetailData({
this.bvid,
@@ -87,6 +90,7 @@ class VideoDetailData {
this.rights,
this.owner,
this.stat,
this.argueMsg,
this.videoDynamic,
this.cid,
this.dimension,
@@ -105,6 +109,8 @@ class VideoDetailData {
this.needJumpBv,
this.epId,
this.staff,
this.isPageReversed = false,
this.isSeasonReversed = false,
});
VideoDetailData.fromJson(Map<String, dynamic> json) {
@@ -128,6 +134,7 @@ class VideoDetailData {
Map.from(json["rights"]!).map((k, v) => MapEntry<String, int>(k, v));
owner = json["owner"] == null ? null : Owner.fromJson(json["owner"]);
stat = json["stat"] == null ? null : Stat.fromJson(json["stat"]);
argueMsg = json['argue_info']?['argue_msg'];
videoDynamic = json["dynamic"];
cid = json["cid"];
dimension = json["dimension"] == null
@@ -160,6 +167,8 @@ class VideoDetailData {
if (json['redirect_url'] != null) {
epId = resolveEpId(json['redirect_url']);
}
isPageReversed = false;
isSeasonReversed = false;
}
String resolveEpId(url) {
@@ -485,7 +494,6 @@ class Stat {
int? like;
int? dislike;
String? evaluation;
String? argueMsg;
Stat({
this.aid,
@@ -500,7 +508,6 @@ class Stat {
this.like,
this.dislike,
this.evaluation,
this.argueMsg,
});
fromRawJson(String str) => Stat.fromJson(json.decode(str));
@@ -520,7 +527,6 @@ class Stat {
like = json["like"];
dislike = json["dislike"];
evaluation = json["evaluation"];
argueMsg = json["argue_msg"];
}
Map<String, dynamic> toJson() {
@@ -538,7 +544,6 @@ class Stat {
data["like"] = like;
data["dislike"] = dislike;
data["evaluation"] = evaluation;
data["argue_msg"] = argueMsg;
return data;
}
}

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