Compare commits

..

258 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
361 changed files with 19737 additions and 12831 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: steps:
- name: 代码迁出 - name: 代码迁出
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
fetch-depth: 0
- name: 构建Java环境 - name: 构建Java环境
uses: actions/setup-java@v4 uses: actions/setup-java@v4
@@ -28,12 +30,30 @@ jobs:
if: steps.cache-flutter.outputs.cache-hit != 'true' if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: 3.24.0 channel: stable
channel: any 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: 下载项目依赖 - name: 下载项目依赖
run: flutter pub get 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 - name: Write key
run: | run: |
if [ ! -z "${{ secrets.SIGN_KEYSTORE_BASE64 }}" ]; then if [ ! -z "${{ secrets.SIGN_KEYSTORE_BASE64 }}" ]; then
@@ -45,17 +65,10 @@ jobs:
fi fi
- name: flutter build apk - name: flutter build apk
run: flutter build apk --release --target-platform=android-arm64 run: |
chmod +x lib/scripts/build.sh
- name: flutter build apk lib/scripts/build.sh
run: flutter build apk --release --split-per-abi 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
- name: 上传 - name: 上传
uses: actions/upload-artifact@v4 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 uses: actions/checkout@v4
with: with:
ref: ${{ github.event.inputs.branch }} ref: ${{ github.event.inputs.branch }}
fetch-depth: 0
- name: Setup flutter - name: Setup flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
channel: stable channel: stable
flutter-version-file: pubspec.yaml
- name: Set up xcode - name: 更新版本号
uses: BoundfoxStudios/action-xcode-select@v1 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 - name: Build iOS
run: | run: |
chmod +x lib/scripts/build.sh
lib/scripts/build.sh
flutter build ios --release --no-codesign flutter build ios --release --no-codesign
ln -sf ./build/ios/iphoneos Payload ln -sf ./build/ios/iphoneos Payload
zip -r9 ios-release-no-sign.ipa Payload/runner.app zip -r9 ios-release-no-sign.ipa Payload/runner.app

4
.gitignore vendored
View File

