Compare commits

..

98 Commits

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

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

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

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

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

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

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

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

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

opt: video progress indicator

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

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

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

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

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

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

ref pilipala

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

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

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

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

View File

@@ -47,6 +47,17 @@
## feat
- [x] 互动视频
- [x] 发评反诈
- [x] 高能进度条
- [x] 滑动跳转预览视频缩略图
- [x] Live Photo
- [x] 复制/移动收藏夹/稍后再看视频
- [x] 超分辨率
- [x] 合并弹幕
- [x] 会员彩色弹幕
- [x] 播放全部/继续播放/倒序播放
- [x] Cookie登录
- [x] 显示视频分段信息
- [x] 调节字幕大小
- [x] 调节全屏弹幕大小
@@ -74,7 +85,7 @@
- [x] 转发动态
- [x] 合集图片
- [x] 删除/置顶私信
- [x] 举报用户/评论/视频
- [x] 举报用户/评论/视频/动态
- [x] 删除/发布文本/图片动态
- [x] 其他

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

@@ -254,7 +254,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
String _getActualUrl(int index) => _thumbList[index] && _quality != 100
? '${widget.sources[index]}@${_quality}q.webp'.http2https
? '${widget.sources[index].url}@${_quality}q.webp'.http2https
: widget.sources[index].url.http2https;
void onClose() {

View File

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

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

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

@@ -79,10 +79,14 @@ class DynamicsHttp {
//
static Future dynamicDetail({
String? id,
dynamic rid,
dynamic type,
}) async {
var res = await Request().get(Api.dynamicDetail, queryParameters: {
'timezone_offset': -480,
'id': id,
if (id != null) 'id': id,
if (rid != null) 'rid': rid,
if (type != null) 'type': type,
'features': 'itemOpusStyle',
});
if (res.data['code'] == 0) {

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 {

View File

@@ -560,9 +560,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,17 @@
import 'dart:io';
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart';
import '../models/video/reply/data.dart';
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,
@@ -23,13 +21,9 @@ class ReplyHttp {
int sort = 1,
required String banWordForReply,
}) 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 +31,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 +42,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']);
@@ -134,28 +128,25 @@ class ReplyHttp {
}
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,
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']);
@@ -168,7 +159,11 @@ class ReplyHttp {
}
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'],
);
}
}

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

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

@@ -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'];
@@ -364,7 +364,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 +411,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 +557,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 +577,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 +596,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 +635,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

@@ -186,13 +186,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']);
@@ -418,11 +426,7 @@ class BangumiIntroController extends CommonController {
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
..plPlayerController.pause()
..makeHeartBeat()
..playedTime = null
..videoUrl = null
..audioUrl = null
..vttSubtitlesIndex = null
..savedDanmaku = null
..onReset()
..epId = epId
..bvid = bvid
..cid.value = cid
@@ -600,21 +604,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

@@ -82,48 +82,48 @@ 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(

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.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 +10,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 +29,11 @@ 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 enableCommAntifraud = GStorage.enableCommAntifraud;
@override
void onInit() {
@@ -43,7 +46,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 +98,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 +120,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 +207,44 @@ abstract class ReplyController extends CommonController {
}
count.value += 1;
loadingState.value = LoadingState.success(response);
if (enableCommAntifraud && context.mounted) {
checkReply(
context,
oid ?? replyItem.oid.toInt(),
replyItem?.id.toInt(),
replyItem?.type.toInt() ??
replyType?.index ??
ReplyType.video.index,
replyInfo.id.toInt(),
replyInfo.content.message,
);
}
} 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,
oid ?? replyItem.oid,
replyItem?.rpid,
replyItem?.type.toInt() ??
replyType?.index ??
ReplyType.video.index,
replyInfo.rpid ?? 0,
replyInfo.content?.message ?? '',
);
}
}
}
},
@@ -262,4 +288,186 @@ abstract class ReplyController extends CommonController {
loadingState.value = LoadingState.success(response);
}
}
// ref https://github.com/freedom-introvert/biliSendCommAntifraud
void checkReply(
BuildContext context,
dynamic oid,
dynamic rpid,
int replyType,
int replyId,
String message,
) async {
void showReplyCheckResult(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('评论检查结果'),
content: SelectableText(message),
),
);
}
await Future.delayed(const Duration(seconds: 5));
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: '',
);
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: '',
);
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,
);
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,
);
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,
);
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;
@@ -50,7 +47,7 @@ class PlDanmakuController {
final DmSegMobileReply result = await DanmakaHttp.queryDanmaku(
cid: cid,
segmentIndex: segmentIndex + 1,
mergeDanmaku: mergeDanmaku,
mergeDanmaku: plPlayerController.mergeDanmaku,
);
if (result.elems.isNotEmpty) {
for (var element in result.elems) {

View File

@@ -45,7 +45,7 @@ class DynamicDetailController extends ReplyController {
oid: oid!,
cursor: CursorReq(
next: cursor?.next ?? $fixnum.Int64(0),
mode: mode,
mode: mode.value,
),
banWordForReply: banWordForReply,
)

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

@@ -110,78 +110,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 +199,16 @@ class AuthorPanel extends StatelessWidget {
),
)
: null,
),
),
),
),
),
],
),
// ),
_moreWidget(context),
],
),
// ),
_moreWidget(context),
],
)
: _moreWidget(context),
)
: _moreWidget(context),
)
],
);
@@ -306,6 +312,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 +370,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,40 @@ 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) {
ItemOrigModel() => articlePanel(item, context, callback, floor: floor),
DynamicItemModel() => 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 +239,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 +335,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

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

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

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

View File

@@ -55,7 +55,7 @@ class HtmlRenderController extends ReplyController {
oid: oid.value,
cursor: CursorReq(
next: cursor?.next ?? $fixnum.Int64(0),
mode: mode,
mode: mode.value,
),
banWordForReply: banWordForReply,
)

View File

@@ -230,9 +230,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 +253,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,
@@ -564,14 +564,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 +586,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();
}

