Compare commits

...

123 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
415c68a570 fix: typo
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-19 12:16:20 +08:00
bggRGjQaUbCoE
15b949bb9c mod: webview jump
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-19 11:42:05 +08:00
bggRGjQaUbCoE
316a9809e4 mod: delay checking dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-18 22:42:39 +08:00
bggRGjQaUbCoE
3f5aa03056 mod: insert dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-18 22:03:59 +08:00
bggRGjQaUbCoE
6bc33795a3 feat: create dyn antifraud
Closes #278

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

opt: video progress indicator

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

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

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

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

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

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

ref pilipala

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

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

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

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-05 13:36:44 +08:00
bggRGjQaUbCoE
65e7c0c4f4 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-05 12:19:45 +08:00
bggRGjQaUbCoE
70aecd1e38 mod: view point
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-04 14:38:06 +08:00
bggRGjQaUbCoE
a40c773491 fix: interceptor
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-04 13:09:52 +08:00
bggRGjQaUbCoE
b4abb58a41 mod: seg bar, dyn decorate
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-04 11:33:23 +08:00
bggRGjQaUbCoE
e368436bc6 feat: reply: sync to dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-03 11:43:42 +08:00
bggRGjQaUbCoE
6c96b3a7f5 fix: check reply url
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-03 10:49:25 +08:00
bggRGjQaUbCoE
149f0c082d fix: reply2reply mode
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-03 10:20:30 +08:00
bggRGjQaUbCoE
994199b5a2 fix: check reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 23:44:47 +08:00
bggRGjQaUbCoE
8db3d80151 fix: onVideoDetailChange
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 22:29:25 +08:00
bggRGjQaUbCoE
93af1e7c44 opt: reply check
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 22:29:10 +08:00
dom
54e90bd986 feat: comment antifraud (#193)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 21:24:07 +08:00
bggRGjQaUbCoE
ca16551917 mod: dm chart height
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 17:18:21 +08:00
bggRGjQaUbCoE
f4977d2855 mod: def hardwareDecoding
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-01 18:04:16 +08:00
bggRGjQaUbCoE
bd91fb7c6d mod: show volume when hiding sysui for ios
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-01 17:59:22 +08:00
bggRGjQaUbCoE
e1805896f4 Revert "opt: dm chart"
This reverts commit 31a639400e.
2025-01-31 20:40:18 +08:00
174 changed files with 8132 additions and 5485 deletions

View File

@@ -47,6 +47,9 @@
## feat
- [x] 互动视频
- [x] 发评反诈
- [x] 高能进度条
- [x] 滑动跳转预览视频缩略图
- [x] Live Photo
- [x] 复制/移动收藏夹/稍后再看视频

View File

@@ -4,6 +4,7 @@ import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import com.ryanheise.audioservice.AudioServiceActivity
import android.content.ComponentName
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
@@ -21,6 +22,39 @@ class MainActivity : AudioServiceActivity() {
methodChannel.setMethodCallHandler { call, result ->
if (call.method == "back") {
back()
} else if (call.method == "biliSendCommAntifraud") {
try {
val action = call.argument<Int>("action") ?: 0
val oid = call.argument<Number>("oid") ?: 0L
val type = call.argument<Int>("type") ?: 0
val rpid = call.argument<Number>("rpid") ?: 0L
val root = call.argument<Number>("root") ?: 0L
val parent = call.argument<Number>("parent") ?: 0L
val ctime = call.argument<Number>("ctime") ?: 0L
val commentText = call.argument<String>("comment_text") ?: ""
val pictures = call.argument<String?>("pictures")
val sourceId = call.argument<String>("source_id") ?: ""
val uid = call.argument<Number>("uid") ?: 0L
val cookies = call.argument<List<String>>("cookies") ?: emptyList<String>()
val intent = Intent().apply {
component = ComponentName("icu.freedomIntrovert.biliSendCommAntifraud", "icu.freedomIntrovert.biliSendCommAntifraud.ByXposedLaunchedActivity")
putExtra("action", action)
putExtra("oid", oid.toLong())
putExtra("type", type)
putExtra("rpid", rpid.toLong())
putExtra("root", root.toLong())
putExtra("parent", parent.toLong())
putExtra("ctime", ctime.toLong())
putExtra("comment_text", commentText)
if(pictures != null)
putExtra("pictures", pictures)
putExtra("source_id", sourceId)
putExtra("uid", uid.toLong())
putStringArrayListExtra("cookies", ArrayList(cookies))
}
startActivity(intent)
} catch (e: Exception) {}
} else {
result.notImplemented()
}

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -26,6 +26,11 @@ class Constants {
'%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D';
//Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
static const urlPattern =
r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]';
static const goodsUrlPrefix = "https://gaoneng.bilibili.com/tetris";
// 超分辨率滤镜
static const List<String> mpvAnime4KShaders = [
'Anime4K_Clamp_Highlights.glsl',

View File

@@ -11,7 +11,9 @@ Widget articleContent({
required BuildContext context,
required List<ArticleContentModel> list,
Function(List<String>, int)? callback,
required double maxWidth,
}) {
debugPrint('articleContent');
List<String>? imgList = list
.where((item) => item.pic != null)
.toList()
@@ -57,31 +59,28 @@ Widget articleContent({
),
);
} else if (item.pic != null) {
return LayoutBuilder(
builder: (context, constraints) => Hero(
tag: item.pic!.pics!.first.url!,
child: GestureDetector(
onTap: () {
if (callback != null) {
callback(
imgList,
imgList.indexOf(item.pic!.pics!.first.url!),
);
} else {
context.imageView(
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
imgList:
imgList.map((url) => SourceModel(url: url)).toList(),
);
}
},
child: NetworkImgLayer(
width: constraints.maxWidth,
height: constraints.maxWidth *
item.pic!.pics!.first.height! /
item.pic!.pics!.first.width!,
src: item.pic!.pics!.first.url,
),
return Hero(
tag: item.pic!.pics!.first.url!,
child: GestureDetector(
onTap: () {
if (callback != null) {
callback(
imgList,
imgList.indexOf(item.pic!.pics!.first.url!),
);
} else {
context.imageView(
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
imgList: imgList.map((url) => SourceModel(url: url)).toList(),
);
}
},
child: NetworkImgLayer(
width: maxWidth,
height: maxWidth *
item.pic!.pics!.first.height! /
item.pic!.pics!.first.width!,
src: item.pic!.pics!.first.url,
),
),
);

View File

@@ -12,6 +12,7 @@ class PBadge extends StatelessWidget {
final double? fs;
final String? semanticsLabel;
final bool bold;
final double? textScaleFactor;
const PBadge({
super.key,
@@ -26,6 +27,7 @@ class PBadge extends StatelessWidget {
this.fs = 11,
this.semanticsLabel,
this.bold = true,
this.textScaleFactor,
});
@override
@@ -71,6 +73,9 @@ class PBadge extends StatelessWidget {
),
child: Text(
text ?? "",
textScaler: textScaleFactor != null
? TextScaler.linear(textScaleFactor!)
: null,
style: TextStyle(
height: 1,
fontSize: fs ?? fontSize,

View File

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

View File

@@ -13,6 +13,7 @@ Widget htmlRender({
required double constrainedWidth,
Function(List<String>, int)? callback,
}) {
debugPrint('htmlRender');
return SelectionArea(
child: Html(
data: htmlContent,

View File

@@ -275,8 +275,7 @@ class _ListSheetContentState extends State<ListSheetContent>
@override
Widget build(BuildContext context) {
return Container(
height: Utils.getSheetHeight(context),
return ColoredBox(
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
@@ -412,6 +411,7 @@ class _ListSheetContentState extends State<ListSheetContent>
),
if (_isList)
Material(
color: Theme.of(context).colorScheme.surface,
child: TabBar(
controller: _ctr,
padding: const EdgeInsets.only(right: 60),
@@ -425,15 +425,21 @@ class _ListSheetContentState extends State<ListSheetContent>
),
Expanded(
child: _isList
? TabBarView(
controller: _ctr,
children: List.generate(
widget.season.sections.length,
(index) => _buildBody(
index, widget.season.sections[index].episodes),
? Material(
color: Theme.of(context).colorScheme.surface,
child: TabBarView(
controller: _ctr,
children: List.generate(
widget.season.sections.length,
(index) => _buildBody(
index, widget.season.sections[index].episodes),
),
),
)
: _buildBody(null, episodes),
: Material(
color: Theme.of(context).colorScheme.surface,
child: _buildBody(null, episodes),
),
),
],
),
@@ -464,30 +470,28 @@ class _ListSheetContentState extends State<ListSheetContent>
},
);
Widget _buildBody(i, episodes) => Material(
child: ScrollablePositionedList.separated(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
reverse: reverse[i ?? 0],
itemCount: episodes.length,
itemBuilder: (BuildContext context, int index) {
return buildEpisodeListItem(
episodes[index],
index,
episodes.length,
i != null
? i == (_index)
? currentIndex == index
: false
: currentIndex == index,
);
},
itemScrollController: itemScrollController[i ?? 0],
separatorBuilder: (context, index) => Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
Widget _buildBody(i, episodes) => ScrollablePositionedList.separated(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
reverse: reverse[i ?? 0],
itemCount: episodes.length,
itemBuilder: (BuildContext context, int index) {
return buildEpisodeListItem(
episodes[index],
index,
episodes.length,
i != null
? i == (_index)
? currentIndex == index
: false
: currentIndex == index,
);
},
itemScrollController: itemScrollController[i ?? 0],
separatorBuilder: (context, index) => Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
);
}

View File

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

View File

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

View File

@@ -6,13 +6,14 @@ Widget statDanMu({
String? theme,
dynamic danmu,
String? size,
Color? textColor,
}) {
Map<String, Color> colorObject = {
'white': Colors.white,
'gray': Theme.of(context).colorScheme.outline.withOpacity(0.8),
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
};
Color color = colorObject[theme]!;
Color color = textColor ?? colorObject[theme]!;
return Row(
children: [
Icon(

View File

@@ -7,13 +7,14 @@ Widget statView({
dynamic view,
String? size,
String? goto,
Color? textColor,
}) {
Map<String, Color> colorObject = {
'white': Colors.white,
'gray': Theme.of(context).colorScheme.outline.withOpacity(0.8),
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
};
Color color = colorObject[theme]!;
Color color = textColor ?? colorObject[theme]!;
return Row(
children: [
Icon(

View File

@@ -58,7 +58,7 @@ class VideoCardHGrpc extends StatelessWidget {
return;
}
try {
PiliScheme.routePush(Uri.parse(videoItem.smallCoverV5.base.uri));
PiliScheme.routePushFromUrl(videoItem.smallCoverV5.base.uri);
} catch (err) {
SmartDialog.showToast(err.toString());
}

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/common/widgets/stat/danmu.dart';
import 'package:PiliPlus/common/widgets/stat/view.dart';
import 'package:PiliPlus/common/widgets/video_popup_menu.dart';
import 'package:PiliPlus/common/widgets/video_progress_indicator.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -98,6 +99,22 @@ class VideoCardHMemberVideo extends StatelessWidget {
bottom: 6.0,
type: 'gray',
),
if (videoItem.history != null)
Builder(builder: (context) {
try {
return Positioned(
left: 0,
right: 0,
bottom: 0,
child: videoProgressIndicator(
videoItem.history!['progress'] /
videoItem.history!['duration'],
),
);
} catch (_) {
return const SizedBox.shrink();
}
}),
],
);
},

View File

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

View File

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

View File

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

View File

@@ -718,4 +718,8 @@ class Api {
/// 我的关注 - 正在直播
static const String getFollowingLive =
'${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following';
static const String pgcIndexCondition = '/pgc/season/index/condition';
static const String pgcIndexResult = '/pgc/season/index/result';
}

View File

@@ -1,9 +1,55 @@
import 'package:PiliPlus/http/loading_state.dart';
import '../models/bangumi/list.dart';
import '../models/bangumi/pgc_index/condition.dart';
import 'index.dart';
class BangumiHttp {
static Future<LoadingState> pgcIndexResult({
required int page,
required Map<String, dynamic> params,
seasonType,
type,
indexType,
}) async {
dynamic res = await Request().get(
Api.pgcIndexResult,
queryParameters: {
...params,
if (seasonType != null) 'season_type': seasonType,
if (type != null) 'type': type,
if (indexType != null) 'index_type': indexType,
'page': page,
'pagesize': 21,
},
);
if (res.data['code'] == 0) {
return LoadingState.success(res.data['data']);
} else {
return LoadingState.error(res.data['message']);
}
}
static Future<LoadingState> pgcIndexCondition({
seasonType,
type,
indexType,
}) async {
dynamic res = await Request().get(
Api.pgcIndexCondition,
queryParameters: {
if (seasonType != null) 'season_type': seasonType,
if (type != null) 'type': type,
if (indexType != null) 'index_type': indexType,
},
);
if (res.data['code'] == 0) {
return LoadingState.success(Condition.fromJson(res.data['data']));
} else {
return LoadingState.error(res.data['message']);
}
}
static Future<LoadingState> bangumiList({
int? page,
int? indexType,

View File

@@ -3,7 +3,7 @@ import 'package:PiliPlus/utils/extension.dart';
import 'package:dio/dio.dart';
import 'index.dart';
class DanmakaHttp {
class DanmakuHttp {
// 获取视频弹幕
static Future queryDanmaku({
required int cid,

View File

@@ -1,4 +1,5 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:dio/dio.dart';
import '../models/dynamics/result.dart';
import '../models/dynamics/up.dart';
@@ -9,6 +10,7 @@ class DynamicsHttp {
String? type,
String? offset,
int? mid,
required bool antiGoodsDyn,
}) async {
Map<String, dynamic> data = {
'type': type ?? 'all',
@@ -24,6 +26,11 @@ class DynamicsHttp {
if (res.data['code'] == 0) {
try {
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
if (antiGoodsDyn) {
data.items?.removeWhere((item) =>
item.modules?.moduleDynamic?.additional?.type ==
'ADDITIONAL_TYPE_GOODS');
}
return LoadingState.success(data);
} catch (err) {
return LoadingState.error(err.toString());
@@ -78,13 +85,23 @@ class DynamicsHttp {
//
static Future dynamicDetail({
String? id,
dynamic id,
dynamic rid,
dynamic type,
bool? clearCookie,
}) async {
var res = await Request().get(Api.dynamicDetail, queryParameters: {
'timezone_offset': -480,
'id': id,
'features': 'itemOpusStyle',
});
var res = await Request().get(
Api.dynamicDetail,
queryParameters: {
'timezone_offset': -480,
if (id != null) 'id': id,
if (rid != null) 'rid': rid,
if (type != null) 'type': type,
'features': 'itemOpusStyle',
},
options:
clearCookie == true ? Options(extra: {'clearCookie': true}) : null,
);
if (res.data['code'] == 0) {
try {
return {

View File

@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:math' show Random;
import 'package:PiliPlus/build_config.dart';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
@@ -14,7 +15,6 @@ import '../utils/utils.dart';
import 'api.dart';
import 'constants.dart';
import 'interceptor.dart';
import 'interceptor_anonymity.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
class Request {
@@ -37,7 +37,7 @@ class Request {
);
cookieManager = CookieManager(cookieJar);
dio.interceptors.add(cookieManager);
dio.interceptors.add(AnonymityInterceptor());
dio.interceptors.add(ApiInterceptor());
final List<Cookie> cookies = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.baseUrl));
for (Cookie item in cookies) {
@@ -175,15 +175,14 @@ class Request {
);
}
//添加拦截器
dio.interceptors.add(ApiInterceptor());
// 日志拦截器 输出请求、响应内容
dio.interceptors.add(LogInterceptor(
request: false,
requestHeader: false,
responseHeader: false,
));
if (BuildConfig.isDebug) {
dio.interceptors.add(LogInterceptor(
request: false,
requestHeader: false,
responseHeader: false,
));
}
dio.transformer = BackgroundTransformer();
dio.options.validateStatus = (int? status) {

View File

@@ -1,17 +1,67 @@
import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class ApiInterceptor extends Interceptor {
// @override
// void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// debugPrint("请求之前");
// // 在请求之前添加头部或认证信息
// options.headers['Authorization'] = 'Bearer token';
// options.headers['Content-Type'] = 'application/json';
// handler.next(options);
// }
static const List<String> anonymityList = [
Api.videoUrl,
Api.videoIntro,
Api.relatedList,
Api.replyList,
Api.replyReplyList,
Api.searchSuggest,
Api.searchByType,
Api.heartBeat,
Api.ab2c,
Api.bangumiInfo,
Api.liveRoomInfo,
Api.onlineTotal,
Api.webDanmaku,
Api.dynamicDetail,
Api.aiConclusion,
Api.getSeasonDetailApi,
Api.liveRoomDmToken,
Api.liveRoomDmPrefetch,
];
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
void onRemoveCookie() {
options.headers.remove('x-bili-mid');
options.headers.remove('x-bili-aurora-eid');
options.headers.remove('x-bili-aurora-zone');
options.headers['cookie'] = '';
options.queryParameters.remove('access_key');
options.queryParameters.remove('csrf');
options.queryParameters.remove('csrf_token');
if (options.data is Map) {
options.data.remove('access_key');
options.data.remove('csrf');
options.data.remove('csrf_token');
}
}
if (options.extra['clearCookie'] == true) {
onRemoveCookie();
} else if (MineController.anonymity.value) {
String uri = options.uri.toString();
for (var i in anonymityList) {
// 如果请求的url包含无痕列表中的url则清空cookie
// 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤
int index = uri.indexOf(i);
if (index == -1) continue;
if (uri.lastIndexOf('/') >= index + i.length) continue;
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
onRemoveCookie();
break;
}
}
handler.next(options);
}
// @override
// void onResponse(Response response, ResponseInterceptorHandler handler) {

View File

@@ -1,51 +0,0 @@
// ignore_for_file: avoid_print
import 'dart:io';
import 'package:dio/dio.dart';
// import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../pages/mine/controller.dart';
import 'api.dart';
class AnonymityInterceptor extends Interceptor {
static const List<String> anonymityList = [
Api.videoUrl,
Api.videoIntro,
Api.relatedList,
Api.replyList,
Api.replyReplyList,
Api.searchSuggest,
Api.searchByType,
Api.heartBeat,
Api.ab2c,
Api.bangumiInfo,
Api.liveRoomInfo,
Api.onlineTotal,
Api.webDanmaku,
Api.dynamicDetail,
Api.aiConclusion,
Api.getSeasonDetailApi,
Api.liveRoomDmToken,
Api.liveRoomDmPrefetch,
];
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (MineController.anonymity.value) {
String uri = options.uri.toString();
for (var i in anonymityList) {
// 如果请求的url包含无痕列表中的url则清空cookie
// 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤
int index = uri.indexOf(i);
if (index == -1) continue;
if (uri.lastIndexOf('/') >= index + i.length) continue;
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
options.headers[HttpHeaders.cookieHeader] = "";
if (options.data != null && options.data.csrf != null) {
options.data.csrf = "";
}
break;
}
}
handler.next(options);
}
}

View File

@@ -334,8 +334,13 @@ class MemberHttp {
}
static Future memberCardInfo({int? mid}) async {
var res = await Request()
.get(Api.memberCardInfo, queryParameters: {'mid': mid, 'photo': true});
var res = await Request().get(
Api.memberCardInfo,
queryParameters: {
'mid': mid,
'photo': false,
},
);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -397,7 +402,11 @@ class MemberHttp {
}
// 用户动态
static Future<LoadingState> memberDynamic({String? offset, int? mid}) async {
static Future<LoadingState> memberDynamic({
String? offset,
int? mid,
required bool antiGoodsDyn,
}) async {
String dmImgStr = Utils.base64EncodeRandomString(16, 64);
String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
Map params = await WbiSign().makSign({
@@ -416,7 +425,13 @@ class MemberHttp {
});
var res = await Request().get(Api.memberDynamic, queryParameters: params);
if (res.data['code'] == 0) {
return LoadingState.success(DynamicsDataModel.fromJson(res.data['data']));
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
if (antiGoodsDyn) {
data.items?.removeWhere((item) =>
item.modules?.moduleDynamic?.additional?.type ==
'ADDITIONAL_TYPE_GOODS');
}
return LoadingState.success(data);
} else {
Map errMap = {
-352: '风控校验失败,请检查登录状态',
@@ -593,7 +608,7 @@ class MemberHttp {
}
// 最近投币
static Future getRecentCoinVideo({required int mid}) async {
static Future<LoadingState> getRecentCoinVideo({required int mid}) async {
Map params = await WbiSign().makSign({
'mid': mid,
'gaia_source': 'main_web',
@@ -610,23 +625,16 @@ class MemberHttp {
},
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data']
.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
.toList(),
};
return LoadingState.success(res.data['data']
.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
.toList());
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
return LoadingState.error(res.data['message']);
}
}
// 最近点赞
static Future getRecentLikeVideo({required int mid}) async {
static Future<LoadingState> getRecentLikeVideo({required int mid}) async {
Map params = await WbiSign().makSign({
'mid': mid,
'gaia_source': 'main_web',
@@ -643,16 +651,11 @@ class MemberHttp {
},
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
};
return LoadingState.success(res.data['data']['list']
.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
.toList());
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
return LoadingState.error(res.data['message']);
}
}

View File

@@ -144,11 +144,13 @@ class MsgHttp {
static Future createDynamic({
dynamic mid,
dynamic dynIdStr, // repost
dynamic dynIdStr, // repost dyn
dynamic rid, // repost video
dynamic dynType,
dynamic rawText,
List? pics,
int? publishTime,
ReplyOption replyOption = ReplyOption.allow,
ReplyOption? replyOption,
int? privatePub,
}) async {
String csrf = await Request.getCsrf();
@@ -171,17 +173,21 @@ class MsgHttp {
}
]
},
if (dynIdStr == null)
if (replyOption != null || publishTime != null)
"option": {
if (publishTime != null) "timer_pub_time": publishTime,
if (replyOption == ReplyOption.close) "close_comment": 1,
if (replyOption == ReplyOption.choose) "up_choose_comment": 1,
if (replyOption == ReplyOption.close)
"close_comment": 1
else if (replyOption == ReplyOption.choose)
"up_choose_comment": 1,
},
"scene": dynIdStr != null
? 4
: pics != null
? 2
: 1,
"scene": rid != null
? 5
: dynIdStr != null
? 4
: pics != null
? 2
: 1,
if (privatePub != null)
'create_option': {
'private_pub': privatePub,
@@ -189,16 +195,27 @@ class MsgHttp {
if (pics != null) 'pics': pics,
"attach_card": null,
"upload_id":
"${mid}_${DateTime.now().millisecondsSinceEpoch ~/ 1000}_${Random().nextInt(9000) + 1000}",
"${rid != null ? 0 : mid}_${DateTime.now().millisecondsSinceEpoch ~/ 1000}_${Random().nextInt(9000) + 1000}",
"meta": {
"app_meta": {"from": "create.dynamic.web", "mobi_app": "web"}
}
},
if (dynIdStr != null) "web_repost_src": {"dyn_id_str": dynIdStr}
if (dynIdStr != null || rid != null)
"web_repost_src": {
if (dynIdStr != null) "dyn_id_str": dynIdStr,
if (rid != null)
"revs_id": {
"dyn_type": dynType,
"rid": rid,
}
}
},
);
if (res.data['code'] == 0) {
return {'status': true};
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
@@ -560,9 +577,7 @@ class MsgHttp {
return {
'status': false,
'date': [],
'msg': "message: ${res.data['message']},"
" msg: ${res.data['msg']},"
" code: ${res.data['code']}",
'msg': res.data['message'] ?? res.data['msg'],
};
}
}

View File

@@ -1,19 +1,19 @@
import 'dart:io';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/video/reply/item.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart';
import '../models/video/reply/data.dart';
import '../models/video/reply/emote.dart';
import 'api.dart';
import 'constants.dart';
import 'init.dart';
class ReplyHttp {
static Options get _options => Options(extra: {'clearCookie': true});
static Future<LoadingState> replyList({
required bool isLogin,
required int oid,
@@ -22,14 +22,11 @@ class ReplyHttp {
required int page,
int sort = 1,
required String banWordForReply,
required bool antiGoodsReply,
}) async {
Options? options = !isLogin
? Options(
headers: {HttpHeaders.cookieHeader: "buvid3= ; b_nut= ; sid= "})
: null;
var res = !isLogin
? await Request().get(
'${HttpString.apiBaseUrl}${Api.replyList}/main',
'${Api.replyList}/main',
queryParameters: {
'oid': oid,
'type': type,
@@ -37,10 +34,10 @@ class ReplyHttp {
'{"offset":"${nextOffset.replaceAll('"', '\\"')}"}',
'mode': sort + 2, //2:按时间排序3按热度排序
},
options: options,
options: isLogin.not ? _options : null,
)
: await Request().get(
'${HttpString.apiBaseUrl}${Api.replyList}',
Api.replyList,
queryParameters: {
'oid': oid,
'type': type,
@@ -48,7 +45,7 @@ class ReplyHttp {
'pn': page,
'ps': 20,
},
options: options,
options: isLogin.not ? _options : null,
);
if (res.data['code'] == 0) {
ReplyData replyData = ReplyData.fromJson(res.data['data']);
@@ -87,6 +84,37 @@ class ReplyHttp {
});
}
}
// antiGoodsReply
if (antiGoodsReply) {
// topReplies
if (replyData.topReplies?.isNotEmpty == true) {
replyData.topReplies!.removeWhere((item) {
bool hasMatch = needRemove(item);
// remove subreplies
if (hasMatch.not) {
if (item.replies?.isNotEmpty == true) {
item.replies!.removeWhere(needRemove);
}
}
return hasMatch;
});
}
// replies
if (replyData.replies?.isNotEmpty == true) {
replyData.replies!.removeWhere((item) {
bool hasMatch = needRemove(item);
// remove subreplies
if (hasMatch.not) {
if (item.replies?.isNotEmpty == true) {
item.replies!.removeWhere(needRemove);
}
}
return hasMatch;
});
}
}
return LoadingState.success(replyData);
} else {
return LoadingState.error(res.data['message']);
@@ -98,10 +126,12 @@ class ReplyHttp {
required int oid,
required CursorReq cursor,
required String banWordForReply,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.mainList(type: type, oid: oid, cursor: cursor);
if (res['status']) {
MainListReply mainListReply = res['data'];
// keyword filter
if (banWordForReply.isNotEmpty) {
// upTop
if (mainListReply.hasUpTop() &&
@@ -127,35 +157,86 @@ class ReplyHttp {
});
}
}
// antiGoodsReply
if (antiGoodsReply) {
// upTop
if (mainListReply.hasUpTop() && needRemoveGrpc(mainListReply.upTop)) {
mainListReply.clearUpTop();
}
// replies
if (mainListReply.replies.isNotEmpty) {
mainListReply.replies.removeWhere((item) {
bool hasMatch = needRemoveGrpc(item);
// remove subreplies
if (hasMatch.not) {
if (item.replies.isNotEmpty) {
item.replies.removeWhere(needRemoveGrpc);
}
}
return hasMatch;
});
}
}
return LoadingState.success(mainListReply);
} else {
return LoadingState.error(res['msg']);
}
}
// ref BiliRoamingX
static bool needRemoveGrpc(ReplyInfo reply) {
if ((reply.content.url.isNotEmpty &&
reply.content.url.values.any((url) {
return url.hasExtra() &&
(url.extra.goodsCmControl == 1 ||
url.extra.goodsItemId != 0 ||
url.extra.goodsPrefetchedCache.isNotEmpty);
})) ||
reply.content.message.contains(Constants.goodsUrlPrefix)) {
return true;
}
return false;
}
static bool needRemove(ReplyItemModel reply) {
try {
if ((reply.content?.jumpUrl?.isNotEmpty == true &&
reply.content!.jumpUrl!.values.any((url) {
return url['extra'] != null &&
(url['extra']['goods_cm_control'] == 1 ||
url['extra']['goods_item_id'] != 0 ||
url['extra']['goods_prefetched_cache'].isNotEmpty);
})) ||
reply.content?.message?.contains(Constants.goodsUrlPrefix) == true) {
return true;
}
} catch (_) {}
return false;
}
static Future<LoadingState> replyReplyList({
required bool isLogin,
required int oid,
required int root,
required int pageNum,
required int type,
int sort = 1,
required String banWordForReply,
required bool antiGoodsReply,
bool? isCheck,
}) async {
Options? options = GStorage.userInfo.get('userInfoCache') == null
? Options(
headers: {HttpHeaders.cookieHeader: "buvid3= ; b_nut= ; sid= "})
: null;
var res = await Request().get(
'${HttpString.apiBaseUrl}${Api.replyReplyList}',
Api.replyReplyList,
queryParameters: {
'oid': oid,
'root': root,
'pn': pageNum,
'type': type,
'sort': 1,
'csrf': await Request.getCsrf(),
if (isLogin) 'csrf': await Request.getCsrf(),
},
options: options,
options: isLogin.not ? _options : null,
);
if (res.data['code'] == 0) {
ReplyReplyData replyData = ReplyReplyData.fromJson(res.data['data']);
@@ -166,9 +247,18 @@ class ReplyHttp {
.hasMatch(item.content?.message ?? ''));
}
}
if (antiGoodsReply) {
if (replyData.replies?.isNotEmpty == true) {
replyData.replies!.removeWhere(needRemove);
}
}
return LoadingState.success(replyData);
} else {
return LoadingState.error(res.data['message']);
return LoadingState.error(
isCheck == true
? '${res.data['code']}${res.data['message']}'
: res.data['message'],
);
}
}
@@ -179,6 +269,7 @@ class ReplyHttp {
required int rpid,
required CursorReq cursor,
required String banWordForReply,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.detailList(
type: type,
@@ -196,6 +287,11 @@ class ReplyHttp {
.hasMatch(item.content.message));
}
}
if (antiGoodsReply) {
if (detailListReply.root.replies.isNotEmpty) {
detailListReply.root.replies.removeWhere(needRemoveGrpc);
}
}
return LoadingState.success(detailListReply);
} else {
return LoadingState.error(res['msg']);
@@ -209,6 +305,7 @@ class ReplyHttp {
required int rpid,
required CursorReq cursor,
required String banWordForReply,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.dialogList(
type: type,
@@ -226,6 +323,11 @@ class ReplyHttp {
.hasMatch(item.content.message));
}
}
if (antiGoodsReply) {
if (dialogListReply.replies.isNotEmpty) {
dialogListReply.replies.removeWhere(needRemoveGrpc);
}
}
return LoadingState.success(dialogListReply);
} else {
return LoadingState.error(res['msg']);

View File

@@ -174,7 +174,10 @@ class UserHttp {
var res = await Request().get(Api.seeYouLater);
if (res.data['code'] == 0) {
if (res.data['data']['count'] == 0) {
return LoadingState.success([]);
return LoadingState.success({
'list': [],
'count': 0,
});
}
List<HotVideoItemModel> list = [];
for (var i in res.data['data']['list']) {

View File

@@ -421,14 +421,18 @@ class VideoHttp {
}
// 投币
static Future coinVideo({required String bvid, required int multiply}) async {
static Future coinVideo({
required String bvid,
required int multiply,
int selectLike = 0,
}) async {
var res = await Request().post(
Api.coinVideo,
queryParameters: {
'aid': IdUtils.bv2av(bvid),
// 'bvid': bvid,
'multiply': multiply,
'select_like': 0,
'select_like': selectLike,
'access_key': GStorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
// 'csrf': await Request.getCsrf(),
@@ -759,6 +763,7 @@ class VideoHttp {
int? root,
int? parent,
List? pictures,
bool? syncToDynamic,
}) async {
if (message == '') {
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
@@ -766,10 +771,11 @@ class VideoHttp {
Map<String, dynamic> data = {
'type': type.index,
'oid': oid,
'root': root == null || root == 0 ? '' : root,
'parent': parent == null || parent == 0 ? '' : parent,
if (root != null && root != 0) 'root': root,
if (parent != null && parent != 0) 'parent': parent,
'message': message,
if (pictures != null) 'pictures': jsonEncode(pictures),
if (syncToDynamic == true) 'sync_to_dynamic': 1,
'csrf': await Request.getCsrf(),
};
var res = await Request().post(
@@ -975,7 +981,6 @@ class VideoHttp {
);
if (res.data['code'] == 0) {
dynamic data = res.data['data'];
List subtitlesJson = data['subtitle']['subtitles'];
/*
[
{
@@ -993,10 +998,11 @@ class VideoHttp {
*/
return {
'status': true,
'data': subtitlesJson,
'data': data['subtitle']['subtitles'],
'view_points': data['view_points'],
// 'last_play_time': data['last_play_time'],
'last_play_cid': data['last_play_cid'],
'interaction': data['interaction'],
};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:PiliPlus/build_config.dart';
import 'package:PiliPlus/utils/cache_manage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
@@ -177,6 +178,7 @@ class MyApp extends StatelessWidget {
// tones: FlexTones.soft(Brightness.dark),
);
}
// 图片缓存
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
return GetMaterialApp(
@@ -229,7 +231,7 @@ class MyApp extends StatelessWidget {
bool isDark = false,
required FlexSchemeVariant variant,
}) {
return ThemeData(
ThemeData themeData = ThemeData(
colorScheme: colorScheme,
useMaterial3: true,
appBarTheme: AppBarTheme(
@@ -282,6 +284,10 @@ class MyApp extends StatelessWidget {
),
),
);
if (isDark && GStorage.isPureBlackTheme) {
themeData = Utils.darkenTheme(themeData);
}
return themeData;
}
}

View File

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

View File

@@ -15,11 +15,9 @@ class BangumiListDataModel {
BangumiListDataModel.fromJson(Map<String, dynamic> json) {
hasNext = json['has_next'];
list = json['list'] != null
? json['list']
.map<BangumiListItemModel>((e) => BangumiListItemModel.fromJson(e))
.toList()
: [];
list = (json['list'] as List?)
?.map<BangumiListItemModel>((e) => BangumiListItemModel.fromJson(e))
.toList();
num = json['num'];
size = json['size'];
total = json['total'];

View File

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

View File

@@ -14,7 +14,7 @@ class DynamicsDataModel {
DynamicsDataModel.fromJson(Map<String, dynamic> json) {
hasMore = json['has_more'];
items = json['items']
items = (json['items'] as List?)
?.map<DynamicItemModel>((e) => DynamicItemModel.fromJson(e))
.toList();
offset = json['offset'];
@@ -35,47 +35,23 @@ class DynamicItemModel {
Map? basic;
dynamic idStr;
ItemModulesModel? modules;
ItemOrigModel? orig;
DynamicItemModel? orig;
String? type;
bool? visible;
bool? isForwarded;
DynamicItemModel.fromJson(Map<String, dynamic> json) {
basic = json['basic'];
idStr = json['id_str'];
modules = ItemModulesModel.fromJson(json['modules']);
orig = json['orig'] != null ? ItemOrigModel.fromJson(json['orig']) : null;
orig =
json['orig'] != null ? DynamicItemModel.fromJson(json['orig']) : null;
orig?.isForwarded = true;
type = json['type'];
visible = json['visible'];
}
}
class ItemOrigModel {
ItemOrigModel({
this.basic,
this.isStr,
this.modules,
this.type,
this.visible,
this.idStr,
});
Map? basic;
String? isStr;
ItemModulesModel? modules;
String? type;
bool? visible;
dynamic idStr;
ItemOrigModel.fromJson(Map<String, dynamic> json) {
basic = json['basic'];
isStr = json['is_str'];
modules = ItemModulesModel.fromJson(json['modules']);
type = json['type'];
visible = json['visible'];
idStr = json['id_str'];
}
}
// 单个动态详情
class ItemModulesModel {
ItemModulesModel({
@@ -364,7 +340,9 @@ class Good {
Good.fromJson(Map<String, dynamic> json) {
headIcon = json['head_icon'];
headText = json['head_text'];
items = json['items'].map<GoodItem>((e) => GoodItem.fromJson(e)).toList();
items = (json['items'] as List?)
?.map<GoodItem>((e) => GoodItem.fromJson(e))
.toList();
jumpUrl = json['jump_url'];
}
}
@@ -409,11 +387,9 @@ class DynamicDescModel {
String? text;
DynamicDescModel.fromJson(Map<String, dynamic> json) {
richTextNodes = json['rich_text_nodes'] != null
? json['rich_text_nodes']
.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
.toList()
: [];
richTextNodes = (json['rich_text_nodes'] as List?)
?.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
.toList();
text = json['text'];
}
}
@@ -557,12 +533,9 @@ class DynamicDrawModel {
DynamicDrawModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
// ignore: prefer_null_aware_operators
items = json['items'] != null
? json['items']
.map<DynamicDrawItemModel>((e) => DynamicDrawItemModel.fromJson(e))
.toList()
: null;
items = (json['items'] as List?)
?.map<DynamicDrawItemModel>((e) => DynamicDrawItemModel.fromJson(e))
.toList();
}
}
@@ -580,7 +553,7 @@ class DynamicOpusModel {
String? title;
DynamicOpusModel.fromJson(Map<String, dynamic> json) {
jumpUrl = json['jump_url'];
pics = json['pics']
pics = (json['pics'] as List?)
?.map<OpusPicsModel>((e) => OpusPicsModel.fromJson(e))
.toList();
summary =
@@ -599,8 +572,8 @@ class SummaryModel {
String? text;
SummaryModel.fromJson(Map<String, dynamic> json) {
richTextNodes = json['rich_text_nodes']
.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
richTextNodes = (json['rich_text_nodes'] as List?)
?.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
.toList();
text = json['text'];
}
@@ -638,11 +611,15 @@ class Emoji {
});
String? iconUrl;
String? webpUrl;
String? gifUrl;
double? size;
String? text;
int? type;
Emoji.fromJson(Map<String, dynamic> json) {
iconUrl = json['icon_url'];
webpUrl = json['webp_url'];
gifUrl = json['gif_url'];
size = json['size'].toDouble();
text = json['text'];
type = json['type'];

View File

@@ -12,9 +12,9 @@ class FollowUpModel {
liveUsers = json['live_users'] != null
? LiveUsers.fromJson(json['live_users'])
: null;
upList = json['up_list'] != null
? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList()
: [];
upList = (json['up_list'] as List?)
?.map<UpItem>((e) => UpItem.fromJson(e))
.toList();
}
}
@@ -32,8 +32,8 @@ class LiveUsers {
LiveUsers.fromJson(Map<String, dynamic> json) {
count = json['count'];
group = json['group'];
items = json['items']
.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
items = (json['items'] as List?)
?.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
.toList();
}
}

View File

@@ -9,8 +9,8 @@ class FansDataModel {
FansDataModel.fromJson(Map<String, dynamic> json) {
total = json['total'];
list = json['list']
.map<FansItemModel>((e) => FansItemModel.fromJson(e))
list = (json['list'] as List?)
?.map<FansItemModel>((e) => FansItemModel.fromJson(e))
.toList();
}
}

View File

@@ -9,8 +9,8 @@ class FollowDataModel {
FollowDataModel.fromJson(Map<String, dynamic> json) {
total = json['total'] ?? 0;
list = json['list']
.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
list = (json['list'] as List?)
?.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
.toList();
}
}

View File

@@ -17,9 +17,9 @@ class LatestDataModel {
url = json['url'];
tagName = json['tag_name'];
createdAt = json['created_at'];
assets = json['assets'] != null
? json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList()
: [];
assets = (json['assets'] as List?)
?.map<AssetItem>((e) => AssetItem.fromJson(e))
.toList();
body = json['body'];
}
}

View File

@@ -45,9 +45,12 @@ class Playurl {
Playurl.fromJson(Map<String, dynamic> json) {
cid = json['cid'];
gQnDesc =
json['g_qn_desc'].map<GQnDesc>((e) => GQnDesc.fromJson(e)).toList();
stream = json['stream'].map<Streams>((e) => Streams.fromJson(e)).toList();
gQnDesc = (json['g_qn_desc'] as List?)
?.map<GQnDesc>((e) => GQnDesc.fromJson(e))
.toList();
stream = (json['stream'] as List?)
?.map<Streams>((e) => Streams.fromJson(e))
.toList();
}
}
@@ -83,8 +86,9 @@ class Streams {
Streams.fromJson(Map<String, dynamic> json) {
protocolName = json['protocol_name'];
format =
json['format'].map<FormatItem>((e) => FormatItem.fromJson(e)).toList();
format = (json['format'] as List?)
?.map<FormatItem>((e) => FormatItem.fromJson(e))
.toList();
}
}
@@ -99,7 +103,9 @@ class FormatItem {
FormatItem.fromJson(Map<String, dynamic> json) {
formatName = json['format_name'];
codec = json['codec'].map<CodecItem>((e) => CodecItem.fromJson(e)).toList();
codec = (json['codec'] as List?)
?.map<CodecItem>((e) => CodecItem.fromJson(e))
.toList();
}
}
@@ -129,8 +135,8 @@ class CodecItem {
currentQn = json['current_qn'];
acceptQn = json['accept_qn'];
baseUrl = json['base_url'];
urlInfo = json['url_info']
.map<UrlInfoItem>((e) => UrlInfoItem.fromJson(e))
urlInfo = (json['url_info'] as List?)
?.map<UrlInfoItem>((e) => UrlInfoItem.fromJson(e))
.toList();
hdrQn = json['hdr_n'];
dolbyType = json['dolby_type'];

View File

@@ -27,8 +27,8 @@ class ArchiveListModel {
? Map.from(json['tlist']).map((k, v) =>
MapEntry<String, TListItemModel>(k, TListItemModel.fromJson(v)))
: {};
vlist = json['vlist']
.map<VListItemModel>((e) => VListItemModel.fromJson(e))
vlist = (json['vlist'] as List?)
?.map<VListItemModel>((e) => VListItemModel.fromJson(e))
.toList();
}
}

View File

@@ -9,11 +9,9 @@ class MemberSeasonsDataModel {
MemberSeasonsDataModel.fromJson(Map<String, dynamic> json) {
page = json['page'];
seasonsList = json['seasons_list'] != null
? json['seasons_list']
.map<MemberSeasonsList>((e) => MemberSeasonsList.fromJson(e))
.toList()
: [];
seasonsList = (json['seasons_list'] as List?)
?.map<MemberSeasonsList>((e) => MemberSeasonsList.fromJson(e))
.toList();
}
}
@@ -31,11 +29,9 @@ class MemberSeasonsList {
Map? page;
MemberSeasonsList.fromJson(Map<String, dynamic> json) {
archives = json['archives'] != null
? json['archives']
.map<MemberArchiveItem>((e) => MemberArchiveItem.fromJson(e))
.toList()
: [];
archives = (json['archives'] as List?)
?.map<MemberArchiveItem>((e) => MemberArchiveItem.fromJson(e))
.toList();
meta = MamberMeta.fromJson(json['meta']);
page = json['page'];
}

View File

@@ -12,7 +12,7 @@ class SessionDataModel {
int? hasMore;
SessionDataModel.fromJson(Map<String, dynamic> json) {
sessionList = json['session_list']
sessionList = (json['session_list'] as List?)
?.map<SessionList>((e) => SessionList.fromJson(e))
.toList();
hasMore = json['has_more'];
@@ -175,8 +175,8 @@ class SessionMsgDataModel {
List<dynamic>? eInfos;
SessionMsgDataModel.fromJson(Map<String, dynamic> json) {
messages = json['messages']
.map<MessageItem>((e) => MessageItem.fromJson(e))
messages = (json['messages'] as List?)
?.map<MessageItem>((e) => MessageItem.fromJson(e))
.toList();
hasMore = json['has_more'];
minSeqno = json['min_seqno'];

View File

@@ -12,8 +12,8 @@ class HotSearchModel {
List<HotSearchItem>? list;
HotSearchModel.fromJson(Map<String, dynamic> json) {
list = json['list']
.map<HotSearchItem>((e) => HotSearchItem.fromJson(e))
list = (json['list'] as List?)
?.map<HotSearchItem>((e) => HotSearchItem.fromJson(e))
.toList();
}
}

View File

@@ -12,9 +12,9 @@ class SearchVideoModel {
SearchVideoModel.fromJson(Map<String, dynamic> json) {
numResults = (json['numResults'] as num?)?.toInt();
list = json['result']
list = (json['result'] as List?)
?.where((e) => e['available'] == true)
?.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
.toList();
}
}
@@ -161,7 +161,7 @@ class SearchUserModel {
SearchUserModel.fromJson(Map<String, dynamic> json) {
numResults = (json['numResults'] as num?)?.toInt();
list = json['result']
list = (json['result'] as List?)
?.map<SearchUserItemModel>((e) => SearchUserItemModel.fromJson(e))
.toList();
}
@@ -316,12 +316,10 @@ class SearchMBangumiModel {
SearchMBangumiModel.fromJson(Map<String, dynamic> json) {
numResults = (json['numResults'] as num?)?.toInt();
list = json['result'] != null
? json['result']
.map<SearchMBangumiItemModel>(
(e) => SearchMBangumiItemModel.fromJson(e))
.toList()
: [];
list = (json['result'] as List?)
?.map<SearchMBangumiItemModel>(
(e) => SearchMBangumiItemModel.fromJson(e))
.toList();
}
}
@@ -420,12 +418,9 @@ class SearchArticleModel {
SearchArticleModel.fromJson(Map<String, dynamic> json) {
numResults = (json['numResults'] as num?)?.toInt();
list = json['result'] != null
? json['result']
.map<SearchArticleItemModel>(
(e) => SearchArticleItemModel.fromJson(e))
.toList()
: [];
list = (json['result'] as List?)
?.map<SearchArticleItemModel>((e) => SearchArticleItemModel.fromJson(e))
.toList();
}
}

View File

@@ -11,8 +11,8 @@ class SearchSuggestModel {
String? term;
SearchSuggestModel.fromJson(Map<String, dynamic> json) {
tag = json['tag']
.map<SearchSuggestItem>(
tag = (json['tag'] as List?)
?.map<SearchSuggestItem>(
(e) => SearchSuggestItem.fromJson(e, json['term']))
.toList();
}

View File

@@ -60,6 +60,7 @@ class Item {
String? publishTimeText;
List<Badge>? badges;
Map? season;
Map? history;
Item({
this.title,
@@ -97,6 +98,7 @@ class Item {
this.publishTimeText,
this.badges,
this.season,
this.history,
});
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);

View File

@@ -48,6 +48,7 @@ Item _$ItemFromJson(Map<String, dynamic> json) => Item(
?.map((e) => Badge.fromJson(e as Map<String, dynamic>))
.toList(),
season: json['season'],
history: json['history'],
);
Map<String, dynamic> _$ItemToJson(Item instance) => <String, dynamic>{

View File

@@ -8,8 +8,8 @@ class BlackListDataModel {
int? total;
BlackListDataModel.fromJson(Map<String, dynamic> json) {
list = json['list']
.map<BlackListItem>((e) => BlackListItem.fromJson(e))
list = (json['list'] as List?)
?.map<BlackListItem>((e) => BlackListItem.fromJson(e))
.toList();
total = json['total'];
}

View File

@@ -15,11 +15,9 @@ class FavDetailData {
FavDetailData.fromJson(Map<String, dynamic> json) {
info =
json['info'] == null ? null : FavFolderItemData.fromJson(json['info']);
medias = json['medias'] != null
? json['medias']
.map<FavDetailItemData>((e) => FavDetailItemData.fromJson(e))
.toList()
: [];
medias = (json['medias'] as List?)
?.map<FavDetailItemData>((e) => FavDetailItemData.fromJson(e))
.toList();
hasMore = json['has_more'];
}
}

View File

@@ -11,11 +11,10 @@ class FavFolderData {
FavFolderData.fromJson(Map<String, dynamic> json) {
count = json['count'];
list = json['list'] != null
? json['list']
.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
.toList()
: [FavFolderItemData()];
list = (json['list'] as List?)
?.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
.toList() ??
[FavFolderItemData()];
hasMore = json['has_more'];
}
}

View File

@@ -13,12 +13,12 @@ class HistoryData {
HistoryData.fromJson(Map<String, dynamic> json) {
cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
tab = json['tab'] != null
? json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList()
: [];
list = json['list'] != null
? json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList()
: [];
tab = (json['tab'] as List?)
?.map<HisTabItem>((e) => HisTabItem.fromJson(e))
.toList();
list = (json['list'] as List?)
?.map<HisListItem>((e) => HisListItem.fromJson(e))
.toList();
page = json['page'];
}
}

View File

@@ -237,7 +237,7 @@ class Emote {
class EmoteMeta {
int? size;
List<String>? suggest;
List? suggest;
String? alias;
String? gifUrl;
@@ -245,9 +245,7 @@ class EmoteMeta {
EmoteMeta.fromJson(Map<String, dynamic> json) {
size = json['size'];
suggest = json['suggest'] == null
? null
: List<String>.from(json['suggest'].map((x) => x));
suggest = json['suggest'];
alias = json['alias'];
gifUrl = json['gif_url'];
}

View File

@@ -10,11 +10,9 @@ class SubFolderModelData {
factory SubFolderModelData.fromJson(Map<String, dynamic> json) {
return SubFolderModelData(
count: json['count'],
list: json['list'] != null
? (json['list'] as List)
.map<SubFolderItemData>((i) => SubFolderItemData.fromJson(i))
.toList()
: null,
list: (json['list'] as List?)
?.map<SubFolderItemData>((i) => SubFolderItemData.fromJson(i))
.toList(),
);
}
}

View File

@@ -39,8 +39,8 @@ class ModelResult {
ModelResult.fromJson(Map<String, dynamic> json) {
resultType = json['result_type'];
summary = json['summary'];
outline = json['outline']
.map<OutlineItem>((e) => OutlineItem.fromJson(e))
outline = (json['outline'] as List?)
?.map<OutlineItem>((e) => OutlineItem.fromJson(e))
.toList();
}
}
@@ -56,8 +56,8 @@ class OutlineItem {
OutlineItem.fromJson(Map<String, dynamic> json) {
title = json['title'];
partOutline = json['part_outline']
.map<PartOutline>((e) => PartOutline.fromJson(e))
partOutline = (json['part_outline'] as List?)
?.map<PartOutline>((e) => PartOutline.fromJson(e))
.toList();
}
}

View File

@@ -84,10 +84,7 @@ class MediaVideoItemModel {
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))),
pages: (json["pages"] as List?)?.map((x) => Page.fromJson(x)).toList(),
title: json["title"],
type: json["type"],
upper: Upper.fromJson(json["upper"]),
@@ -149,7 +146,7 @@ class Page {
duration: json["duration"],
link: json["link"],
page: json["page"],
metas: List<Meta>.from(json["metas"].map((x) => Meta.fromJson(x))),
metas: (json["metas"] as List?)?.map((x) => Meta.fromJson(x)).toList(),
from: json["from"],
dimension: Dimension.fromJson(json["dimension"]),
);

View File

@@ -49,17 +49,16 @@ class PlayUrlModel {
timeLength = json['timelength'];
acceptFormat = json['accept_format'];
acceptDesc = json['accept_description'];
acceptQuality = json['accept_quality'].map<int>((e) => e as int).toList();
acceptQuality =
(json['accept_quality'] as List?)?.map<int>((e) => e as int).toList();
videoCodecid = json['video_codecid'];
seekParam = json['seek_param'];
seekType = json['seek_type'];
dash = json['dash'] != null ? Dash.fromJson(json['dash']) : null;
durl = json['durl']?.map<Durl>((e) => Durl.fromJson(e)).toList();
supportFormats = json['support_formats'] != null
? json['support_formats']
.map<FormatItem>((e) => FormatItem.fromJson(e))
.toList()
: [];
durl = (json['durl'] as List?)?.map<Durl>((e) => Durl.fromJson(e)).toList();
supportFormats = (json['support_formats'] as List?)
?.map<FormatItem>((e) => FormatItem.fromJson(e))
.toList();
lastPlayTime = json['last_play_time'];
lastPlayCid = json['last_play_cid'];
}
@@ -85,10 +84,12 @@ class Dash {
Dash.fromJson(Map<String, dynamic> json) {
duration = json['duration'];
minBufferTime = json['minBufferTime'];
video = json['video'].map<VideoItem>((e) => VideoItem.fromJson(e)).toList();
audio = json['audio'] != null
? json['audio'].map<AudioItem>((e) => AudioItem.fromJson(e)).toList()
: [];
video = (json['video'] as List?)
?.map<VideoItem>((e) => VideoItem.fromJson(e))
.toList();
audio = (json['audio'] as List?)
?.map<AudioItem>((e) => AudioItem.fromJson(e))
.toList();
dolby = json['dolby'] != null ? Dolby.fromJson(json['dolby']) : null;
flac = json['flac'] != null ? Flac.fromJson(json['flac']) : null;
}
@@ -288,9 +289,9 @@ class Dolby {
Dolby.fromJson(Map<String, dynamic> json) {
type = json['type'];
audio = json['audio'] != null
? json['audio'].map<AudioItem>((e) => AudioItem.fromJson(e)).toList()
: [];
audio = (json['audio'] as List?)
?.map<AudioItem>((e) => AudioItem.fromJson(e))
.toList();
}
}

View File

@@ -8,7 +8,6 @@ class ReplyContent {
this.pictures, // {}
this.vote,
this.richText,
this.isText,
this.topicsMeta,
});
@@ -20,27 +19,19 @@ class ReplyContent {
List? pictures;
Map? vote;
Map? richText;
bool? isText;
Map? topicsMeta;
ReplyContent.fromJson(Map<String, dynamic> json) {
message = json['message']
.replaceAll('&gt;', '>')
.replaceAll('&#34;', '"')
.replaceAll('&#39;', "'");
message = json['message'];
atNameToMid = json['at_name_to_mid'] ?? {};
members = json['members'] != null
? json['members']
.map<MemberItemModel>((e) => MemberItemModel.fromJson(e))
.toList()
: [];
members = (json['members'] as List?)
?.map<MemberItemModel>((e) => MemberItemModel.fromJson(e))
.toList();
emote = json['emote'] ?? {};
jumpUrl = json['jump_url'] ?? {};
pictures = json['pictures'] ?? [];
vote = json['vote'] ?? {};
richText = json['rich_text'] ?? {};
// 不包含@ 笔记 图片的时候,文字可折叠
isText = atNameToMid!.isEmpty && vote!.isEmpty && pictures!.isEmpty;
topicsMeta = json['topics_meta'] ?? {};
}
}

View File

@@ -27,15 +27,15 @@ class ReplyData {
json['cursor'] == null ? null : ReplyCursor.fromJson(json['cursor']);
config =
json['config'] == null ? null : ReplyConfig.fromJson(json['config']);
replies = json['replies'] != null
? List<ReplyItemModel>.from(json['replies'].map<ReplyItemModel>(
(item) => ReplyItemModel.fromJson(item, json['upper']['mid'])))
: <ReplyItemModel>[];
topReplies = json['top_replies'] != null
? List<ReplyItemModel>.from(json['top_replies'].map<ReplyItemModel>(
(item) => ReplyItemModel.fromJson(item, json['upper']['mid'],
isTopStatus: true)))
: <ReplyItemModel>[];
replies = (json['replies'] as List?)
?.map<ReplyItemModel>(
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
.toList();
topReplies = (json['top_replies'] as List?)
?.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
item, json['upper']['mid'],
isTopStatus: true))
.toList();
upper = json['upper'] == null ? null : ReplyUpper.fromJson(json['upper']);
}
}
@@ -60,15 +60,15 @@ class ReplyReplyData {
ReplyReplyData.fromJson(Map<String, dynamic> json) {
page = ReplyPage.fromJson(json['page']);
config = ReplyConfig.fromJson(json['config']);
replies = json['replies'] != null
? List<ReplyItemModel>.from(json['replies'].map<ReplyItemModel>(
(item) => ReplyItemModel.fromJson(item, json['upper']['mid'])))
: <ReplyItemModel>[];
topReplies = json['top_replies'] != null
? List<ReplyItemModel>.from(json['top_replies'].map<ReplyItemModel>(
(item) => ReplyItemModel.fromJson(item, json['upper']['mid'],
isTopStatus: true)))
: <ReplyItemModel>[];
replies = (json['replies'] as List?)
?.map<ReplyItemModel>(
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
.toList();
topReplies = (json['top_replies'] as List?)
?.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
item, json['upper']['mid'],
isTopStatus: true))
.toList();
upper = ReplyUpper.fromJson(json['upper']);
root = ReplyItemModel.fromJson(json['root'], json['upper']['mid']);
}

View File

@@ -6,7 +6,7 @@ class EmoteModelData {
EmoteModelData.fromJson(Map<String, dynamic> json) {
setting =
json['setting'] != null ? Setting.fromJson(json['setting']) : null;
json['setting'] != null ? Setting.fromJson(json['setting']) : null;
if (json['packages'] != null) {
packages = <Packages>[];
json['packages'].forEach((v) {
@@ -68,17 +68,17 @@ class Packages {
Packages(
{this.id,
this.text,
this.url,
this.mtime,
this.type,
this.attr,
this.meta,
this.emote,
this.flags,
this.label,
this.packageSubTitle,
this.refMid});
this.text,
this.url,
this.mtime,
this.type,
this.attr,
this.meta,
this.emote,
this.flags,
this.label,
this.packageSubTitle,
this.refMid});
Packages.fromJson(Map<String, dynamic> json) {
id = json['id'];
@@ -95,7 +95,7 @@ class Packages {
});
}
flags =
json['flags'] != null ? PackagesFlags.fromJson(json['flags']) : null;
json['flags'] != null ? PackagesFlags.fromJson(json['flags']) : null;
label = json['label'] != null ? Label.fromJson(json['label']) : null;
packageSubTitle = json['package_sub_title'];
refMid = json['ref_mid'];
@@ -189,16 +189,16 @@ class Emote {
Emote(
{this.id,
this.packageId,
this.text,
this.url,
this.mtime,
this.type,
this.attr,
this.meta,
this.flags,
this.activity,
this.gifUrl});
this.packageId,
this.text,
this.url,
this.mtime,
this.type,
this.attr,
this.meta,
this.flags,
this.activity,
this.gifUrl});
Emote.fromJson(Map<String, dynamic> json) {
id = json['id'];
@@ -237,7 +237,7 @@ class Emote {
class EmoteMeta {
int? size;
List<String>? suggest;
List? suggest;
String? alias;
String? gifUrl;
@@ -245,9 +245,7 @@ class EmoteMeta {
EmoteMeta.fromJson(Map<String, dynamic> json) {
size = json['size'];
suggest = json['suggest'] == null
? null
: List<String>.from(json['suggest'].map((x) => x));
suggest = json['suggest'];
alias = json['alias'];
gifUrl = json['gif_url'];
}

View File

@@ -86,10 +86,9 @@ class ReplyItemModel {
action = json['action'];
member = ReplyMember.fromJson(json['member']);
content = ReplyContent.fromJson(json['content']);
replies = json['replies'] != null
? List<ReplyItemModel>.from(json['replies']
.map((item) => ReplyItemModel.fromJson(item, upperMid)))
: <ReplyItemModel>[];
replies = (json['replies'] as List?)
?.map((item) => ReplyItemModel.fromJson(item, upperMid))
.toList();
assist = json['assist'];
upAction = UpAction.fromJson(json['up_action']);
invisible = json['invisible'];
@@ -98,9 +97,8 @@ class ReplyItemModel {
: ReplyControl.fromJson(json['reply_control']);
isUp = upperMid.toString() == json['member']['mid'];
isTop = isTopStatus;
cardLabel = json['card_label'] != null
? json['card_label'].map((e) => e['text_content']).toList()
: [];
cardLabel =
(json['card_label'] as List?)?.map((e) => e['text_content']).toList();
rcount = json['rcount'];
}
}

View File

@@ -54,7 +54,8 @@ class ReplyCursor {
mode = json['mode'];
modeText = json['mode_text'];
allCount = json['all_count'] ?? 0;
supportMode = json['support_mode'].cast<int>();
supportMode =
(json['support_mode'] as List?)?.map((e) => e as int).toList();
name = json['name'];
paginationReply = json['pagination_reply'] != null
? PaginationReply.fromJson(json['pagination_reply'])

View File

@@ -46,7 +46,7 @@ class VideoDetailData {
List<DescV2>? descV2;
int? state;
int? duration;
Map<String, int>? rights;
Map? rights;
Owner? owner;
Stat? stat;
String? argueMsg;
@@ -125,13 +125,11 @@ class VideoDetailData {
pubdate = json["pubdate"];
ctime = json["ctime"];
desc = json["desc"];
descV2 = json["desc_v2"] == null
? []
: List<DescV2>.from(json["desc_v2"]!.map((e) => DescV2.fromJson(e)));
descV2 =
(json["desc_v2"] as List?)?.map((e) => DescV2.fromJson(e)).toList();
state = json["state"];
duration = json["duration"];
rights =
Map.from(json["rights"]!).map((k, v) => MapEntry<String, int>(k, v));
rights = json["rights"];
owner = json["owner"] == null ? null : Owner.fromJson(json["owner"]);
stat = json["stat"] == null ? null : Stat.fromJson(json["stat"]);
argueMsg = json['argue_info']?['argue_msg'];
@@ -145,9 +143,7 @@ class VideoDetailData {
isChargeableSeason = json["is_chargeable_season"];
isStory = json["is_story"];
noCache = json["no_cache"];
pages = json["pages"] == null
? []
: List<Part>.from(json["pages"]!.map((e) => Part.fromJson(e)));
pages = (json["pages"] as List?)?.map((e) => Part.fromJson(e)).toList();
subtitle =
json["subtitle"] == null ? null : Subtitle.fromJson(json["subtitle"]);
ugcSeason = json["ugc_season"] != null
@@ -161,9 +157,8 @@ class VideoDetailData {
: HonorReply.fromJson(json["honor_reply"]);
likeIcon = json["like_icon"];
needJumpBv = json["need_jump_bv"];
staff = json["staff"] == null
? null
: (json["staff"] as List).map((item) => Staff.fromJson(item)).toList();
staff =
(json["staff"] as List?)?.map((item) => Staff.fromJson(item)).toList();
if (json['redirect_url'] != null) {
epId = resolveEpId(json['redirect_url']);
}
@@ -296,6 +291,7 @@ class Staff {
String? name;
String? face;
Vip? vip;
Map? official;
Staff({
this.mid,
@@ -311,6 +307,7 @@ class Staff {
name = json["name"];
face = json["face"];
vip = json["vip"] == null ? null : Vip.fromJson(json["vip"]);
official = json['official'];
}
}
@@ -341,9 +338,7 @@ class HonorReply {
String toRawJson() => json.encode(toJson());
HonorReply.fromJson(Map<String, dynamic> json) {
honor = json["honor"] == null
? []
: List<Honor>.from(json["honor"]!.map((x) => Honor.fromJson(x)));
honor = (json["honor"] as List?)?.map((x) => Honor.fromJson(x)).toList();
}
Map<String, dynamic> toJson() {
@@ -563,9 +558,7 @@ class Subtitle {
Subtitle.fromJson(Map<String, dynamic> json) {
allowSubmit = json["allow_submit"];
list = json["list"] == null
? []
: List<dynamic>.from(json["list"]!.map((x) => x));
list = json["list"];
}
Map<String, dynamic> toJson() {
@@ -634,11 +627,9 @@ class UgcSeason {
intro = json['intro'];
signState = json['sign_state'];
attribute = json['attribute'];
sections = json['sections'] != null
? json['sections']
.map<SectionItem>((e) => SectionItem.fromJson(e))
.toList()
: [];
sections = (json['sections'] as List?)
?.map<SectionItem>((e) => SectionItem.fromJson(e))
.toList();
stat = Stat.fromJson(json['stat']);
epCount = json['ep_count'];
seasonType = json['season_type'];
@@ -680,8 +671,8 @@ class SectionItem {
id = json['id'];
title = json['title'];
type = json['type'];
episodes = json['episodes']
.map<EpisodeItem>((e) => EpisodeItem.fromJson(e))
episodes = (json['episodes'] as List?)
?.map<EpisodeItem>((e) => EpisodeItem.fromJson(e))
.toList();
}
}

View File

@@ -71,7 +71,7 @@ class _AboutPageState extends State<AboutPage> {
onSubmitted: (value) {
Get.back();
if (value.isNotEmpty) {
Get.toNamed('/webview', parameters: {'url': value});
Utils.handleWebview(value, inApp: true);
}
},
),

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart';
import 'package:PiliPlus/pages/video/detail/introduction/controller.dart';
import 'package:PiliPlus/pages/video/detail/introduction/pay_coins_page.dart';
import 'package:PiliPlus/utils/extension.dart';
@@ -186,13 +187,21 @@ class BangumiIntroController extends CommonController {
}
}
void coinVideo(int coin) async {
var res = await VideoHttp.coinVideo(bvid: bvid, multiply: coin);
void coinVideo(int coin, [bool selectLike = false]) async {
var res = await VideoHttp.coinVideo(
bvid: bvid,
multiply: coin,
selectLike: selectLike ? 1 : 0,
);
if (res['status']) {
SmartDialog.showToast('投币成功');
hasCoin.value = true;
dynamic bangumiDetail = (loadingState.value as Success).response;
bangumiDetail.stat!['coins'] = bangumiDetail.stat!['coins'] + coin;
if (selectLike && hasLike.value.not) {
hasLike.value = true;
bangumiDetail.stat!['likes'] = bangumiDetail.stat!['likes'] + 1;
}
loadingState.value = LoadingState.success(bangumiDetail);
} else {
SmartDialog.showToast(res['msg']);
@@ -390,6 +399,46 @@ class BangumiIntroController extends CommonController {
Share.share(videoUrl);
},
),
ListTile(
title: const Text(
'分享至动态',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
EpisodeItem? item = bangumiItem?.episodes
?.firstWhereOrNull((item) => item.epId == epId);
showModalBottomSheet(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (context) => RepostPanel(
rid: epId,
/**
* 1番剧 // 4097
2电影 // 4098
3纪录片 // 4101
4国创 // 4100
5电视剧 // 4099
6漫画
7综艺 // 4099
*/
dynType: switch (Get.parameters['type']) {
'1' => 4097,
'2' => 4098,
'3' => 4101,
'4' => 4100,
'5' || '7' => 4099,
_ => -1,
},
pic: bangumiItem?.cover,
title:
'${bangumiItem?.title}${item != null ? '\n${item.showTitle}' : ''}',
uname: '',
),
);
},
),
],
),
);
@@ -596,21 +645,24 @@ class BangumiIntroController extends CommonController {
RxInt followStatus = (-1).obs;
Future queryIsFollowed() async {
dynamic result = await Request().get(
'https://www.bilibili.com/bangumi/play/ss$seasonId',
);
dom.Document document = html_parser.parse(result.data);
dom.Element? scriptElement = document.querySelector('script#__NEXT_DATA__');
if (scriptElement != null) {
dynamic scriptContent = jsonDecode(scriptElement.text);
isFollowed.value =
scriptContent['props']['pageProps']['followState']['isFollowed'];
followStatus.value =
scriptContent['props']['pageProps']['followState']['followStatus'];
// int progress = scriptContent['props']['pageProps']['dehydratedState']
// ['queries'][0]['state']['data']['result']
// ['play_view_business_info']['user_status']['watch_progress']
// ['current_watch_progress'];
}
try {
dynamic result = await Request().get(
'https://www.bilibili.com/bangumi/play/ss$seasonId',
);
dom.Document document = html_parser.parse(result.data);
dom.Element? scriptElement =
document.querySelector('script#__NEXT_DATA__');
if (scriptElement != null) {
dynamic scriptContent = jsonDecode(scriptElement.text);
isFollowed.value =
scriptContent['props']['pageProps']['followState']['isFollowed'];
followStatus.value =
scriptContent['props']['pageProps']['followState']['followStatus'];
// int progress = scriptContent['props']['pageProps']['dehydratedState']
// ['queries'][0]['state']['data']['result']
// ['play_view_business_info']['user_status']['watch_progress']
// ['current_watch_progress'];
}
} catch (_) {}
}
}

View File

@@ -265,7 +265,7 @@ class _BangumiInfoState extends State<BangumiInfo>
Expanded(
child: GestureDetector(
onTap: showIntroDetail,
behavior: HitTestBehavior.translucent,
behavior: HitTestBehavior.opaque,
child: SizedBox(
height: isLandscape ? 115 : 115 / 0.75,
child: Column(
@@ -450,6 +450,7 @@ class _BangumiInfoState extends State<BangumiInfo>
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Material(
color: Theme.of(context).colorScheme.surface,
child: Padding(
padding: const EdgeInsets.only(top: 1),
child: SizedBox(

View File

@@ -0,0 +1,69 @@
import 'package:PiliPlus/http/bangumi.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/bangumi/pgc_index/condition.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:get/get.dart' hide Condition;
class PgcIndexController extends CommonController {
PgcIndexController(this.indexType);
int? indexType;
Rx<LoadingState> conditionState = LoadingState.loading().obs;
late final RxBool isExpand = false.obs;
RxMap<String, dynamic> indexParams = <String, dynamic>{}.obs;
@override
void onInit() {
super.onInit();
getPgcIndexCondition();
}
Future getPgcIndexCondition() async {
dynamic res = await BangumiHttp.pgcIndexCondition(
seasonType: indexType == null ? 1 : null,
type: 0,
indexType: indexType,
);
if (res is Success) {
Condition data = res.response;
if (data.order?.isNotEmpty == true) {
indexParams['order'] = data.order!.first.field;
}
if (data.filter?.isNotEmpty == true) {
for (Filter item in data.filter!) {
indexParams['${item.field}'] = item.values?.firstOrNull?.keyword;
}
}
queryData();
}
conditionState.value = res;
}
@override
Future<LoadingState> customGetData() => BangumiHttp.pgcIndexResult(
page: currentPage,
params: indexParams,
seasonType: indexType == null ? 1 : null,
type: 0,
indexType: indexType,
);
@override
bool customHandleResponse(Success response) {
if (response.response['has_next'] == null ||
response.response['has_next'] == 0) {
isEnd = true;
}
if (response.response['list'] == null ||
(response.response['list'] as List?)?.isEmpty == true) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
response.response['list']
?.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(response.response['list']);
return true;
}
}

View File

@@ -0,0 +1,242 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/bangumi/pgc_index/pgc_index_controller.dart';
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_pgc_index.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart' hide Condition;
import '../../../models/bangumi/pgc_index/condition.dart';
class PgcIndexPage extends StatefulWidget {
const PgcIndexPage({super.key, this.indexType});
final int? indexType;
@override
State<PgcIndexPage> createState() => _PgcIndexPageState();
}
class _PgcIndexPageState extends State<PgcIndexPage>
with AutomaticKeepAliveClientMixin {
late final _ctr = Get.put(
PgcIndexController(widget.indexType),
tag: '${widget.indexType}',
);
@override
bool get wantKeepAlive => widget.indexType != null;
@override
Widget build(BuildContext context) {
super.build(context);
return widget.indexType == null
? Scaffold(
appBar: AppBar(title: const Text('索引')),
body: Obx(() => _buildBody(_ctr.conditionState.value)),
)
: Obx(() => _buildBody(_ctr.conditionState.value));
}
Widget _buildBody(LoadingState loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => Builder(builder: (context) {
Condition data = loadingState.response;
int count = (data.order?.isNotEmpty == true ? 1 : 0) +
(data.filter?.length ?? 0);
if (count == 0) return const SizedBox.shrink();
return CustomScrollView(
slivers: [
if (widget.indexType != null)
SliverToBoxAdapter(child: const SizedBox(height: 12)),
SliverToBoxAdapter(
child: AnimatedSize(
curve: Curves.easeInOut,
alignment: Alignment.topCenter,
duration: const Duration(milliseconds: 200),
child: count > 5
? Obx(() => _buildSortWidget(count, data))
: _buildSortWidget(count, data),
),
),
SliverPadding(
padding: EdgeInsets.only(
left: StyleString.safeSpace,
right: StyleString.safeSpace,
top: 12,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(() => _buildList(_ctr.loadingState.value)),
),
],
);
}),
Error() => scrollErrorWidget(
errMsg: loadingState.errMsg,
callback: () {
_ctr.conditionState.value = LoadingState.loading();
_ctr.getPgcIndexCondition();
},
),
LoadingState() => throw UnimplementedError(),
};
}
Widget _buildSortWidget(count, data) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...List.generate(
count > 5
? _ctr.isExpand.value
? count
: count ~/ 2
: count,
(index) {
List? item = data.order?.isNotEmpty == true
? index == 0
? data.order
: data.filter![index - 1].values
: data.filter![index].values;
return item?.isNotEmpty == true
? Padding(
padding: EdgeInsets.only(
top: index == 0 ? 0 : 10,
),
child: SelfSizedHorizontalList(
gapSize: 12,
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
childBuilder: (childIndex) => Obx(
() => SearchText(
bgColor: (item[childIndex] is Order
? _ctr.indexParams['order']
: _ctr.indexParams[data
.filter![
data.order?.isNotEmpty == true
? index - 1
: index]
.field]) ==
(item[childIndex] is Order
? item[childIndex].field
: item[childIndex].keyword)
? Theme.of(context)
.colorScheme
.secondaryContainer
: Colors.transparent,
textColor: (item[childIndex] is Order
? _ctr.indexParams['order']
: _ctr.indexParams[data
.filter![
data.order?.isNotEmpty == true
? index - 1
: index]
.field]) ==
(item[childIndex] is Order
? item[childIndex].field
: item[childIndex].keyword)
? Theme.of(context)
.colorScheme
.onSecondaryContainer
: Theme.of(context)
.colorScheme
.onSurfaceVariant,
text: item[childIndex].name,
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 3,
),
onTap: (_) {
String name = item[childIndex] is Order
? 'order'
: data
.filter![data.order?.isNotEmpty == true
? index - 1
: index]
.field!;
_ctr.indexParams[name] =
(item[childIndex] is Order
? item[childIndex].field
: item[childIndex].keyword);
_ctr.onReload();
},
),
),
itemCount: item!.length,
),
)
: const SizedBox.shrink();
},
),
if (count > 5) ...[
const SizedBox(height: 8),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_ctr.isExpand.value = _ctr.isExpand.value.not;
},
child: Container(
width: double.infinity,
alignment: Alignment.center,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_ctr.isExpand.value ? '收起' : '展开',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
Icon(
_ctr.isExpand.value
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
color: Theme.of(context).colorScheme.outline,
),
],
),
),
),
],
],
);
Widget _buildList(LoadingState loadingState) {
return switch (loadingState) {
Loading() => HttpError(errMsg: '加载中'),
Success() => (loadingState.response as List?)?.isNotEmpty == true
? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.cardSpace,
maxCrossAxisExtent: Grid.smallCardWidth / 3 * 2,
childAspectRatio: 0.75,
mainAxisExtent: MediaQuery.textScalerOf(context).scale(50),
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == loadingState.response.length - 1) {
_ctr.onLoadMore();
}
return BangumiCardVPgcIndex(
bangumiItem: loadingState.response[index]);
},
childCount: loadingState.response.length,
),
)
: HttpError(callback: _ctr.onReload),
Error() => HttpError(
errMsg: loadingState.errMsg,
callback: _ctr.onReload,
),
LoadingState() => throw UnimplementedError(),
};
}
}

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/tab_type.dart';
import 'package:PiliPlus/pages/bangumi/pgc_index/pgc_index_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
@@ -82,53 +83,58 @@ class _BangumiPageState extends State<BangumiPage>
slivers: [
SliverToBoxAdapter(
child: Obx(
() => Visibility(
visible: _bangumiController.isLogin.value,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Obx(
() => Text(
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${_bangumiController.followCount.value == -1 ? '' : ' ${_bangumiController.followCount.value}'}',
style: Theme.of(context).textTheme.titleMedium,
),
() => _bangumiController.isLogin.value
? Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Obx(
() => Text(
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${_bangumiController.followCount.value == -1 ? '' : ' ${_bangumiController.followCount.value}'}',
style:
Theme.of(context).textTheme.titleMedium,
),
),
IconButton(
tooltip: '刷新',
onPressed: () {
_bangumiController
..followPage = 1
..followEnd = false
..queryBangumiFollow();
},
icon: const Icon(
Icons.refresh,
size: 20,
),
),
],
),
IconButton(
tooltip: '刷新',
onPressed: () {
_bangumiController
..followPage = 1
..followEnd = false
..queryBangumiFollow();
},
icon: const Icon(
Icons.refresh,
size: 20,
),
),
SizedBox(
height: Grid.smallCardWidth / 2 / 0.75 +
MediaQuery.textScalerOf(context).scale(50),
child: Obx(
() => _buildFollowBody(
_bangumiController.followState.value),
),
],
),
),
SizedBox(
height: Grid.smallCardWidth / 2 / 0.75 +
MediaQuery.textScalerOf(context).scale(50),
child: Obx(
() => _buildFollowBody(
_bangumiController.followState.value),
),
),
],
),
),
),
],
)
: const SizedBox.shrink(),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10, left: 16),
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 16,
right: 10,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -136,6 +142,56 @@ class _BangumiPageState extends State<BangumiPage>
'推荐',
style: Theme.of(context).textTheme.titleMedium,
),
GestureDetector(
onTap: () {
if (widget.tabType == TabType.bangumi) {
Get.to(PgcIndexPage());
} else {
List titles = const ['全部', '电影', '电视剧', '纪录片', '综艺'];
List types = const [102, 2, 5, 3, 7];
Get.to(
Scaffold(
appBar: AppBar(title: const Text('索引')),
body: DefaultTabController(
length: types.length,
child: Column(
children: [
TabBar(
tabs: titles
.map((title) => Tab(text: title))
.toList()),
Expanded(
child: TabBarView(
children: types
.map((type) =>
PgcIndexPage(indexType: type))
.toList()),
)
],
),
),
),
);
}
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'查看更多',
strutStyle: StrutStyle(leading: 0, height: 1),
style: TextStyle(
height: 1,
color: Theme.of(context).colorScheme.secondary,
),
),
Icon(
Icons.chevron_right,
color: Theme.of(context).colorScheme.secondary,
),
],
),
),
],
),
),

View File

@@ -0,0 +1,127 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:flutter/material.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
// 视频卡片 - 垂直布局
class BangumiCardVPgcIndex extends StatelessWidget {
const BangumiCardVPgcIndex({
super.key,
required this.bangumiItem,
});
final dynamic bangumiItem;
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: InkWell(
onLongPress: () => imageSaveDialog(
context: context,
title: bangumiItem['title'],
cover: bangumiItem['cover'],
),
onTap: () {
Utils.viewBangumi(seasonId: bangumiItem['season_id']);
},
child: Column(
children: [
ClipRRect(
borderRadius: StyleString.mdRadius,
child: AspectRatio(
aspectRatio: 0.75,
child: LayoutBuilder(builder: (context, boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
NetworkImgLayer(
src: bangumiItem['cover'],
width: maxWidth,
height: maxHeight,
),
if (bangumiItem['badge'] != null &&
bangumiItem['badge'] != '')
PBadge(
text: bangumiItem['badge'],
top: 6,
right: 6,
bottom: null,
left: null,
),
if (bangumiItem['order'] != null &&
bangumiItem['order'] != '')
PBadge(
text: bangumiItem['order'],
top: null,
right: null,
bottom: 6,
left: 6,
type: 'gray',
),
],
);
}),
),
),
bagumiContent(context)
],
),
),
);
}
Widget bagumiContent(context) {
return Expanded(
child: Padding(
// 多列
padding: const EdgeInsets.fromLTRB(4, 5, 0, 3),
// 单列
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Expanded(
child: Text(
bangumiItem['title'],
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
)),
],
),
const SizedBox(height: 1),
if (bangumiItem['index_show'] != null)
Text(
bangumiItem['index_show'],
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
// if (bangumiItem.progress != null)
// Text(
// bangumiItem.progress,
// maxLines: 1,
// style: TextStyle(
// fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
// color: Theme.of(context).colorScheme.outline,
// ),
// ),
],
),
),
);
}
}

View File

@@ -1,5 +1,11 @@
import 'dart:convert';
import 'dart:io';
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:PiliPlus/models/common/reply_type.dart';
import 'package:PiliPlus/models/video/reply/data.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
@@ -9,6 +15,7 @@ import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/models/common/reply_sort_type.dart';
import 'package:PiliPlus/models/video/reply/item.dart';
@@ -27,10 +34,18 @@ abstract class ReplyController extends CommonController {
late final bool isLogin = GStorage.userInfo.get('userInfoCache') != null;
CursorReply? cursor;
late Mode mode = Mode.MAIN_LIST_HOT;
late Rx<Mode> mode = Mode.MAIN_LIST_HOT.obs;
late bool hasUpTop = false;
late final banWordForReply = GStorage.banWordForReply;
late final antiGoodsReply = GStorage.antiGoodsReply;
// comment antifraud
late final _enableCommAntifraud = GStorage.enableCommAntifraud;
late final _biliSendCommAntifraud = GStorage.biliSendCommAntifraud;
bool get enableCommAntifraud =>
_enableCommAntifraud || _biliSendCommAntifraud;
dynamic get sourceId;
@override
void onInit() {
@@ -43,7 +58,7 @@ abstract class ReplyController extends CommonController {
}
sortType.value = ReplySortType.values[defaultReplySortIndex];
if (sortType.value == ReplySortType.time) {
mode = Mode.MAIN_LIST_TIME;
mode.value = Mode.MAIN_LIST_TIME;
}
}
@@ -95,7 +110,7 @@ abstract class ReplyController extends CommonController {
hasUpTop = true;
}
}
if (response.response.topReplies != null) {
if ((response.response.topReplies as List?)?.isNotEmpty == true) {
replies.insertAll(0, response.response.topReplies);
hasUpTop = true;
}
@@ -117,11 +132,11 @@ abstract class ReplyController extends CommonController {
switch (sortType.value) {
case ReplySortType.time:
sortType.value = ReplySortType.like;
mode = Mode.MAIN_LIST_HOT;
mode.value = Mode.MAIN_LIST_HOT;
break;
case ReplySortType.like:
sortType.value = ReplySortType.time;
mode = Mode.MAIN_LIST_TIME;
mode.value = Mode.MAIN_LIST_TIME;
break;
}
nextOffset = '';
@@ -204,21 +219,58 @@ abstract class ReplyController extends CommonController {
}
count.value += 1;
loadingState.value = LoadingState.success(response);
if (enableCommAntifraud && context.mounted) {
checkReply(
context: context,
oid: oid ?? replyItem.oid.toInt(),
rpid: replyItem?.id.toInt(),
replyType: replyItem?.type.toInt() ??
replyType?.index ??
ReplyType.video.index,
replyId: replyInfo.id.toInt(),
message: replyInfo.content.message,
//
root: replyInfo.root.toInt(),
parent: replyInfo.parent.toInt(),
ctime: replyInfo.ctime.toInt(),
pictures: replyInfo.content.pictures
.map((item) => item.toProto3Json())
.toList(),
mid: replyInfo.mid.toInt(),
);
}
} else {
ReplyData response = loadingState.value is Success
? (loadingState.value as Success).response
: ReplyData();
response.replies ??= <ReplyItemModel>[];
ReplyItemModel replyInfo = ReplyItemModel.fromJson(res, '');
if (oid != null) {
response.replies
?.insert(hasUpTop ? 1 : 0, ReplyItemModel.fromJson(res, ''));
response.replies?.insert(hasUpTop ? 1 : 0, replyInfo);
} else {
response.replies?[index].replies ??= <ReplyItemModel>[];
response.replies?[index].replies
?.add(ReplyItemModel.fromJson(res, ''));
response.replies?[index].replies?.add(replyInfo);
}
count.value += 1;
loadingState.value = LoadingState.success(response);
if (enableCommAntifraud && context.mounted) {
checkReply(
context: context,
oid: oid ?? replyItem.oid,
rpid: replyItem?.rpid,
replyType: replyItem?.type.toInt() ??
replyType?.index ??
ReplyType.video.index,
replyId: replyInfo.rpid ?? 0,
message: replyInfo.content?.message ?? '',
//
root: replyInfo.root,
parent: replyInfo.parent,
ctime: replyInfo.ctime,
pictures: replyInfo.content?.pictures,
mid: replyInfo.mid,
);
}
}
}
},
@@ -262,4 +314,229 @@ abstract class ReplyController extends CommonController {
loadingState.value = LoadingState.success(response);
}
}
// ref https://github.com/freedom-introvert/biliSendCommAntifraud
void checkReply({
required BuildContext context,
required dynamic oid,
required dynamic rpid,
required int replyType,
required int replyId,
required String message,
dynamic root,
dynamic parent,
dynamic ctime,
dynamic pictures,
dynamic mid,
}) async {
await Future.delayed(const Duration(seconds: 5));
// biliSendCommAntifraud
if (_biliSendCommAntifraud && Platform.isAndroid) {
try {
List<Cookie> cookies = await Request.cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.apiBaseUrl));
final String cookieString = cookies
.map((Cookie cookie) => '${cookie.name}=${cookie.value}')
.join(';');
Utils.channel.invokeMethod(
'biliSendCommAntifraud',
{
'action': 0,
'oid': oid,
'type': replyType,
'rpid': replyId,
'root': root,
'parent': parent,
'ctime': ctime,
'comment_text': message,
if (pictures.isNotEmpty == true) 'pictures': jsonEncode(pictures),
'source_id': '$sourceId',
'uid': mid,
'cookies': [cookieString],
},
);
} catch (e) {
debugPrint('biliSendCommAntifraud: $e');
}
return;
}
// CommAntifraud
void showReplyCheckResult(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('评论检查结果'),
content: SelectableText(message),
),
);
}
if (context.mounted.not) return;
// root reply
if (rpid == null) {
// no cookie check
dynamic res = await ReplyHttp.replyList(
isLogin: false,
oid: oid,
nextOffset: '',
type: replyType,
sort: ReplySortType.time.index,
page: 1,
banWordForReply: '',
antiGoodsReply: false,
);
if (context.mounted.not) return;
if (res is Error) {
SmartDialog.showToast('获取评论主列表时发生错误:${res.errMsg}');
return;
} else if (res is Success) {
ReplyData replies = res.response;
int index =
replies.replies?.indexWhere((item) => item.rpid == replyId) ?? -1;
if (index != -1) {
// found
if (context.mounted) {
showReplyCheckResult(
'无账号状态下找到了你的评论,评论正常!\n\n你的评论:$message',
);
}
} else {
// not found
if (context.mounted.not) return;
// cookie check
dynamic res1 = await ReplyHttp.replyReplyList(
isLogin: isLogin,
oid: oid,
root: rpid ?? replyId,
pageNum: 1,
type: replyType,
banWordForReply: '',
antiGoodsReply: false,
);
if (context.mounted.not) return;
if (res1 is Error) {
// not found
if (context.mounted) {
showReplyCheckResult(
'无法找到你的评论。\n\n你的评论:$message',
);
}
} else if (res1 is Success) {
// found
if (context.mounted.not) return;
// no cookie check
dynamic res2 = await ReplyHttp.replyReplyList(
isLogin: false,
oid: oid,
root: rpid ?? replyId,
pageNum: 1,
type: replyType,
banWordForReply: '',
isCheck: true,
antiGoodsReply: false,
);
if (context.mounted.not) return;
if (res2 is Error) {
// not found
if (context.mounted) {
showReplyCheckResult(
res2.errMsg.startsWith('12022')
? '你的评论被shadow ban仅自己可见\n\n你的评论: $message'
: '评论不可见(${res2.errMsg}): $message',
);
}
} else if (res2 is Success) {
// found
if (context.mounted) {
showReplyCheckResult('''
你评论状态有点可疑,虽然无账号翻找评论区获取不到你的评论,但是无账号可通过
https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=${rpid ?? replyId}&type=$replyType
获取你的评论,疑似评论区被戒严或者这是你的视频。
你的评论:$message''');
}
}
}
}
}
} else {
for (int i = 1; true; i++) {
if (context.mounted.not) return;
dynamic res3 = await ReplyHttp.replyReplyList(
isLogin: false,
oid: oid,
root: rpid ?? replyId,
pageNum: i,
type: replyType,
banWordForReply: '',
isCheck: true,
antiGoodsReply: false,
);
if (res3 is Error) {
break;
} else if (res3 is Success) {
ReplyReplyData data = res3.response;
if (data.replies.isNullOrEmpty) {
break;
}
int index =
data.replies?.indexWhere((item) => item.rpid == replyId) ?? -1;
if (index == -1) {
// not found
} else {
// found
if (context.mounted) {
showReplyCheckResult(
'无账号状态下找到了你的评论,评论正常!\n\n你的评论:$message',
);
}
return;
}
}
}
for (int i = 1; true; i++) {
if (context.mounted.not) return;
dynamic res4 = await ReplyHttp.replyReplyList(
isLogin: true,
oid: oid,
root: rpid ?? replyId,
pageNum: i,
type: replyType,
banWordForReply: '',
isCheck: true,
antiGoodsReply: false,
);
if (res4 is Error) {
break;
} else if (res4 is Success) {
ReplyReplyData data = res4.response;
if (data.replies.isNullOrEmpty) {
break;
}
int index =
data.replies?.indexWhere((item) => item.rpid == replyId) ?? -1;
if (index == -1) {
// not found
} else {
// found
if (context.mounted) {
showReplyCheckResult(
'你的评论被shadow ban仅自己可见\n\n你的评论: $message',
);
}
return;
}
}
}
if (context.mounted) {
showReplyCheckResult(
'评论不可见: $message',
);
}
}
}
}

View File

@@ -1,7 +1,6 @@
import 'package:PiliPlus/grpc/dm/v1/dm.pb.dart';
import 'package:PiliPlus/http/danmaku.dart';
import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/utils/storage.dart';
class PlDanmakuController {
PlDanmakuController(
@@ -19,8 +18,6 @@ class PlDanmakuController {
static int segmentLength = 60 * 6 * 1000;
late final mergeDanmaku = GStorage.mergeDanmaku;
void initiate(int videoDuration, int progress) {
if (videoDuration <= 0) {
return;
@@ -47,10 +44,10 @@ class PlDanmakuController {
}
assert(requestedSeg[segmentIndex] == false);
requestedSeg[segmentIndex] = true;
final DmSegMobileReply result = await DanmakaHttp.queryDanmaku(
final DmSegMobileReply result = await DanmakuHttp.queryDanmaku(
cid: cid,
segmentIndex: segmentIndex + 1,
mergeDanmaku: mergeDanmaku,
mergeDanmaku: plPlayerController.mergeDanmaku,
);
if (result.elems.isNotEmpty) {
for (var element in result.elems) {

View File

@@ -5,6 +5,7 @@ import 'package:PiliPlus/pages/emote/controller.dart';
import 'package:PiliPlus/pages/emote/view.dart';
import 'package:PiliPlus/pages/video/detail/reply_new/toolbar_icon_button.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -483,12 +484,15 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
replyOption: _replyOption,
privatePub: _isPrivate ? 1 : null,
);
SmartDialog.dismiss();
if (result['status']) {
Get.back();
SmartDialog.dismiss();
SmartDialog.showToast('发布成功');
Future.wait([
Utils.insertCreatedDyn(result),
Utils.checkCreatedDyn(result, editController.text)
]);
} else {
SmartDialog.dismiss();
SmartDialog.showToast(result['msg']);
debugPrint('failed to publish: ${result['msg']}');
}

View File

@@ -1,7 +1,10 @@
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/reply_type.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/common/reply_controller.dart';
import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/http/html.dart';
@@ -10,20 +13,24 @@ import 'package:fixnum/fixnum.dart' as $fixnum;
class DynamicDetailController extends ReplyController {
DynamicDetailController(this.oid, this.type);
int? oid;
int? type;
dynamic item;
int oid;
int type;
late DynamicItemModel item;
int? floor;
late final horizontalPreview = GStorage.horizontalPreview;
@override
dynamic get sourceId =>
type == ReplyType.video.index ? IdUtils.av2bv(oid) : oid;
@override
void onInit() {
super.onInit();
item = Get.arguments['item'];
floor = Get.arguments['floor'];
if (floor == 1) {
count.value = int.parse(item!.modules!.moduleStat!.comment!.count ?? '0');
count.value = int.parse(item.modules!.moduleStat!.comment!.count ?? '0');
}
if (oid != 0) {
@@ -41,21 +48,23 @@ class DynamicDetailController extends ReplyController {
@override
Future<LoadingState> customGetData() => GlobalData().grpcReply
? ReplyHttp.replyListGrpc(
type: type ?? 1,
oid: oid!,
type: type,
oid: oid,
cursor: CursorReq(
next: cursor?.next ?? $fixnum.Int64(0),
mode: mode,
mode: mode.value,
),
banWordForReply: banWordForReply,
antiGoodsReply: antiGoodsReply,
)
: ReplyHttp.replyList(
isLogin: isLogin,
oid: oid!,
oid: oid,
nextOffset: nextOffset,
type: type!,
type: type,
sort: sortType.value.index,
page: currentPage,
banWordForReply: banWordForReply,
antiGoodsReply: antiGoodsReply,
);
}

View File

@@ -4,8 +4,10 @@ import 'dart:math';
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/reply_sort_type.dart';
import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart';
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item.dart';
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart';
import 'package:PiliPlus/utils/extension.dart';
@@ -15,6 +17,7 @@ import 'package:PiliPlus/utils/utils.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/skeleton/video_reply.dart';
import 'package:PiliPlus/common/widgets/http_error.dart';
@@ -24,7 +27,7 @@ import 'package:PiliPlus/pages/dynamics/detail/index.dart';
import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart';
import 'package:PiliPlus/pages/video/detail/reply_reply/index.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:share_plus/share_plus.dart';
import '../../../utils/grid.dart';
import '../widgets/dynamic_panel.dart';
@@ -354,7 +357,10 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: EdgeInsets.only(left: padding / 4),
padding: EdgeInsets.only(
left: padding / 4,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: SliverToBoxAdapter(
child: DynamicPanel(
item: _dynamicDetailController.item,
@@ -402,30 +408,222 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
),
if (_fabAnimationCtr != null)
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 14,
right: 14,
left: 0,
right: 0,
bottom: 0,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 2),
begin: const Offset(0, 1),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: _fabAnimationCtr!,
curve: Curves.easeInOut,
)),
child: FloatingActionButton(
heroTag: null,
onPressed: () {
feedBack();
dynamic oid = _dynamicDetailController.oid ??
IdUtils.bv2av(Get.parameters['bvid']!);
_dynamicDetailController.onReply(
context,
oid: oid,
replyType: ReplyType.values[replyType],
);
},
tooltip: '评论动态',
child: const Icon(Icons.reply),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.only(right: 14, bottom: 14),
child: FloatingActionButton(
heroTag: null,
onPressed: () {
feedBack();
_dynamicDetailController.onReply(
context,
oid: _dynamicDetailController.oid,
replyType: ReplyType.values[replyType],
);
},
tooltip: '评论动态',
child: const Icon(Icons.reply),
),
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border(
top: BorderSide(
color: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.08),
),
),
),
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: Builder(
builder: (btnContext) => TextButton.icon(
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (context) => RepostPanel(
item: _dynamicDetailController.item,
callback: () {
int count = int.tryParse(
_dynamicDetailController
.item
.modules
?.moduleStat
?.forward
?.count ??
'0') ??
0;
_dynamicDetailController.item.modules
?.moduleStat ??= ModuleStatModel();
_dynamicDetailController.item.modules!
.moduleStat?.forward ??= ForWard();
_dynamicDetailController
.item
.modules!
.moduleStat!
.forward!
.count = (count + 1).toString();
if (btnContext.mounted) {
(btnContext as Element?)
?.markNeedsBuild();
}
},
),
);
},
icon: Icon(
FontAwesomeIcons.shareFromSquare,
size: 16,
color: Theme.of(context).colorScheme.outline,
semanticLabel: "转发",
),
style: TextButton.styleFrom(
padding:
const EdgeInsets.fromLTRB(15, 0, 15, 0),
foregroundColor:
Theme.of(context).colorScheme.outline,
),
label: Text(
_dynamicDetailController.item.modules
?.moduleStat?.forward?.count !=
null
? Utils.numFormat(_dynamicDetailController
.item
.modules!
.moduleStat!
.forward!
.count)
: '转发',
),
),
),
),
Expanded(
child: TextButton.icon(
onPressed: () {
Share.share(
'${HttpString.dynamicShareBaseUrl}/${_dynamicDetailController.item.idStr}');
},
icon: Icon(
FontAwesomeIcons.shareNodes,
size: 16,
color: Theme.of(context).colorScheme.outline,
semanticLabel: "分享",
),
style: TextButton.styleFrom(
padding:
const EdgeInsets.fromLTRB(15, 0, 15, 0),
foregroundColor:
Theme.of(context).colorScheme.outline,
),
label: const Text('分享'),
),
),
Expanded(
child: Builder(
builder: (context) => TextButton.icon(
onPressed: () => Utils.onLikeDynamic(
_dynamicDetailController.item,
() {
if (context.mounted) {
(context as Element?)?.markNeedsBuild();
}
},
),
icon: Icon(
_dynamicDetailController.item.modules
?.moduleStat?.like?.status ==
true
? FontAwesomeIcons.solidThumbsUp
: FontAwesomeIcons.thumbsUp,
size: 16,
color: _dynamicDetailController.item.modules
?.moduleStat?.like?.status ==
true
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
semanticLabel: _dynamicDetailController
.item
.modules
?.moduleStat
?.like
?.status ==
true
? "已赞"
: "点赞",
),
style: TextButton.styleFrom(
padding:
const EdgeInsets.fromLTRB(15, 0, 15, 0),
foregroundColor:
Theme.of(context).colorScheme.outline,
),
label: AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder: (Widget child,
Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
_dynamicDetailController.item.modules
?.moduleStat?.like?.count !=
null
? Utils.numFormat(
_dynamicDetailController
.item
.modules!
.moduleStat!
.like!
.count)
: '点赞',
style: TextStyle(
color: _dynamicDetailController
.item
.modules
?.moduleStat
?.like
?.status ==
true
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.outline,
),
),
),
),
),
),
],
),
),
],
),
),
),

View File

@@ -7,6 +7,7 @@ import 'package:PiliPlus/pages/emote/view.dart';
import 'package:PiliPlus/pages/video/detail/reply_new/toolbar_icon_button.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -14,20 +15,37 @@ import 'package:get/get.dart';
class RepostPanel extends CommonPublishPage {
const RepostPanel({
super.key,
required this.item,
required this.callback,
this.item,
this.callback,
// video
this.rid,
this.dynType,
this.pic,
this.title,
this.uname,
this.isMax,
});
// video
final int? rid;
final int? dynType;
final String? pic;
final String? title;
final String? uname;
final bool? isMax;
final dynamic item;
final Function callback;
final VoidCallback? callback;
@override
State<RepostPanel> createState() => _RepostPanelState();
}
class _RepostPanelState extends CommonPublishPageState<RepostPanel> {
bool _isMax = false;
late final dynamic _pic = (widget.item as DynamicItemModel?)
late bool _isMax = widget.isMax ?? false;
late final dynamic _pic = widget.pic ??
(widget.item as DynamicItemModel?)
?.modules
?.moduleDynamic
?.major
@@ -47,7 +65,9 @@ class _RepostPanelState extends CommonPublishPageState<RepostPanel> {
?.pics
?.firstOrNull
?.url;
late final _text = (widget.item as DynamicItemModel?)
late final _text = widget.title ??
(widget.item as DynamicItemModel?)
?.modules
?.moduleDynamic
?.major
@@ -69,6 +89,9 @@ class _RepostPanelState extends CommonPublishPageState<RepostPanel> {
?.title ??
'';
late final _uname = widget.uname ??
(widget.item as DynamicItemModel?)?.modules?.moduleAuthor?.name;
@override
void dispose() {
try {
@@ -154,13 +177,14 @@ class _RepostPanelState extends CommonPublishPageState<RepostPanel> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'@${(widget.item as DynamicItemModel?)?.modules?.moduleAuthor?.name}',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 13,
if (_uname?.isNotEmpty == true)
Text(
'@$_uname',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 13,
),
),
),
Text(
_text,
maxLines: 2,
@@ -224,8 +248,8 @@ class _RepostPanelState extends CommonPublishPageState<RepostPanel> {
? Row(
children: [
const SizedBox(width: 16),
const Text(
'转发动态',
Text(
widget.rid != null ? '分享至动态' : '转发动态',
style: TextStyle(fontWeight: FontWeight.bold),
),
const Spacer(),
@@ -239,7 +263,7 @@ class _RepostPanelState extends CommonPublishPageState<RepostPanel> {
vertical: -2,
),
),
child: const Text('立即转发'),
child: Text(widget.rid != null ? '立即发布' : '立即转发'),
),
const SizedBox(width: 16),
],
@@ -274,8 +298,8 @@ class _RepostPanelState extends CommonPublishPageState<RepostPanel> {
),
),
Center(
child: const Text(
'转发动态',
child: Text(
widget.rid != null ? '分享至动态' : '转发动态',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
),
@@ -294,7 +318,7 @@ class _RepostPanelState extends CommonPublishPageState<RepostPanel> {
vertical: -2,
),
),
child: const Text('转发'),
child: Text(widget.rid != null ? '发布' : '转发'),
),
),
],
@@ -353,13 +377,19 @@ class _RepostPanelState extends CommonPublishPageState<RepostPanel> {
Future onCustomPublish({required String message, List? pictures}) async {
dynamic result = await MsgHttp.createDynamic(
mid: GStorage.userInfo.get('userInfoCache')?.mid,
dynIdStr: widget.item.idStr,
dynIdStr: widget.item?.idStr,
rid: widget.rid,
dynType: widget.dynType,
rawText: editController.text,
);
if (result['status']) {
Get.back();
SmartDialog.showToast('转发成功');
widget.callback();
widget.callback?.call();
Future.wait([
Utils.insertCreatedDyn(result),
Utils.checkCreatedDyn(result, editController.text)
]);
} else {
SmartDialog.showToast(result['msg']);
}

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -41,11 +42,14 @@ class DynamicsTabController extends CommonController {
return true;
}
late final antiGoodsDyn = GStorage.antiGoodsDyn;
@override
Future<LoadingState> customGetData() => DynamicsHttp.followDynamic(
type: dynamicsType == "up" ? "all" : dynamicsType,
offset: offset,
mid: dynamicsType == "up" ? mid : -1,
antiGoodsDyn: antiGoodsDyn,
);
Future onRemove(dynamic dynamicId) async {

View File

@@ -136,7 +136,6 @@ class _DynamicsPageState extends State<DynamicsPage>
super.build(context);
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
appBar: AppBar(
leading: upPanelPosition == UpPanelPosition.rightDrawer
? _createDynamicBtn(false)

View File

@@ -1,6 +1,5 @@
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/http/index.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:cached_network_image/cached_network_image.dart';
@@ -98,7 +97,7 @@ class AuthorPanel extends StatelessWidget {
),
child: Row(
children: [
Text(item is ItemOrigModel
Text(item.isForwarded == true
? Utils.dateFormat(item.modules.moduleAuthor.pubTs)
: item.modules.moduleAuthor.pubTime),
if (item.modules.moduleAuthor.pubTime != '' &&
@@ -110,78 +109,84 @@ class AuthorPanel extends StatelessWidget {
)
],
),
// const Spacer(),
// if (source != 'detail' && item.modules?.moduleTag?.text != null)
// Container(
// padding:
// const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
// decoration: BoxDecoration(
// color: Theme.of(context).colorScheme.surface,
// borderRadius: const BorderRadius.all(Radius.circular(4)),
// border: Border.all(
// width: 1.25,
// color: Theme.of(context).colorScheme.primary,
// ),
// ),
// child: Text(
// item.modules.moduleTag.text,
// style: TextStyle(
// height: 1,
// fontSize: 12,
// color: Theme.of(context).colorScheme.primary,
// ),
// strutStyle: const StrutStyle(
// leading: 0,
// height: 1,
// fontSize: 12,
// ),
// ),
// ),
],
),
),
Align(
alignment: Alignment.centerRight,
child: item.modules.moduleAuthor.decorate != null
child: source != 'detail' && item.modules?.moduleTag?.text != null
? Row(
mainAxisSize: MainAxisSize.min,
children: [
// GestureDetector(
// onTap:
// item.modules.moduleAuthor.decorate['jump_url'] != null
// ? () {
// Get.toNamed(
// '/webview',
// parameters: {
// 'url':
// '${item.modules.moduleAuthor.decorate['jump_url']}'
// },
// );
// }
// : null,
// child:
Stack(
clipBehavior: Clip.none,
alignment: Alignment.centerRight,
children: [
CachedNetworkImage(
height: 32,
imageUrl:
item.modules.moduleAuthor.decorate['card_url'],
Container(
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 2),
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(4)),
border: Border.all(
width: 1.25,
color: Theme.of(context).colorScheme.primary,
),
if ((item.modules.moduleAuthor.decorate?['fan']
?['num_str'] as String?)
?.isNotEmpty ==
true)
Padding(
padding: const EdgeInsets.only(right: 32),
child: Text(
'${item.modules.moduleAuthor.decorate['fan']['num_str']}',
style: TextStyle(
fontSize: 11,
fontFamily: 'digital_id_num',
color:
(item.modules.moduleAuthor.decorate?['fan']
),
child: Text(
item.modules.moduleTag.text,
style: TextStyle(
height: 1,
fontSize: 12,
color: Theme.of(context).colorScheme.primary,
),
strutStyle: const StrutStyle(
leading: 0,
height: 1,
fontSize: 12,
),
),
),
_moreWidget(context),
],
)
: item.modules.moduleAuthor.decorate != null
? Row(
mainAxisSize: MainAxisSize.min,
children: [
// GestureDetector(
// onTap:
// item.modules.moduleAuthor.decorate['jump_url'] != null
// ? () {
// Get.toNamed(
// '/webview',
// parameters: {
// 'url':
// '${item.modules.moduleAuthor.decorate['jump_url']}'
// },
// );
// }
// : null,
// child:
Stack(
clipBehavior: Clip.none,
alignment: Alignment.centerRight,
children: [
CachedNetworkImage(
height: 32,
imageUrl: item
.modules.moduleAuthor.decorate['card_url'],
),
if ((item.modules.moduleAuthor.decorate?['fan']
?['num_str'] as String?)
?.isNotEmpty ==
true)
Padding(
padding: const EdgeInsets.only(right: 32),
child: Text(
'${item.modules.moduleAuthor.decorate['fan']['num_str']}',
style: TextStyle(
height: 1,
fontSize: 11,
fontFamily: 'digital_id_num',
color: (item.modules.moduleAuthor
.decorate?['fan']
?['color'] as String?)
?.startsWith('#') ==
true
@@ -193,16 +198,16 @@ class AuthorPanel extends StatelessWidget {
),
)
: null,
),
),
),
),
),
],
),
// ),
_moreWidget(context),
],
),
// ),
_moreWidget(context),
],
)
: _moreWidget(context),
)
: _moreWidget(context),
)
],
);
@@ -306,6 +311,25 @@ class AuthorPanel extends StatelessWidget {
},
minLeadingWidth: 0,
),
if (GStorage.isLogin)
ListTile(
title: Text(
'举报',
style: Theme.of(context).textTheme.titleSmall!.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
leading: Icon(
Icons.error_outline_outlined,
size: 19,
color: Theme.of(context).colorScheme.error,
),
onTap: () {
Get.back();
_showReportDynDialog(context);
},
minLeadingWidth: 0,
),
if (item.modules.moduleAuthor.mid ==
GStorage.userInfo.get('userInfoCache')?.mid &&
onRemove != null)
@@ -345,25 +369,6 @@ class AuthorPanel extends StatelessWidget {
.titleSmall!
.copyWith(color: Theme.of(context).colorScheme.error)),
),
if (GStorage.isLogin)
ListTile(
title: Text(
'举报',
style: Theme.of(context).textTheme.titleSmall!.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
leading: Icon(
Icons.error_outline_outlined,
size: 19,
color: Theme.of(context).colorScheme.error,
),
onTap: () {
Get.back();
_showReportDynDialog(context);
},
minLeadingWidth: 0,
),
const Divider(thickness: 0.1, height: 1),
ListTile(
onTap: Get.back,

View File

@@ -1,4 +1,5 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
@@ -38,12 +39,20 @@ class DynamicPanel extends StatelessWidget {
child: Material(
elevation: 0,
clipBehavior: Clip.hardEdge,
color: Theme.of(context).cardColor.withOpacity(0.5),
color: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
child: InkWell(
onTap: source == 'detail' && item.type != 'DYNAMIC_TYPE_AV'
onTap: source == 'detail' &&
[
'DYNAMIC_TYPE_AV',
'DYNAMIC_TYPE_UGC_SEASON',
'DYNAMIC_TYPE_PGC_UNION',
'DYNAMIC_TYPE_PGC',
'DYNAMIC_TYPE_LIVE',
'DYNAMIC_TYPE_LIVE_RCMD',
].contains(item.type).not
? null
: () => Utils.pushDynDetail(item, 1),
onLongPress: () {

View File

@@ -161,9 +161,41 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
return videoSeasonWidget(item, context, 'archive', floor: floor);
// 文章
case 'DYNAMIC_TYPE_ARTICLE':
return item is ItemOrigModel
? articlePanel(item, context, callback, floor: floor)
: const SizedBox.shrink();
return switch (item) {
DynamicItemModel() => item.isForwarded == true
? articlePanel(item, context, callback, floor: floor)
: item.modules?.moduleDynamic?.major?.blocked != null
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.modules?.moduleDynamic?.major
?.blocked?['title'] !=
null)
Text(
'${item.modules?.moduleDynamic?.major?.blocked!['title']}',
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
),
),
if (item.modules?.moduleDynamic?.major
?.blocked?['hint_message'] !=
null)
Text(
'${item.modules?.moduleDynamic?.major?.blocked!['hint_message']}',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline,
),
)
],
),
)
: const SizedBox.shrink(),
_ => const SizedBox.shrink(),
};
// return Container(
// padding:
// const EdgeInsets.only(left: 10, top: 12, right: 10, bottom: 10),
@@ -208,8 +240,7 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
}
},
child: Container(
padding:
const EdgeInsets.only(left: 15, top: 10, right: 15, bottom: 8),
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
color: Theme.of(context).dividerColor.withOpacity(0.08),
child:
forWard(item.orig, context, source, callback, floor: floor + 1),
@@ -305,11 +336,13 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
case 'DYNAMIC_TYPE_COMMON_SQUARE':
return InkWell(
onTap: () {
Get.toNamed('/webview', parameters: {
'url': item.modules.moduleDynamic.major.common['jump_url'],
'type': 'url',
'pageTitle': item.modules.moduleDynamic.major.common['title']
});
try {
String url = item.modules.moduleDynamic.major.common['jump_url'];
if (url.contains('bangumi/play') && Utils.viewPgcFromUri(url)) {
return;
}
Utils.handleWebview(url, inApp: true);
} catch (_) {}
},
child: Container(
width: double.infinity,

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
@@ -7,7 +8,7 @@ import 'rich_node_panel.dart';
Widget livePanel(item, context, {floor = 1}) {
dynamic content = item.modules.moduleDynamic.major;
TextStyle authorStyle =
late final TextStyle authorStyle =
TextStyle(color: Theme.of(context).colorScheme.primary);
InlineSpan? richNodes = richNode(item, context);
return Column(
@@ -55,7 +56,18 @@ Widget livePanel(item, context, {floor = 1}) {
const SizedBox(height: 6),
],
GestureDetector(
onTap: () {},
behavior: HitTestBehavior.opaque,
onTap: () {
Get.toNamed('/liveRoom?roomid=${content.live?.id}');
},
onLongPress: () {
Feedback.forLongPress(context);
imageSaveDialog(
context: context,
title: content.live.title,
cover: content.live.cover,
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -104,13 +104,12 @@ InlineSpan? richNode(item, context) {
return;
}
if (url.startsWith('//')) {
url = url.replaceFirst('//', 'https://');
PiliScheme.routePush(Uri.parse(url));
PiliScheme.routePushFromUrl('https:$url');
return;
}
Utils.handleWebview(url.startsWith('//')
? "https://${url.split('//').last}"
: url);
Utils.handleWebview(
url.startsWith('//') ? "https://$url" : url,
);
},
child: Text(
i.text ?? '',
@@ -153,7 +152,7 @@ InlineSpan? richNode(item, context) {
spanChildren.add(
WidgetSpan(
child: NetworkImgLayer(
src: i.emoji!.iconUrl,
src: i.emoji!.webpUrl ?? i.emoji!.gifUrl ?? i.emoji!.iconUrl,
type: 'emote',
width: (i.emoji!.size ?? 1) * 20,
height: (i.emoji!.size ?? 1) * 20,

View File

@@ -1,5 +1,4 @@
// 视频or合集
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/constants.dart';
@@ -159,7 +158,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
if (richNodes != null) Text.rich(richNodes),
const SizedBox(height: 6),
],
if (item is ItemOrigModel)
if (item.isForwarded == true)
buildCover()
else
Padding(

View File

@@ -7,7 +7,7 @@ import '../../http/reply.dart';
class EmotePanelController extends CommonController
with GetTickerProviderStateMixin {
late TabController tabController;
TabController? tabController;
@override
void onInit() {
@@ -29,7 +29,7 @@ class EmotePanelController extends CommonController
@override
void onClose() {
tabController.dispose();
tabController?.dispose();
super.onClose();
}
}

View File

@@ -95,6 +95,7 @@ class _EmotePanelState extends State<EmotePanel>
),
TabBar(
controller: _emotePanelController.tabController,
padding: const EdgeInsets.only(right: 60),
dividerColor: Colors.transparent,
isScrollable: true,
tabs: (loadingState.response as List<Packages>)

View File

@@ -3,6 +3,7 @@ import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/models/user/fav_detail.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
@@ -35,23 +36,24 @@ class FavDetailController extends MultiSelectController {
@override
bool customHandleResponse(Success response) {
FavDetailData data = response.response;
if (currentPage == 1) {
item.value = response.response.info;
isOwner.value = response.response.info.mid == mid;
item.value = data.info ?? FavFolderItemData();
isOwner.value = data.info?.mid == mid;
}
if (response.response.medias.isEmpty) {
if (data.medias.isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
response.response.medias?.insertAll(
data.medias?.insertAll(
0,
List<FavDetailItemData>.from((loadingState.value as Success).response),
);
}
if (response.response.medias.length >= response.response.info.mediaCount) {
if ((data.medias?.length ?? 0) >= (data.info?.mediaCount ?? 0)) {
isEnd = true;
}
loadingState.value = LoadingState.success(response.response.medias);
loadingState.value = LoadingState.success(data.medias);
return true;
}

View File

@@ -1,7 +1,8 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/common/widgets/video_progress_indicator.dart';
import 'package:PiliPlus/models/user/history.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/pages/fav_search/controller.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -15,6 +16,7 @@ import 'package:PiliPlus/models/common/business_type.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
class HistoryItem extends StatelessWidget {
final dynamic videoItem;
@@ -50,8 +52,15 @@ class HistoryItem extends StatelessWidget {
// 'pageTitle': videoItem.title
// },
// );
PiliScheme.routePush(Uri.parse(
"https://www.bilibili.com/read/cv${videoItem.history.oid}"));
Utils.toDupNamed(
'/htmlRender',
parameters: {
'url': 'https://www.bilibili.com/read/cv${videoItem.history.oid}',
'title': '',
'id': 'cv${videoItem.history.oid}',
'dynamicType': 'read'
},
);
} else if (videoItem.history.business == 'live') {
if (videoItem.liveStatus == 1) {
// LiveItemModel liveItem = LiveItemModel.fromJson({
@@ -185,7 +194,6 @@ class HistoryItem extends StatelessWidget {
return Stack(
children: [
NetworkImgLayer(
radius: 12,
src: (videoItem.cover != ''
? videoItem.cover
: videoItem.covers.first),
@@ -227,7 +235,7 @@ class HistoryItem extends StatelessWidget {
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(10),
color: Colors.black.withOpacity(0.6),
),
child: SizedBox(
@@ -273,20 +281,10 @@ class HistoryItem extends StatelessWidget {
left: 0,
right: 0,
bottom: 0,
child: ClipRect(
clipper: _Clipper(),
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(12),
bottomRight: Radius.circular(12),
),
child: LinearProgressIndicator(
minHeight: 12,
value: videoItem.progress == -1
? 100
: videoItem.progress / videoItem.duration,
),
),
child: videoProgressIndicator(
videoItem.progress == -1
? 1
: videoItem.progress / videoItem.duration,
),
),
],
@@ -350,59 +348,79 @@ class HistoryItem extends StatelessWidget {
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline),
),
SizedBox(
width: 24,
height: 24,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
tooltip: '功能菜单',
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
position: PopupMenuPosition.under,
// constraints: const BoxConstraints(maxHeight: 35),
onSelected: (String type) {},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
if (videoItem.history?.business != 'pgc' &&
videoItem.badge != '番剧' &&
!videoItem.tagName.contains('动画') &&
videoItem.history.business != 'live' &&
!videoItem.history.business.contains('article'))
if (videoItem is HisListItem)
SizedBox(
width: 29,
height: 29,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
tooltip: '功能菜单',
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
position: PopupMenuPosition.under,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
if (videoItem.authorMid != null &&
videoItem.authorName?.isNotEmpty == true)
PopupMenuItem<String>(
onTap: () {
Get.toNamed(
'/member?mid=${videoItem.authorMid}',
arguments: {
'heroTag': '${videoItem.authorMid}',
},
);
},
height: 35,
child: Row(
children: [
Icon(MdiIcons.accountCircleOutline, size: 16),
SizedBox(width: 6),
Text(
'访问:${videoItem.authorName}',
style: TextStyle(fontSize: 13),
)
],
),
),
if (videoItem.history?.business != 'pgc' &&
videoItem.badge != '番剧' &&
!videoItem.tagName.contains('动画') &&
videoItem.history.business != 'live' &&
!videoItem.history.business.contains('article'))
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
bvid: videoItem.history.bvid);
SmartDialog.showToast(res['msg']);
},
height: 35,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
bvid: videoItem.history.bvid);
SmartDialog.showToast(res['msg']);
},
value: 'pause',
onTap: () => ctr!.delHistory(
videoItem.kid, videoItem.history.business),
height: 35,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
Icon(Icons.close_outlined, size: 16),
SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
Text('删除记录', style: TextStyle(fontSize: 13))
],
),
),
PopupMenuItem<String>(
onTap: () => ctr!.delHistory(
videoItem.kid, videoItem.history.business),
value: 'pause',
height: 35,
child: const Row(
children: [
Icon(Icons.close_outlined, size: 16),
SizedBox(width: 6),
Text('删除记录', style: TextStyle(fontSize: 13))
],
),
),
],
],
),
),
),
],
),
],
@@ -410,15 +428,3 @@ class HistoryItem extends StatelessWidget {
);
}
}
class _Clipper extends CustomClipper<Rect> {
@override
Rect getClip(Size size) {
return Rect.fromLTWH(0, 8, size.width, size.height - 8);
}
@override
bool shouldReclip(CustomClipper<Rect> oldClipper) {
return false;
}
}

View File

@@ -105,10 +105,12 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
}
void querySearchDefault() async {
var res = await Request().get(Api.searchDefault);
if (res.data['code'] == 0) {
defaultSearch.value = res.data['data']['name'];
}
try {
var res = await Request().get(Api.searchDefault);
if (res.data['code'] == 0) {
defaultSearch.value = res.data['data']['name'];
}
} catch (_) {}
}
showUserInfoDialog(context) {

View File

@@ -47,6 +47,7 @@ class _HomePageState extends State<HomePage>
if (_homeController.tabs.length > 1) ...[
const SizedBox(height: 4),
Material(
color: Theme.of(context).colorScheme.surface,
child: SizedBox(
height: 42,
child: TabBar(

View File

@@ -5,7 +5,6 @@ import 'package:PiliPlus/common/widgets/video_card_h.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/tab_type.dart';
import 'package:PiliPlus/pages/rank/view.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
@@ -70,7 +69,7 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
mainAxisSize: MainAxisSize.min,
children: [
Image.network(width: 35, height: 35, iconUrl),
const SizedBox(height: 2),
const SizedBox(height: 4),
Text(
title,
style: TextStyle(fontSize: 12),
@@ -129,9 +128,13 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
'http://i0.hdslb.com/bfs/archive/552ebe8c4794aeef30ebd1568b59ad35f15e21ad.png',
title: '每周必看',
onTap: () {
Utils.handleWebview(
'https://www.bilibili.com/h5/weekly-recommend',
inApp: true,
Get.toNamed(
'/webview',
parameters: {
'url':
'https://www.bilibili.com/h5/weekly-recommend'
},
arguments: {'off': false},
);
},
),
@@ -140,9 +143,13 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
'http://i0.hdslb.com/bfs/archive/3693ec9335b78ca57353ac0734f36a46f3d179a9.png',
title: '入站必刷',
onTap: () {
Utils.handleWebview(
'https://www.bilibili.com/h5/good-history',
inApp: true,
Get.toNamed(
'/webview',
parameters: {
'url':
'https://www.bilibili.com/h5/good-history'
},
arguments: {'off': false},
);
},
),

View File

@@ -1,8 +1,12 @@
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPlus/http/dynamics.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/common/reply_controller.dart';
import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/url_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/http/html.dart';
import 'package:PiliPlus/http/reply.dart';
@@ -16,20 +20,44 @@ class HtmlRenderController extends ReplyController {
late Map response;
int? floor;
Rx<DynamicItemModel> item = DynamicItemModel().obs;
RxBool loaded = false.obs;
late final horizontalPreview = GStorage.horizontalPreview;
@override
dynamic get sourceId => id;
@override
void onInit() {
super.onInit();
id = Get.parameters['id']!;
dynamicType = Get.parameters['dynamicType']!;
type = dynamicType == 'picture' ? 11 : 12;
if (RegExp(r'^cv', caseSensitive: false).hasMatch(id)) {
UrlUtils.parseRedirectUrl('https://www.bilibili.com/read/$id/')
.then((url) {
if (url != null) {
_queryDyn(url.split('/').last);
}
});
} else {
_queryDyn(id);
}
reqHtml();
}
_queryDyn(id) {
DynamicsHttp.dynamicDetail(id: id).then((res) {
if (res['status']) {
item.value = res['data'];
} else {
debugPrint('${res['msg']}');
}
});
}
// 请求动态内容
Future reqHtml() async {
late dynamic res;
@@ -55,9 +83,10 @@ class HtmlRenderController extends ReplyController {
oid: oid.value,
cursor: CursorReq(
next: cursor?.next ?? $fixnum.Int64(0),
mode: mode,
mode: mode.value,
),
banWordForReply: banWordForReply,
antiGoodsReply: antiGoodsReply,
)
: ReplyHttp.replyList(
isLogin: isLogin,
@@ -67,5 +96,6 @@ class HtmlRenderController extends ReplyController {
sort: sortType.value.index,
page: currentPage,
banWordForReply: banWordForReply,
antiGoodsReply: antiGoodsReply,
);
}

View File

@@ -3,8 +3,10 @@ import 'dart:math';
import 'package:PiliPlus/common/widgets/article_content.dart';
import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/reply_sort_type.dart';
import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart';
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item.dart';
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart';
import 'package:PiliPlus/utils/extension.dart';
@@ -14,6 +16,7 @@ import 'package:PiliPlus/utils/utils.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/skeleton/video_reply.dart';
import 'package:PiliPlus/common/widgets/html_render.dart';
@@ -230,9 +233,9 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
),
),
IconButton(
tooltip: '用内置浏览器打开',
tooltip: '浏览器打开',
onPressed: () {
Utils.handleWebview(url.startsWith('http') ? url : 'https:$url');
Utils.inAppWebview(url.startsWith('http') ? url : 'https:$url');
},
icon: const Icon(Icons.open_in_browser_outlined, size: 19),
),
@@ -253,9 +256,9 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
),
),
PopupMenuItem(
onTap: () => {
Utils.handleWebview(
url.startsWith('http') ? url : 'https:$url')
onTap: () {
Utils.inAppWebview(
url.startsWith('http') ? url : 'https:$url');
},
child: const Row(
mainAxisSize: MainAxisSize.min,
@@ -323,7 +326,11 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
SliverPadding(
padding: orientation == Orientation.portrait
? EdgeInsets.symmetric(horizontal: padding)
: EdgeInsets.only(left: padding / 4),
: EdgeInsets.only(
left: padding / 4,
bottom:
MediaQuery.paddingOf(context).bottom + 80,
),
sliver: _buildContent,
),
if (orientation == Orientation.portrait) ...[
@@ -388,28 +395,265 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
},
),
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 14,
right: 14,
left: 0,
right: 0,
bottom: 0,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 2),
begin: const Offset(0, 1),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: fabAnimationCtr,
curve: Curves.easeInOut,
)),
child: FloatingActionButton(
heroTag: null,
onPressed: () {
feedBack();
_htmlRenderCtr.onReply(
context,
oid: _htmlRenderCtr.oid.value,
replyType: ReplyType.values[type],
);
},
tooltip: '评论动态',
child: const Icon(Icons.reply),
child: Obx(
() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Padding(
padding: EdgeInsets.only(
right: 14,
bottom: 14 +
(_htmlRenderCtr.item.value.idStr != null
? 0
: MediaQuery.of(context).padding.bottom),
),
child: FloatingActionButton(
heroTag: null,
onPressed: () {
feedBack();
_htmlRenderCtr.onReply(
context,
oid: _htmlRenderCtr.oid.value,
replyType: ReplyType.values[type],
);
},
tooltip: '评论动态',
child: const Icon(Icons.reply),
),
),
_htmlRenderCtr.item.value.idStr != null
? Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border(
top: BorderSide(
color: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.08),
),
),
),
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: Builder(
builder: (btnContext) => TextButton.icon(
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (context) => RepostPanel(
item: _htmlRenderCtr.item.value,
callback: () {
int count = int.tryParse(
_htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.forward
?.count ??
'0') ??
0;
_htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.forward!
.count =
(count + 1).toString();
if (btnContext.mounted) {
(btnContext as Element?)
?.markNeedsBuild();
}
},
),
);
},
icon: Icon(
FontAwesomeIcons.shareFromSquare,
size: 16,
color: Theme.of(context)
.colorScheme
.outline,
semanticLabel: "转发",
),
style: TextButton.styleFrom(
padding: const EdgeInsets.fromLTRB(
15, 0, 15, 0),
foregroundColor: Theme.of(context)
.colorScheme
.outline,
),
label: Text(
_htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.forward!
.count !=
null
? Utils.numFormat(_htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.forward!
.count)
: '转发',
),
),
),
),
Expanded(
child: TextButton.icon(
onPressed: () {
Share.share(
'${HttpString.dynamicShareBaseUrl}/${_htmlRenderCtr.item.value.idStr}');
},
icon: Icon(
FontAwesomeIcons.shareNodes,
size: 16,
color:
Theme.of(context).colorScheme.outline,
semanticLabel: "分享",
),
style: TextButton.styleFrom(
padding: const EdgeInsets.fromLTRB(
15, 0, 15, 0),
foregroundColor:
Theme.of(context).colorScheme.outline,
),
label: const Text('分享'),
),
),
Expanded(
child: Builder(
builder: (context) => TextButton.icon(
onPressed: () => Utils.onLikeDynamic(
_htmlRenderCtr.item.value,
() {
if (context.mounted) {
(context as Element?)
?.markNeedsBuild();
}
},
),
icon: Icon(
_htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.like
?.status ==
true
? FontAwesomeIcons.solidThumbsUp
: FontAwesomeIcons.thumbsUp,
size: 16,
color: _htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.like
?.status ==
true
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.outline,
semanticLabel: _htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.like
?.status ==
true
? "已赞"
: "点赞",
),
style: TextButton.styleFrom(
padding: const EdgeInsets.fromLTRB(
15, 0, 15, 0),
foregroundColor: Theme.of(context)
.colorScheme
.outline,
),
label: AnimatedSwitcher(
duration:
const Duration(milliseconds: 400),
transitionBuilder: (Widget child,
Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
_htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.like
?.count !=
null
? Utils.numFormat(_htmlRenderCtr
.item
.value
.modules!
.moduleStat!
.like!
.count)
: '点赞',
style: TextStyle(
color: _htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.like
?.status ==
true
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.outline,
),
),
),
),
),
),
],
),
)
: const SizedBox.shrink(),
],
),
),
),
),
@@ -564,14 +808,17 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
);
Widget get _buildContent => SliverPadding(
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
sliver: Obx(
() => _htmlRenderCtr.loaded.value
? _htmlRenderCtr.response['isJsonContent'] == true
? articleContent(
context: context,
list: _htmlRenderCtr.response['content'],
callback: _getImageCallback,
? SliverLayoutBuilder(
builder: (context, constraints) => articleContent(
context: context,
list: _htmlRenderCtr.response['content'],
callback: _getImageCallback,
maxWidth: constraints.crossAxisExtent,
),
)
: SliverToBoxAdapter(
child: LayoutBuilder(
@@ -583,9 +830,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
),
),
)
: SliverToBoxAdapter(
child: const SizedBox(),
),
: SliverToBoxAdapter(child: const SizedBox()),
),
);
}

View File

@@ -8,7 +8,6 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/live/controller.dart';
import 'package:PiliPlus/pages/live/widgets/live_item.dart';
import 'package:PiliPlus/pages/live/widgets/live_item_follow.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
@@ -226,7 +225,7 @@ class _LivePageState extends State<LivePage>
);
},
onLongPress: () {
feedBack();
Feedback.forLongPress(context);
Get.toNamed(
'/member?mid=${loadingState.response[index].uid}',
arguments: {

View File

@@ -298,7 +298,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
IconButton(
tooltip: '浏览器打开',
onPressed: () {
Utils.handleWebview(
Utils.inAppWebview(
'https://live.bilibili.com/h5/${_liveRoomController.roomId}',
off: true,
);
@@ -313,28 +313,30 @@ class _LiveRoomPageState extends State<LiveRoomPage>
},
),
),
PopScope(
canPop: plPlayerController.isFullScreen.value != true,
onPopInvokedWithResult: (bool didPop, Object? result) {
if (plPlayerController.isFullScreen.value == true) {
plPlayerController.triggerFullScreen(status: false);
// if (MediaQuery.of(context).orientation ==
// Orientation.landscape) {
// verticalScreenForTwoSeconds();
// }
}
},
child: Listener(
onPointerDown: (_) {
_node.unfocus();
Obx(
() => PopScope(
canPop: plPlayerController.isFullScreen.value != true,
onPopInvokedWithResult: (bool didPop, Object? result) {
if (plPlayerController.isFullScreen.value == true) {
plPlayerController.triggerFullScreen(status: false);
// if (MediaQuery.of(context).orientation ==
// Orientation.landscape) {
// verticalScreenForTwoSeconds();
// }
}
},
child: SizedBox(
width: Get.size.width,
height: MediaQuery.of(context).orientation ==
Orientation.landscape
? Get.size.height
: Get.size.width * 9 / 16,
child: videoPlayerPanel,
child: Listener(
onPointerDown: (_) {
_node.unfocus();
},
child: SizedBox(
width: Get.size.width,
height: MediaQuery.of(context).orientation ==
Orientation.landscape
? Get.size.height
: Get.size.width * 9 / 16,
child: videoPlayerPanel,
),
),
),
),

View File

@@ -83,7 +83,6 @@ class _BottomControlState extends State<BottomControl> {
margin: const EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center,
child: PopupMenuButton<BoxFit>(
onSelected: widget.controller.toggleVideoFit,
initialValue: widget.controller.videoFit.value,
color: Colors.black.withOpacity(0.8),
itemBuilder: (BuildContext context) {
@@ -92,6 +91,9 @@ class _BottomControlState extends State<BottomControl> {
height: 35,
padding: const EdgeInsets.only(left: 30),
value: boxFit,
onTap: () {
widget.controller.toggleVideoFit(boxFit);
},
child: Text(
"${PlPlayerController.videoFitType[boxFit.index]['desc']}",
style:
@@ -142,9 +144,6 @@ class _BottomControlState extends State<BottomControl> {
child: PopupMenuButton<int>(
padding: EdgeInsets.zero,
initialValue: widget.liveRoomCtr.currentQn,
onSelected: (value) {
widget.liveRoomCtr.changeQn(value);
},
child: Text(
widget.liveRoomCtr.currentQnDesc.value,
style: const TextStyle(color: Colors.white, fontSize: 13),
@@ -153,6 +152,9 @@ class _BottomControlState extends State<BottomControl> {
return widget.liveRoomCtr.acceptQnList.map((e) {
return PopupMenuItem<int>(
value: e['code'],
onTap: () {
widget.liveRoomCtr.changeQn(e['code']);
},
child: Text(e['desc']),
);
}).toList();

View File

@@ -124,37 +124,41 @@ class MainController extends GetxController {
}
Future _queryPMUnread() async {
dynamic res = await Request().get(Api.msgUnread);
if (res.data['code'] == 0) {
return {
'status': true,
'data': ((res.data['data']?['unfollow_unread'] as int?) ?? 0) +
((res.data['data']?['follow_unread'] as int?) ?? 0),
};
} else {
return {
'status': false,
'msg': res.data['message'],
};
}
try {
dynamic res = await Request().get(Api.msgUnread);
if (res.data['code'] == 0) {
return {
'status': true,
'data': ((res.data['data']?['unfollow_unread'] as int?) ?? 0) +
((res.data['data']?['follow_unread'] as int?) ?? 0),
};
} else {
return {
'status': false,
'msg': res.data['message'],
};
}
} catch (_) {}
}
Future _queryMsgFeedUnread() async {
if (isLogin.value.not) {
return;
}
dynamic res = await Request().get(Api.msgFeedUnread);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
'msg': res.data['message'],
};
}
try {
dynamic res = await Request().get(Api.msgFeedUnread);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
'msg': res.data['message'],
};
}
} catch (_) {}
}
void getUnreadDynamic() async {

View File

@@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/tabs.dart';
import 'package:PiliPlus/grpc/grpc_client.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -64,12 +65,19 @@ class _MainAppState extends State<MainApp>
@override
void didPopNext() {
WidgetsBinding.instance.addObserver(this);
_mainController.checkUnreadDynamic();
_checkDefaultSearch(true);
_checkUnread(context.orientation == Orientation.portrait);
super.didPopNext();
}
@override
void didPushNext() {
WidgetsBinding.instance.removeObserver(this);
super.didPushNext();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
@@ -156,6 +164,7 @@ class _MainAppState extends State<MainApp>
await GrpcClient.instance.shutdown();
await GStorage.close();
EventBus().off(EventName.loginEvent);
PiliScheme.listener?.cancel();
super.dispose();
}

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