@@ -133,4 +133,6 @@ app.*.symbols
!**/ios/**/default.perspectivev3 !**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock !/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", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "pilipala", "name": "piliplus",
"request": "launch", "request": "launch",
"type": "dart" "type": "dart"
}, },
{ {
"name": "pilipala (profile mode)", "name": "piliplus (profile mode)",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"flutterMode": "profile" "flutterMode": "profile"
}, },
{ {
"name": "pilipala (release mode)", "name": "piliplus (release mode)",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"flutterMode": "release" "flutterMode": "release"

View File

@@ -1,24 +1,24 @@
<div align="center"> <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>
<div align="center"> <div align="center">
<h1>PiliPalaX</h1> <h1>PiliPlus</h1>
<div align="center"> <div align="center">
![GitHub repo size](https://img.shields.io/github/repo-size/bggRGjQaUbCoE/PiliPalaX) ![GitHub repo size](https://img.shields.io/github/repo-size/bggRGjQaUbCoE/PiliPlus)
![GitHub Repo stars](https://img.shields.io/github/stars/bggRGjQaUbCoE/PiliPalaX) ![GitHub Repo stars](https://img.shields.io/github/stars/bggRGjQaUbCoE/PiliPlus)
![GitHub all releases](https://img.shields.io/github/downloads/bggRGjQaUbCoE/PiliPalaX/total) ![GitHub all releases](https://img.shields.io/github/downloads/bggRGjQaUbCoE/PiliPlus/total)
</div> </div>
<p>使用Flutter开发的BiliBili第三方客户端</p> <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/bggRGjQaUbCoE/PiliPlus/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/bggRGjQaUbCoE/PiliPlus/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/850shots_so.png" width="32%" alt="home" />
<br/> <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/> <br/>
</div> </div>
@@ -186,9 +186,10 @@
## 声明 ## 声明
此项目PiliPalaX)是个人为了兴趣而开发, 仅用于学习和测试请于下载后24小时内删除。 此项目PiliPlus)是个人为了兴趣而开发, 仅用于学习和测试请于下载后24小时内删除。
所用API皆从官方网站收集, 不提供任何破解内容。 所用API皆从官方网站收集, 不提供任何破解内容。
在此致敬原作者:[guozhigq/pilipala](https://github.com/guozhigq/pilipala) 在此致敬原作者:[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"] def _keyPassword = System.getenv("KEY_PASSWORD") ?: keystoreProperties["keyPassword"]
android { android {
compileSdkVersion 34 compileSdkVersion flutter.compileSdkVersion
namespace 'com.example.pilipalax' namespace 'com.example.pilipalax'
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
@@ -61,7 +61,7 @@ android {
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
minSdkVersion 21 minSdkVersion flutter.minSdkVersion
multiDexEnabled true multiDexEnabled true
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1 +1 @@
PiliPalaX PiliPlus

View File

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

View File

@@ -1 +1 @@
PiliPalaX PiliPlus

View File

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

View File

@@ -21,13 +21,10 @@ class Constants {
static const String traceId = static const String traceId =
'11111111111111111111111111111111:1111111111111111:0:0'; '11111111111111111111111111111111:1111111111111111:0:0';
static const String userAgent = 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 = 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'; '%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D';
// jsonEncode( //Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
// {"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 //内容来自 https://passport.bilibili.com/web/generic/country/list
static const List<Map<String, dynamic>> internationalDialingPrefix = [ static const List<Map<String, dynamic>> internationalDialingPrefix = [

View File

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

View File

@@ -1,7 +1,6 @@
import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/models/dynamics/article_content_model.dart'; import 'package:PiliPlus/models/dynamics/article_content_model.dart';
import 'package:PiliPalaX/pages/preview/view.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPalaX/utils/extension.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
@@ -9,6 +8,7 @@ import 'package:flutter_html/flutter_html.dart';
Widget articleContent({ Widget articleContent({
required BuildContext context, required BuildContext context,
required List<ArticleContentModel> list, required List<ArticleContentModel> list,
Function(List<String>, int)? callback,
}) { }) {
List<String>? imgList = list List<String>? imgList = list
.where((item) => item.pic != null) .where((item) => item.pic != null)
@@ -17,7 +17,7 @@ Widget articleContent({
.toList(); .toList();
return SliverList.separated( return SliverList.separated(
itemCount: list.length, itemCount: list.length,
itemBuilder: (_, index) { itemBuilder: (context, index) {
ArticleContentModel item = list[index]; ArticleContentModel item = list[index];
if (item.text != null) { if (item.text != null) {
List<InlineSpan> spanList = []; List<InlineSpan> spanList = [];
@@ -56,25 +56,29 @@ Widget articleContent({
); );
} else if (item.pic != null) { } else if (item.pic != null) {
return LayoutBuilder( return LayoutBuilder(
builder: (_, constraints) => GestureDetector( builder: (context, constraints) => Hero(
onTap: () { tag: item.pic!.pics!.first.url!,
showDialog( child: GestureDetector(
useSafeArea: false, onTap: () {
context: context, if (callback != null) {
builder: (context) { callback(
return ImagePreview( imgList,
imgList.indexOf(item.pic!.pics!.first.url!),
);
} else {
context.imageView(
initialPage: imgList.indexOf(item.pic!.pics!.first.url!), initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
imgList: imgList, imgList: imgList,
); );
}, }
); },
}, child: NetworkImgLayer(
child: NetworkImgLayer( width: constraints.maxWidth,
width: constraints.maxWidth, height: constraints.maxWidth *
height: constraints.maxWidth * item.pic!.pics!.first.height! /
item.pic!.pics!.first.height! / item.pic!.pics!.first.width!,
item.pic!.pics!.first.width!, src: item.pic!.pics!.first.url,
src: item.pic!.pics!.first.url, ),
), ),
), ),
); );

View File

@@ -42,8 +42,8 @@ class PBadge extends StatelessWidget {
color = Colors.white; color = Colors.white;
} }
if (type == 'color') { if (type == 'color') {
bgColor = t.primaryContainer.withOpacity(0.5); bgColor = t.secondaryContainer.withOpacity(0.5);
color = t.primary; color = t.onSecondaryContainer;
} }
if (type == 'line') { if (type == 'line') {
bgColor = Colors.transparent; 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:flutter/material.dart';
import 'package:hive/hive.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPalaX/utils/storage.dart';
Box<dynamic> setting = GStorage.setting;
class CustomToast extends StatelessWidget { class CustomToast extends StatelessWidget {
const CustomToast({super.key, required this.msg}); const CustomToast({super.key, required this.msg});
@@ -11,8 +8,8 @@ class CustomToast extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final double toastOpacity = final double toastOpacity = GStorage.setting
setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0) as double; .get(SettingBoxKey.defaultToastOp, defaultValue: 1.0) as double;
return Container( return Container(
margin: margin:
EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 30), EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 30),
@@ -28,7 +25,7 @@ class CustomToast extends StatelessWidget {
msg, msg,
style: TextStyle( style: TextStyle(
fontSize: 13, 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) { if (_height == 0) {
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: Stack( child: Container(
children: [ key: _childKey,
Padding( child: widget.flexibleSpace ?? SizedBox(height: kToolbarHeight),
// 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,
),
),
)
],
), ),
); );
} }

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/material.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'network_img_layer.dart'; import 'network_img_layer.dart';
@@ -9,6 +9,7 @@ Widget htmlRender({
int? imgCount, int? imgCount,
List<String>? imgList, List<String>? imgList,
required double constrainedWidth, required double constrainedWidth,
Function(List<String>, int)? callback,
}) { }) {
return SelectionArea( return SelectionArea(
child: Html( child: Html(
@@ -40,29 +41,29 @@ Widget htmlRender({
// extensionContext.element!.previousElementSibling == null || // extensionContext.element!.previousElementSibling == null ||
// extensionContext.element!.nextElementSibling == null; // extensionContext.element!.nextElementSibling == null;
// imgUrl = Utils().imageUrl(imgUrl!); // imgUrl = Utils().imageUrl(imgUrl!);
// return Image.network( // return CachedNetworkImage(
// imgUrl, // imageUrl: imgUrl,
// width: isEmote ? 22 : null, // width: isEmote ? 22 : null,
// height: isEmote ? 22 : null, // height: isEmote ? 22 : null,
// ); // );
return GestureDetector( return Hero(
onTap: () { tag: imgUrl,
showDialog( child: GestureDetector(
useSafeArea: false, onTap: () {
context: context, if (callback != null) {
builder: (context) { callback([imgUrl], 0);
return ImagePreview( } else {
initialPage: 0, context.imageView(
imgList: [imgUrl], imgList: [imgUrl],
); );
}, }
); },
}, child: NetworkImgLayer(
child: NetworkImgLayer( width: isEmote ? 22 : constrainedWidth,
width: isEmote ? 22 : constrainedWidth, height: isEmote ? 22 : 200,
height: isEmote ? 22 : 200, src: imgUrl,
src: imgUrl, ignoreHeight: !isEmote,
ignoreHeight: !isEmote, ),
), ),
); );
} catch (err) { } catch (err) {
@@ -73,7 +74,7 @@ Widget htmlRender({
], ],
style: { style: {
'html': Style( 'html': Style(
fontSize: FontSize(17), fontSize: FontSize(16),
lineHeight: LineHeight.percent(160), lineHeight: LineHeight.percent(160),
letterSpacing: 0.3, letterSpacing: 0.3,
), ),
@@ -109,7 +110,7 @@ Widget htmlRender({
margin: Margins.only(bottom: 8), margin: Margins.only(bottom: 8),
), ),
'h3,h4,h5': Style( 'h3,h4,h5': Style(
fontSize: FontSize(17), fontSize: FontSize(16),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
margin: Margins.only(bottom: 4), margin: Margins.only(bottom: 4),
), ),

View File

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

View File

@@ -6,6 +6,7 @@ Widget iconButton({
required IconData icon, required IconData icon,
required VoidCallback? onPressed, required VoidCallback? onPressed,
double size = 36, double size = 36,
double? iconSize,
Color? bgColor, Color? bgColor,
Color? iconColor, Color? iconColor,
}) { }) {
@@ -17,7 +18,7 @@ Widget iconButton({
onPressed: onPressed, onPressed: onPressed,
icon: Icon( icon: Icon(
icon, icon,
size: size / 2, size: iconSize ?? size / 2,
color: iconColor ?? Theme.of(context).colorScheme.onSecondaryContainer, color: iconColor ?? Theme.of(context).colorScheme.onSecondaryContainer,
), ),
style: IconButton.styleFrom( 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 'dart:math';
import 'package:PiliPalaX/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/common/widgets/nine_grid_view.dart'; import 'package:PiliPlus/common/widgets/nine_grid_view.dart';
import 'package:PiliPalaX/pages/preview/view.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ImageModel { class ImageModel {
@@ -23,10 +23,13 @@ class ImageModel {
bool get isLongPic => _isLongPic ??= (safeHeight / safeWidth) > (22 / 9); bool get isLongPic => _isLongPic ??= (safeHeight / safeWidth) > (22 / 9);
} }
Widget image( Widget imageview(
double maxWidth, 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 imageWidth = (maxWidth - 2 * 5) / 3;
double imageHeight = imageWidth; double imageHeight = imageWidth;
if (picArr.length == 1) { if (picArr.length == 1) {
@@ -53,39 +56,43 @@ Widget image(
height: picArr.length == 1 ? imageHeight : null, height: picArr.length == 1 ? imageHeight : null,
width: picArr.length == 1 ? imageWidth : maxWidth, width: picArr.length == 1 ? imageWidth : maxWidth,
itemCount: picArr.length, itemCount: picArr.length,
itemBuilder: (context, index) => GestureDetector( itemBuilder: (context, index) => Hero(
onTap: () { tag: picArr[index].url,
showDialog( child: GestureDetector(
useSafeArea: false, onTap: () {
context: context, if (callback != null) {
builder: (context) { callback(picArr.map((item) => item.url).toList(), index);
return ImagePreview( } else {
onViewImage?.call();
context.imageView(
initialPage: index, initialPage: index,
imgList: picArr.map((item) => item.url).toList(), imgList: picArr.map((item) => item.url).toList(),
onDismissed: onDismissed,
); );
}, }
); },
}, child: Stack(
child: Stack( alignment: Alignment.center,
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: NetworkImgLayer( child: NetworkImgLayer(
src: picArr[index].url, src: picArr[index].url,
width: imageWidth, width: imageWidth,
height: imageHeight, height: imageHeight,
isLongPic: () => picArr[index].isLongPic, isLongPic: () => picArr[index].isLongPic,
callback: () => callback: () =>
picArr[index].safeWidth <= picArr[index].safeHeight, picArr[index].safeWidth <= picArr[index].safeHeight,
),
), ),
), if (picArr[index].isLongPic)
if (picArr[index].isLongPic) const PBadge(
const PBadge( text: '长图',
text: '长图', right: 8,
right: 8, bottom: 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,13 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:PiliPalaX/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPalaX/http/video.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/models/bangumi/info.dart' as bangumi; import 'package:PiliPlus/http/video.dart';
import 'package:PiliPalaX/models/video_detail_res.dart' as video; 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/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
@@ -17,13 +18,18 @@ import '../../utils/utils.dart';
class ListSheetContent extends StatefulWidget { class ListSheetContent extends StatefulWidget {
const ListSheetContent({ const ListSheetContent({
super.key, super.key,
this.index, this.index, // tab index
this.season, this.season,
required this.episodes, this.episodes,
this.bvid, this.bvid,
this.aid, this.aid,
required this.currentCid, required this.currentCid,
required this.changeFucCall, required this.changeFucCall,
this.onClose,
this.onReverse,
this.showTitle,
this.isSupportReverse,
this.isReversed,
}); });
final dynamic index; final dynamic index;
@@ -33,6 +39,11 @@ class ListSheetContent extends StatefulWidget {
final int? aid; final int? aid;
final int currentCid; final int currentCid;
final Function changeFucCall; final Function changeFucCall;
final VoidCallback? onClose;
final VoidCallback? onReverse;
final bool? showTitle;
final bool? isSupportReverse;
final bool? isReversed;
@override @override
State<ListSheetContent> createState() => _ListSheetContentState(); State<ListSheetContent> createState() => _ListSheetContentState();
@@ -41,26 +52,63 @@ class ListSheetContent extends StatefulWidget {
class _ListSheetContentState extends State<ListSheetContent> class _ListSheetContentState extends State<ListSheetContent>
with TickerProviderStateMixin { with TickerProviderStateMixin {
late List<ItemScrollController> itemScrollController = []; late List<ItemScrollController> itemScrollController = [];
late final int currentIndex = late int currentIndex = _currentIndex;
widget.episodes!.indexWhere((dynamic e) => e.cid == widget.currentCid) ??
0;
late List<bool> reverse; late List<bool> reverse;
int get _index => widget.index ?? 0; int get _index => widget.index ?? 0;
bool get _isList => late final bool _isList = widget.season != null &&
widget.season != null &&
widget.season?.sections is List && widget.season?.sections is List &&
widget.season.sections.length > 1; widget.season.sections.length > 1;
dynamic get episodes =>
widget.episodes ?? widget.season?.sections[_index].episodes;
TabController? _ctr; TabController? _ctr;
StreamController? _indexStream; StreamController? _indexStream;
int? _seasonFav; int? _seasonFav;
StreamController? _favStream; 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 @override
void initState() { void initState() {
super.initState(); super.initState();
if (_isList) { if (_isList) {
_indexStream = StreamController<int>(); _indexStream ??= StreamController<int>.broadcast();
_ctr = TabController( _ctr = TabController(
vsync: this, vsync: this,
length: widget.season.sections.length, length: widget.season.sections.length,
@@ -76,11 +124,8 @@ class _ListSheetContentState extends State<ListSheetContent>
reverse = _isList reverse = _isList
? List.generate(widget.season.sections.length, (_) => false) ? List.generate(widget.season.sections.length, (_) => false)
: [false]; : [false];
WidgetsBinding.instance.addPostFrameCallback((_) {
itemScrollController[_index].jumpTo(index: currentIndex);
});
if (widget.bvid != null && widget.season != null) { if (widget.bvid != null && widget.season != null) {
_favStream = StreamController<int>(); _favStream ??= StreamController<int>();
() async { () async {
dynamic result = await VideoHttp.videoRelation(bvid: widget.bvid); dynamic result = await VideoHttp.videoRelation(bvid: widget.bvid);
if (result['status']) { if (result['status']) {
@@ -89,12 +134,19 @@ class _ListSheetContentState extends State<ListSheetContent>
} }
}(); }();
} }
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
itemScrollController[_index].jumpTo(index: currentIndex);
} catch (_) {}
});
} }
@override @override
void dispose() { void dispose() {
_favStream?.close(); _favStream?.close();
_favStream = null;
_indexStream?.close(); _indexStream?.close();
_indexStream = null;
_ctr?.removeListener(() {}); _ctr?.removeListener(() {});
_ctr?.dispose(); _ctr?.dispose();
super.dispose(); super.dispose();
@@ -136,7 +188,8 @@ class _ListSheetContentState extends State<ListSheetContent>
} }
} }
SmartDialog.showToast('切换到:$title'); SmartDialog.showToast('切换到:$title');
Get.back(); widget.onClose?.call();
currentIndex = index;
widget.changeFucCall( widget.changeFucCall(
episode is bangumi.EpisodeItem ? episode.epId : null, episode is bangumi.EpisodeItem ? episode.epId : null,
episode.runtimeType.toString() == "EpisodeItem" episode.runtimeType.toString() == "EpisodeItem"
@@ -170,7 +223,7 @@ class _ListSheetContentState extends State<ListSheetContent>
) )
: null, : null,
child: LayoutBuilder( child: LayoutBuilder(
builder: (_, constraints) => NetworkImgLayer( builder: (context, constraints) => NetworkImgLayer(
radius: 6, radius: 6,
src: episode is video.EpisodeItem src: episode is video.EpisodeItem
? episode.arc?.pic ? episode.arc?.pic
@@ -229,17 +282,19 @@ class _ListSheetContentState extends State<ListSheetContent>
children: [ children: [
Container( Container(
height: 45, height: 45,
padding: const EdgeInsets.only(left: 14, right: 14), padding: EdgeInsets.symmetric(
horizontal: widget.showTitle != false ? 14 : 6),
child: Row( child: Row(
children: [ children: [
Text( if (widget.showTitle != false)
'合集(${_isList ? widget.season.epCount : widget.episodes!.length}', Text(
style: Theme.of(context).textTheme.titleMedium, '合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
), style: Theme.of(context).textTheme.titleMedium,
),
StreamBuilder( StreamBuilder(
stream: _favStream?.stream, stream: _favStream?.stream,
builder: (_, snapshot) => snapshot.hasData builder: (context, snapshot) => snapshot.hasData
? _mediumButton( ? mediumButton(
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅', tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
icon: _seasonFav == 1 icon: _seasonFav == 1
? Icons.notifications_off_outlined ? Icons.notifications_off_outlined
@@ -261,39 +316,43 @@ class _ListSheetContentState extends State<ListSheetContent>
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
), ),
_mediumButton( mediumButton(
tooltip: '跳至顶部', tooltip: '跳至顶部',
icon: Icons.vertical_align_top, icon: Icons.vertical_align_top,
onPressed: () { onPressed: () {
itemScrollController[_ctr?.index ?? 0].scrollTo( try {
index: !reverse[_ctr?.index ?? 0] itemScrollController[_ctr?.index ?? 0].scrollTo(
? 0 index: !reverse[_ctr?.index ?? 0]
: _isList ? 0
? widget.season.sections[_ctr?.index].episodes : _isList
.length - ? widget.season.sections[_ctr?.index].episodes
1 .length -
: widget.episodes.length - 1, 1
duration: const Duration(milliseconds: 200), : episodes.length - 1,
); duration: const Duration(milliseconds: 200),
);
} catch (_) {}
}, },
), ),
_mediumButton( mediumButton(
tooltip: '跳至底部', tooltip: '跳至底部',
icon: Icons.vertical_align_bottom, icon: Icons.vertical_align_bottom,
onPressed: () { onPressed: () {
itemScrollController[_ctr?.index ?? 0].scrollTo( try {
index: !reverse[_ctr?.index ?? 0] itemScrollController[_ctr?.index ?? 0].scrollTo(
? _isList index: !reverse[_ctr?.index ?? 0]
? widget.season.sections[_ctr?.index].episodes ? _isList
.length - ? widget.season.sections[_ctr?.index].episodes
1 .length -
: widget.episodes.length - 1 1
: 0, : episodes.length - 1
duration: const Duration(milliseconds: 200), : 0,
); duration: const Duration(milliseconds: 200),
);
} catch (_) {}
}, },
), ),
_mediumButton( mediumButton(
tooltip: '跳至当前', tooltip: '跳至当前',
icon: Icons.my_location, icon: Icons.my_location,
onPressed: () async { onPressed: () async {
@@ -301,21 +360,36 @@ class _ListSheetContentState extends State<ListSheetContent>
_ctr?.animateTo(_index); _ctr?.animateTo(_index);
await Future.delayed(const Duration(milliseconds: 225)); await Future.delayed(const Duration(milliseconds: 225));
} }
itemScrollController[_ctr?.index ?? 0].scrollTo( try {
index: currentIndex, itemScrollController[_ctr?.index ?? 0].scrollTo(
duration: const Duration(milliseconds: 200), 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(), const Spacer(),
StreamBuilder( StreamBuilder(
stream: _indexStream?.stream, stream: _indexStream?.stream,
initialData: 0, initialData: _index,
builder: (_, snapshot) => _mediumButton( builder: (context, snapshot) => mediumButton(
tooltip: reverse[snapshot.data] ? '' : '', tooltip: reverse[snapshot.data] ? '' : '',
icon: !reverse[snapshot.data] icon: !reverse[snapshot.data]
? MdiIcons.sortAscending ? MdiIcons.sortNumericAscending
: MdiIcons.sortDescending, : MdiIcons.sortNumericDescending,
onPressed: () { onPressed: () {
setState(() { setState(() {
reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0]; reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0];
@@ -323,11 +397,12 @@ class _ListSheetContentState extends State<ListSheetContent>
}, },
), ),
), ),
_mediumButton( if (widget.onClose != null)
tooltip: '关闭', mediumButton(
icon: Icons.close, tooltip: '关闭',
onPressed: Get.back, icon: Icons.close,
), onPressed: widget.onClose,
),
], ],
), ),
), ),
@@ -358,36 +433,41 @@ class _ListSheetContentState extends State<ListSheetContent>
index, widget.season.sections[index].episodes), index, widget.season.sections[index].episodes),
), ),
) )
: _buildBody(null, widget.episodes), : _buildBody(null, episodes),
), ),
], ],
), ),
); );
} }
Widget _mediumButton({ Widget get _reverseButton => mediumButton(
String? tooltip, tooltip: widget.isReversed == true ? '正序播放' : '倒序播放',
IconData? icon, icon: widget.isReversed == true
VoidCallback? onPressed, ? MdiIcons.sortDescending
}) { : MdiIcons.sortAscending,
return SizedBox( onPressed: () async {
width: 34, if (widget.showTitle == false) {
height: 34, // jump to current
child: IconButton( if (_ctr != null && _ctr?.index != (_index)) {
tooltip: tooltip, _ctr?.animateTo(_index);
icon: Icon(icon), await Future.delayed(const Duration(milliseconds: 225));
style: ButtonStyle( }
padding: WidgetStateProperty.all(EdgeInsets.zero), try {
), itemScrollController[_ctr?.index ?? 0].scrollTo(
onPressed: onPressed, index: currentIndex,
), duration: const Duration(milliseconds: 200),
); );
} } catch (_) {}
}
widget.onReverse?.call();
},
);
Widget _buildBody(i, episodes) => Material( Widget _buildBody(i, episodes) => Material(
child: ScrollablePositionedList.separated( child: ScrollablePositionedList.separated(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 20, bottom: MediaQuery.of(context).padding.bottom + 80,
), ),
reverse: reverse[i ?? 0], reverse: reverse[i ?? 0],
itemCount: episodes.length, itemCount: episodes.length,
@@ -404,7 +484,7 @@ class _ListSheetContentState extends State<ListSheetContent>
); );
}, },
itemScrollController: itemScrollController[i ?? 0], itemScrollController: itemScrollController[i ?? 0],
separatorBuilder: (_, index) => Divider( separatorBuilder: (context, index) => Divider(
height: 1, height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1), color: Theme.of(context).dividerColor.withOpacity(0.1),
), ),

View File

@@ -80,7 +80,7 @@ class LiveCard extends StatelessWidget {
Text( Text(
liveItem.title as String, liveItem.title as String,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w400), style: const TextStyle(fontSize: 13),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, 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'; import 'package:flutter/material.dart';
Widget get loadingWidget => Center(child: CircularProgressIndicator()); Widget get loadingWidget => Center(child: CircularProgressIndicator());

View File

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

View File

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

@@ -21,12 +21,10 @@ class Segment {
} }
class SegmentProgressBar extends CustomPainter { class SegmentProgressBar extends CustomPainter {
final double progress;
final List<Segment> segmentColors; final List<Segment> segmentColors;
double? _defHeight; late double _defHeight;
SegmentProgressBar({ SegmentProgressBar({
required this.progress,
required this.segmentColors, required this.segmentColors,
}); });
@@ -38,52 +36,44 @@ class SegmentProgressBar extends CustomPainter {
paint.color = segmentColors[i].color; paint.color = segmentColors[i].color;
final segmentStart = segmentColors[i].start * size.width; final segmentStart = segmentColors[i].start * size.width;
final segmentEnd = segmentColors[i].end * size.width; final segmentEnd = segmentColors[i].end * size.width;
final progressEnd = progress * size.width;
if (progressEnd < segmentStart) { if (segmentEnd > segmentStart ||
break; (segmentEnd == segmentStart && segmentStart > 0)) {
}
final segmentWidth =
(progressEnd < segmentEnd ? progressEnd : segmentEnd) - segmentStart;
if (segmentWidth >= 0) {
if (segmentColors[i].title != null) { if (segmentColors[i].title != null) {
double fontSize = 8; double fontSize = 8;
TextPainter textPainter = TextPainter(
text: TextSpan(
text: segmentColors[i].title,
style: TextStyle(color: Colors.white, fontSize: fontSize),
),
textDirection: TextDirection.ltr,
)..layout();
_defHeight ??= textPainter.height;
double? prevStart; 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) { if (i != 0) {
prevStart = segmentColors[i - 1].start * size.width; prevStart = segmentColors[i - 1].start * size.width;
} }
double width = i == 0 ? segmentStart : segmentStart - prevStart!; double width = i == 0 ? segmentStart : segmentStart - prevStart;
while (textPainter.width > width - 2 && fontSize >= 2) { while (textPainter.width > width - 2 && fontSize >= 2) {
fontSize -= 1; fontSize -= 1;
textPainter = TextPainter( textPainter = getTextPainter();
text: TextSpan(
text: segmentColors[i].title,
style: TextStyle(
color: Colors.white,
fontSize: fontSize,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
} }
if (i == 0) { if (i == 0) {
canvas.drawRect( canvas.drawRect(
Rect.fromLTRB( Rect.fromLTRB(
0, 0,
-_defHeight!, -_defHeight,
size.width, size.width,
0, 0,
), ),
@@ -94,26 +84,26 @@ class SegmentProgressBar extends CustomPainter {
canvas.drawRect( canvas.drawRect(
Rect.fromLTWH( Rect.fromLTWH(
segmentStart, segmentStart,
-_defHeight!, -_defHeight,
segmentWidth == 0 ? 2 : segmentWidth, segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
size.height + _defHeight!, size.height + _defHeight,
), ),
paint, paint,
); );
double textX = i == 0 double textX = i == 0
? (segmentStart - textPainter.width) / 2 ? (segmentStart - textPainter.width) / 2
: (segmentStart - prevStart! - textPainter.width) / 2 + : (segmentStart - prevStart - textPainter.width) / 2 +
prevStart + prevStart +
1; 1;
double textY = -_defHeight! / 2 - textPainter.height / 2; double textY = (-_defHeight - textPainter.height) / 2;
textPainter.paint(canvas, Offset(textX, textY)); textPainter.paint(canvas, Offset(textX, textY));
} else { } else {
canvas.drawRect( canvas.drawRect(
Rect.fromLTWH( Rect.fromLTWH(
segmentStart, segmentStart,
0, 0,
segmentWidth == 0 ? 2 : segmentWidth, segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
size.height, size.height,
), ),
paint, paint,

View File

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

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:PiliPalaX/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
Widget statView({ Widget statView({
required BuildContext context, required BuildContext context,
@@ -27,7 +27,6 @@ Widget statView({
Text( Text(
Utils.numFormat(view!), Utils.numFormat(view!),
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: size == 'medium' ? 12 : 11, fontSize: size == 'medium' ? 12 : 11,
color: color, 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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../http/search.dart'; import '../../http/search.dart';
@@ -16,24 +17,22 @@ class VideoCardH extends StatelessWidget {
const VideoCardH({ const VideoCardH({
super.key, super.key,
required this.videoItem, required this.videoItem,
this.longPress,
this.longPressEnd,
this.source = 'normal', this.source = 'normal',
this.showOwner = true, this.showOwner = true,
this.showView = true, this.showView = true,
this.showDanmaku = true, this.showDanmaku = true,
this.showPubdate = false, this.showPubdate = false,
this.onTap, this.onTap,
this.onLongPress,
}); });
final dynamic videoItem; final dynamic videoItem;
final Function()? longPress;
final Function()? longPressEnd;
final String source; final String source;
final bool showOwner; final bool showOwner;
final bool showView; final bool showView;
final bool showDanmaku; final bool showDanmaku;
final bool showPubdate; final bool showPubdate;
final GestureTapCallback? onTap; final VoidCallback? onTap;
final VoidCallback? onLongPress;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -43,21 +42,33 @@ class VideoCardH extends StatelessWidget {
try { try {
type = videoItem.type; type = videoItem.type;
} catch (_) {} } catch (_) {}
List<VideoCustomAction> actions =
VideoCustomActions(videoItem, context).actions;
final String heroTag = Utils.makeHeroTag(aid);
return Stack(children: [ return Stack(children: [
Semantics( Semantics(
label: Utils.videoItemSemantics(videoItem), label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true, excludeSemantics: true,
customSemanticsActions: <CustomSemanticsAction, void Function()>{ // customSemanticsActions: <CustomSemanticsAction, void Function()>{
for (var item in actions) // for (var item in actions)
CustomSemanticsAction( // CustomSemanticsAction(
label: item.title.isEmpty ? 'label' : item.title): item.onTap!, // label: item.title.isEmpty ? 'label' : item.title): item.onTap!,
}, // },
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(12), onLongPress: () {
onLongPress: longPress, 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 { onTap: () async {
if (onTap != null) { if (onTap != null) {
onTap?.call(); onTap?.call();
@@ -67,76 +78,102 @@ class VideoCardH extends StatelessWidget {
SmartDialog.showToast('课堂视频暂不支持播放'); SmartDialog.showToast('课堂视频暂不支持播放');
return; 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 { try {
final int cid = final int cid =
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
Get.toNamed('/video?bvid=$bvid&cid=$cid', Get.toNamed(
arguments: {'videoItem': videoItem, 'heroTag': heroTag}); '/video?bvid=$bvid&cid=$cid',
arguments: {
'videoItem': videoItem,
'heroTag': Utils.makeHeroTag(aid)
},
);
} catch (err) { } catch (err) {
SmartDialog.showToast(err.toString()); SmartDialog.showToast(err.toString());
} }
}, },
child: LayoutBuilder( child: Padding(
builder: (BuildContext context, BoxConstraints boxConstraints) { padding: const EdgeInsets.symmetric(
return Row( horizontal: StyleString.safeSpace,
mainAxisAlignment: MainAxisAlignment.start, vertical: 5,
crossAxisAlignment: CrossAxisAlignment.start, ),
children: <Widget>[ child: Row(
AspectRatio( mainAxisAlignment: MainAxisAlignment.start,
aspectRatio: StyleString.aspectRatio, crossAxisAlignment: CrossAxisAlignment.start,
child: LayoutBuilder( children: <Widget>[
builder: (BuildContext context, AspectRatio(
BoxConstraints boxConstraints) { aspectRatio: StyleString.aspectRatio,
final double maxWidth = boxConstraints.maxWidth; child: LayoutBuilder(
final double maxHeight = boxConstraints.maxHeight; builder:
return Stack( (BuildContext context, BoxConstraints boxConstraints) {
children: [ final double maxWidth = boxConstraints.maxWidth;
Hero( final double maxHeight = boxConstraints.maxHeight;
tag: heroTag, return Stack(
child: NetworkImgLayer( children: [
src: videoItem.pic as String, NetworkImgLayer(
width: maxWidth, src: videoItem.pic as String,
height: maxHeight, 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) if (videoItem.duration != 0)
PBadge( PBadge(
text: Utils.timeFormat(videoItem.duration!), text: Utils.timeFormat(videoItem.duration!),
right: 6.0, right: 6.0,
bottom: 6.0, bottom: 6.0,
type: 'gray', type: 'gray',
), ),
if (type != 'video') if (type != 'video')
PBadge( PBadge(
text: type, text: type,
left: 6.0, left: 6.0,
bottom: 6.0, bottom: 6.0,
type: 'primary', type: 'primary',
), ),
// if (videoItem.rcmdReason != null && // if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '') // videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context, // pBadge(videoItem.rcmdReason.content, context,
// 6.0, 6.0, null, null), // 6.0, 6.0, null, null),
], ],
); );
}, },
),
), ),
videoContent(context) ),
], videoContent(context)
); ],
}, ),
), ),
), ),
), ),
if (source == 'normal') if (source == 'normal')
Positioned( Positioned(
bottom: 0, bottom: 0,
right: 0, right: 12,
child: VideoPopupMenu( child: VideoPopupMenu(
size: 29, size: 29,
iconSize: 17, iconSize: 17,
actions: actions, videoItem: videoItem,
), ),
), ),
]); ]);
@@ -153,13 +190,12 @@ class VideoCardH extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (videoItem.title is String) ...[ if (videoItem.title is String)
Expanded( Expanded(
child: Text( child: Text(
videoItem.title as String, videoItem.title as String,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize, fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42, height: 1.42,
letterSpacing: 0.3, letterSpacing: 0.3,
@@ -167,19 +203,19 @@ class VideoCardH extends StatelessWidget {
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), )
] else ...[ else
Expanded( Expanded(
child: RichText( child: RichText(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 2, maxLines: 2,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan( text: TextSpan(
children: [ children: [
for (final i in videoItem.title) ...[ for (final i in videoItem.title) ...[
TextSpan( TextSpan(
text: i['text'] as String, text: i['text'] as String,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context) fontSize: Theme.of(context)
.textTheme .textTheme
.bodyMedium! .bodyMedium!
@@ -195,7 +231,6 @@ class VideoCardH extends StatelessWidget {
), ),
), ),
), ),
],
// const Spacer(), // const Spacer(),
// if (videoItem.rcmdReason != null && // if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '') // videoItem.rcmdReason.content != '')
@@ -221,7 +256,6 @@ class VideoCardH extends StatelessWidget {
"$pubdate ${showOwner ? videoItem.owner.name : ''}", "$pubdate ${showOwner ? videoItem.owner.name : ''}",
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
height: 1, height: 1,
color: Theme.of(context).colorScheme.outline, 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:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPalaX/utils/app_scheme.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/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../../utils/utils.dart'; import '../../utils/utils.dart';
@@ -12,8 +13,6 @@ class VideoCardHGrpc extends StatelessWidget {
const VideoCardHGrpc({ const VideoCardHGrpc({
super.key, super.key,
required this.videoItem, required this.videoItem,
this.longPress,
this.longPressEnd,
this.source = 'normal', this.source = 'normal',
this.showOwner = true, this.showOwner = true,
this.showView = true, this.showView = true,
@@ -21,8 +20,6 @@ class VideoCardHGrpc extends StatelessWidget {
this.showPubdate = false, this.showPubdate = false,
}); });
final card.Card videoItem; final card.Card videoItem;
final Function()? longPress;
final Function()? longPressEnd;
final String source; final String source;
final bool showOwner; final bool showOwner;
final bool showView; final bool showView;
@@ -50,7 +47,11 @@ class VideoCardHGrpc extends StatelessWidget {
// }, // },
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
onLongPress: longPress, onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.smallCoverV5.base.title,
cover: videoItem.smallCoverV5.base.cover,
),
onTap: () async { onTap: () async {
if (type == 'ketang') { if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放'); SmartDialog.showToast('课堂视频暂不支持播放');
@@ -58,13 +59,6 @@ class VideoCardHGrpc extends StatelessWidget {
} }
try { try {
PiliScheme.routePush(Uri.parse(videoItem.smallCoverV5.base.uri)); 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) { } catch (err) {
SmartDialog.showToast(err.toString()); SmartDialog.showToast(err.toString());
} }
@@ -150,7 +144,6 @@ class VideoCardHGrpc extends StatelessWidget {
videoItem.smallCoverV5.base.title, videoItem.smallCoverV5.base.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize, fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42, height: 1.42,
letterSpacing: 0.3, letterSpacing: 0.3,
@@ -183,7 +176,6 @@ class VideoCardHGrpc extends StatelessWidget {
videoItem.smallCoverV5.rightDesc1, videoItem.smallCoverV5.rightDesc1,
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1, height: 1,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
@@ -195,7 +187,6 @@ class VideoCardHGrpc extends StatelessWidget {
videoItem.smallCoverV5.rightDesc2, videoItem.smallCoverV5.rightDesc2,
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1, height: 1,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,

View File

@@ -1,7 +1,8 @@
import 'package:PiliPalaX/common/widgets/stat/danmu.dart'; import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPalaX/common/widgets/stat/view.dart'; import 'package:PiliPlus/common/widgets/stat/danmu.dart';
import 'package:PiliPalaX/common/widgets/video_popup_menu.dart'; import 'package:PiliPlus/common/widgets/stat/view.dart';
import 'package:PiliPalaX/models/space_archive/item.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/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -15,90 +16,105 @@ class VideoCardHMemberVideo extends StatelessWidget {
const VideoCardHMemberVideo({ const VideoCardHMemberVideo({
super.key, super.key,
required this.videoItem, required this.videoItem,
this.longPress, this.onTap,
this.longPressEnd, this.bvid,
}); });
final Item videoItem; final Item videoItem;
final Function()? longPress; final VoidCallback? onTap;
final Function()? longPressEnd; final dynamic bvid;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final int aid = int.tryParse(videoItem.param ?? '') ?? -1; final int aid = int.tryParse(videoItem.param ?? '') ?? -1;
final String bvid = videoItem.bvid ?? ''; final String bvid = videoItem.bvid ?? '';
final String heroTag = Utils.makeHeroTag(aid); return Stack(
return Stack(children: [ children: [
InkWell( InkWell(
borderRadius: BorderRadius.circular(12), onLongPress: () => imageSaveDialog(
onLongPress: longPress, context: context,
onTap: () async { title: videoItem.title,
try { cover: videoItem.cover,
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.firstCid}', ),
arguments: {'heroTag': heroTag}); onTap: () async {
} catch (err) { if (onTap != null) {
SmartDialog.showToast(err.toString()); onTap!();
} return;
}, }
child: LayoutBuilder( try {
builder: (BuildContext context, BoxConstraints boxConstraints) { Get.toNamed(
return Row( '/video?bvid=$bvid&cid=${videoItem.firstCid}',
mainAxisAlignment: MainAxisAlignment.start, arguments: {
crossAxisAlignment: CrossAxisAlignment.start, 'heroTag': Utils.makeHeroTag(aid),
children: <Widget>[ },
AspectRatio( );
aspectRatio: StyleString.aspectRatio, } catch (err) {
child: LayoutBuilder( SmartDialog.showToast(err.toString());
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)
],
);
}, },
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(
Positioned( bottom: 0,
bottom: 0, right: 12,
right: 0, child: VideoPopupMenu(
child: VideoPopupMenu( size: 29,
size: 29, iconSize: 17,
iconSize: 17, videoItem: videoItem,
actions: VideoCustomActions(videoItem, context).actions, ),
), ),
), ],
]); );
} }
Widget videoContent(context) { Widget videoContent(context) {
@@ -115,10 +131,13 @@ class VideoCardHMemberVideo extends StatelessWidget {
videoItem.title ?? '', videoItem.title ?? '',
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w400, fontWeight: videoItem.bvid == bvid ? FontWeight.bold : null,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize, fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42, height: 1.42,
letterSpacing: 0.3, letterSpacing: 0.3,
color: videoItem.bvid == bvid
? Theme.of(context).colorScheme.primary
: null,
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -131,7 +150,6 @@ class VideoCardHMemberVideo extends StatelessWidget {
: videoItem.publishTimeText ?? '', : videoItem.publishTimeText ?? '',
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1, height: 1,
color: Theme.of(context).colorScheme.outline, 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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../models/home/rcmd/result.dart'; import '../../models/home/rcmd/result.dart';
@@ -18,14 +18,12 @@ import 'video_popup_menu.dart';
// 视频卡片 - 垂直布局 // 视频卡片 - 垂直布局
class VideoCardV extends StatelessWidget { class VideoCardV extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
final Function()? longPress; final VoidCallback? onRemove;
final Function()? longPressEnd;
const VideoCardV({ const VideoCardV({
super.key, super.key,
required this.videoItem, required this.videoItem,
this.longPress, this.onRemove,
this.longPressEnd,
}); });
bool isStringNumeric(String str) { bool isStringNumeric(String str) {
@@ -83,11 +81,14 @@ class VideoCardV extends StatelessWidget {
if (cid == -1) { if (cid == -1) {
cid = await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid); cid = await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid);
} }
Get.toNamed('/video?bvid=$bvid&cid=$cid', arguments: { Get.toNamed(
// 'videoItem': videoItem, '/video?bvid=$bvid&cid=$cid',
'pic': videoItem.pic, arguments: {
'heroTag': heroTag, // 'videoItem': videoItem,
}); 'pic': videoItem.pic,
'heroTag': heroTag,
},
);
break; break;
// 动态 // 动态
case 'picture': case 'picture':
@@ -135,7 +136,7 @@ class VideoCardV extends StatelessWidget {
default: default:
SmartDialog.showToast(videoItem.goto); SmartDialog.showToast(videoItem.goto);
Get.toNamed( Get.toNamed(
'/webviewnew', '/webview',
parameters: { parameters: {
'url': videoItem.uri, 'url': videoItem.uri,
'type': 'url', 'type': 'url',
@@ -147,23 +148,24 @@ class VideoCardV extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(videoItem.id);
List<VideoCustomAction> actions =
VideoCustomActions(videoItem, context).actions;
return Stack(children: [ return Stack(children: [
Semantics( Semantics(
label: Utils.videoItemSemantics(videoItem), label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true, excludeSemantics: true,
customSemanticsActions: <CustomSemanticsAction, void Function()>{ // customSemanticsActions: <CustomSemanticsAction, void Function()>{
for (var item in actions) // for (var item in actions)
CustomSemanticsAction(label: item.title): item.onTap!, // CustomSemanticsAction(label: item.title): item.onTap!,
}, // },
child: Card( child: Card(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
child: InkWell( child: InkWell(
onTap: () async => onPushDetail(heroTag), onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.id)),
onLongPress: longPress, onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.pic,
),
child: Column( child: Column(
children: [ children: [
AspectRatio( AspectRatio(
@@ -173,13 +175,10 @@ class VideoCardV extends StatelessWidget {
double maxHeight = boxConstraints.maxHeight; double maxHeight = boxConstraints.maxHeight;
return Stack( return Stack(
children: [ children: [
Hero( NetworkImgLayer(
tag: heroTag, src: videoItem.pic,
child: NetworkImgLayer( width: maxWidth,
src: videoItem.pic, height: maxHeight,
width: maxWidth,
height: maxHeight,
),
), ),
if (videoItem.duration > 0) if (videoItem.duration > 0)
PBadge( PBadge(
@@ -203,13 +202,15 @@ class VideoCardV extends StatelessWidget {
), ),
if (videoItem.goto == 'av') if (videoItem.goto == 'av')
Positioned( Positioned(
right: -5, right: -5,
bottom: -2, bottom: -2,
child: VideoPopupMenu( child: VideoPopupMenu(
size: 29, size: 29,
iconSize: 17, iconSize: 17,
actions: actions, videoItem: videoItem,
)), onRemove: onRemove,
),
),
]); ]);
} }
@@ -229,7 +230,6 @@ class VideoCardV extends StatelessWidget {
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w400,
height: 1.38, height: 1.38,
)), )),
), ),
@@ -247,7 +247,8 @@ class VideoCardV extends StatelessWidget {
size: 'small', size: 'small',
type: 'line', type: 'line',
fs: 9, fs: 9,
) ),
const SizedBox(width: 2),
], ],
if (videoItem.rcmdReason != null) ...[ if (videoItem.rcmdReason != null) ...[
PBadge( PBadge(
@@ -255,7 +256,8 @@ class VideoCardV extends StatelessWidget {
stack: 'normal', stack: 'normal',
size: 'small', size: 'small',
type: 'color', type: 'color',
) ),
const SizedBox(width: 2),
], ],
if (videoItem.goto == 'picture') ...[ if (videoItem.goto == 'picture') ...[
const PBadge( const PBadge(
@@ -264,7 +266,8 @@ class VideoCardV extends StatelessWidget {
size: 'small', size: 'small',
type: 'line', type: 'line',
fs: 9, fs: 9,
) ),
const SizedBox(width: 2),
], ],
if (videoItem.isFollowed == 1) ...[ if (videoItem.isFollowed == 1) ...[
const PBadge( const PBadge(
@@ -272,7 +275,8 @@ class VideoCardV extends StatelessWidget {
stack: 'normal', stack: 'normal',
size: 'small', size: 'small',
type: 'color', type: 'color',
) ),
const SizedBox(width: 2),
], ],
Expanded( Expanded(
flex: 1, flex: 1,
@@ -307,7 +311,7 @@ class VideoCardV extends StatelessWidget {
view: videoItem.stat.view, view: videoItem.stat.view,
goto: videoItem.goto, goto: videoItem.goto,
), ),
const SizedBox(width: 6), const SizedBox(width: 4),
if (videoItem.goto != 'picture') if (videoItem.goto != 'picture')
statDanMu( statDanMu(
context: context, context: context,
@@ -320,6 +324,7 @@ class VideoCardV extends StatelessWidget {
flex: 0, flex: 0,
child: RichText( child: RichText(
maxLines: 1, maxLines: 1,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan( text: TextSpan(
style: TextStyle( style: TextStyle(
fontSize: fontSize:
@@ -342,6 +347,7 @@ class VideoCardV extends StatelessWidget {
flex: 0, flex: 0,
child: RichText( child: RichText(
maxLines: 1, maxLines: 1,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan( text: TextSpan(
style: TextStyle( style: TextStyle(
fontSize: 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/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -10,14 +11,10 @@ import 'network_img_layer.dart';
// 视频卡片 - 垂直布局 // 视频卡片 - 垂直布局
class VideoCardVMemberHome extends StatelessWidget { class VideoCardVMemberHome extends StatelessWidget {
final Item videoItem; final Item videoItem;
final Function()? longPress;
final Function()? longPressEnd;
const VideoCardVMemberHome({ const VideoCardVMemberHome({
super.key, super.key,
required this.videoItem, required this.videoItem,
this.longPress,
this.longPressEnd,
}); });
void onPushDetail(heroTag) async { void onPushDetail(heroTag) async {
@@ -67,11 +64,14 @@ class VideoCardVMemberHome extends StatelessWidget {
// break; // break;
case 'av': case 'av':
String bvid = videoItem.bvid ?? ''; String bvid = videoItem.bvid ?? '';
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.firstCid}', arguments: { Get.toNamed(
// 'videoItem': videoItem, '/video?bvid=$bvid&cid=${videoItem.firstCid}',
'pic': videoItem.cover, arguments: {
'heroTag': heroTag, // 'videoItem': videoItem,
}); 'pic': videoItem.cover,
'heroTag': heroTag,
},
);
break; break;
// 动态 // 动态
// case 'picture': // case 'picture':
@@ -119,7 +119,7 @@ class VideoCardVMemberHome extends StatelessWidget {
default: default:
SmartDialog.showToast(goto); SmartDialog.showToast(goto);
Get.toNamed( Get.toNamed(
'/webviewnew', '/webview',
parameters: { parameters: {
'url': videoItem.uri ?? '', 'url': videoItem.uri ?? '',
'type': 'url', 'type': 'url',
@@ -131,7 +131,6 @@ class VideoCardVMemberHome extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(videoItem.bvid);
// List<VideoCustomAction> actions = // List<VideoCustomAction> actions =
// VideoCustomActions(videoItem, context).actions; // VideoCustomActions(videoItem, context).actions;
return Stack(children: [ return Stack(children: [
@@ -146,8 +145,12 @@ class VideoCardVMemberHome extends StatelessWidget {
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
child: InkWell( child: InkWell(
onTap: () async => onPushDetail(heroTag), onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.bvid)),
onLongPress: longPress, onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.cover,
),
child: Column( child: Column(
children: [ children: [
AspectRatio( AspectRatio(
@@ -157,13 +160,10 @@ class VideoCardVMemberHome extends StatelessWidget {
double maxHeight = boxConstraints.maxHeight; double maxHeight = boxConstraints.maxHeight;
return Stack( return Stack(
children: [ children: [
Hero( NetworkImgLayer(
tag: heroTag, src: videoItem.cover,
child: NetworkImgLayer( width: maxWidth,
src: videoItem.cover, height: maxHeight,
width: maxWidth,
height: maxHeight,
),
), ),
if ((videoItem.duration ?? -1) > 0) if ((videoItem.duration ?? -1) > 0)
PBadge( PBadge(
@@ -214,70 +214,69 @@ Widget videoContent(BuildContext context, Item videoItem) {
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w400,
height: 1.38, height: 1.38,
)), )),
), ),
], ],
), ),
const Spacer(), // const Spacer(),
// const SizedBox(height: 2), // const SizedBox(height: 2),
// VideoStat( // VideoStat(
// videoItem: videoItem, // videoItem: videoItem,
// ), // ),
Row( // Row(
children: [ // children: [
// if (videoItem.goto == 'bangumi') ...[ // if (videoItem.goto == 'bangumi') ...[
// PBadge( // PBadge(
// text: videoItem.bangumiBadge, // text: videoItem.bangumiBadge,
// stack: 'normal', // stack: 'normal',
// size: 'small', // size: 'small',
// type: 'line', // type: 'line',
// fs: 9, // fs: 9,
// ) // )
// ], // ],
// if (videoItem.rcmdReason != null) ...[ // if (videoItem.rcmdReason != null) ...[
// PBadge( // PBadge(
// text: videoItem.rcmdReason, // text: videoItem.rcmdReason,
// stack: 'normal', // stack: 'normal',
// size: 'small', // size: 'small',
// type: 'color', // type: 'color',
// ) // )
// ], // ],
if (videoItem.goto == 'picture') ...[ // if (videoItem.goto == 'picture') ...[
const PBadge( // const PBadge(
text: '动态', // text: '动态',
stack: 'normal', // stack: 'normal',
size: 'small', // size: 'small',
type: 'line', // type: 'line',
fs: 9, // fs: 9,
) // )
], // ],
// if (videoItem.isFollowed == 1) ...[ // if (videoItem.isFollowed == 1) ...[
// const PBadge( // const PBadge(
// text: '已关注', // text: '已关注',
// stack: 'normal', // stack: 'normal',
// size: 'small', // size: 'small',
// type: 'color', // type: 'color',
// ) // )
// ], // ],
Expanded( // Expanded(
flex: 1, // flex: 1,
child: Text( // child: Text(
videoItem.author ?? '', // videoItem.author ?? '',
// semanticsLabel: "Up主${videoItem.owner.name}", // // semanticsLabel: "Up主${videoItem.owner.name}",
maxLines: 1, // maxLines: 1,
overflow: TextOverflow.clip, // overflow: TextOverflow.clip,
style: TextStyle( // style: TextStyle(
height: 1.5, // height: 1.5,
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, // fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline, // color: Theme.of(context).colorScheme.outline,
), // ),
), // ),
), // ),
if (videoItem.goto == 'av') const SizedBox(width: 10) // 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/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -9,7 +10,7 @@ import '../../http/video.dart';
import '../../models/home/rcmd/result.dart'; import '../../models/home/rcmd/result.dart';
import '../../pages/mine/controller.dart'; import '../../pages/mine/controller.dart';
import '../../utils/storage.dart'; import '../../utils/storage.dart';
import 'package:PiliPalaX/models/space_archive/item.dart'; import 'package:PiliPlus/models/space_archive/item.dart';
class VideoCustomAction { class VideoCustomAction {
String title; String title;
@@ -23,7 +24,9 @@ class VideoCustomActions {
dynamic videoItem; dynamic videoItem;
BuildContext context; BuildContext context;
late List<VideoCustomAction> actions; late List<VideoCustomAction> actions;
VideoCustomActions(this.videoItem, this.context) { VoidCallback? onRemove;
VideoCustomActions(this.videoItem, this.context, [this.onRemove]) {
actions = [ actions = [
if ((videoItem.bvid as String?)?.isNotEmpty == true) if ((videoItem.bvid as String?)?.isNotEmpty == true)
VideoCustomAction( VideoCustomAction(
@@ -82,12 +85,10 @@ class VideoCustomActions {
return; return;
} }
Widget actionButton(DislikeReason? r, FeedbackReason? f) { Widget actionButton(DislikeReason? r, FeedbackReason? f) {
return ElevatedButton( return SearchText(
style: ElevatedButton.styleFrom( text: r?.name ?? f?.name ?? '未知',
padding: const EdgeInsets.symmetric( onTap: (_) async {
horizontal: 12.0, vertical: 0.0), Get.back();
),
onPressed: () async {
SmartDialog.showLoading(msg: '正在提交'); SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.feedDislike( var res = await VideoHttp.feedDislike(
reasonId: r?.id, reasonId: r?.id,
@@ -97,10 +98,12 @@ class VideoCustomActions {
); );
SmartDialog.dismiss(); SmartDialog.dismiss();
SmartDialog.showToast( SmartDialog.showToast(
res['status'] ? (r?.toast ?? f?.toast) : res['msg']); res['status'] ? (r?.toast ?? f?.toast) : res['msg'],
Get.back(); );
if (res['status']) {
onRemove?.call();
}
}, },
child: Text(r?.name ?? f?.name ?? '未知'),
); );
} }
@@ -108,53 +111,57 @@ class VideoCustomActions {
context: context, context: context,
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('请选择'),
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (tp.dislikeReasons != null) if (tp.dislikeReasons != null) ...[
const Padding( Text('我不想看'),
padding: EdgeInsets.symmetric(vertical: 8.0), const SizedBox(height: 5),
child: Text('我不想看'),
),
if (tp.dislikeReasons != null)
Wrap( Wrap(
spacing: 5.0, spacing: 8.0,
runSpacing: 2.0, runSpacing: 8.0,
children: tp.dislikeReasons!.map((item) { children: tp.dislikeReasons!.map((item) {
return actionButton(item, null); return actionButton(item, null);
}).toList(), }).toList(),
), ),
if (tp.feedbacks != null) ],
const Padding( if (tp.feedbacks != null) ...[
padding: EdgeInsets.symmetric(vertical: 8.0), const SizedBox(height: 5),
child: Text('反馈'), Text('反馈'),
), const SizedBox(height: 5),
if (tp.feedbacks != null)
Wrap( Wrap(
spacing: 5.0, spacing: 8.0,
runSpacing: 2.0, runSpacing: 8.0,
children: tp.feedbacks!.map((item) { children: tp.feedbacks!.map((item) {
return actionButton(null, item); return actionButton(null, item);
}).toList(), }).toList(),
), ),
//分割线 ],
const Divider(), const Divider(),
ElevatedButton( Center(
onPressed: () async { child: FilledButton.tonal(
SmartDialog.showLoading(msg: '正在提交'); onPressed: () async {
var res = await VideoHttp.feedDislikeCancel( SmartDialog.showLoading(msg: '正在提交');
// reasonId: r?.id, var res = await VideoHttp.feedDislikeCancel(
// feedbackId: f?.id, // reasonId: r?.id,
id: v.param!, // feedbackId: f?.id,
goto: v.goto!, id: v.param!,
); goto: v.goto!,
SmartDialog.dismiss(); );
SmartDialog.showToast( SmartDialog.dismiss();
res['status'] ? "成功" : res['msg']); SmartDialog.showToast(
Get.back(); res['status'] ? "成功" : res['msg']);
}, Get.back();
child: const Text("撤销"), },
style: FilledButton.styleFrom(
visualDensity: VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: const Text("撤销"),
),
), ),
], ],
), ),
@@ -167,7 +174,6 @@ class VideoCustomActions {
context: context, context: context,
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('点踩该视频?'),
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
@@ -178,20 +184,28 @@ class VideoCustomActions {
spacing: 5.0, spacing: 5.0,
runSpacing: 2.0, runSpacing: 2.0,
children: [ children: [
ElevatedButton( FilledButton.tonal(
onPressed: () async { onPressed: () async {
Get.back();
SmartDialog.showLoading(msg: '正在提交'); SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.dislikeVideo( var res = await VideoHttp.dislikeVideo(
bvid: videoItem.bvid as String, type: true); bvid: videoItem.bvid as String, type: true);
SmartDialog.dismiss(); SmartDialog.dismiss();
SmartDialog.showToast( SmartDialog.showToast(
res['status'] ? "点踩成功" : res['msg']); res['status'] ? "点踩成功" : res['msg'],
Get.back(); );
}, },
style: FilledButton.styleFrom(
visualDensity: VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: const Text("点踩"), child: const Text("点踩"),
), ),
ElevatedButton( FilledButton.tonal(
onPressed: () async { onPressed: () async {
Get.back();
SmartDialog.showLoading(msg: '正在提交'); SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.dislikeVideo( var res = await VideoHttp.dislikeVideo(
bvid: videoItem.bvid as String, bvid: videoItem.bvid as String,
@@ -199,8 +213,13 @@ class VideoCustomActions {
SmartDialog.dismiss(); SmartDialog.dismiss();
SmartDialog.showToast( SmartDialog.showToast(
res['status'] ? "取消踩" : res['msg']); res['status'] ? "取消踩" : res['msg']);
Get.back();
}, },
style: FilledButton.styleFrom(
visualDensity: VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: const Text("撤销"), child: const Text("撤销"),
), ),
], ],
@@ -254,10 +273,10 @@ class VideoCustomActions {
); );
}), }),
VideoCustomAction( VideoCustomAction(
"${MineController.anonymity ? '退出' : '进入'}无痕模式", "${MineController.anonymity.value ? '退出' : '进入'}无痕模式",
'anonymity', 'anonymity',
Icon( Icon(
MineController.anonymity MineController.anonymity.value
? MdiIcons.incognitoOff ? MdiIcons.incognitoOff
: MdiIcons.incognito, : MdiIcons.incognito,
size: 16, size: 16,
@@ -270,14 +289,16 @@ class VideoCustomActions {
class VideoPopupMenu extends StatelessWidget { class VideoPopupMenu extends StatelessWidget {
final double? size; final double? size;
final double? iconSize; final double? iconSize;
final List<VideoCustomAction> actions;
final double menuItemHeight = 45; final double menuItemHeight = 45;
final dynamic videoItem;
final VoidCallback? onRemove;
const VideoPopupMenu({ const VideoPopupMenu({
super.key, super.key,
required this.size, required this.size,
required this.iconSize, required this.iconSize,
required this.actions, required this.videoItem,
this.onRemove,
}); });
@override @override
@@ -295,7 +316,8 @@ class VideoPopupMenu extends StatelessWidget {
), ),
position: PopupMenuPosition.under, position: PopupMenuPosition.under,
onSelected: (String type) {}, onSelected: (String type) {},
itemBuilder: (BuildContext context) => actions.map((e) { itemBuilder: (BuildContext context) =>
VideoCustomActions(videoItem, context, onRemove).actions.map((e) {
return PopupMenuItem<String>( return PopupMenuItem<String>(
value: e.value, value: e.value,
height: menuItemHeight, height: menuItemHeight,

View File

@@ -1,8 +1,8 @@
import 'package:PiliPalaX/grpc/app/dynamic/v1/dynamic.pbgrpc.dart' as v1; import 'package:PiliPlus/grpc/app/dynamic/v1/dynamic.pbgrpc.dart' as v1;
import 'package:PiliPalaX/grpc/app/dynamic/v2/dynamic.pbgrpc.dart' as v2; import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pbgrpc.dart' as v2;
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pbgrpc.dart'; import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pbgrpc.dart';
import 'package:PiliPalaX/grpc/app/playeronline/v1/playeronline.pbgrpc.dart'; import 'package:PiliPlus/grpc/app/playeronline/v1/playeronline.pbgrpc.dart';
import 'package:PiliPalaX/grpc/app/show/popular/v1/popular.pbgrpc.dart'; import 'package:PiliPlus/grpc/app/show/popular/v1/popular.pbgrpc.dart';
import 'package:grpc/grpc.dart'; import 'package:grpc/grpc.dart';
class GrpcClient { class GrpcClient {

View File

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

View File

@@ -19,8 +19,8 @@ class Api {
static const String videoUrl = '/x/player/wbi/playurl'; static const String videoUrl = '/x/player/wbi/playurl';
// 番剧视频流 // 番剧视频流
// https://api.bilibili.com/pgc/player/web/playurl?cid=104236640&bvid=BV13t411n7ex // https://api.bilibili.com/pgc/player/web/v2/playurl?cid=104236640&bvid=BV13t411n7ex
static const String bangumiVideoUrl = '/pgc/player/web/playurl'; static const String bangumiVideoUrl = '/pgc/player/web/v2/playurl';
// 字幕 // 字幕
// aid, cid // aid, cid
@@ -247,6 +247,8 @@ class Api {
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/report.md // 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 heartBeat = '/x/click-interface/web/heartbeat';
static const String mediaListHistory = '/x/v1/medialist/history';
// 查询视频分P列表 (avid/bvid转cid) // 查询视频分P列表 (avid/bvid转cid)
static const String ab2c = '/x/player/pagelist'; static const String ab2c = '/x/player/pagelist';
@@ -400,7 +402,7 @@ class Api {
// github 获取最新版 // github 获取最新版
static const String latestApp = 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 // https://api.bilibili.com/x/player/online/total?aid=913663681&cid=1203559746&bvid=BV1MM4y1s7NZ&ts=56427838
@@ -442,6 +444,11 @@ class Api {
// 获取指定分组下的up // 获取指定分组下的up
static const String followUpGroup = '/x/relation/tag'; 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'; static const String msgFeedUnread = '/x/msgfeed/unread';
//https://api.bilibili.com/x/msgfeed/reply?platform=web&build=0&mobi_app=web //https://api.bilibili.com/x/msgfeed/reply?platform=web&build=0&mobi_app=web
@@ -587,6 +594,9 @@ class Api {
static const String safeCenterSmsVerify = static const String safeCenterSmsVerify =
'${HttpString.passBaseUrl}/x/safecenter/login/tel/verify'; '${HttpString.passBaseUrl}/x/safecenter/login/tel/verify';
static const String oauth2AccessToken =
'${HttpString.passBaseUrl}/x/passport-login/oauth2/access_token';
/// 密码加密密钥 /// 密码加密密钥
/// disable_rcmd /// disable_rcmd
/// local_id /// local_id
@@ -690,4 +700,7 @@ class Api {
static const String videoRelation = '/x/web-interface/archive/relation'; static const String videoRelation = '/x/web-interface/archive/relation';
static const String seasonFav = '/x/v3/fav/season/'; // + fav unfav 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 '../models/bangumi/list.dart';
import 'index.dart'; import 'index.dart';
class BangumiHttp { class BangumiHttp {
static Future<LoadingState> bangumiList({int? page}) async { 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) { if (res.data['code'] == 0) {
BangumiListDataModel data = BangumiListDataModel data =
BangumiListDataModel.fromJson(res.data['data']); BangumiListDataModel.fromJson(res.data['data']);
@@ -16,7 +17,8 @@ class BangumiHttp {
} }
static Future<LoadingState> bangumiFollow({int? mid}) async { 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) { if (res.data['code'] == 0) {
BangumiListDataModel data = BangumiListDataModel data =
BangumiListDataModel.fromJson(res.data['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 '../models/user/black.dart';
import 'index.dart'; import 'index.dart';
class BlackHttp { class BlackHttp {
static Future<LoadingState> blackList({required int pn, int? ps}) async { 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, 'pn': pn,
'ps': ps ?? 50, 'ps': ps ?? 50,
're_version': 0, 're_version': 0,

View File

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

View File

@@ -16,7 +16,7 @@ class DanmakaHttp {
}; };
var response = await Request().get( var response = await Request().get(
Api.webDanmaku, Api.webDanmaku,
data: params, queryParameters: params,
options: Options(responseType: ResponseType.bytes), options: Options(responseType: ResponseType.bytes),
); );
if (response.statusCode != 200 || response.data == null) { if (response.statusCode != 200 || response.data == null) {
@@ -29,8 +29,8 @@ class DanmakaHttp {
int type = 1, //弹幕类选择(1视频弹幕 2漫画弹幕) int type = 1, //弹幕类选择(1视频弹幕 2漫画弹幕)
required int oid, // 视频cid required int oid, // 视频cid
required String msg, //弹幕文本(长度小于 100 字符) required String msg, //弹幕文本(长度小于 100 字符)
int mode = // 弹幕类型(1滚动弹幕 4底端弹幕 5顶端弹幕 6逆向弹幕(不能使用) 7高级弹幕 8代码弹幕不能使用 9BAS弹幕pool必须为2)
1, // 弹幕类型(1滚动弹幕 4底端弹幕 5顶端弹幕 6逆向弹幕(不能使用) 7高级弹幕 8代码弹幕不能使用 9BAS弹幕pool必须为2) int mode = 1,
// String? aid,// 稿件avid // String? aid,// 稿件avid
// String? bvid,// bvid与aid必须有一个 // String? bvid,// bvid与aid必须有一个
required String bvid, required String bvid,
@@ -47,7 +47,6 @@ class DanmakaHttp {
// 构建参数对象 // 构建参数对象
// assert(aid != null || bvid != null); // assert(aid != null || bvid != null);
// assert(csrf != null || access_key != null); // assert(csrf != null || access_key != null);
assert(msg.length < 100);
// 构建参数对象 // 构建参数对象
var params = <String, dynamic>{ var params = <String, dynamic>{
'type': type, '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/result.dart';
import '../models/dynamics/up.dart'; import '../models/dynamics/up.dart';
@@ -20,7 +20,7 @@ class DynamicsHttp {
data['host_mid'] = mid; data['host_mid'] = mid;
data.remove('timezone_offset'); 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) { if (res.data['code'] == 0) {
try { try {
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']); DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
@@ -80,7 +80,7 @@ class DynamicsHttp {
static Future dynamicDetail({ static Future dynamicDetail({
String? id, String? id,
}) async { }) async {
var res = await Request().get(Api.dynamicDetail, data: { var res = await Request().get(Api.dynamicDetail, queryParameters: {
'timezone_offset': -480, 'timezone_offset': -480,
'id': id, 'id': id,
'features': 'itemOpusStyle', '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 '../models/fans/result.dart';
import 'index.dart'; import 'index.dart';
@@ -6,7 +6,7 @@ import 'index.dart';
class FanHttp { class FanHttp {
static Future<LoadingState> fans( static Future<LoadingState> fans(
{int? vmid, int? pn, int? ps, String? orderType}) async { {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, 'vmid': vmid,
'pn': pn, 'pn': pn,
'ps': ps, 'ps': ps,

View File

@@ -4,7 +4,7 @@ import 'index.dart';
class FollowHttp { class FollowHttp {
static Future followings( static Future followings(
{int? vmid, int? pn, int? ps, String? orderType}) async { {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, 'vmid': vmid,
'pn': pn, 'pn': pn,
'ps': ps, 'ps': ps,

View File

@@ -1,6 +1,6 @@
import 'dart:convert'; 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:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:html/dom.dart' as dom; import 'package:html/dom.dart' as dom;

View File

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

View File

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

View File

@@ -24,12 +24,13 @@ class AnonymityInterceptor extends Interceptor {
Api.dynamicDetail, Api.dynamicDetail,
Api.aiConclusion, Api.aiConclusion,
Api.getSeasonDetailApi, Api.getSeasonDetailApi,
Api.liveRoomDmToken,
Api.liveRoomDmPrefetch,
]; ];
@override @override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) { void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (MineController.anonymity) { if (MineController.anonymity.value) {
String uri = options.uri.toString(); String uri = options.uri.toString();
for (var i in anonymityList) { for (var i in anonymityList) {
// 如果请求的url包含无痕列表中的url则清空cookie // 如果请求的url包含无痕列表中的url则清空cookie
@@ -39,7 +40,7 @@ class AnonymityInterceptor extends Interceptor {
if (uri.lastIndexOf('/') >= index + i.length) continue; if (uri.lastIndexOf('/') >= index + i.length) continue;
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}'); //SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
options.headers[HttpHeaders.cookieHeader] = ""; options.headers[HttpHeaders.cookieHeader] = "";
if(options.data != null && options.data.csrf != null) { if (options.data != null && options.data.csrf != null) {
options.data.csrf = ""; options.data.csrf = "";
} }
break; break;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
import 'dart:io'; import 'dart:io';
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPalaX/grpc/grpc_repo.dart'; import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import '../models/video/reply/data.dart'; import '../models/video/reply/data.dart';
@@ -28,7 +29,7 @@ class ReplyHttp {
var res = !isLogin var res = !isLogin
? await Request().get( ? await Request().get(
'${HttpString.apiBaseUrl}${Api.replyList}/main', '${HttpString.apiBaseUrl}${Api.replyList}/main',
data: { queryParameters: {
'oid': oid, 'oid': oid,
'type': type, 'type': type,
'pagination_str': 'pagination_str':
@@ -39,7 +40,7 @@ class ReplyHttp {
) )
: await Request().get( : await Request().get(
'${HttpString.apiBaseUrl}${Api.replyList}', '${HttpString.apiBaseUrl}${Api.replyList}',
data: { queryParameters: {
'oid': oid, 'oid': oid,
'type': type, 'type': type,
'sort': sort, 'sort': sort,
@@ -49,7 +50,44 @@ class ReplyHttp {
options: options, options: options,
); );
if (res.data['code'] == 0) { 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 { } else {
return LoadingState.error(res.data['message']); return LoadingState.error(res.data['message']);
} }
@@ -62,7 +100,34 @@ class ReplyHttp {
}) async { }) async {
dynamic res = await GrpcRepo.mainList(type: type, oid: oid, cursor: cursor); dynamic res = await GrpcRepo.mainList(type: type, oid: oid, cursor: cursor);
if (res['status']) { 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 { } else {
return LoadingState.error( return LoadingState.error(
'${res['msg'].startsWith('gRPC Error') ? '如无法加载评论:\n关闭代理\n或设置中关闭使用gRPC加载评论\n\n' : ''}${res['msg']}'); '${res['msg'].startsWith('gRPC Error') ? '如无法加载评论:\n关闭代理\n或设置中关闭使用gRPC加载评论\n\n' : ''}${res['msg']}');
@@ -82,7 +147,7 @@ class ReplyHttp {
: null; : null;
var res = await Request().get( var res = await Request().get(
'${HttpString.apiBaseUrl}${Api.replyReplyList}', '${HttpString.apiBaseUrl}${Api.replyReplyList}',
data: { queryParameters: {
'oid': oid, 'oid': oid,
'root': root, 'root': root,
'pn': pageNum, 'pn': pageNum,
@@ -93,33 +158,21 @@ class ReplyHttp {
options: options, options: options,
); );
if (res.data['code'] == 0) { 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 { } else {
return LoadingState.error(res.data['message']); 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({ static Future<LoadingState> replyReplyListGrpc({
int type = 1, int type = 1,
required int oid, required int oid,
@@ -135,7 +188,46 @@ class ReplyHttp {
cursor: cursor, cursor: cursor,
); );
if (res['status']) { 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 { } else {
return LoadingState.error(res['msg']); return LoadingState.error(res['msg']);
} }
@@ -200,7 +292,7 @@ class ReplyHttp {
} }
static Future<LoadingState> getEmoteList({String? business}) async { 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', 'business': business ?? 'reply',
'web_location': '333.1245', 'web_location': '333.1245',
}); });

View File

@@ -1,8 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:hive/hive.dart';
import '../models/bangumi/info.dart'; import '../models/bangumi/info.dart';
import '../models/common/search_type.dart'; import '../models/common/search_type.dart';
import '../models/search/hot.dart'; import '../models/search/hot.dart';
@@ -12,7 +11,6 @@ import '../utils/storage.dart';
import 'index.dart'; import 'index.dart';
class SearchHttp { class SearchHttp {
static Box localCache = GStorage.localCache;
static Future hotSearchList() async { static Future hotSearchList() async {
var res = await Request().get(Api.hotSearchList); var res = await Request().get(Api.hotSearchList);
if (res.data is String) { if (res.data is String) {
@@ -40,7 +38,7 @@ class SearchHttp {
// 获取搜索建议 // 获取搜索建议
static Future searchSuggest({required term}) async { static Future searchSuggest({required term}) async {
var res = await Request().get(Api.searchSuggest, 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) { if (res.data is String) {
Map<String, dynamic> resultMap = json.decode(res.data); Map<String, dynamic> resultMap = json.decode(res.data);
if (resultMap['code'] == 0) { if (resultMap['code'] == 0) {
@@ -98,8 +96,8 @@ class SearchHttp {
if (pubBegin != null) 'pubtime_begin_s': pubBegin, if (pubBegin != null) 'pubtime_begin_s': pubBegin,
if (pubEnd != null) 'pubtime_end_s': pubEnd, if (pubEnd != null) 'pubtime_end_s': pubEnd,
}; };
var res = await Request().get(Api.searchByType, data: reqData); var res = await Request().get(Api.searchByType, queryParameters: reqData);
if (res.data['code'] == 0) { if (res.data['code'] is int && res.data['code'] == 0) {
dynamic data; dynamic data;
try { try {
switch (searchType) { switch (searchType) {
@@ -139,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 = {}; Map<String, dynamic> data = {};
if (aid != null) { if (aid != null) {
data['aid'] = aid; data['aid'] = aid;
} else if (bvid != null) { } else if (bvid != null) {
data['bvid'] = bvid; data['bvid'] = bvid;
} }
final dynamic res = final dynamic res = await Request()
await Request().get(Api.ab2c, data: <String, dynamic>{...data}); .get(Api.ab2c, queryParameters: <String, dynamic>{...data});
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return res.data['data'].first['cid']; return res.data['data'].first['cid'];
} else { } else {
@@ -159,7 +157,7 @@ class SearchHttp {
static Future<LoadingState> bangumiInfoNew({int? seasonId, int? epId}) async { static Future<LoadingState> bangumiInfoNew({int? seasonId, int? epId}) async {
final dynamic res = await Request().get( final dynamic res = await Request().get(
Api.bangumiInfo, Api.bangumiInfo,
data: { queryParameters: {
if (seasonId != null) 'season_id': seasonId, if (seasonId != null) 'season_id': seasonId,
if (epId != null) 'ep_id': epId, if (epId != null) 'ep_id': epId,
}, },
@@ -182,8 +180,8 @@ class SearchHttp {
} else if (epId != null) { } else if (epId != null) {
data['ep_id'] = epId; data['ep_id'] = epId;
} }
final dynamic res = final dynamic res = await Request()
await Request().get(Api.bangumiInfo, data: <String, dynamic>{...data}); .get(Api.bangumiInfo, queryParameters: <String, dynamic>{...data});
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return { return {
'status': true, '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:dio/dio.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../common/constants.dart'; import '../common/constants.dart';
@@ -15,7 +16,7 @@ import 'init.dart';
class UserHttp { class UserHttp {
static Future<dynamic> userStat({required int mid}) async { 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) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {'status': true, 'data': res.data['data']};
} else { } else {
@@ -47,9 +48,9 @@ class UserHttp {
static Future<LoadingState> userfavFolder({ static Future<LoadingState> userfavFolder({
required int pn, required int pn,
required int ps, required int ps,
required int mid, required dynamic mid,
}) async { }) async {
var res = await Request().get(Api.userFavFolder, data: { var res = await Request().get(Api.userFavFolder, queryParameters: {
'pn': pn, 'pn': pn,
'ps': ps, 'ps': ps,
'up_mid': mid, 'up_mid': mid,
@@ -113,7 +114,7 @@ class UserHttp {
static Future folderInfo({ static Future folderInfo({
dynamic mediaId, dynamic mediaId,
}) async { }) async {
var res = await Request().get(Api.folderInfo, data: { var res = await Request().get(Api.folderInfo, queryParameters: {
'media_id': mediaId, 'media_id': mediaId,
}); });
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
@@ -130,7 +131,7 @@ class UserHttp {
String keyword = '', String keyword = '',
String order = 'mtime', String order = 'mtime',
int type = 0}) async { int type = 0}) async {
var res = await Request().get(Api.userFavFolderDetail, data: { var res = await Request().get(Api.userFavFolderDetail, queryParameters: {
'media_id': mediaId, 'media_id': mediaId,
'pn': pn, 'pn': pn,
'ps': ps, 'ps': ps,
@@ -172,7 +173,7 @@ class UserHttp {
int? max, int? max,
int? viewAt, int? viewAt,
}) async { }) async {
var res = await Request().get(Api.historyList, data: { var res = await Request().get(Api.historyList, queryParameters: {
'type': 'all', 'type': 'all',
'ps': 20, 'ps': 20,
'max': max ?? 0, 'max': max ?? 0,
@@ -265,7 +266,7 @@ class UserHttp {
static Future thirdLogin() async { static Future thirdLogin() async {
var res = await Request().get( var res = await Request().get(
'https://passport.bilibili.com/login/app/third', 'https://passport.bilibili.com/login/app/third',
data: { queryParameters: {
'appkey': Constants.appKey, 'appkey': Constants.appKey,
'api': Constants.thirdApi, 'api': Constants.thirdApi,
'sign': Constants.thirdSign, 'sign': Constants.thirdSign,
@@ -319,7 +320,7 @@ class UserHttp {
static Future hasFollow(int mid) async { static Future hasFollow(int mid) async {
var res = await Request().get( var res = await Request().get(
Api.hasFollow, Api.hasFollow,
data: { queryParameters: {
'fid': mid, 'fid': mid,
}, },
); );
@@ -359,7 +360,7 @@ class UserHttp {
{required int pn, required String keyword}) async { {required int pn, required String keyword}) async {
var res = await Request().get( var res = await Request().get(
Api.searchHistory, Api.searchHistory,
data: { queryParameters: {
'pn': pn, 'pn': pn,
'keyword': keyword, 'keyword': keyword,
'business': 'all', 'business': 'all',
@@ -373,24 +374,25 @@ class UserHttp {
} }
// 我的订阅 // 我的订阅
static Future userSubFolder({ static Future<LoadingState> userSubFolder({
required int mid, required int mid,
required int pn, required int pn,
required int ps, required int ps,
}) async { }) async {
var res = await Request().get(Api.userSubFolder, data: { var res = await Request().get(
'up_mid': mid, Api.userSubFolder,
'ps': ps, queryParameters: {
'pn': pn, 'up_mid': mid,
'platform': 'web', 'ps': ps,
}); 'pn': pn,
if (res.data['code'] == 0) { 'platform': 'web',
return { },
'status': true, );
'data': SubFolderModelData.fromJson(res.data['data']) if (res.data['code'] == 0 && res.data['data'] is Map) {
}; return LoadingState.success(
SubFolderModelData.fromJson(res.data['data']).list);
} else { } 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 pn,
required int ps, required int ps,
}) async { }) async {
var res = await Request().get(Api.favSeasonList, data: { var res = await Request().get(Api.favSeasonList, queryParameters: {
'season_id': id, 'season_id': id,
'ps': ps, 'ps': ps,
'pn': pn, 'pn': pn,
@@ -419,7 +421,7 @@ class UserHttp {
required int pn, required int pn,
required int ps, required int ps,
}) async { }) async {
var res = await Request().get(Api.favResourceList, data: { var res = await Request().get(Api.favResourceList, queryParameters: {
'media_id': id, 'media_id': id,
'ps': ps, 'ps': ps,
'pn': pn, 'pn': pn,
@@ -463,11 +465,118 @@ class UserHttp {
} }
static videoTags({required String bvid}) async { 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) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {'status': true, 'data': res.data['data']};
} else { } else {
return {'status': false}; 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,11 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:PiliPalaX/grpc/app/card/v1/card.pb.dart' as card; import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
import 'package:PiliPalaX/grpc/grpc_repo.dart'; import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import '../common/constants.dart'; import '../common/constants.dart';
import '../models/common/reply_type.dart'; import '../models/common/reply_type.dart';
import '../models/home/rcmd/result.dart'; import '../models/home/rcmd/result.dart';
@@ -30,18 +29,15 @@ import 'login.dart';
/// 返回{'status': bool, 'data': List} /// 返回{'status': bool, 'data': List}
/// view层根据 status 判断渲染逻辑 /// view层根据 status 判断渲染逻辑
class VideoHttp { class VideoHttp {
static Box localCache = GStorage.localCache;
static Box setting = GStorage.setting;
static bool enableRcmdDynamic = static bool enableRcmdDynamic =
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true); GStorage.setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
static Box userInfoCache = GStorage.userInfo;
// 首页推荐视频 // 首页推荐视频
static Future<LoadingState> rcmdVideoList( static Future<LoadingState> rcmdVideoList(
{required int ps, required int freshIdx}) async { {required int ps, required int freshIdx}) async {
var res = await Request().get( var res = await Request().get(
Api.recommendListWeb, Api.recommendListWeb,
data: { queryParameters: {
'version': 1, 'version': 1,
'feed_version': 'V8', 'feed_version': 'V8',
'homepage_ver': 1, 'homepage_ver': 1,
@@ -76,7 +72,7 @@ class VideoHttp {
{bool loginStatus = true, required int freshIdx}) async { {bool loginStatus = true, required int freshIdx}) async {
Map<String, String> data = { Map<String, String> data = {
'access_key': loginStatus 'access_key': loginStatus
? (localCache ? (GStorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ?? .get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
'') '')
: '', : '',
@@ -119,7 +115,7 @@ class VideoHttp {
var res = await Request().get( var res = await Request().get(
Api.recommendListApp, Api.recommendListApp,
data: data, queryParameters: data,
options: Options(headers: { options: Options(headers: {
'Host': 'app.bilibili.com', 'Host': 'app.bilibili.com',
'buvid': LoginHttp.buvid, 'buvid': LoginHttp.buvid,
@@ -165,13 +161,16 @@ class VideoHttp {
{required int pn, required int ps}) async { {required int pn, required int ps}) async {
var res = await Request().get( var res = await Request().get(
Api.hotList, Api.hotList,
data: {'pn': pn, 'ps': ps}, queryParameters: {'pn': pn, 'ps': ps},
); );
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
List<HotVideoItemModel> list = []; List<HotVideoItemModel> list = [];
List<int> blackMidsList = GStorage.blackMidsList; List<int> blackMidsList = GStorage.blackMidsList;
for (var i in res.data['data']['list']) { 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)); list.add(HotVideoItemModel.fromJson(i));
} }
} }
@@ -198,25 +197,29 @@ class VideoHttp {
} }
// 视频流 // 视频流
static Future videoUrl( static Future videoUrl({
{int? avid, String? bvid, required int cid, int? qn}) async { int? avid,
String? bvid,
required int cid,
int? qn,
dynamic epid,
dynamic seasonId,
}) async {
Map<String, dynamic> data = { 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, 'cid': cid,
'qn': qn ?? 80, 'qn': qn ?? 80,
// 获取所有格式的视频 // 获取所有格式的视频
'fnval': 4048, 'fnval': 4048,
}; };
if (avid != null) {
data['avid'] = avid;
}
if (bvid != null) {
data['bvid'] = bvid;
}
// 免登录查看1080p // 免登录查看1080p
if ((userInfoCache.get('userInfoCache') == null || if ((GStorage.userInfo.get('userInfoCache') == null ||
MineController.anonymity) && MineController.anonymity.value) &&
setting.get(SettingBoxKey.p1080, defaultValue: true)) { GStorage.setting.get(SettingBoxKey.p1080, defaultValue: true)) {
data['try_look'] = 1; data['try_look'] = 1;
} }
@@ -228,12 +231,24 @@ class VideoHttp {
'web_location': 1550101, 'web_location': 1550101,
}); });
late final isLogin = GStorage.isLogin;
try { 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) { 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 { return {
'status': true, 'status': true,
'data': PlayUrlModel.fromJson(res.data['data']) 'data': data,
}; };
} else { } else {
return { return {
@@ -250,7 +265,8 @@ class VideoHttp {
// 视频信息 标题、简介 // 视频信息 标题、简介
static Future videoIntro({required String bvid}) async { 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); VideoDetailResponse result = VideoDetailResponse.fromJson(res.data);
if (result.code == 0) { if (result.code == 0) {
return { return {
@@ -305,7 +321,7 @@ class VideoHttp {
static Future videoRelation({required dynamic bvid}) async { static Future videoRelation({required dynamic bvid}) async {
var res = await Request().get( var res = await Request().get(
Api.videoRelation, Api.videoRelation,
data: { queryParameters: {
'aid': IdUtils.bv2av(bvid), 'aid': IdUtils.bv2av(bvid),
'bvid': bvid, 'bvid': bvid,
}, },
@@ -325,7 +341,8 @@ class VideoHttp {
// 相关视频 // 相关视频
static Future<LoadingState> relatedVideoList({required String bvid}) async { 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) { if (res.data['code'] == 0) {
List<HotVideoItemModel> list = []; List<HotVideoItemModel> list = [];
for (var i in res.data['data']) { for (var i in res.data['data']) {
@@ -344,7 +361,7 @@ class VideoHttp {
static Future bangumiLikeCoinFav({dynamic epId}) async { static Future bangumiLikeCoinFav({dynamic epId}) async {
var res = await Request().get( var res = await Request().get(
Api.bangumiLikeCoinFav, Api.bangumiLikeCoinFav,
data: {'ep_id': epId}, queryParameters: {'ep_id': epId},
); );
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {'status': true, 'data': res.data['data']};
@@ -355,7 +372,8 @@ class VideoHttp {
// 获取点赞状态 // 获取点赞状态
static Future hasLikeVideo({required String bvid}) async { 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) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {'status': true, 'data': res.data['data']};
} else { } else {
@@ -365,7 +383,8 @@ class VideoHttp {
// 获取投币状态 // 获取投币状态
static Future hasCoinVideo({required String bvid}) async { 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'); debugPrint('res: $res');
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {'status': true, 'data': res.data['data']};
@@ -397,7 +416,8 @@ class VideoHttp {
// 获取收藏状态 // 获取收藏状态
static Future hasFavVideo({required int aid}) async { 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) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {'status': true, 'data': res.data['data']};
} else { } else {
@@ -507,7 +527,7 @@ class VideoHttp {
return {'status': false, 'msg': "请退出账号后重新登录"}; return {'status': false, 'msg': "请退出账号后重新登录"};
} }
assert((reasonId != null) ^ (feedbackId != null)); assert((reasonId != null) ^ (feedbackId != null));
var res = await Request().get(Api.feedDislike, data: { var res = await Request().get(Api.feedDislike, queryParameters: {
'goto': goto, 'goto': goto,
'id': id, 'id': id,
// 'mid': mid, // 'mid': mid,
@@ -537,7 +557,7 @@ class VideoHttp {
return {'status': false, 'msg': "请退出账号后重新登录"}; return {'status': false, 'msg': "请退出账号后重新登录"};
} }
// assert ((reasonId != null) ^ (feedbackId != null)); // assert ((reasonId != null) ^ (feedbackId != null));
var res = await Request().get(Api.feedDislikeCancel, data: { var res = await Request().get(Api.feedDislikeCancel, queryParameters: {
'goto': goto, 'goto': goto,
'id': id, 'id': id,
// 'mid': mid, // 'mid': mid,
@@ -637,7 +657,7 @@ class VideoHttp {
}) async { }) async {
var res = await Request().get( var res = await Request().get(
Api.videoInFolder, Api.videoInFolder,
data: { queryParameters: {
'up_mid': mid, 'up_mid': mid,
'rid': rid, 'rid': rid,
if (type != null) 'type': type, if (type != null) 'type': type,
@@ -712,7 +732,7 @@ class VideoHttp {
// 查询是否关注up // 查询是否关注up
static Future hasFollow({required int mid}) async { 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) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {'status': true, 'data': res.data['data']};
} else { } else {
@@ -737,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: { await Request().post(Api.heartBeat, queryParameters: {
// 'aid': aid, // 'aid': aid,
'bvid': bvid, 'bvid': bvid,
'cid': cid, 'cid': cid,
// 'epid': '', if (epid != null) 'epid': epid,
// 'sid': '', if (seasonId != null) 'sid': seasonId,
// 'mid': '', if (epid != null) 'type': 4,
if (subType != null) 'sub_type': subType,
'played_time': progress, 'played_time': progress,
// 'realtime': realtime, 'csrf': await Request.getCsrf(),
// 'type': '', });
// 'sub_type': '', }
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(), 'csrf': await Request.getCsrf(),
}); });
} }
@@ -818,7 +856,7 @@ class VideoHttp {
// 查看视频同时在看人数 // 查看视频同时在看人数
static Future onlineTotal({int? aid, String? bvid, int? cid}) async { 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, 'aid': aid,
'bvid': bvid, 'bvid': bvid,
'cid': cid, 'cid': cid,
@@ -840,7 +878,7 @@ class VideoHttp {
'cid': cid, 'cid': cid,
'up_mid': upMid, '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) { if (res.data['code'] == 0 && res.data['data']['code'] == 0) {
return { return {
'status': true, 'status': true,
@@ -856,7 +894,7 @@ class VideoHttp {
assert(aid != null || bvid != null); assert(aid != null || bvid != null);
var res = await Request().get( var res = await Request().get(
Api.subtitleUrl, Api.subtitleUrl,
data: { queryParameters: {
if (aid != null) 'aid': aid, if (aid != null) 'aid': aid,
if (bvid != null) 'bvid': bvid, if (bvid != null) 'bvid': bvid,
'cid': cid, 'cid': cid,
@@ -884,6 +922,8 @@ class VideoHttp {
'status': true, 'status': true,
'data': subtitlesJson, 'data': subtitlesJson,
'view_points': data['view_points'], 'view_points': data['view_points'],
// 'last_play_time': data['last_play_time'],
'last_play_cid': data['last_play_cid'],
}; };
} else { } else {
return {'status': false, 'data': [], 'msg': res.data['message']}; return {'status': false, 'data': [], 'msg': res.data['message']};
@@ -973,7 +1013,10 @@ class VideoHttp {
List<HotVideoItemModel> list = []; List<HotVideoItemModel> list = [];
List<int> blackMidsList = GStorage.blackMidsList; List<int> blackMidsList = GStorage.blackMidsList;
for (var i in res.data['data']['list']) { 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)); list.add(HotVideoItemModel.fromJson(i));
} }
} }

View File

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

View File

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

View File

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

View File

@@ -1,43 +1,43 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
List defaultNavigationBars = [ List get defaultNavigationBars => [
{ {
'id': 0, 'id': 0,
'icon': const Icon( 'icon': const Icon(
Icons.home_outlined, Icons.home_outlined,
size: 23, size: 23,
), ),
'selectIcon': const Icon( 'selectIcon': const Icon(
Icons.home, Icons.home,
size: 23, size: 23,
), ),
'label': "首页", 'label': "首页",
'count': 0, 'count': 0,
}, },
{ {
'id': 1, 'id': 1,
'icon': const Icon( 'icon': const Icon(
Icons.motion_photos_on_outlined, Icons.motion_photos_on_outlined,
size: 21, size: 21,
), ),
'selectIcon': const Icon( 'selectIcon': const Icon(
Icons.motion_photos_on, Icons.motion_photos_on,
size: 21, size: 21,
), ),
'label': "动态", 'label': "动态",
'count': 0, 'count': 0,
}, },
{ {
'id': 2, 'id': 2,
'icon': const Icon( 'icon': const Icon(
Icons.video_collection_outlined, Icons.video_collection_outlined,
size: 21, size: 21,
), ),
'selectIcon': const Icon( 'selectIcon': const Icon(
Icons.video_collection, Icons.video_collection,
size: 21, size: 21,
), ),
'label': "媒体库", 'label': "媒体库",
'count': 0, '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:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPalaX/pages/bangumi/index.dart'; import 'package:PiliPlus/pages/bangumi/index.dart';
import 'package:PiliPalaX/pages/hot/index.dart'; import 'package:PiliPlus/pages/hot/index.dart';
import 'package:PiliPalaX/pages/live/index.dart'; import 'package:PiliPlus/pages/live/index.dart';
import 'package:PiliPalaX/pages/rcmd/index.dart'; import 'package:PiliPlus/pages/rcmd/index.dart';
enum TabType { live, rcmd, hot, rank, bangumi } enum TabType { live, rcmd, hot, rank, bangumi }
@@ -13,55 +13,55 @@ extension TabTypeDesc on TabType {
String get id => ['live', 'rcmd', 'hot', 'rank', 'bangumi'][index]; String get id => ['live', 'rcmd', 'hot', 'rank', 'bangumi'][index];
} }
List tabsConfig = [ List get tabsConfig => [
{ {
'icon': const Icon( 'icon': const Icon(
Icons.live_tv_outlined, Icons.live_tv_outlined,
size: 15, size: 15,
), ),
'label': '直播', 'label': '直播',
'type': TabType.live, 'type': TabType.live,
'ctr': Get.find<LiveController>, 'ctr': Get.find<LiveController>,
'page': const RcmdPage(tabType: TabType.live), 'page': const RcmdPage(tabType: TabType.live),
}, },
{ {
'icon': const Icon( 'icon': const Icon(
Icons.thumb_up_off_alt_outlined, Icons.thumb_up_off_alt_outlined,
size: 15, size: 15,
), ),
'label': '推荐', 'label': '推荐',
'type': TabType.rcmd, 'type': TabType.rcmd,
'ctr': Get.find<RcmdController>, 'ctr': Get.find<RcmdController>,
'page': const RcmdPage(tabType: TabType.rcmd), 'page': const RcmdPage(tabType: TabType.rcmd),
}, },
{ {
'icon': const Icon( 'icon': const Icon(
Icons.whatshot_outlined, Icons.whatshot_outlined,
size: 15, size: 15,
), ),
'label': '热门', 'label': '热门',
'type': TabType.hot, 'type': TabType.hot,
'ctr': Get.find<HotController>, 'ctr': Get.find<HotController>,
'page': const HotPage(), 'page': const HotPage(),
}, },
{ {
'icon': const Icon( 'icon': const Icon(
Icons.category_outlined, Icons.category_outlined,
size: 15, size: 15,
), ),
'label': '分区', 'label': '分区',
'type': TabType.rank, 'type': TabType.rank,
'ctr': Get.find<RankController>, 'ctr': Get.find<RankController>,
'page': const RankPage(), 'page': const RankPage(),
}, },
{ {
'icon': const Icon( 'icon': const Icon(
Icons.play_circle_outlined, Icons.play_circle_outlined,
size: 15, size: 15,
), ),
'label': '番剧', 'label': '番剧',
'type': TabType.bangumi, 'type': TabType.bangumi,
'ctr': Get.find<BangumiController>, 'ctr': Get.find<BangumiController>,
'page': const BangumiPage(), 'page': const BangumiPage(),
}, },
]; ];

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:PiliPalaX/models/msg/account.dart'; import 'package:PiliPlus/models/msg/account.dart';
class SessionDataModel { class SessionDataModel {
SessionDataModel({ SessionDataModel({
@@ -47,6 +47,7 @@ class SessionList {
this.liveStatus, this.liveStatus,
this.bizMsgUnreadCount, this.bizMsgUnreadCount,
// this.userLabel, // this.userLabel,
this.accountInfo,
}); });
int? talkerId; int? talkerId;
@@ -105,6 +106,9 @@ class SessionList {
liveStatus = json["live_status"]; liveStatus = json["live_status"];
bizMsgUnreadCount = json["biz_msg_unread_count"]; bizMsgUnreadCount = json["biz_msg_unread_count"];
// userLabel = json["user_label"]; // 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:PiliPlus/utils/em.dart';
import 'package:PiliPalaX/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
class SearchVideoModel { class SearchVideoModel {
SearchVideoModel({ SearchVideoModel({

View File

@@ -29,16 +29,16 @@ class SearchSuggestItem {
String? value; String? value;
String? term; String? term;
int? spid; int? spid;
Widget? textRich; dynamic textRich;
SearchSuggestItem.fromJson(Map<String, dynamic> json, String inputTerm) { SearchSuggestItem.fromJson(Map<String, dynamic> json, String inputTerm) {
value = json['value']; value = json['value'];
term = json['term']; 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> 格式的文本 // 创建正则表达式,匹配 <em class="suggest_high_light">...</em> 格式的文本
RegExp regex = RegExp(r'<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, text: highlightedText,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, 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 列表中
children.add(TextSpan( children.add(TextSpan(
text: remainingText, 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 'package:json_annotation/json_annotation.dart';
import 'achieve.dart'; import 'achieve.dart';

View File

@@ -13,7 +13,7 @@ import 'series.dart';
import 'setting.dart'; import 'setting.dart';
import 'tab.dart'; import 'tab.dart';
import 'tab2.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'; 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'; import 'package:json_annotation/json_annotation.dart';
part 'season.g.dart'; part 'season.g.dart';

View File

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

View File

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

View File

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

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