View File

@@ -60,7 +60,7 @@ class _MemberArticleState extends State<MemberArticle>
return ListTile(
dense: true,
onTap: () {
PiliScheme.routePush(Uri.parse(item.uri ?? ''));
PiliScheme.routePushFromUrl(item.uri ?? '');
},
leading: item.originImageUrls?.isNotEmpty == true
? Container(

View File

@@ -142,7 +142,7 @@ class _MemberFavoriteState extends State<MemberFavorite>
});
}
} else if (item1.type == 21) {
PiliScheme.routePush(Uri.parse(item1.link ?? ''));
PiliScheme.routePushFromUrl(item1.link ?? '');
} else if (item1.type == 11) {
Get.toNamed(
'/subDetail',

View File

@@ -118,9 +118,9 @@ class _MemberHomeState extends State<MemberHome>
child: ListTile(
dense: true,
onTap: () {
PiliScheme.routePush(Uri.parse(
loadingState.response.article.item.first.uri ??
''));
PiliScheme.routePushFromUrl(
loadingState.response.article.item.first.uri ?? '',
);
},
leading: loadingState.response.article.item.first
.originImageUrls?.isNotEmpty ==

View File

@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/http/video.dart';
@@ -46,6 +48,14 @@ class MemberControllerNew extends CommonController
int? silence;
String? endTime;
late final implTabs = const [
'home',
'dynamic',
'contribute',
'favorite',
'bangumi',
];
@override
bool customHandleResponse(Success response) {
Data data = response.response;
@@ -68,7 +78,8 @@ class MemberControllerNew extends CommonController
':至 ${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(data.card!.endTime! * 1000))}';
}
}
if (tab2 != null && tab2!.isNotEmpty) {
tab2?.retainWhere((item) => implTabs.contains(item.param));
if (tab2?.isNotEmpty == true) {
if (!data.tab!.toJson().values.contains(true) &&
tab2!.first.param == 'home') {
// remove empty home tab
@@ -93,8 +104,8 @@ class MemberControllerNew extends CommonController
tabs = tab2!.map((item) => Tab(text: item.title ?? '')).toList();
tabController = TabController(
vsync: this,
length: tab2!.length,
initialIndex: initialIndex == -1 ? 0 : initialIndex,
length: tabs.length,
initialIndex: max(0, initialIndex),
);
}
}

View File

@@ -134,14 +134,17 @@ class _MemberPageNewState extends State<MemberPageNew>
);
}
Widget get _buildTab => TabBar(
controller: _userController.tabController,
tabs: _userController.tabs,
onTap: (value) {
if (_userController.tabController?.indexIsChanging == false) {
_key.currentState?.outerController.animToTop();
}
},
Widget get _buildTab => Material(
color: Theme.of(context).colorScheme.surface,
child: TabBar(
controller: _userController.tabController,
tabs: _userController.tabs,
onTap: (value) {
if (_userController.tabController?.indexIsChanging == false) {
_key.currentState?.outerController.animToTop();
}
},
),
);
Widget get _buildBody => SafeArea(
@@ -198,9 +201,7 @@ class _MemberPageNewState extends State<MemberPageNew>
bottom: needTab && (_userController.tab2?.length ?? -1) > 1
? PreferredSize(
preferredSize: Size.fromHeight(48),
child: Material(
child: _buildTab,
),
child: _buildTab,
)
: null,
actions: [

View File

@@ -55,7 +55,8 @@ class _EditProfilePageState extends State<EditProfilePage> {
_getInfo() async {
Map<String, String> data = {
'access_key': GStorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
'',
'appkey': Constants.appKey,
'build': '1462100',
'c_locale': 'zh_CN',
@@ -329,7 +330,8 @@ class _EditProfilePageState extends State<EditProfilePage> {
}) async {
Map<String, String> data = {
'access_key': GStorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
'',
'appkey': Constants.appKey,
'build': '1462100',
'c_locale': 'zh_CN',

View File

@@ -79,11 +79,11 @@ class UserInfoCard extends StatelessWidget {
: images.nightImgurl?.http2https)
: images.imgUrl?.http2https;
return Hero(
tag: imgUrl ?? 'bgTag',
tag: imgUrl ?? '',
child: GestureDetector(
onTap: () {
context.imageView(
imgList: [SourceModel(url: imgUrl ?? 'bgTag')],
imgList: [SourceModel(url: imgUrl ?? '')],
);
},
child: CachedNetworkImage(
@@ -447,22 +447,22 @@ class UserInfoCard extends StatelessWidget {
),
);
_buildAvatar(BuildContext context) => Container(
decoration: BoxDecoration(
border: Border.all(
width: 2.5,
color: Theme.of(context).colorScheme.surface,
),
shape: BoxShape.circle,
),
child: Hero(
tag: card.face ?? 'avatarTag',
child: GestureDetector(
onTap: () {
context.imageView(
imgList: [SourceModel(url: card.face ?? 'avatarTag')],
);
},
_buildAvatar(BuildContext context) => Hero(
tag: card.face ?? '',
child: GestureDetector(
onTap: () {
context.imageView(
imgList: [SourceModel(url: card.face ?? '')],
);
},
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 2.5,
color: Theme.of(context).colorScheme.surface,
),
shape: BoxShape.circle,
),
child: NetworkImgLayer(
src: card.face,
type: 'avatar',

View File

@@ -127,7 +127,7 @@ class _MinePageState extends State<MinePage> {
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
behavior: HitTestBehavior.opaque,
onTap: _mineController.onLogin,
child: Row(
mainAxisSize: MainAxisSize.min,

View File

@@ -73,7 +73,7 @@ class _AtMePageState extends State<AtMePage> {
String? nativeUri =
_atMeController.msgFeedAtMeList[i].item?.nativeUri;
if (nativeUri != null) {
PiliScheme.routePush(Uri.parse(nativeUri));
PiliScheme.routePushFromUrl(nativeUri);
}
// SmartDialog.showToast("跳转至:$nativeUri暂未实现");
},

View File

@@ -122,7 +122,7 @@ class LikeMeList extends StatelessWidget {
onTap: () {
String? nativeUri = msgFeedLikeMeList[i].item?.nativeUri;
if (nativeUri != null) {
PiliScheme.routePush(Uri.parse(nativeUri));
PiliScheme.routePushFromUrl(nativeUri);
}
// SmartDialog.showToast("跳转至:$nativeUri暂未实现");
},

View File

@@ -72,7 +72,7 @@ class _ReplyMePageState extends State<ReplyMePage> {
String? nativeUri = _replyMeController
.msgFeedReplyMeList[i].item?.nativeUri;
if (nativeUri != null) {
PiliScheme.routePush(Uri.parse(nativeUri));
PiliScheme.routePushFromUrl(nativeUri);
}
// SmartDialog.showToast("跳转至:$nativeUri暂未实现");
},

View File

@@ -184,8 +184,7 @@ class _SysMsgPageState extends State<SysMsgPage> {
recognizer: TapGestureRecognizer()
..onTap = () {
try {
Uri uri = Uri.parse(match[2]!.replaceAll('"', ''));
PiliScheme.routePush(uri);
PiliScheme.routePushFromUrl(match[2]!.replaceAll('"', ''));
} catch (err) {
SmartDialog.showToast(err.toString());
}
@@ -209,8 +208,7 @@ class _SysMsgPageState extends State<SysMsgPage> {
recognizer: TapGestureRecognizer()
..onTap = () {
try {
Uri uri = Uri.parse(match[3]!);
PiliScheme.routePush(uri);
PiliScheme.routePushFromUrl(match[3]!);
} catch (err) {
SmartDialog.showToast(err.toString());
}
@@ -231,8 +229,7 @@ class _SysMsgPageState extends State<SysMsgPage> {
recognizer: TapGestureRecognizer()
..onTap = () {
try {
Uri uri = Uri.parse(match[0]!);
PiliScheme.routePush(uri);
PiliScheme.routePushFromUrl(match[0]!);
} catch (err) {
SmartDialog.showToast(err.toString());
Utils.copyText(match[0] ?? '');

View File

@@ -23,6 +23,7 @@ class HotKeyword extends StatelessWidget {
SizedBox(
width: width! / 2 - 4,
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(3),
clipBehavior: Clip.hardEdge,
child: InkWell(

View File

@@ -31,9 +31,11 @@ class SearchText extends StatelessWidget {
onTap: () {
onTap?.call(text);
},
onLongPress: () {
onLongPress?.call(text);
},
onLongPress: onLongPress != null
? () {
onLongPress!(text);
}
: null,
borderRadius: BorderRadius.circular(6),
child: Padding(
padding: padding ??

View File

@@ -1,19 +1,18 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/search_result/controller.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/common/search_type.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
class SearchPanelController extends CommonController {
SearchPanelController({
this.keyword,
required this.keyword,
required this.searchType,
required this.tag,
});
String? keyword;
String keyword;
SearchType searchType;
// 结果排序方式 搜索类型为视频、专栏及相簿时
RxString order = ''.obs;
@@ -26,10 +25,14 @@ class SearchPanelController extends CommonController {
String tag;
int? pubBegin;
int? pubEnd;
bool? hasJump2Video;
@override
void onInit() {
super.onInit();
if (searchType == SearchType.video) {
jump2Video();
}
queryData();
}
@@ -46,7 +49,10 @@ class SearchPanelController extends CommonController {
?.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(response.response.list);
if (currentPage == 1) {
if (searchType == SearchType.video &&
hasJump2Video != true &&
currentPage == 1) {
hasJump2Video = true;
onPushDetail(response.response.list);
}
} else {
@@ -58,48 +64,32 @@ class SearchPanelController extends CommonController {
return true;
}
void jump2Video() {
if (RegExp(r'^av\d+$', caseSensitive: false).hasMatch(keyword)) {
hasJump2Video = true;
PiliScheme.videoPush(
int.parse(keyword.substring(2)),
null,
showDialog: false,
);
} else if (RegExp(r'^bv[a-z\d]{10}$', caseSensitive: false)
.hasMatch(keyword)) {
hasJump2Video = true;
PiliScheme.videoPush(null, keyword, showDialog: false);
}
}
void onPushDetail(resultList) async {
// 匹配输入内容如果是AV、BV号且有结果 直接跳转详情页
Map matchRes = IdUtils.matchAvorBv(input: keyword);
List matchKeys = matchRes.keys.toList();
String? bvid;
try {
bvid = resultList.first.bvid;
} catch (_) {
bvid = null;
}
// keyword 可能输入纯数字
int? aid;
try {
aid = resultList.first.aid;
} catch (_) {
aid = null;
}
if (matchKeys.isNotEmpty && searchType == SearchType.video ||
aid.toString() == keyword) {
int cid = await SearchHttp.ab2c(aid: aid, bvid: bvid);
if (matchKeys.isNotEmpty &&
matchKeys.first == 'BV' &&
matchRes[matchKeys.first] == bvid ||
matchKeys.isNotEmpty &&
matchKeys.first == 'AV' &&
matchRes[matchKeys.first] == aid ||
aid.toString() == keyword) {
Get.toNamed(
'/video?bvid=$bvid&cid=$cid',
arguments: {
'videoItem': resultList.first,
'heroTag': Utils.makeHeroTag(bvid),
},
);
}
int? aid = int.tryParse(keyword);
if (aid != null && resultList.first.aid == aid) {
PiliScheme.videoPush(aid, null, showDialog: false);
}
}
@override
Future<LoadingState> customGetData() => SearchHttp.searchByType(
searchType: searchType,
keyword: keyword!,
keyword: keyword,
page: currentPage,
order: order.value,
duration: searchType.name != 'video' ? null : duration.value,

View File

@@ -16,7 +16,7 @@ import 'widgets/media_bangumi_panel.dart';
import 'widgets/user_panel.dart';
class SearchPanel extends StatefulWidget {
final String? keyword;
final String keyword;
final SearchType searchType;
final String tag;
const SearchPanel({
@@ -46,7 +46,7 @@ class _SearchPanelState extends State<SearchPanel>
searchType: widget.searchType,
tag: widget.tag,
),
tag: widget.searchType.name + widget.keyword!,
tag: widget.searchType.name + widget.keyword,
);
}

View File

@@ -257,6 +257,7 @@ class ArticlePanelController extends GetxController {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Theme.of(context).colorScheme.surface,
builder: (context) => SingleChildScrollView(
child: Container(
width: double.infinity,
@@ -287,13 +288,12 @@ class ArticlePanelController extends GetxController {
SmartDialog.showToast("${item['label']}」的筛选结果");
SearchPanelController ctr =
Get.find<SearchPanelController>(
tag: 'article${searchPanelCtr.keyword!}');
tag: 'article${searchPanelCtr.keyword}');
ctr.order.value = item['order'];
SmartDialog.showLoading(msg: 'loading');
await ctr.onRefresh();
SmartDialog.dismiss();
},
onLongPress: (_) {},
bgColor: item['value'] == currentOrderFilterval.value
? Theme.of(context).colorScheme.secondaryContainer
: null,
@@ -321,13 +321,12 @@ class ArticlePanelController extends GetxController {
SmartDialog.showToast("${item['label']}」的筛选结果");
SearchPanelController ctr =
Get.find<SearchPanelController>(
tag: 'article${searchPanelCtr.keyword!}');
tag: 'article${searchPanelCtr.keyword}');
ctr.categoryId = item['categoryId'];
SmartDialog.showLoading(msg: 'loading');
await ctr.onRefresh();
SmartDialog.dismiss();
},
onLongPress: (_) {},
bgColor: item['value'] == currentZoneFilterval.value
? Theme.of(context).colorScheme.secondaryContainer
: null,

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