Compare commits

..

788 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
ea32f705f5 fix check reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-20 13:20:02 +08:00
bggRGjQaUbCoE
66b7d27dc4 tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-20 12:47:22 +08:00
bggRGjQaUbCoE
05b512e8cc opt icon
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-19 22:24:48 +08:00
bggRGjQaUbCoE
a2da381f1a opt icon
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-19 20:42:20 +08:00
bggRGjQaUbCoE
e4654d63c3 fix vote
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-19 20:40:32 +08:00
bggRGjQaUbCoE
38b1af2696 opt member video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-19 15:38:56 +08:00
bggRGjQaUbCoE
81c6abb879 opt member video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-19 15:09:25 +08:00
bggRGjQaUbCoE
d4ad738888 opt live area
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-19 14:37:10 +08:00
bggRGjQaUbCoE
a62670eecf opt follow btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-19 14:23:55 +08:00
bggRGjQaUbCoE
25adc4face opt opus
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-19 13:44:59 +08:00
bggRGjQaUbCoE
8fd62cf2f3 opt opus rich text
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-19 11:59:37 +08:00
My-Responsitories
a360212dc7 feat: filter dyn (#860) 2025-05-19 01:31:41 +00:00
bggRGjQaUbCoE
d7dec1bc4d opt slide dismiss
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-19 00:06:26 +08:00
bggRGjQaUbCoE
8be86a2d95 opt slide dismiss
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 23:39:08 +08:00
bggRGjQaUbCoE
34949b8a7f opt to top
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 22:53:33 +08:00
bggRGjQaUbCoE
40502e3bff opt dyn topic
opt member opus

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 21:54:38 +08:00
bggRGjQaUbCoE
0de2603e30 opt search panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 17:29:36 +08:00
bggRGjQaUbCoE
e330359192 opt member search
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 17:14:33 +08:00
bggRGjQaUbCoE
ab80b2a5af opt list
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 17:01:55 +08:00
bggRGjQaUbCoE
f642bfcf48 opt get settings
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 15:15:23 +08:00
bggRGjQaUbCoE
805a63cf59 opt get horizontalScreen
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 15:01:49 +08:00
bggRGjQaUbCoE
4d430ba42c opt get pgc follow status
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 14:21:19 +08:00
bggRGjQaUbCoE
5f734758b4 opt playlist jump
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 13:32:58 +08:00
bggRGjQaUbCoE
8157dbc530 fix member opus jump
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 12:52:20 +08:00
bggRGjQaUbCoE
391d862b17 opt member info
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 12:28:07 +08:00
bggRGjQaUbCoE
271856ca89 opt member info
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 11:19:47 +08:00
bggRGjQaUbCoE
d7eb734aaf feat: fav topic
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-18 10:46:05 +08:00
bggRGjQaUbCoE
1d4eabb770 tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-17 19:01:46 +08:00
bggRGjQaUbCoE
906c21e252 tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-17 17:26:01 +08:00
dom
7ae92970ef bump flutter (#859)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-16 23:05:02 +08:00
My-Responsitories
cf0bf1e587 opt: vote (#858) 2025-05-16 13:44:14 +00:00
bggRGjQaUbCoE
616c129ffd opt top up panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-16 18:16:47 +08:00
bggRGjQaUbCoE
1326cc4966 opt rank type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-16 18:16:32 +08:00
bggRGjQaUbCoE
35bc4a6ece top up panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-15 18:48:31 +08:00
bggRGjQaUbCoE
e54a0f127f auto fill
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-15 18:16:26 +08:00
bggRGjQaUbCoE
070ecad54b dyn addition jump
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-15 18:05:30 +08:00
bggRGjQaUbCoE
205ae2bf55 reserve btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-15 13:24:43 +08:00
bggRGjQaUbCoE
d35c85f389 dyn detail
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-15 13:15:17 +08:00
bggRGjQaUbCoE
026e40855c auto fill
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-14 21:12:33 +08:00
bggRGjQaUbCoE
553be52260 reserve btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-14 17:58:43 +08:00
bggRGjQaUbCoE
69f9fb398f emoji setting
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-14 17:46:23 +08:00
bggRGjQaUbCoE
98985a7fa4 episode badge
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-14 17:46:15 +08:00
bggRGjQaUbCoE
3f71e79809 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-13 19:01:28 +08:00
bggRGjQaUbCoE
55138957b7 fix: zanbtn state
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-13 18:58:31 +08:00
bggRGjQaUbCoE
901e8d9cb8 fix: msg type 16
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-13 18:45:13 +08:00
bggRGjQaUbCoE
f140fc53ad opt: pgc url
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-13 18:30:30 +08:00
bggRGjQaUbCoE
9b8b699ace fix: whisper mid
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-13 18:16:51 +08:00
bggRGjQaUbCoE
39a355ab4c fix: multi vote
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-13 18:08:11 +08:00
bggRGjQaUbCoE
22f9285627 fix: #848
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-13 15:47:29 +08:00
bggRGjQaUbCoE
152eaf2627 feat: dyn reserve
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-13 14:57:27 +08:00
bggRGjQaUbCoE
d15b8091bc opt: readlist url, note item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-13 12:15:23 +08:00
bggRGjQaUbCoE
de9eb2292e mod: member video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-13 00:11:10 +08:00
徽忆.
9b86e24513 feat: decoration color (#856) 2025-05-13 00:03:35 +08:00
bggRGjQaUbCoE
9a97a5d110 feat: msg link setting
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-12 18:08:39 +08:00
bggRGjQaUbCoE
964668c982 feat: setMsgDnd
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-12 14:39:14 +08:00
bggRGjQaUbCoE
0514c0d999 opt: livelist
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-12 12:21:13 +08:00
bggRGjQaUbCoE
4a782332d3 mod: err string
fix: typo

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-11 23:20:29 +08:00
My-Responsitories
72734d4b4e opt: unread & zan grpc & readlist open with browser (#852)
* opt: unread

* opt: zan grpc

* feat: readlist open with browser
2025-05-11 10:58:00 +00:00
My-Responsitories
8d34e6f340 opt: model (#851)
* opt: readlist model

* opt: video item model
2025-05-11 09:00:24 +00:00
My-Responsitories
c899ea95e1 opt: reply type (#850) 2025-05-11 08:38:15 +00:00
bggRGjQaUbCoE
0b57cd3555 opt: delete reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-11 14:54:31 +08:00
bggRGjQaUbCoE
f9b4f587c2 opt: reply footer
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-11 14:48:47 +08:00
bggRGjQaUbCoE
279f586a90 opt: pgc coin
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-11 14:42:23 +08:00
bggRGjQaUbCoE
2f3f712256 refa: pgc intro
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-11 14:34:45 +08:00
bggRGjQaUbCoE
6748a20ddb fix: update filter
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-11 14:12:39 +08:00
bggRGjQaUbCoE
90ccb86a6f opt: space tab
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-11 13:38:58 +08:00
bggRGjQaUbCoE
574bf861f0 opt: common ctr
opt: state

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-11 12:22:47 +08:00
My-Responsitories
5bff1747e6 opt: IdUtils (#849) 2025-05-11 04:11:10 +00:00
My-Responsitories
17ea416c98 opt: buvid3 2025-05-11 00:29:03 +08:00
My-Responsitories
ab57aee8c1 opt: account (#846) 2025-05-10 16:19:19 +00:00
bggRGjQaUbCoE
8c80fc3578 fix: #844
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 23:47:37 +08:00
bggRGjQaUbCoE
85ab250551 opt: msg item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 17:57:26 +08:00
bggRGjQaUbCoE
3f3a1a6d7f opt: msg item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 17:49:50 +08:00
bggRGjQaUbCoE
68fe3bbd4b revert: dyn font size
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 16:57:31 +08:00
bggRGjQaUbCoE
a8054be82e mod: handle readlist url
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 16:12:18 +08:00
bggRGjQaUbCoE
3b6fd8019b opt: article list page
opt: fav/sub detail

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 15:54:43 +08:00
bggRGjQaUbCoE
91af974bd4 feat: article list
Closes #841

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 15:12:13 +08:00
bggRGjQaUbCoE
024a249e6b refa: whisper detail
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 15:08:19 +08:00
My-Responsitories
024e74115e opt: type & grpc message (#842)
* opt: grpc type

* opt: grpc message

* opt: http type
2025-05-10 04:40:27 +00:00
bggRGjQaUbCoE
7b4f08bb05 opt: msg btn action
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 00:53:03 +08:00
bggRGjQaUbCoE
f75036cb8e opt: msg ctr
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 00:40:41 +08:00
bggRGjQaUbCoE
0510fbb65a opt: msg btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-10 00:28:09 +08:00
bggRGjQaUbCoE
9e4bc24365 fix: msg secondary type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-09 23:29:41 +08:00
bggRGjQaUbCoE
0f41d5b2f8 feat: im settings
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-09 22:17:31 +08:00
bggRGjQaUbCoE
a282baf5a2 feat: session secondary
Closes #837

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-09 21:55:34 +08:00
bggRGjQaUbCoE
dea29054e6 opt: member opus
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-09 14:36:07 +08:00
bggRGjQaUbCoE
efaff0ae79 opt: replyReplyPanel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-09 13:24:14 +08:00
bggRGjQaUbCoE
2d75d89825 feat: space opus
Closes #833

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-09 12:32:09 +08:00
bggRGjQaUbCoE
bcd0d63db7 opt: dyn panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-09 00:28:35 +08:00
bggRGjQaUbCoE
26f921b7e4 fix: vote
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-08 15:29:47 +08:00
bggRGjQaUbCoE
4d1a9517e1 opt: dyn block
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-08 14:48:21 +08:00
bggRGjQaUbCoE
222070feba fix: dyn: temp ban
Closes #829

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-08 12:01:38 +08:00
bggRGjQaUbCoE
b28882cff5 opt: dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-07 22:55:29 +08:00
bggRGjQaUbCoE
fb22e5ab66 opt: live area
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-07 21:06:35 +08:00
bggRGjQaUbCoE
11a0f2faca feat: dyn topic
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-07 18:09:14 +08:00
bggRGjQaUbCoE
dd6ff101d1 opt: func
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-07 15:19:27 +08:00
bggRGjQaUbCoE
286193f08f opt: func
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-07 14:32:07 +08:00
bggRGjQaUbCoE
6353ecc13e feat: pm: clear unread
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-07 12:16:41 +08:00
bggRGjQaUbCoE
767e93615c mod: msg item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-07 11:59:57 +08:00
bggRGjQaUbCoE
76998e7761 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-07 00:27:14 +08:00
bggRGjQaUbCoE
df205f2b9d mod: remove refresh fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-07 00:25:17 +08:00
bggRGjQaUbCoE
3e63875659 mod: try-catch get dyn ctr
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-06 22:49:46 +08:00
bggRGjQaUbCoE
fcb7330970 mod: update whisper badge
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-06 22:44:34 +08:00
bggRGjQaUbCoE
b19c718a2a refa: whisper page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-06 22:31:04 +08:00
bggRGjQaUbCoE
661e7bfa78 feat: live search
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-06 20:34:07 +08:00
bggRGjQaUbCoE
867efecc54 refa: member search
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-06 20:31:20 +08:00
bggRGjQaUbCoE
bd31ab5d07 feat: live area page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-06 16:58:30 +08:00
My-Responsitories
bd1ffb0f24 fix: dynamics pendant 2025-05-06 12:34:04 +08:00
bggRGjQaUbCoE
a8fa4d72f3 feat: msg: set notice
Closes #821

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-06 00:27:08 +08:00
My-Responsitories
2d1697064d fix: card vip (#825) 2025-05-05 16:20:12 +00:00
My-Responsitories
a915650bb6 opt: enum (#824)
* opt: enum

* opt: member page type
2025-05-05 16:18:30 +00:00
bggRGjQaUbCoE
1da30d5d8f fix: reply cast
Closes #822

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 22:33:29 +08:00
bggRGjQaUbCoE
a2f72ee3f3 feat: live area
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 22:15:55 +08:00
bggRGjQaUbCoE
2e4c24393d mod: article: show top
Closes #819

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 20:16:45 +08:00
bggRGjQaUbCoE
e7b229a60f mod: refresh live data
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 18:02:39 +08:00
bggRGjQaUbCoE
562f9035e8 refa: live page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 17:50:02 +08:00
bggRGjQaUbCoE
7689fe8aa4 chore: rename tabsConfig
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 15:51:17 +08:00
bggRGjQaUbCoE
ceca78368d mod: update video tags api
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 15:36:32 +08:00
bggRGjQaUbCoE
3fa6d9820f fix: #817
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 15:25:12 +08:00
bggRGjQaUbCoE
2f4c739f0b opt: enum
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 15:13:17 +08:00
bggRGjQaUbCoE
4e68c765c5 opt: vote panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 14:00:39 +08:00
My-Responsitories
0dfc4e15bd refix: #779 (#816)
* Revert "fix: #779"

This reverts commit ddf7d82656.

* refix #779
2025-05-05 04:36:06 +00:00
dom
e8147680e6 Update 功能请求.yml 2025-05-05 12:07:45 +08:00
dom
2b3d326c41 Update bug-反馈.yml 2025-05-05 12:07:01 +08:00
bggRGjQaUbCoE
6414b377da revert: mainlist req
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 11:49:57 +08:00
bggRGjQaUbCoE
ea80d9a39c mod: update block page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 01:18:59 +08:00
bggRGjQaUbCoE
ef671f6503 fix: update grpc headers
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 01:06:53 +08:00
bggRGjQaUbCoE
cfc66e4364 fix: share selectedindex
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 01:06:53 +08:00
bggRGjQaUbCoE
1477a9058a mod: reply: remove unused val
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-05 01:06:53 +08:00
My-Responsitories
cdeb843a84 opt: avatar model (#814) 2025-05-04 16:45:24 +00:00
My-Responsitories
07d2b3b464 opt: merge danmaku in loop (#813) 2025-05-04 16:38:05 +00:00
bggRGjQaUbCoE
a49caa871d mod: update proto
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-04 23:42:08 +08:00
bggRGjQaUbCoE
fb004a0bb9 fix: get subtitles
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-04 23:40:22 +08:00
My-Responsitories
6f69a45195 opt: use cascade (#812) 2025-05-04 15:08:06 +00:00
bggRGjQaUbCoE
877732e1e7 chore: organize imports
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-04 16:27:52 +08:00
bggRGjQaUbCoE
caa58e9d7d mod: lint
mod: tweaks

opt: publish page

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-04 14:56:56 +08:00
My-Responsitories
2cfad80214 feat: vote pabel (#807) 2025-05-04 05:53:00 +00:00
bggRGjQaUbCoE
9b3c3efb09 chore: rename
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-03 15:51:56 +08:00
bggRGjQaUbCoE
c491b5283b refa: dir
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-03 15:39:54 +08:00
bggRGjQaUbCoE
7f70ee5045 refa: dir
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-03 15:26:06 +08:00
bggRGjQaUbCoE
57fa8b4f3e opt: video title
Closes #799

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-03 13:38:32 +08:00
bggRGjQaUbCoE
974a74a3c7 mod: opus
Closes #802

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-03 13:07:49 +08:00
bggRGjQaUbCoE
478b71d6b3 mod: check istablet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-03 12:49:15 +08:00
bggRGjQaUbCoE
5940c4f032 opt: get blockserver
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-03 12:49:15 +08:00
bggRGjQaUbCoE
9e50a195a4 opt: search settings
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-03 12:49:09 +08:00
My-Responsitories
b7b3460248 mod: scheme (#804) 2025-05-03 01:56:21 +00:00
徽忆.
36bf6f4ceb opt: webdav classification (#794)
* 优化设置备份[#739](https://github.com/bggRGjQaUbCoE/PiliPlus/issues/739)
2025-05-02 10:13:07 +08:00
My-Responsitories
56491591ab fix: three point (#792) 2025-05-01 15:16:47 +00:00
My-Responsitories
0b05edd6ff mod: quote color (#789) 2025-05-01 03:46:42 +00:00
My-Responsitories
c090cae1a1 opt: post redirect (#788)
* opt: cookie

* opt: post redirect
2025-05-01 02:08:48 +00:00
My-Responsitories
a46bde68f5 opt: parseRedirect use head (#787) 2025-05-01 02:04:38 +00:00
bggRGjQaUbCoE
ddf7d82656 fix: #779
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-30 18:52:25 +08:00
bggRGjQaUbCoE
23813eb224 fix: parse live info
opt: items

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-30 13:40:43 +08:00
bggRGjQaUbCoE
77e4a30bc5 opt: emote panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-30 12:03:47 +08:00
bggRGjQaUbCoE
15f4ae2567 mod: update remove dyn api
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-30 11:43:22 +08:00
bggRGjQaUbCoE
b3f117d28e opt: coin/like item jump
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-30 10:39:04 +08:00
bggRGjQaUbCoE
17a75da540 opt: dyn author panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-30 10:20:26 +08:00
bggRGjQaUbCoE
f8caa46eab fix: share dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-30 09:59:37 +08:00
bggRGjQaUbCoE
8d4bbc1a1c opt: article blocked item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-30 09:59:37 +08:00
bggRGjQaUbCoE
b5f2510cce opt: video card goto
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-30 09:59:37 +08:00
bggRGjQaUbCoE
978f27c700 fix: validate
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-30 09:59:06 +08:00
bggRGjQaUbCoE
b4ca42e0c0 opt: btn, stack
Closes #775

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 22:30:39 +08:00
bggRGjQaUbCoE
4abffeed32 fix: #753
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 21:09:01 +08:00
bggRGjQaUbCoE
9b5628cb65 opt: fav/sub page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 17:42:25 +08:00
bggRGjQaUbCoE
85f06ed65d opt: post redirect
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 16:23:10 +08:00
bggRGjQaUbCoE
f6b5d358e0 opt: show blocked item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 15:20:25 +08:00
bggRGjQaUbCoE
a42881ba9f fix: pm emoji
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 14:32:46 +08:00
bggRGjQaUbCoE
d5991b4354 mod: member article
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 14:17:52 +08:00
bggRGjQaUbCoE
101e49fe74 mod: handle medialist url
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 14:13:36 +08:00
bggRGjQaUbCoE
1cbeacbd0f fix: check dyn blocked
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 14:01:00 +08:00
bggRGjQaUbCoE
4b6b3e8377 opt: pm emoji
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 13:57:59 +08:00
bggRGjQaUbCoE
b3ab417c85 opt: pm share dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 13:43:35 +08:00
bggRGjQaUbCoE
defc6911d6 opt: show dyn blocked item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 13:05:07 +08:00
bggRGjQaUbCoE
6c757ec395 mod: update thumb dyn api
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 12:10:09 +08:00
bggRGjQaUbCoE
b876840d08 mod: opus: show itemnull, moduleblocked
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 12:10:09 +08:00
bggRGjQaUbCoE
30bad3a066 fix: reply message
opt: pm share

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-29 12:10:09 +08:00
bggRGjQaUbCoE
ca993df0c6 opt: get theme color
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-28 21:32:46 +08:00
My-Responsitories
451a84e696 opt: opus quote (#771) 2025-04-28 12:57:30 +00:00
bggRGjQaUbCoE
e65ec1b0b9 revert: lazy to opus
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-28 20:15:23 +08:00
bggRGjQaUbCoE
aed45b08ac opt: pm share
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-28 15:21:29 +08:00
My-Responsitories
7f93b42a1b opt: share origin img & lazy to opus (#768)
* opt: type

* opt: share origin img

* opt: lazy to opus
2025-04-28 06:17:41 +00:00
bggRGjQaUbCoE
a831b41623 opt: video sheet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-28 11:53:51 +08:00
bggRGjQaUbCoE
4d193a1f72 opt: pm share
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-28 11:41:04 +08:00
bggRGjQaUbCoE
51750a4ad5 opt: video tag
Closes #767

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-28 11:25:54 +08:00
bggRGjQaUbCoE
8fe6e3f4b7 opt: share article
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-27 23:24:35 +08:00
bggRGjQaUbCoE
6d7b0e8dd5 mod: handle search url
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-27 23:24:35 +08:00
bggRGjQaUbCoE
43409826f3 opt: morepanel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-27 23:24:35 +08:00
My-Responsitories
bb6bd95e9b opt: sealed LoadingState (#765) 2025-04-27 14:17:36 +00:00
bggRGjQaUbCoE
d4d1602b45 fix: article
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-27 18:40:12 +08:00
My-Responsitories
bd3c76ef43 refa: opus (#762)
* feat: opus

* fix

* fix

* fix

* fix

* .

* fix

* remove

* wbi sign

* fix

* opus content null check

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-27 17:24:14 +08:00
bggRGjQaUbCoE
3722ff1f33 opt: show video sheet
Closes #761

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-27 14:02:47 +08:00
bggRGjQaUbCoE
dc1cca0d4c mod: article: show list
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-27 11:59:05 +08:00
bggRGjQaUbCoE
3dad24e7b4 mod: article: show code
Closes #759

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-27 10:36:11 +08:00
bggRGjQaUbCoE
c591b57f22 fix: rcmd data parse
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 22:21:32 +08:00
bggRGjQaUbCoE
91389f91d1 mod: playurl query params
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 22:02:23 +08:00
bggRGjQaUbCoE
ec811f75e6 mod: handle post segment redirect
related #755

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 21:51:23 +08:00
bggRGjQaUbCoE
51e88939d6 opt: remove params check
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 20:47:14 +08:00
bggRGjQaUbCoE
f4470c383e mod: article: show linkcard
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 20:39:28 +08:00
bggRGjQaUbCoE
ed99aee3fd fix: refresh uplist
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 20:39:12 +08:00
dom
40fb93f036 refa: article (#757)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 14:54:22 +08:00
bggRGjQaUbCoE
64f7ba2a1a mod: refilter rcmd tname
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 13:51:25 +08:00
bggRGjQaUbCoE
6a45f993ae opt: request err code
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 12:49:28 +08:00
bggRGjQaUbCoE
0bdf620c2f opt: pm share
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 10:26:28 +08:00
bggRGjQaUbCoE
b8d2ff7e9b opt: anon list dialog
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 10:20:33 +08:00
bggRGjQaUbCoE
91142be3bd fix: rank: anim to top
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-26 10:20:33 +08:00
dom
8159e1b1df Update bug-反馈.yml 2025-04-26 10:19:16 +08:00
bggRGjQaUbCoE
27b05098cc mod: remove RandType
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-25 19:52:57 +08:00
bggRGjQaUbCoE
1e851d34b6 feat: new pgc rank
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-25 19:48:16 +08:00
bggRGjQaUbCoE
f10aa38bfd fix: update rank id
Closes #747

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-25 18:21:19 +08:00
bggRGjQaUbCoE
9a1b15029e opt: handle pm share type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-25 16:58:51 +08:00
bggRGjQaUbCoE
2063c366c2 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-25 13:57:53 +08:00
bggRGjQaUbCoE
afe812e2be feat: pm: share video
Closes #693

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-25 13:03:50 +08:00
bggRGjQaUbCoE
738cd61825 fix: remove wwebid
related #715

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-25 10:17:46 +08:00
bggRGjQaUbCoE
c28729af5b opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-24 20:28:03 +08:00
bggRGjQaUbCoE
4d7d9abc60 opt: html page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-24 15:59:20 +08:00
bggRGjQaUbCoE
8c7001c801 opt: safearea
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-24 11:55:59 +08:00
bggRGjQaUbCoE
039e1696dd mod: img preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-24 11:22:55 +08:00
bggRGjQaUbCoE
636e083044 opt: fav article
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-24 11:04:28 +08:00
bggRGjQaUbCoE
fcaba24cee fix: view later
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-24 10:55:20 +08:00
bggRGjQaUbCoE
33b8902375 opt: addUsers
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-24 10:36:17 +08:00
bggRGjQaUbCoE
65eecb8dcf mod: update error widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-23 16:16:51 +08:00
My-Responsitories
e0fe16fd14 mod: dynamic panel (#738) 2025-04-23 08:01:09 +00:00
My-Responsitories
7bb0307e6a opt: BoxFit desc (#737) 2025-04-23 07:57:27 +00:00
bggRGjQaUbCoE
cba70c3507 fix: rx
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-23 13:35:48 +08:00
bggRGjQaUbCoE
f779ed63e8 fix: showDecorate
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-23 13:20:34 +08:00
bggRGjQaUbCoE
07e34eb17b opt: rx
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-23 12:34:30 +08:00
bggRGjQaUbCoE
f220db96ed revert: select article
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-23 12:34:29 +08:00
bggRGjQaUbCoE
a0abd472e0 feat: fav article
Closes #727

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-23 11:59:54 +08:00
bggRGjQaUbCoE
0d27d88719 feat: create/update/del follow tag
opt: owner follow page

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-23 11:32:00 +08:00
bggRGjQaUbCoE
e212144250 fix: parse whisper data
mod: load more pm

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-23 11:31:54 +08:00
bggRGjQaUbCoE
2f5a3d66fc mod: opt tablet nav option
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 22:02:02 +08:00
bggRGjQaUbCoE
ff0ff42222 fix: search member arc
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 21:49:18 +08:00
bggRGjQaUbCoE
0dc209d30a Revert "fix: search member arc"
This reverts commit 2aeecb05d3.
2025-04-22 21:38:33 +08:00
bggRGjQaUbCoE
2aeecb05d3 fix: search member arc
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 21:30:08 +08:00
bggRGjQaUbCoE
65404ce356 mod: remove relation 3
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 20:33:09 +08:00
bggRGjQaUbCoE
246061c69e mod: view user from whisper
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 20:33:09 +08:00
bggRGjQaUbCoE
92f96c93f0 fix: #700
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 19:06:23 +08:00
bggRGjQaUbCoE
993c1f309a fix: mod relation
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 18:53:50 +08:00
bggRGjQaUbCoE
7856857cca feat: remove fan
Closes #733

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 18:29:43 +08:00
bggRGjQaUbCoE
1f2f00d49c mod: later view forwarded video
Closes #729

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 15:09:58 +08:00
bggRGjQaUbCoE
3afdd9d3f3 opt: safearea
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 14:36:43 +08:00
bggRGjQaUbCoE
42fa4a2fff opt: emote
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 14:36:43 +08:00
My-Responsitories
3d4bcbc082 refa: avatar (not radical) (#731)
* refa: avatar (not radical)

* update

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

---------

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 14:36:01 +08:00
bggRGjQaUbCoE
4c0443ec28 fix: #723
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-22 00:37:21 +08:00
bggRGjQaUbCoE
8b28a31d09 refa: follow page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-21 20:25:46 +08:00
bggRGjQaUbCoE
e6e9ce7d57 opt: live emote
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-21 18:49:19 +08:00
bggRGjQaUbCoE
9ad57dccb0 opt: safearea
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-21 16:22:41 +08:00
bggRGjQaUbCoE
95caf111ae mod: add skeleton
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-21 10:19:57 +08:00
bggRGjQaUbCoE
abdde1f811 opt: pic
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-21 01:37:10 +08:00
bggRGjQaUbCoE
ae901c709d feat: max cache size
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-21 01:37:02 +08:00
bggRGjQaUbCoE
a2af297a84 opt: relation
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-20 20:06:13 +08:00
bggRGjQaUbCoE
f9e28d1de9 opt: pic
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-20 18:28:46 +08:00
bggRGjQaUbCoE
a2ef4e6f84 chore: clean up
opt: pages

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-20 18:07:48 +08:00
bggRGjQaUbCoE
e5f3c3c922 Revert "refa: avatar (#722)"
This reverts commit ed60c274fc.
2025-04-20 18:04:08 +08:00
bggRGjQaUbCoE
6f4321ae14 Revert "chore: clean up"
This reverts commit 538494b7ec.
2025-04-20 18:04:06 +08:00
bggRGjQaUbCoE
a5c7ec0d60 Revert "fix: showDecorate"
This reverts commit 6bc0a8b4aa.
2025-04-20 18:04:04 +08:00
My-Responsitories
6bc0a8b4aa fix: showDecorate 2025-04-20 17:44:45 +08:00
bggRGjQaUbCoE
538494b7ec chore: clean up
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-20 11:21:21 +08:00
My-Responsitories
ed60c274fc refa: avatar (#722)
* mod: unify icon

* refa: avatar
2025-04-20 02:24:39 +00:00
dom
bbc498f882 Update bug-反馈.yml 2025-04-19 16:41:53 +08:00
dom
0932b3d625 Update 功能请求.yml 2025-04-19 16:41:34 +08:00
bggRGjQaUbCoE
9d4d37f2e7 opt: NavigationDrawer
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-19 15:04:26 +08:00
bggRGjQaUbCoE
6fc7e47111 opt: trending page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-19 14:22:58 +08:00
bggRGjQaUbCoE
c05ad1e724 fix: #715
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-19 14:14:18 +08:00
bggRGjQaUbCoE
5ed86b9165 opt: view whisper user
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-19 14:07:16 +08:00
bggRGjQaUbCoE
75cbd20f54 opt: msg feed top item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 23:13:17 +08:00
bggRGjQaUbCoE
3c07b7347b fix: member relation
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 23:08:08 +08:00
bggRGjQaUbCoE
d0ebedac0a fix: part: jump to curr
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 21:48:04 +08:00
bggRGjQaUbCoE
d86caac189 fix: change rcmd type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 21:43:13 +08:00
bggRGjQaUbCoE
c2b02b9b8d opt: search page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 21:23:03 +08:00
bggRGjQaUbCoE
a4e8ea37aa opt: trending page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 21:16:41 +08:00
bggRGjQaUbCoE
f56ca9c082 revert: getWbiKeys
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 21:16:06 +08:00
bggRGjQaUbCoE
e27476bc32 mod: disable search all
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 15:20:00 +08:00
bggRGjQaUbCoE
8ca4f7c8d3 opt: trending page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 15:16:40 +08:00
bggRGjQaUbCoE
1c4eb0766b chore: update live icon
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 15:16:40 +08:00
bggRGjQaUbCoE
87a812b7e0 feat: search all
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 14:34:01 +08:00
bggRGjQaUbCoE
f42a6200ed opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 12:32:11 +08:00
bggRGjQaUbCoE
a252ee0655 opt: search trending page
Closes #697

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 11:16:06 +08:00
bggRGjQaUbCoE
498988c2e3 refa: sub detail page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 10:22:17 +08:00
bggRGjQaUbCoE
261922d73a refa: whisper detail page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-18 09:49:57 +08:00
My-Responsitories
ebe08c23e4 mod: csrf 2025-04-17 22:04:39 +08:00
bggRGjQaUbCoE
70edd4cc3a refa: whisper page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 21:57:35 +08:00
bggRGjQaUbCoE
fa48a07970 opt: dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 17:36:09 +08:00
bggRGjQaUbCoE
0259ca963a opt: dm color panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 17:30:09 +08:00
bggRGjQaUbCoE
8dc9f68584 opt: search reload
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 17:21:30 +08:00
bggRGjQaUbCoE
4db7711a36 refa: search panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 16:13:34 +08:00
bggRGjQaUbCoE
7b9e4b2f82 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 11:11:54 +08:00
bggRGjQaUbCoE
07c04a9e7e opt: blacklist page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 10:48:03 +08:00
bggRGjQaUbCoE
8427ebc36e opt: later search
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 10:34:50 +08:00
bggRGjQaUbCoE
a99fc8fa72 opt: search page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 10:32:42 +08:00
bggRGjQaUbCoE
5959288491 fix: search page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 10:28:18 +08:00
bggRGjQaUbCoE
0522dd5ad4 refa: split fav search page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 10:17:20 +08:00
bggRGjQaUbCoE
d886569dc3 opt: item
opt: util

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-17 09:23:55 +08:00
My-Responsitories
12c711424b fix: follow up (#702) 2025-04-16 23:55:14 +00:00
bggRGjQaUbCoE
cb6ead96d1 opt: change theme
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-16 22:12:24 +08:00
bggRGjQaUbCoE
c4e7263ed6 chore: remove deprecated item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-16 21:57:16 +08:00
bggRGjQaUbCoE
4972e64cad opt: item
chore: clean up widgets

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-16 21:47:14 +08:00
My-Responsitories
5ea8a7d313 opt: remove duplicate em highlight 2025-04-16 20:52:34 +08:00
My-Responsitories
296cd863d2 fix: trending 2025-04-16 20:49:31 +08:00
bggRGjQaUbCoE
9ccf91659f opt: tablet nav
Closes #692

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-16 18:44:49 +08:00
My-Responsitories
f0e3b776bb opt: unify trending api & feat: search recommend (#694)
* opt: unify trending api

* opt: disable icon

* feat: search recommend

* mod: recommend api
2025-04-16 04:16:45 +00:00
bggRGjQaUbCoE
3638d65008 feat: set top dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-16 08:01:21 +08:00
bggRGjQaUbCoE
2cc9324f08 mod: show RICH_TEXT_NODE_TYPE_VIEW_PICTURE
Closes #691

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-16 07:33:10 +08:00
bggRGjQaUbCoE
bc8907b3ef mod: show lv6_s
Closes #687

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

opt: more panel

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: account override

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* opt: subtitle

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

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

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

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

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

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

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

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

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

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

Closes #540

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

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

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

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

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

* opt: model

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

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

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

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

* check ctr client

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: change anonymity

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

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

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

* fix profile update

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

* remove dep

* some fixes

* migrate accounts

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

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

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

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

related #459

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* partical revert

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-19 15:42:24 +08:00
My-Responsitories
cfb6c674ea skip mcdn on parsing (#281) 2025-02-19 14:55:02 +08:00
1016 changed files with 213809 additions and 128738 deletions

View File

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

60
.github/ISSUE_TEMPLATE/bug-反馈.yml vendored Normal file
View File

@@ -0,0 +1,60 @@
name: Bug 反馈
description: 描述你所遇到的bug
labels: [ "bug" ]
title: "[Bug] "
body:
- type: checkboxes
id: checklist
attributes:
label: 检查清单
options:
- label: 之前没有人提交过类似或相同的 bug report。
required: true
- label: 无视上一条 => block
required: true
- label: 正在使用最新版本。
required: true
- type: textarea
id: version
attributes:
label: 版本号
validations:
required: true
- type: textarea
id: bug
attributes:
label: 问题描述
description: 请提供一个清晰而简明的问题描述。
validations:
required: true
- type: textarea
id: steps
attributes:
label: 复现步骤
description: 请提供复现该问题所需的具体步骤。
validations:
required: true
- type: textarea
id: expected
attributes:
label: 预期行为
description: 请描述你期望的正确行为或结果。
validations:
required: true
- type: textarea
id: log
attributes:
label: 错误日志
description: 请提供设置->关于->错误日志中的内容粘贴在下方代码框中。如果没有请提供您的app版本号、系统版本、设备型号等相关信息。
- type: textarea
id: info
attributes:
label: 相关信息
description: 请补充截图、录屏、BV号等其他有助于解决问题的信息。

View File

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

45
.github/ISSUE_TEMPLATE/功能请求.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: 功能请求
description: 对于功能的一些建议
labels: [ "enhancement" ]
title: "[FR] "
body:
- type: checkboxes
id: checklist
attributes:
label: 检查清单
options:
- label: 之前没有人提交过类似或相同的功能请求。
required: true
- label: 无视上一条 => block
required: true
- label: 正在使用最新版本。
required: true
- type: textarea
id: desc
attributes:
label: 功能描述
description: 请提供对所请求功能的清晰描述。
validations:
required: true
- type: textarea
id: propose
attributes:
label: 目标
description: 请描述你希望通过这个功能实现的目标。
validations:
required: true
- type: textarea
id: solution
attributes:
label: 解决方案
description: 如果你有任何关于如何实现这个功能的想法或建议,请在这里提供。
- type: textarea
id: addition
attributes:
label: 其他
description: 请提供已实现该功能或类似功能的应用

View File

@@ -33,19 +33,6 @@ jobs:
channel: stable
flutter-version-file: pubspec.yaml
- name: 修复3.24的stable显示中文不正确问题 // from orz12
run: |
version=$(grep -m 1 'flutter:' pubspec.yaml | awk '{print $2}')
if [ "$(echo "$version < 3.27.0" | awk '{print ($1 < $2)}')" -eq 1 ]; then
cd $FLUTTER_ROOT
git config --global user.name "orz12"
git config --global user.email "orz12@test.com"
git cherry-pick d4124bd --strategy-option theirs
# flutter precache
flutter --version
cd -
fi
- name: 下载项目依赖
run: flutter pub get

View File

@@ -2,5 +2,9 @@
"editor.formatOnSave": true,
"[dart]": {
"editor.formatOnType": true
},
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
// "source.fixAll": "explicit",
}
}

View File

@@ -47,12 +47,31 @@
## feat
- [x] 修改消息设置
- [x] 修改聊天设置
- [x] 展示折叠消息
- [x] 查看用户图文
- [x] 动态话题
- [x] 直播分区
- [x] 分享`视频`/`番剧`/`动态`/`专栏`/`直播`至消息
- [x] 创建/修改/删除关注分组
- [x] 移除粉丝
- [x] 直播弹幕发送表情
- [x] 收藏夹排序
- [x] 稍后再看`未看`/`未看完`/`已看完`分类
- [x] WebDAV 备份/恢复设置
- [x] 保存评论/动态
- [x] 高级弹幕 by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] 取消/置顶评论
- [x] 记笔记
- [x] 多账号支持 by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] 屏蔽带货动态/评论
- [x] 互动视频
- [x] 发评反诈
- [x] 发评/动态反诈
- [x] 高能进度条
- [x] 滑动跳转预览视频缩略图
- [x] Live Photo
- [x] 复制/移动收藏夹/稍后再看视频
- [x] 复制/移动/排序收藏夹/稍后再看视频
- [x] 超分辨率
- [x] 合并弹幕
- [x] 会员彩色弹幕
@@ -70,7 +89,6 @@
- [x] 评论楼中楼定位点击查看的评论
- [x] 评论楼中楼按热度/时间排序
- [x] 评论点踩
- [x] 显示ops专栏
- [x] 私信发图
- [x] 投币动画
- [x] 取消/追番,更新追番状态
@@ -84,9 +102,9 @@
- [x] 筛选搜索
- [x] 转发动态
- [x] 合集图片
- [x] 删除/置顶私信
- [x] 删除/置顶/撤回私信
- [x] 举报用户/评论/视频/动态
- [x] 删除/发布文本/图片动态
- [x] 删除/发布/置顶文本/图片动态
- [x] 其他
## opt
@@ -155,7 +173,6 @@
- [x] 音质选择(视视频而定)
- [x] 解码格式选择(视视频而定)
- [x] 弹幕
- [ ] 直播弹幕
- [x] 字幕
- [x] 记忆播放
- [x] 视频比例:高度/宽度适应、填充、包含等

View File

@@ -21,9 +21,37 @@ linter:
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
# https://dart.dev/tools/linter-rules
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# - always_specify_types
# - avoid_positional_boolean_parameters
# - use_null_aware_elements
- always_declare_return_types
- always_use_package_imports
- avoid_empty_else
- avoid_field_initializers_in_const_classes
- avoid_print
- avoid_relative_lib_imports
- avoid_shadowing_type_parameters
- avoid_single_cascade_in_expression_statements
- avoid_slow_async_io
- avoid_type_to_string
- avoid_types_as_parameter_names
- avoid_unnecessary_containers
- avoid_void_async
- await_only_futures
- camel_case_extensions
- camel_case_types
- cancel_subscriptions
- cascade_invocations
- prefer_const_constructors
- prefer_const_declarations
- sized_box_for_whitespace
- unnecessary_late
- use_colored_box
- use_decorated_box
- use_named_constants
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

2
android/app/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/.cxx
/build

View File

@@ -41,12 +41,12 @@ android {
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '17'
}
sourceSets {
@@ -85,6 +85,10 @@ android {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig _storeFile != null ? signingConfigs.release : signingConfigs.debug
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
debug {
applicationIdSuffix ".debug"

1
android/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1 @@
-keep class com.yalantis.ucrop.util.RectUtils { *; }

View File

@@ -44,6 +44,9 @@
android:allowBackup="false"
android:fullBackupContent="false"
tools:replace="android:allowBackup">
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />
<activity
android:name=".MainActivity"
android:exported="true"

View File

@@ -7,8 +7,10 @@ import com.ryanheise.audioservice.AudioServiceActivity
import android.content.ComponentName
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.WindowManager.LayoutParams
import kotlin.system.exitProcess
@@ -55,6 +57,22 @@ class MainActivity : AudioServiceActivity() {
}
startActivity(intent)
} catch (e: Exception) {}
} else if (call.method == "linkVerifySettings") {
try {
val intent = Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:" + context.packageName))
context.startActivity(intent)
} catch (t: Throwable) {
try {
val intent = Intent("android.intent.action.MAIN", Uri.parse("package:" + context.packageName))
intent.setClassName("com.android.settings", "com.android.settings.applications.InstalledAppOpenByDefaultActivity")
context.startActivity(intent)
} catch (t2: Throwable) {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.parse("package:" + context.packageName))
context.startActivity(intent)
}
}
} else {
result.notImplemented()
}

View File

@@ -1,36 +1,49 @@
allprojects {
repositories {
maven { url "https://maven.aliyun.com/repository/google" }
maven { url "https://maven.aliyun.com/repository/central" }
maven { url "https://maven.aliyun.com/repository/jcenter" }
maven { url "https://maven.aliyun.com/repository/public" }
maven { url "http://download.flutter.io"
allowInsecureProtocol = true
}
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
rootProject.buildDir = '../build'
subprojects {
afterEvaluate { project ->
if (project.extensions.findByName("android") != null) {
if (project.hasProperty('android')) {
project.android {
if (namespace == null) {
namespace project.group
}
}
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
jvmTarget = '17'
}
}
}
Integer pluginCompileSdk = project.android.compileSdk
if (pluginCompileSdk != null && pluginCompileSdk < 31) {
project.logger.error(
"Warning: Overriding compileSdk version in Flutter plugin: "
+ project.name
+ " from "
+ pluginCompileSdk
+ " to 31 (to work around https://issuetracker.google.com/issues/199180389)."
+ "\nIf there is not a new version of " + project.name + ", consider filing an issue against "
+ project.name
+ " to increase their compileSdk to the latest (otherwise try updating to the latest version)."
)
project.android {
compileSdk 31
if (pluginCompileSdk != null) {
if (pluginCompileSdk < 31) {
project.logger.error(
"Warning: Overriding compileSdk version in Flutter plugin: "
+ project.name
+ " from "
+ pluginCompileSdk
+ " to 31 (to work around https://issuetracker.google.com/issues/199180389)."
+ "\nIf there is not a new version of " + project.name + ", consider filing an issue against "
+ project.name
+ " to increase their compileSdk to the latest (otherwise try updating to the latest version)."
)
project.android {
compileSdk 31
}
}
}
}

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
zipStorePath=wrapper/dists

View File

@@ -10,13 +10,6 @@ pluginManagement {
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
maven { url "https://maven.aliyun.com/repository/google" }
maven { url "https://maven.aliyun.com/repository/central" }
maven { url "https://maven.aliyun.com/repository/jcenter" }
maven { url "https://maven.aliyun.com/repository/public" }
maven { url "http://download.flutter.io"
allowInsecureProtocol = true
}
google()
mavenCentral()
gradlePluginPortal()
@@ -25,7 +18,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.2.0" apply false
id "com.android.application" version '8.4.1' apply false
id "org.jetbrains.kotlin.android" version "1.9.22" apply false
}

Binary file not shown.

BIN
assets/images/live/live.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 B

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 1.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

View File

@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
class StyleString {
static const double cardSpace = 8;
static const double safeSpace = 12;
static BorderRadius mdRadius = BorderRadius.circular(10);
static const BorderRadius mdRadius = BorderRadius.all(imgRadius);
static const Radius imgRadius = Radius.circular(10);
static const double aspectRatio = 16 / 10;
}
@@ -23,15 +23,21 @@ class Constants {
static const String userAgent =
'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android_hd build/2001100 channel/yingyongbao innerVer/2001100 osVer/14 network/2';
static const String statistics =
'%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D';
//Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
'{"appId":5,"platform":3,"version":"1.46.2","abtest":""}';
// 请求时会自动encodeComponent
// app
static const String userAgentApp =
'Mozilla/5.0 BiliDroid/8.43.0 (bbcallen@gmail.com) os/android model/android mobi_app/android build/8430300 channel/bili innerVer/8430300 osVer/15 network/2';
static const String statisticsApp =
'{"appId":5,"platform":3,"version":"8.43.0","abtest":""}';
static const urlPattern =
r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]';
static const goodsUrlPrefix = "https://gaoneng.bilibili.com/tetris";
// 超分辨率滤镜
// 超分辨率滤镜
static const List<String> mpvAnime4KShaders = [
'Anime4K_Clamp_Highlights.glsl',
'Anime4K_Restore_CNN_VL.glsl',
@@ -42,7 +48,7 @@ class Constants {
];
// 超分辨率滤镜 (轻量)
static const List<String> mpvAnime4KShadersLite = [
static const mpvAnime4KShadersLite = [
'Anime4K_Clamp_Highlights.glsl',
'Anime4K_Restore_CNN_M.glsl',
'Anime4K_Restore_CNN_S.glsl',
@@ -53,221 +59,221 @@ class Constants {
];
//内容来自 https://passport.bilibili.com/web/generic/country/list
static const List<Map<String, dynamic>> internationalDialingPrefix = [
{"id": 1, "cname": "中国大陆", "country_id": "86"},
{"id": 5, "cname": "中国香港特别行政区", "country_id": "852"},
{"id": 2, "cname": "中国澳门特别行政区", "country_id": "853"},
{"id": 3, "cname": "中国台湾", "country_id": "886"},
{"id": 4, "cname": "美国", "country_id": "1"},
{"id": 6, "cname": "比利时", "country_id": "32"},
{"id": 7, "cname": "澳大利亚", "country_id": "61"},
{"id": 8, "cname": "法国", "country_id": "33"},
{"id": 9, "cname": "加拿大", "country_id": "1"},
{"id": 10, "cname": "日本", "country_id": "81"},
{"id": 11, "cname": "新加坡", "country_id": "65"},
{"id": 12, "cname": "韩国", "country_id": "82"},
{"id": 13, "cname": "马来西亚", "country_id": "60"},
{"id": 14, "cname": "英国", "country_id": "44"},
{"id": 15, "cname": "意大利", "country_id": "39"},
{"id": 16, "cname": "德国", "country_id": "49"},
{"id": 18, "cname": "俄罗斯", "country_id": "7"},
{"id": 19, "cname": "新西兰", "country_id": "64"}, //common:1-19
{"id": 153, "cname": "瓦利斯群岛和富图纳群岛", "country_id": "1681"},
{"id": 152, "cname": "葡萄牙", "country_id": "351"},
{"id": 151, "cname": "帕劳", "country_id": "680"},
{"id": 150, "cname": "诺福克岛", "country_id": "672"},
{"id": 149, "cname": "挪威", "country_id": "47"},
{"id": 148, "cname": "纽埃岛", "country_id": "683"},
{"id": 147, "cname": "尼日利亚", "country_id": "234"},
{"id": 146, "cname": "尼日尔", "country_id": "227"},
{"id": 145, "cname": "尼加拉瓜", "country_id": "505"},
{"id": 144, "cname": "尼泊尔", "country_id": "977"},
{"id": 143, "cname": "瑙鲁", "country_id": "674"},
{"id": 154, "cname": "格鲁吉亚", "country_id": "995"},
{"id": 155, "cname": "瑞典", "country_id": "46"},
{"id": 165, "cname": "沙特阿拉伯", "country_id": "966"},
{"id": 164, "cname": "桑给巴尔岛", "country_id": "259"},
{"id": 163, "cname": "塞舌尔共和国", "country_id": "248"},
{"id": 162, "cname": "塞浦路斯", "country_id": "357"},
{"id": 161, "cname": "塞内加尔", "country_id": "221"},
{"id": 160, "cname": "塞拉利昂", "country_id": "232"},
{"id": 159, "cname": "萨摩亚,东部", "country_id": "684"},
{"id": 158, "cname": "萨摩亚,西部", "country_id": "685"},
{"id": 157, "cname": "萨尔瓦多", "country_id": "503"},
{"id": 156, "cname": "瑞士", "country_id": "41"},
{"id": 166, "cname": "圣多美和普林西比", "country_id": "239"},
{"id": 142, "cname": "塞尔维亚", "country_id": "381"},
{"id": 141, "cname": "南非", "country_id": "27"},
{"id": 128, "cname": "毛里塔尼亚", "country_id": "222"},
{"id": 127, "cname": "毛里求斯", "country_id": "230"},
{"id": 126, "cname": "马歇尔岛", "country_id": "692"},
{"id": 125, "cname": "马提尼克岛", "country_id": "596"},
{"id": 124, "cname": "马其顿", "country_id": "389"},
{"id": 123, "cname": "马里亚纳岛", "country_id": "1670"},
{"id": 122, "cname": "马里", "country_id": "223"},
{"id": 121, "cname": "马拉维", "country_id": "265"},
{"id": 120, "cname": "马耳他", "country_id": "356"},
{"id": 119, "cname": "马尔代夫", "country_id": "960"},
{"id": 129, "cname": "蒙古", "country_id": "976"},
{"id": 130, "cname": "蒙特塞拉特岛", "country_id": "1664"},
{"id": 140, "cname": "纳米比亚", "country_id": "264"},
{"id": 139, "cname": "墨西哥", "country_id": "52"},
{"id": 138, "cname": "莫桑比克", "country_id": "258"},
{"id": 137, "cname": "摩纳哥", "country_id": "377"},
{"id": 136, "cname": "摩洛哥", "country_id": "212"},
{"id": 135, "cname": "摩尔多瓦", "country_id": "373"},
{"id": 134, "cname": "缅甸", "country_id": "95"},
{"id": 133, "cname": "密克罗尼西亚", "country_id": "691"},
{"id": 132, "cname": "秘鲁", "country_id": "51"},
{"id": 131, "cname": "孟加拉国", "country_id": "880"},
{"id": 118, "cname": "马达加斯加", "country_id": "261"},
{"id": 167, "cname": "圣卢西亚", "country_id": "1784"},
{"id": 216, "cname": "智利", "country_id": "56"},
{"id": 203, "cname": "牙买加", "country_id": "1876"},
{"id": 202, "cname": "叙利亚", "country_id": "963"},
{"id": 201, "cname": "匈牙利", "country_id": "36"},
{"id": 200, "cname": "科特迪瓦", "country_id": "225"},
{"id": 199, "cname": "希腊", "country_id": "30"},
{"id": 198, "cname": "西班牙", "country_id": "34"},
{"id": 197, "cname": "乌兹别克斯坦", "country_id": "998"},
{"id": 196, "cname": "乌拉圭", "country_id": "598"},
{"id": 195, "cname": "乌克兰", "country_id": "380"},
{"id": 194, "cname": "乌干达", "country_id": "256"},
{"id": 204, "cname": "亚美尼亚", "country_id": "374"},
{"id": 205, "cname": "也门", "country_id": "967"},
{"id": 215, "cname": "直布罗陀", "country_id": "350"},
{"id": 214, "cname": "乍得", "country_id": "235"},
{"id": 213, "cname": "赞比亚", "country_id": "260"},
{"id": 212, "cname": "越南", "country_id": "84"},
{"id": 211, "cname": "约旦", "country_id": "962"},
{"id": 210, "cname": "印尼", "country_id": "62"},
{"id": 209, "cname": "印度", "country_id": "91"},
{"id": 208, "cname": "以色列", "country_id": "972"},
{"id": 207, "cname": "伊朗", "country_id": "98"},
{"id": 206, "cname": "伊拉克", "country_id": "964"},
{"id": 193, "cname": "文莱", "country_id": "673"},
{"id": 192, "cname": "委内瑞拉", "country_id": "58"},
{"id": 191, "cname": "维珍群岛(英属)", "country_id": "1284"},
{"id": 178, "cname": "泰国", "country_id": "66"},
{"id": 177, "cname": "索马里", "country_id": "252"},
{"id": 176, "cname": "所罗门群岛", "country_id": "677"},
{"id": 175, "cname": "苏里南", "country_id": "597"},
{"id": 174, "cname": "苏丹", "country_id": "249"},
{"id": 173, "cname": "斯威士兰", "country_id": "268"},
{"id": 172, "cname": "斯洛文尼亚", "country_id": "386"},
{"id": 171, "cname": "斯洛伐克", "country_id": "421"},
{"id": 170, "cname": "斯里兰卡", "country_id": "94"},
{"id": 169, "cname": "圣皮埃尔和密克隆群岛", "country_id": "508"},
{"id": 179, "cname": "坦桑尼亚", "country_id": "255"},
{"id": 180, "cname": "汤加", "country_id": "676"},
{"id": 190, "cname": "维珍群岛(美属)", "country_id": "1340"},
{"id": 189, "cname": "瓦努阿图", "country_id": "678"},
{"id": 188, "cname": "托克劳岛", "country_id": "690"},
{"id": 187, "cname": "土库曼斯坦", "country_id": "993"},
{"id": 186, "cname": "土耳其", "country_id": "90"},
{"id": 185, "cname": "图瓦卢", "country_id": "688"},
{"id": 184, "cname": "突尼斯", "country_id": "216"},
{"id": 183, "cname": "阿森松岛", "country_id": "247"},
{"id": 182, "cname": "特立尼达和多巴哥", "country_id": "1868"},
{"id": 181, "cname": "特克斯和凯科斯", "country_id": "1649"},
{"id": 168, "cname": "圣马力诺", "country_id": "378"},
{"id": 67, "cname": "法属圭亚那", "country_id": "594"},
{"id": 54, "cname": "不丹", "country_id": "975"},
{"id": 53, "cname": "博茨瓦纳", "country_id": "267"},
{"id": 52, "cname": "伯利兹", "country_id": "501"},
{"id": 51, "cname": "玻利维亚", "country_id": "591"},
{"id": 50, "cname": "波兰", "country_id": "48"},
{"id": 49, "cname": "波黑", "country_id": "387"},
{"id": 48, "cname": "波多黎各", "country_id": "1787"},
{"id": 47, "cname": "冰岛", "country_id": "354"},
{"id": 46, "cname": "贝宁", "country_id": "229"},
{"id": 45, "cname": "保加利亚", "country_id": "359"},
{"id": 55, "cname": "布基纳法索", "country_id": "226"},
{"id": 56, "cname": "布隆迪", "country_id": "257"},
{"id": 66, "cname": "法属波利尼西亚", "country_id": "689"},
{"id": 65, "cname": "法罗岛", "country_id": "298"},
{"id": 64, "cname": "厄立特里亚", "country_id": "291"},
{"id": 63, "cname": "厄瓜多尔", "country_id": "593"},
{"id": 62, "cname": "多米尼加代表", "country_id": "1809"},
{"id": 61, "cname": "多米尼加", "country_id": "1767"},
{"id": 60, "cname": "多哥", "country_id": "228"},
{"id": 59, "cname": "迪戈加西亚岛", "country_id": "246"},
{"id": 58, "cname": "丹麦", "country_id": "45"},
{"id": 57, "cname": "赤道几内亚", "country_id": "240"},
{"id": 44, "cname": "百慕大群岛", "country_id": "1441"},
{"id": 43, "cname": "白俄罗斯", "country_id": "375"},
{"id": 42, "cname": "巴西", "country_id": "55"},
{"id": 29, "cname": "爱尔兰", "country_id": "353"},
{"id": 28, "cname": "埃塞俄比亚", "country_id": "251"},
{"id": 27, "cname": "埃及", "country_id": "20"},
{"id": 26, "cname": "阿塞拜疆", "country_id": "994"},
{"id": 25, "cname": "阿曼", "country_id": "968"},
{"id": 24, "cname": "阿联酋", "country_id": "971"},
{"id": 23, "cname": "阿根廷", "country_id": "54"},
{"id": 22, "cname": "阿富汗", "country_id": "93"},
{"id": 21, "cname": "阿尔及利亚", "country_id": "213"},
{"id": 20, "cname": "阿尔巴尼亚", "country_id": "355"},
{"id": 30, "cname": "爱沙尼亚", "country_id": "372"},
{"id": 31, "cname": "安道尔", "country_id": "376"},
{"id": 41, "cname": "巴拿马", "country_id": "507"},
{"id": 40, "cname": "巴林", "country_id": "973"},
{"id": 39, "cname": "巴拉圭", "country_id": "595"},
{"id": 38, "cname": "巴基斯坦", "country_id": "92"},
{"id": 37, "cname": "巴哈马群岛", "country_id": "1242"},
{"id": 36, "cname": "巴布亚新几内亚", "country_id": "675"},
{"id": 35, "cname": "巴巴多斯", "country_id": "1246"},
{"id": 34, "cname": "奥地利", "country_id": "43"},
{"id": 33, "cname": "安提瓜岛和巴布达", "country_id": "1268"},
{"id": 32, "cname": "安哥拉", "country_id": "244"},
{"id": 68, "cname": "非洲中部", "country_id": "236"},
{"id": 117, "cname": "罗马尼亚", "country_id": "40"},
{"id": 104, "cname": "科威特", "country_id": "965"},
{"id": 103, "cname": "科摩罗", "country_id": "269"},
{"id": 102, "cname": "开曼群岛", "country_id": "1345"},
{"id": 101, "cname": "卡塔尔", "country_id": "974"},
{"id": 100, "cname": "喀麦隆", "country_id": "237"},
{"id": 99, "cname": "聚会岛", "country_id": "262"},
{"id": 98, "cname": "津巴布韦", "country_id": "263"},
{"id": 97, "cname": "捷克", "country_id": "420"},
{"id": 96, "cname": "柬埔寨", "country_id": "855"},
{"id": 95, "cname": "加蓬", "country_id": "241"},
{"id": 105, "cname": "克罗地亚", "country_id": "385"},
{"id": 106, "cname": "肯尼亚", "country_id": "254"},
{"id": 116, "cname": "卢旺达", "country_id": "250"},
{"id": 115, "cname": "卢森堡", "country_id": "352"},
{"id": 114, "cname": "利比亚", "country_id": "218"},
{"id": 113, "cname": "利比里亚", "country_id": "231"},
{"id": 112, "cname": "立陶宛", "country_id": "370"},
{"id": 111, "cname": "黎巴嫩", "country_id": "961"},
{"id": 110, "cname": "老挝", "country_id": "856"},
{"id": 109, "cname": "莱索托", "country_id": "266"},
{"id": 108, "cname": "拉脱维亚", "country_id": "371"},
{"id": 107, "cname": "库克岛", "country_id": "682"},
{"id": 94, "cname": "加纳", "country_id": "233"},
{"id": 93, "cname": "几内亚比绍", "country_id": "245"},
{"id": 92, "cname": "几内亚", "country_id": "224"},
{"id": 79, "cname": "格林纳达", "country_id": "1473"},
{"id": 78, "cname": "哥斯达黎加", "country_id": "506"},
{"id": 77, "cname": "哥伦比亚", "country_id": "57"},
{"id": 76, "cname": "刚果(金)", "country_id": "243"},
{"id": 75, "cname": "刚果", "country_id": "242"},
{"id": 74, "cname": "冈比亚", "country_id": "220"},
{"id": 73, "cname": "福克兰岛", "country_id": "500"},
{"id": 72, "cname": "佛得角", "country_id": "238"},
{"id": 71, "cname": "芬兰", "country_id": "358"},
{"id": 70, "cname": "斐济", "country_id": "679"},
{"id": 80, "cname": "格陵兰岛", "country_id": "299"},
{"id": 81, "cname": "古巴", "country_id": "53"},
{"id": 91, "cname": "吉尔吉斯斯坦", "country_id": "996"},
{"id": 90, "cname": "吉布提", "country_id": "253"},
{"id": 89, "cname": "基里巴斯", "country_id": "686"},
{"id": 88, "cname": "维克岛", "country_id": "1808"},
{"id": 87, "cname": "洪都拉斯", "country_id": "504"},
{"id": 86, "cname": "荷兰", "country_id": "31"},
{"id": 85, "cname": "朝鲜", "country_id": "850"},
{"id": 84, "cname": "海地", "country_id": "509"},
{"id": 83, "cname": "关岛", "country_id": "1671"},
{"id": 82, "cname": "瓜德罗普岛", "country_id": "590"},
{"id": 69, "cname": "菲律宾", "country_id": "63"}
];
static List<Map<String, dynamic>> get internationalDialingPrefix => [
{"id": 1, "cname": "中国大陆", "country_id": "86"},
{"id": 5, "cname": "中国香港特别行政区", "country_id": "852"},
{"id": 2, "cname": "中国澳门特别行政区", "country_id": "853"},
{"id": 3, "cname": "中国台湾", "country_id": "886"},
{"id": 4, "cname": "美国", "country_id": "1"},
{"id": 6, "cname": "比利时", "country_id": "32"},
{"id": 7, "cname": "澳大利亚", "country_id": "61"},
{"id": 8, "cname": "法国", "country_id": "33"},
{"id": 9, "cname": "加拿大", "country_id": "1"},
{"id": 10, "cname": "日本", "country_id": "81"},
{"id": 11, "cname": "新加坡", "country_id": "65"},
{"id": 12, "cname": "韩国", "country_id": "82"},
{"id": 13, "cname": "马来西亚", "country_id": "60"},
{"id": 14, "cname": "英国", "country_id": "44"},
{"id": 15, "cname": "意大利", "country_id": "39"},
{"id": 16, "cname": "德国", "country_id": "49"},
{"id": 18, "cname": "俄罗斯", "country_id": "7"},
{"id": 19, "cname": "新西兰", "country_id": "64"}, //common:1-19
{"id": 153, "cname": "瓦利斯群岛和富图纳群岛", "country_id": "1681"},
{"id": 152, "cname": "葡萄牙", "country_id": "351"},
{"id": 151, "cname": "帕劳", "country_id": "680"},
{"id": 150, "cname": "诺福克岛", "country_id": "672"},
{"id": 149, "cname": "挪威", "country_id": "47"},
{"id": 148, "cname": "纽埃岛", "country_id": "683"},
{"id": 147, "cname": "尼日利亚", "country_id": "234"},
{"id": 146, "cname": "尼日尔", "country_id": "227"},
{"id": 145, "cname": "尼加拉瓜", "country_id": "505"},
{"id": 144, "cname": "尼泊尔", "country_id": "977"},
{"id": 143, "cname": "瑙鲁", "country_id": "674"},
{"id": 154, "cname": "格鲁吉亚", "country_id": "995"},
{"id": 155, "cname": "瑞典", "country_id": "46"},
{"id": 165, "cname": "沙特阿拉伯", "country_id": "966"},
{"id": 164, "cname": "桑给巴尔岛", "country_id": "259"},
{"id": 163, "cname": "塞舌尔共和国", "country_id": "248"},
{"id": 162, "cname": "塞浦路斯", "country_id": "357"},
{"id": 161, "cname": "塞内加尔", "country_id": "221"},
{"id": 160, "cname": "塞拉利昂", "country_id": "232"},
{"id": 159, "cname": "萨摩亚,东部", "country_id": "684"},
{"id": 158, "cname": "萨摩亚,西部", "country_id": "685"},
{"id": 157, "cname": "萨尔瓦多", "country_id": "503"},
{"id": 156, "cname": "瑞士", "country_id": "41"},
{"id": 166, "cname": "圣多美和普林西比", "country_id": "239"},
{"id": 142, "cname": "塞尔维亚", "country_id": "381"},
{"id": 141, "cname": "南非", "country_id": "27"},
{"id": 128, "cname": "毛里塔尼亚", "country_id": "222"},
{"id": 127, "cname": "毛里求斯", "country_id": "230"},
{"id": 126, "cname": "马歇尔岛", "country_id": "692"},
{"id": 125, "cname": "马提尼克岛", "country_id": "596"},
{"id": 124, "cname": "马其顿", "country_id": "389"},
{"id": 123, "cname": "马里亚纳岛", "country_id": "1670"},
{"id": 122, "cname": "马里", "country_id": "223"},
{"id": 121, "cname": "马拉维", "country_id": "265"},
{"id": 120, "cname": "马耳他", "country_id": "356"},
{"id": 119, "cname": "马尔代夫", "country_id": "960"},
{"id": 129, "cname": "蒙古", "country_id": "976"},
{"id": 130, "cname": "蒙特塞拉特岛", "country_id": "1664"},
{"id": 140, "cname": "纳米比亚", "country_id": "264"},
{"id": 139, "cname": "墨西哥", "country_id": "52"},
{"id": 138, "cname": "莫桑比克", "country_id": "258"},
{"id": 137, "cname": "摩纳哥", "country_id": "377"},
{"id": 136, "cname": "摩洛哥", "country_id": "212"},
{"id": 135, "cname": "摩尔多瓦", "country_id": "373"},
{"id": 134, "cname": "缅甸", "country_id": "95"},
{"id": 133, "cname": "密克罗尼西亚", "country_id": "691"},
{"id": 132, "cname": "秘鲁", "country_id": "51"},
{"id": 131, "cname": "孟加拉国", "country_id": "880"},
{"id": 118, "cname": "马达加斯加", "country_id": "261"},
{"id": 167, "cname": "圣卢西亚", "country_id": "1784"},
{"id": 216, "cname": "智利", "country_id": "56"},
{"id": 203, "cname": "牙买加", "country_id": "1876"},
{"id": 202, "cname": "叙利亚", "country_id": "963"},
{"id": 201, "cname": "匈牙利", "country_id": "36"},
{"id": 200, "cname": "科特迪瓦", "country_id": "225"},
{"id": 199, "cname": "希腊", "country_id": "30"},
{"id": 198, "cname": "西班牙", "country_id": "34"},
{"id": 197, "cname": "乌兹别克斯坦", "country_id": "998"},
{"id": 196, "cname": "乌拉圭", "country_id": "598"},
{"id": 195, "cname": "乌克兰", "country_id": "380"},
{"id": 194, "cname": "乌干达", "country_id": "256"},
{"id": 204, "cname": "亚美尼亚", "country_id": "374"},
{"id": 205, "cname": "也门", "country_id": "967"},
{"id": 215, "cname": "直布罗陀", "country_id": "350"},
{"id": 214, "cname": "乍得", "country_id": "235"},
{"id": 213, "cname": "赞比亚", "country_id": "260"},
{"id": 212, "cname": "越南", "country_id": "84"},
{"id": 211, "cname": "约旦", "country_id": "962"},
{"id": 210, "cname": "印尼", "country_id": "62"},
{"id": 209, "cname": "印度", "country_id": "91"},
{"id": 208, "cname": "以色列", "country_id": "972"},
{"id": 207, "cname": "伊朗", "country_id": "98"},
{"id": 206, "cname": "伊拉克", "country_id": "964"},
{"id": 193, "cname": "文莱", "country_id": "673"},
{"id": 192, "cname": "委内瑞拉", "country_id": "58"},
{"id": 191, "cname": "维珍群岛(英属)", "country_id": "1284"},
{"id": 178, "cname": "泰国", "country_id": "66"},
{"id": 177, "cname": "索马里", "country_id": "252"},
{"id": 176, "cname": "所罗门群岛", "country_id": "677"},
{"id": 175, "cname": "苏里南", "country_id": "597"},
{"id": 174, "cname": "苏丹", "country_id": "249"},
{"id": 173, "cname": "斯威士兰", "country_id": "268"},
{"id": 172, "cname": "斯洛文尼亚", "country_id": "386"},
{"id": 171, "cname": "斯洛伐克", "country_id": "421"},
{"id": 170, "cname": "斯里兰卡", "country_id": "94"},
{"id": 169, "cname": "圣皮埃尔和密克隆群岛", "country_id": "508"},
{"id": 179, "cname": "坦桑尼亚", "country_id": "255"},
{"id": 180, "cname": "汤加", "country_id": "676"},
{"id": 190, "cname": "维珍群岛(美属)", "country_id": "1340"},
{"id": 189, "cname": "瓦努阿图", "country_id": "678"},
{"id": 188, "cname": "托克劳岛", "country_id": "690"},
{"id": 187, "cname": "土库曼斯坦", "country_id": "993"},
{"id": 186, "cname": "土耳其", "country_id": "90"},
{"id": 185, "cname": "图瓦卢", "country_id": "688"},
{"id": 184, "cname": "突尼斯", "country_id": "216"},
{"id": 183, "cname": "阿森松岛", "country_id": "247"},
{"id": 182, "cname": "特立尼达和多巴哥", "country_id": "1868"},
{"id": 181, "cname": "特克斯和凯科斯", "country_id": "1649"},
{"id": 168, "cname": "圣马力诺", "country_id": "378"},
{"id": 67, "cname": "法属圭亚那", "country_id": "594"},
{"id": 54, "cname": "不丹", "country_id": "975"},
{"id": 53, "cname": "博茨瓦纳", "country_id": "267"},
{"id": 52, "cname": "伯利兹", "country_id": "501"},
{"id": 51, "cname": "玻利维亚", "country_id": "591"},
{"id": 50, "cname": "波兰", "country_id": "48"},
{"id": 49, "cname": "波黑", "country_id": "387"},
{"id": 48, "cname": "波多黎各", "country_id": "1787"},
{"id": 47, "cname": "冰岛", "country_id": "354"},
{"id": 46, "cname": "贝宁", "country_id": "229"},
{"id": 45, "cname": "保加利亚", "country_id": "359"},
{"id": 55, "cname": "布基纳法索", "country_id": "226"},
{"id": 56, "cname": "布隆迪", "country_id": "257"},
{"id": 66, "cname": "法属波利尼西亚", "country_id": "689"},
{"id": 65, "cname": "法罗岛", "country_id": "298"},
{"id": 64, "cname": "厄立特里亚", "country_id": "291"},
{"id": 63, "cname": "厄瓜多尔", "country_id": "593"},
{"id": 62, "cname": "多米尼加代表", "country_id": "1809"},
{"id": 61, "cname": "多米尼加", "country_id": "1767"},
{"id": 60, "cname": "多哥", "country_id": "228"},
{"id": 59, "cname": "迪戈加西亚岛", "country_id": "246"},
{"id": 58, "cname": "丹麦", "country_id": "45"},
{"id": 57, "cname": "赤道几内亚", "country_id": "240"},
{"id": 44, "cname": "百慕大群岛", "country_id": "1441"},
{"id": 43, "cname": "白俄罗斯", "country_id": "375"},
{"id": 42, "cname": "巴西", "country_id": "55"},
{"id": 29, "cname": "爱尔兰", "country_id": "353"},
{"id": 28, "cname": "埃塞俄比亚", "country_id": "251"},
{"id": 27, "cname": "埃及", "country_id": "20"},
{"id": 26, "cname": "阿塞拜疆", "country_id": "994"},
{"id": 25, "cname": "阿曼", "country_id": "968"},
{"id": 24, "cname": "阿联酋", "country_id": "971"},
{"id": 23, "cname": "阿根廷", "country_id": "54"},
{"id": 22, "cname": "阿富汗", "country_id": "93"},
{"id": 21, "cname": "阿尔及利亚", "country_id": "213"},
{"id": 20, "cname": "阿尔巴尼亚", "country_id": "355"},
{"id": 30, "cname": "爱沙尼亚", "country_id": "372"},
{"id": 31, "cname": "安道尔", "country_id": "376"},
{"id": 41, "cname": "巴拿马", "country_id": "507"},
{"id": 40, "cname": "巴林", "country_id": "973"},
{"id": 39, "cname": "巴拉圭", "country_id": "595"},
{"id": 38, "cname": "巴基斯坦", "country_id": "92"},
{"id": 37, "cname": "巴哈马群岛", "country_id": "1242"},
{"id": 36, "cname": "巴布亚新几内亚", "country_id": "675"},
{"id": 35, "cname": "巴巴多斯", "country_id": "1246"},
{"id": 34, "cname": "奥地利", "country_id": "43"},
{"id": 33, "cname": "安提瓜岛和巴布达", "country_id": "1268"},
{"id": 32, "cname": "安哥拉", "country_id": "244"},
{"id": 68, "cname": "非洲中部", "country_id": "236"},
{"id": 117, "cname": "罗马尼亚", "country_id": "40"},
{"id": 104, "cname": "科威特", "country_id": "965"},
{"id": 103, "cname": "科摩罗", "country_id": "269"},
{"id": 102, "cname": "开曼群岛", "country_id": "1345"},
{"id": 101, "cname": "卡塔尔", "country_id": "974"},
{"id": 100, "cname": "喀麦隆", "country_id": "237"},
{"id": 99, "cname": "聚会岛", "country_id": "262"},
{"id": 98, "cname": "津巴布韦", "country_id": "263"},
{"id": 97, "cname": "捷克", "country_id": "420"},
{"id": 96, "cname": "柬埔寨", "country_id": "855"},
{"id": 95, "cname": "加蓬", "country_id": "241"},
{"id": 105, "cname": "克罗地亚", "country_id": "385"},
{"id": 106, "cname": "肯尼亚", "country_id": "254"},
{"id": 116, "cname": "卢旺达", "country_id": "250"},
{"id": 115, "cname": "卢森堡", "country_id": "352"},
{"id": 114, "cname": "利比亚", "country_id": "218"},
{"id": 113, "cname": "利比里亚", "country_id": "231"},
{"id": 112, "cname": "立陶宛", "country_id": "370"},
{"id": 111, "cname": "黎巴嫩", "country_id": "961"},
{"id": 110, "cname": "老挝", "country_id": "856"},
{"id": 109, "cname": "莱索托", "country_id": "266"},
{"id": 108, "cname": "拉脱维亚", "country_id": "371"},
{"id": 107, "cname": "库克岛", "country_id": "682"},
{"id": 94, "cname": "加纳", "country_id": "233"},
{"id": 93, "cname": "几内亚比绍", "country_id": "245"},
{"id": 92, "cname": "几内亚", "country_id": "224"},
{"id": 79, "cname": "格林纳达", "country_id": "1473"},
{"id": 78, "cname": "哥斯达黎加", "country_id": "506"},
{"id": 77, "cname": "哥伦比亚", "country_id": "57"},
{"id": 76, "cname": "刚果(金)", "country_id": "243"},
{"id": 75, "cname": "刚果", "country_id": "242"},
{"id": 74, "cname": "冈比亚", "country_id": "220"},
{"id": 73, "cname": "福克兰岛", "country_id": "500"},
{"id": 72, "cname": "佛得角", "country_id": "238"},
{"id": 71, "cname": "芬兰", "country_id": "358"},
{"id": 70, "cname": "斐济", "country_id": "679"},
{"id": 80, "cname": "格陵兰岛", "country_id": "299"},
{"id": 81, "cname": "古巴", "country_id": "53"},
{"id": 91, "cname": "吉尔吉斯斯坦", "country_id": "996"},
{"id": 90, "cname": "吉布提", "country_id": "253"},
{"id": 89, "cname": "基里巴斯", "country_id": "686"},
{"id": 88, "cname": "维克岛", "country_id": "1808"},
{"id": 87, "cname": "洪都拉斯", "country_id": "504"},
{"id": 86, "cname": "荷兰", "country_id": "31"},
{"id": 85, "cname": "朝鲜", "country_id": "850"},
{"id": 84, "cname": "海地", "country_id": "509"},
{"id": 83, "cname": "关岛", "country_id": "1671"},
{"id": 82, "cname": "瓜德罗普岛", "country_id": "590"},
{"id": 69, "cname": "菲律宾", "country_id": "63"}
];
}

View File

@@ -1,11 +1,13 @@
import 'package:PiliPlus/common/skeleton/skeleton.dart';
import 'package:flutter/material.dart';
import 'skeleton.dart';
class DynamicCardSkeleton extends StatelessWidget {
const DynamicCardSkeleton({super.key});
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final color = theme.colorScheme.onInverseSurface;
return Skeleton(
child: Container(
padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
@@ -13,7 +15,7 @@ class DynamicCardSkeleton extends StatelessWidget {
border: Border(
bottom: BorderSide(
width: 8,
color: Theme.of(context).dividerColor.withOpacity(0.05),
color: theme.dividerColor.withValues(alpha: 0.05),
),
),
),
@@ -25,8 +27,8 @@ class DynamicCardSkeleton extends StatelessWidget {
width: 40,
height: 40,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface,
borderRadius: BorderRadius.circular(20),
color: color,
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
),
const SizedBox(width: 10),
@@ -34,13 +36,13 @@ class DynamicCardSkeleton extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
width: 100,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
width: 50,
height: 11,
),
@@ -55,31 +57,31 @@ class DynamicCardSkeleton extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
width: double.infinity,
height: 13,
margin: const EdgeInsets.only(bottom: 7),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
width: double.infinity,
height: 13,
margin: const EdgeInsets.only(bottom: 7),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
width: 300,
height: 13,
margin: const EdgeInsets.only(bottom: 7),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
width: 250,
height: 13,
margin: const EdgeInsets.only(bottom: 7),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
width: 100,
height: 13,
margin: const EdgeInsets.only(bottom: 7),
@@ -87,6 +89,7 @@ class DynamicCardSkeleton extends StatelessWidget {
],
),
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
@@ -99,10 +102,8 @@ class DynamicCardSkeleton extends StatelessWidget {
),
style: TextButton.styleFrom(
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
foregroundColor: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.2),
foregroundColor:
theme.colorScheme.outline.withValues(alpha: 0.2),
),
label: Text(
i == 0

View File

@@ -0,0 +1,67 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/skeleton.dart';
import 'package:flutter/material.dart';
class FavPgcItemSkeleton extends StatelessWidget {
const FavPgcItemSkeleton({super.key});
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: 5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 3 / 4,
child: LayoutBuilder(
builder: (context, boxConstraints) {
return Container(
decoration: BoxDecoration(
color: color,
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
width: boxConstraints.maxWidth,
height: boxConstraints.maxHeight,
);
},
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 175,
height: 12,
color: color,
),
const SizedBox(height: 10),
Container(
width: 55,
height: 11,
color: color,
),
const SizedBox(height: 5),
Container(
width: 35,
height: 11,
color: color,
),
],
),
),
],
),
),
);
}
}

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:PiliPlus/common/constants.dart';
import 'skeleton.dart';
import 'package:PiliPlus/common/skeleton/skeleton.dart';
import 'package:flutter/material.dart';
class MediaBangumiSkeleton extends StatefulWidget {
const MediaBangumiSkeleton({super.key});
@@ -24,8 +23,9 @@ class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
width: 111,
height: 148,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(6)),
color: bgColor),
borderRadius: const BorderRadius.all(Radius.circular(6)),
color: bgColor,
),
),
const SizedBox(width: 10),
Expanded(
@@ -35,25 +35,25 @@ class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: bgColor,
width: 200,
height: 20,
margin: const EdgeInsets.only(bottom: 15),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: bgColor,
width: 150,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: bgColor,
width: 150,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
color: bgColor,
width: 150,
height: 13,
),
@@ -64,7 +64,7 @@ class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(20)),
color: Theme.of(context).colorScheme.onInverseSurface,
color: bgColor,
),
),
],

View File

@@ -0,0 +1,53 @@
import 'package:PiliPlus/common/skeleton/skeleton.dart';
import 'package:flutter/material.dart';
class MsgFeedSysMsgSkeleton extends StatelessWidget {
const MsgFeedSysMsgSkeleton({super.key});
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 125,
height: 16,
color: color,
),
const SizedBox(height: 6),
Container(
width: double.infinity,
height: 12,
color: color,
),
const SizedBox(height: 4),
Container(
width: double.infinity,
height: 12,
color: color,
),
const SizedBox(height: 4),
Container(
width: 100,
height: 12,
color: color,
),
const SizedBox(height: 4),
Align(
alignment: Alignment.centerRight,
child: Container(
width: 100,
height: 10,
color: color,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,36 @@
import 'package:PiliPlus/common/skeleton/skeleton.dart';
import 'package:flutter/material.dart';
class MsgFeedTopSkeleton extends StatelessWidget {
const MsgFeedTopSkeleton({super.key});
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton(
child: ListTile(
leading: Container(
width: 45,
height: 45,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
),
),
title: UnconstrainedBox(
alignment: Alignment.centerLeft,
child: Container(
width: 100,
height: 11,
color: color,
),
),
subtitle: Container(
color: color,
width: 125,
height: 11,
),
),
);
}
}

View File

@@ -10,11 +10,12 @@ class Skeleton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.surface.withAlpha(10);
var shimmerGradient = LinearGradient(
colors: [
Colors.transparent,
Theme.of(context).colorScheme.surface.withAlpha(10),
Theme.of(context).colorScheme.surface.withAlpha(10),
color,
color,
Colors.transparent,
],
stops: const [
@@ -99,7 +100,7 @@ class ShimmerState extends State<Shimmer> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return widget.child ?? const SizedBox();
return widget.child ?? const SizedBox.shrink();
}
}
@@ -165,7 +166,7 @@ class _ShimmerLoadingState extends State<ShimmerLoading> {
final shimmer = Shimmer.of(context)!;
if (!shimmer.isSized) {
return const SizedBox();
return const SizedBox.shrink();
}
final shimmerSize = shimmer.size;
final gradient = shimmer.gradient;

View File

@@ -0,0 +1,48 @@
import 'package:PiliPlus/common/skeleton/skeleton.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
class SpaceOpusSkeleton extends StatelessWidget {
const SpaceOpusSkeleton({super.key});
@override
Widget build(BuildContext context) {
final surface = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton(
child: Card(
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
),
child: LayoutBuilder(
builder: (context, constraints) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: (0.68 + 0.82 * Utils.random.nextDouble()) *
constraints.maxWidth,
color: surface,
),
Container(
height: 10,
color: surface,
margin: const EdgeInsets.all(10),
width: constraints.maxWidth * 0.7,
),
Container(
height: 10,
color: surface,
margin:
const EdgeInsets.only(left: 10, right: 10, bottom: 10),
width: constraints.maxWidth,
),
],
);
},
),
),
);
}
}

View File

@@ -1,97 +1,80 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/skeleton.dart';
import 'package:flutter/material.dart';
import 'skeleton.dart';
class VideoCardHSkeleton extends StatelessWidget {
const VideoCardHSkeleton({super.key});
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: 5,
),
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
return Container(
decoration: BoxDecoration(
color:
Theme.of(context).colorScheme.onInverseSurface,
borderRadius: StyleString.mdRadius,
),
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
return DecoratedBox(
decoration: BoxDecoration(
color: color,
borderRadius: StyleString.mdRadius,
),
),
// VideoContent(videoItem: videoItem)
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color:
Theme.of(context).colorScheme.onInverseSurface,
width: 200,
height: 11,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color:
Theme.of(context).colorScheme.onInverseSurface,
width: 150,
height: 13,
),
const Spacer(),
Container(
color:
Theme.of(context).colorScheme.onInverseSurface,
width: 100,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Row(
children: [
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
margin: const EdgeInsets.only(right: 8),
),
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
),
],
)
],
),
),
),
],
);
},
),
);
},
),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: color,
width: 200,
height: 11,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color: color,
width: 150,
height: 13,
),
const Spacer(),
Container(
color: color,
width: 100,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Row(
children: [
Container(
color: color,
width: 40,
height: 13,
margin: const EdgeInsets.only(right: 8),
),
Container(
color: color,
width: 40,
height: 13,
),
],
)
],
),
),
),
],
),
),
);

View File

@@ -1,12 +1,13 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/skeleton.dart';
import 'package:flutter/material.dart';
import 'skeleton.dart';
class VideoCardVSkeleton extends StatelessWidget {
const VideoCardVSkeleton({super.key});
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton(
child: Column(
children: [
@@ -14,9 +15,9 @@ class VideoCardVSkeleton extends StatelessWidget {
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
return Container(
return DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
borderRadius: StyleString.mdRadius,
),
);
@@ -37,24 +38,24 @@ class VideoCardVSkeleton extends StatelessWidget {
width: 200,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
),
Container(
width: 150,
height: 13,
margin: const EdgeInsets.only(bottom: 12),
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
),
Container(
width: 110,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
),
Container(
width: 75,
height: 13,
color: Theme.of(context).colorScheme.onInverseSurface,
color: color,
),
],
),

View File

@@ -1,5 +1,5 @@
import 'package:PiliPlus/common/skeleton/skeleton.dart';
import 'package:flutter/material.dart';
import 'skeleton.dart';
class VideoReplySkeleton extends StatelessWidget {
const VideoReplySkeleton({super.key});

View File

@@ -0,0 +1,41 @@
import 'package:PiliPlus/common/skeleton/skeleton.dart';
import 'package:flutter/material.dart';
class WhisperItemSkeleton extends StatelessWidget {
const WhisperItemSkeleton({super.key});
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton(
child: ListTile(
leading: Container(
width: 45,
height: 45,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
),
),
title: UnconstrainedBox(
alignment: Alignment.centerLeft,
child: Container(
width: 100,
height: 11,
color: color,
),
),
subtitle: Container(
color: color,
width: 125,
height: 11,
),
trailing: Container(
color: color,
width: 50,
height: 11,
),
),
);
}
}

View File

@@ -1,74 +0,0 @@
import 'package:PiliPlus/common/widgets/no_splash_factory.dart';
import 'package:PiliPlus/common/widgets/overlay_pop.dart';
import 'package:flutter/material.dart';
class AnimatedDialog extends StatefulWidget {
const AnimatedDialog({
super.key,
required this.videoItem,
required this.closeFn,
});
final dynamic videoItem;
final Function closeFn;
@override
State<StatefulWidget> createState() => AnimatedDialogState();
}
class AnimatedDialogState extends State<AnimatedDialog>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> opacityAnimation;
late Animation<double> scaleAnimation;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 255));
opacityAnimation = Tween<double>(begin: 0.0, end: 0.6)
.animate(CurvedAnimation(parent: controller, curve: Curves.linear));
scaleAnimation = CurvedAnimation(parent: controller, curve: Curves.linear);
controller.addListener(() => setState(() {}));
controller.forward();
}
@override
void dispose() {
controller.removeListener(() {});
controller.dispose();
super.dispose();
}
void closeFn() async {
await controller.reverse();
widget.closeFn();
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.black.withOpacity(opacityAnimation.value),
child: InkWell(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
splashFactory: NoSplashFactory(),
onTap: closeFn,
child: Center(
child: FadeTransition(
opacity: scaleAnimation,
child: ScaleTransition(
scale: scaleAnimation,
child: OverlayPop(
videoItem: widget.videoItem,
closeFn: closeFn,
),
),
),
),
),
);
}
}

View File

@@ -1,351 +0,0 @@
import 'package:flutter/material.dart';
const double _kPanelHeaderCollapsedHeight = kMinInteractiveDimension;
class _SaltedKey<S, V> extends LocalKey {
const _SaltedKey(this.salt, this.value);
final S salt;
final V value;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is _SaltedKey<S, V> &&
other.salt == salt &&
other.value == value;
}
@override
int get hashCode => Object.hash(runtimeType, salt, value);
@override
String toString() {
final String saltString = S == String ? "<'$salt'>" : '<$salt>';
final String valueString = V == String ? "<'$value'>" : '<$value>';
return '[$saltString $valueString]';
}
}
class AppExpansionPanelList extends StatefulWidget {
/// Creates an expansion panel list widget. The [expansionCallback] is
/// triggered when an expansion panel expand/collapse button is pushed.
///
/// The [children] and [animationDuration] arguments must not be null.
const AppExpansionPanelList({
super.key,
required this.children,
this.expansionCallback,
this.animationDuration = kThemeAnimationDuration,
this.expandedHeaderPadding = EdgeInsets.zero,
this.dividerColor,
this.elevation = 2,
}) : _allowOnlyOnePanelOpen = false,
initialOpenPanelValue = null;
/// The children of the expansion panel list. They are laid out in a similar
/// fashion to [ListBody].
final List<AppExpansionPanel> children;
/// The callback that gets called whenever one of the expand/collapse buttons
/// is pressed. The arguments passed to the callback are the index of the
/// pressed panel and whether the panel is currently expanded or not.
///
/// If AppExpansionPanelList.radio is used, the callback may be called a
/// second time if a different panel was previously open. The arguments
/// passed to the second callback are the index of the panel that will close
/// and false, marking that it will be closed.
///
/// For AppExpansionPanelList, the callback needs to setState when it's notified
/// about the closing/opening panel. On the other hand, the callback for
/// AppExpansionPanelList.radio is simply meant to inform the parent widget of
/// changes, as the radio panels' open/close states are managed internally.
///
/// This callback is useful in order to keep track of the expanded/collapsed
/// panels in a parent widget that may need to react to these changes.
final ExpansionPanelCallback? expansionCallback;
/// The duration of the expansion animation.
final Duration animationDuration;
// Whether multiple panels can be open simultaneously
final bool _allowOnlyOnePanelOpen;
/// The value of the panel that initially begins open. (This value is
/// only used when initializing with the [AppExpansionPanelList.radio]
/// constructor.)
final Object? initialOpenPanelValue;
/// The padding that surrounds the panel header when expanded.
///
/// By default, 16px of space is added to the header vertically (above and below)
/// during expansion.
final EdgeInsets expandedHeaderPadding;
/// Defines color for the divider when [AppExpansionPanel.isExpanded] is false.
///
/// If `dividerColor` is null, then [DividerThemeData.color] is used. If that
/// is null, then [ThemeData.dividerColor] is used.
final Color? dividerColor;
/// Defines elevation for the [AppExpansionPanel] while it's expanded.
///
/// By default, the value of elevation is 2.
final double elevation;
@override
State<AppExpansionPanelList> createState() => _AppExpansionPanelListState();
}
class _AppExpansionPanelListState extends State<AppExpansionPanelList> {
ExpansionPanelRadio? _currentOpenPanel;
@override
void initState() {
super.initState();
if (widget._allowOnlyOnePanelOpen) {
assert(_allIdentifiersUnique(),
'All ExpansionPanelRadio identifier values must be unique.');
if (widget.initialOpenPanelValue != null) {
_currentOpenPanel = searchPanelByValue(
widget.children.cast<ExpansionPanelRadio>(),
widget.initialOpenPanelValue);
}
}
}
@override
void didUpdateWidget(AppExpansionPanelList oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget._allowOnlyOnePanelOpen) {
assert(_allIdentifiersUnique(),
'All ExpansionPanelRadio identifier values must be unique.');
// If the previous widget was non-radio AppExpansionPanelList, initialize the
// open panel to widget.initialOpenPanelValue
if (!oldWidget._allowOnlyOnePanelOpen) {
_currentOpenPanel = searchPanelByValue(
widget.children.cast<ExpansionPanelRadio>(),
widget.initialOpenPanelValue);
}
} else {
_currentOpenPanel = null;
}
}
bool _allIdentifiersUnique() {
final Map<Object, bool> identifierMap = <Object, bool>{};
for (final ExpansionPanelRadio child
in widget.children.cast<ExpansionPanelRadio>()) {
identifierMap[child.value] = true;
}
return identifierMap.length == widget.children.length;
}
bool _isChildExpanded(int index) {
if (widget._allowOnlyOnePanelOpen) {
final ExpansionPanelRadio radioWidget =
widget.children[index] as ExpansionPanelRadio;
return _currentOpenPanel?.value == radioWidget.value;
}
return widget.children[index].isExpanded;
}
void _handlePressed(bool isExpanded, int index) {
widget.expansionCallback?.call(index, isExpanded);
if (widget._allowOnlyOnePanelOpen) {
final ExpansionPanelRadio pressedChild =
widget.children[index] as ExpansionPanelRadio;
// If another ExpansionPanelRadio was already open, apply its
// expansionCallback (if any) to false, because it's closing.
for (int childIndex = 0;
childIndex < widget.children.length;
childIndex += 1) {
final ExpansionPanelRadio child =
widget.children[childIndex] as ExpansionPanelRadio;
if (widget.expansionCallback != null &&
childIndex != index &&
child.value == _currentOpenPanel?.value) {
widget.expansionCallback?.call(childIndex, false);
}
}
setState(() {
_currentOpenPanel = isExpanded ? null : pressedChild;
});
}
}
ExpansionPanelRadio? searchPanelByValue(
List<ExpansionPanelRadio> panels, Object? value) {
for (final ExpansionPanelRadio panel in panels) {
if (panel.value == value) return panel;
}
return null;
}
@override
Widget build(BuildContext context) {
assert(
kElevationToShadow.containsKey(widget.elevation),
'Invalid value for elevation. See the kElevationToShadow constant for'
' possible elevation values.',
);
final List<MergeableMaterialItem> items = <MergeableMaterialItem>[];
for (int index = 0; index < widget.children.length; index += 1) {
//todo: Uncomment to add gap between selected panels
/*if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1))
items.add(MaterialGap(key: _SaltedKey<BuildContext, int>(context, index * 2 - 1)));*/
final AppExpansionPanel child = widget.children[index];
final Widget headerWidget = child.headerBuilder(
context,
_isChildExpanded(index),
);
Widget? expandIconContainer = ExpandIcon(
isExpanded: _isChildExpanded(index),
onPressed: !child.canTapOnHeader
? (bool isExpanded) => _handlePressed(isExpanded, index)
: null,
);
if (!child.canTapOnHeader) {
final MaterialLocalizations localizations =
MaterialLocalizations.of(context);
expandIconContainer = Semantics(
label: _isChildExpanded(index)
? localizations.expandedIconTapHint
: localizations.collapsedIconTapHint,
container: true,
child: expandIconContainer,
);
}
final iconContainer = child.iconBuilder;
if (iconContainer != null) {
expandIconContainer = iconContainer(
expandIconContainer,
_isChildExpanded(index),
);
}
Widget header = Row(
children: <Widget>[
Expanded(
child: AnimatedContainer(
duration: widget.animationDuration,
curve: Curves.fastOutSlowIn,
margin: _isChildExpanded(index)
? widget.expandedHeaderPadding
: EdgeInsets.zero,
child: ConstrainedBox(
constraints: const BoxConstraints(
minHeight: _kPanelHeaderCollapsedHeight),
child: headerWidget,
),
),
),
if (expandIconContainer != null) expandIconContainer,
],
);
if (child.canTapOnHeader) {
header = MergeSemantics(
child: InkWell(
onTap: () => _handlePressed(_isChildExpanded(index), index),
child: header,
),
);
}
items.add(
MaterialSlice(
key: _SaltedKey<BuildContext, int>(context, index * 2),
color: child.backgroundColor,
child: Column(
children: <Widget>[
header,
AnimatedCrossFade(
firstChild: Container(height: 0.0),
secondChild: child.body,
firstCurve:
const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve:
const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: _isChildExpanded(index)
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: widget.animationDuration,
),
],
),
),
);
if (_isChildExpanded(index) && index != widget.children.length - 1) {
items.add(MaterialGap(
key: _SaltedKey<BuildContext, int>(context, index * 2 + 1)));
}
}
return MergeableMaterial(
hasDividers: true,
dividerColor: widget.dividerColor,
elevation: widget.elevation,
children: items,
);
}
}
typedef ExpansionPanelIconBuilder = Widget? Function(
Widget child,
bool isExpanded,
);
class AppExpansionPanel {
/// Creates an expansion panel to be used as a child for [ExpansionPanelList].
/// See [ExpansionPanelList] for an example on how to use this widget.
///
/// The [headerBuilder], [body], and [isExpanded] arguments must not be null.
AppExpansionPanel({
required this.headerBuilder,
required this.body,
this.iconBuilder,
this.isExpanded = false,
this.canTapOnHeader = false,
this.backgroundColor,
});
/// The widget builder that builds the expansion panels' header.
final ExpansionPanelHeaderBuilder headerBuilder;
/// The widget builder that builds the expansion panels' icon.
///
/// If not pass any function, then default icon will be displayed.
///
/// If builder function return null, then icon will not displayed.
final ExpansionPanelIconBuilder? iconBuilder;
/// The body of the expansion panel that's displayed below the header.
///
/// This widget is visible only when the panel is expanded.
final Widget body;
/// Whether the panel is expanded.
///
/// Defaults to false.
final bool isExpanded;
/// Whether tapping on the panel's header will expand/collapse it.
///
/// Defaults to false.
final bool canTapOnHeader;
/// Defines the background color of the panel.
///
/// Defaults to [ThemeData.cardColor].
final Color? backgroundColor;
}

View File

@@ -1,32 +0,0 @@
import 'package:flutter/material.dart';
class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
const AppBarWidget({
required this.child,
required this.controller,
required this.visible,
super.key,
});
final PreferredSizeWidget child;
final AnimationController controller;
final bool visible;
@override
Size get preferredSize => child.preferredSize;
@override
Widget build(BuildContext context) {
visible ? controller.reverse() : controller.forward();
return SlideTransition(
position: Tween<Offset>(
begin: Offset.zero,
end: const Offset(0, -1),
).animate(CurvedAnimation(
parent: controller,
curve: Curves.easeInOutBack,
)),
child: child,
);
}
}

View File

@@ -1,94 +0,0 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/dynamics/article_content_model.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
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()
.map((item) => item.pic?.pics?.first.url ?? '')
.toList();
return SliverList.separated(
itemCount: list.length,
itemBuilder: (context, index) {
ArticleContentModel item = list[index];
if (item.text != null) {
List<InlineSpan> spanList = [];
item.text?.nodes?.forEach((item) {
spanList.add(TextSpan(
text: item.word?.words,
style: TextStyle(
letterSpacing: 0.3,
fontSize: 17,
height: LineHeight.percent(125).size,
fontStyle:
item.word?.style?.italic == true ? FontStyle.italic : null,
color: item.word?.color != null
? Color(int.parse(
item.word!.color!.replaceFirst('#', 'FF'),
radix: 16,
))
: null,
decoration: item.word?.style?.strikethrough == true
? TextDecoration.lineThrough
: null,
fontWeight:
item.word?.style?.bold == true ? FontWeight.bold : null,
),
));
});
return SelectableText.rich(TextSpan(children: spanList));
} else if (item.line != null) {
return Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 10),
child: CachedNetworkImage(
imageUrl: item.line?.pic?.url?.http2https ?? '',
height: item.line?.pic?.height?.toDouble(),
),
);
} else if (item.pic != null) {
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,
),
),
);
} else {
return const SizedBox.shrink();
// return Text('unsupported content');
}
},
separatorBuilder: (context, index) => const SizedBox(height: 10),
);
}

View File

@@ -1,97 +1,113 @@
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class PBadge extends StatelessWidget {
final String? text;
final bool isStack;
final double? top;
final double? right;
final double? bottom;
final double? left;
final String? type;
final String? size;
final String? stack;
final double? fs;
final String? semanticsLabel;
final bool bold;
final EdgeInsets? padding;
final PBadgeType type;
final PBadgeSize size;
final double fontSize;
final bool isBold;
final double? textScaleFactor;
const PBadge({
super.key,
this.text,
required this.text,
this.top,
this.right,
this.bottom,
this.left,
this.type = 'primary',
this.size = 'medium',
this.stack = 'position',
this.fs = 11,
this.semanticsLabel,
this.bold = true,
this.type = PBadgeType.primary,
this.size = PBadgeSize.medium,
this.isStack = true,
this.fontSize = 11,
this.isBold = true,
this.textScaleFactor,
this.padding,
});
@override
Widget build(BuildContext context) {
ColorScheme t = Theme.of(context).colorScheme;
// 背景色
Color bgColor = t.primary;
// 前景色
Color color = t.onPrimary;
// 边框色
if (text.isNullOrEmpty) {
return const SizedBox.shrink();
}
ColorScheme theme = Theme.of(context).colorScheme;
Color bgColor;
Color color;
Color borderColor = Colors.transparent;
if (type == 'gray') {
bgColor = Colors.black54.withOpacity(0.4);
color = Colors.white;
}
if (type == 'color') {
bgColor = t.secondaryContainer.withOpacity(0.5);
color = t.onSecondaryContainer;
}
if (type == 'line') {
bgColor = Colors.transparent;
color = t.primary;
borderColor = t.primary;
switch (type) {
case PBadgeType.primary:
bgColor = theme.primary;
color = theme.onPrimary;
case PBadgeType.secondary:
bgColor = theme.secondaryContainer.withValues(alpha: 0.5);
color = theme.onSecondaryContainer;
case PBadgeType.gray:
bgColor = Colors.black45;
color = Colors.white;
case PBadgeType.error:
bgColor = theme.error;
color = theme.onError;
case PBadgeType.line_primary:
color = theme.primary;
bgColor = Colors.transparent;
borderColor = theme.primary;
case PBadgeType.line_secondary:
color = theme.secondary;
bgColor = Colors.transparent;
borderColor = theme.secondary;
case PBadgeType.free:
bgColor =
Get.isDarkMode ? const Color(0xFFD66011) : const Color(0xFFFF7F24);
color = Colors.white;
}
EdgeInsets paddingStyle =
late EdgeInsets paddingStyle =
const EdgeInsets.symmetric(vertical: 2, horizontal: 3);
double fontSize = 11;
BorderRadius br = BorderRadius.circular(4);
if (size == 'small') {
paddingStyle = const EdgeInsets.symmetric(vertical: 2, horizontal: 3);
fontSize = 11;
br = BorderRadius.circular(3);
}
BorderRadius br = size == PBadgeSize.small
? const BorderRadius.all(Radius.circular(3))
: const BorderRadius.all(Radius.circular(4));
Widget content = Container(
padding: paddingStyle,
padding: padding ?? paddingStyle,
decoration: BoxDecoration(
borderRadius: br,
color: bgColor,
border: Border.all(color: borderColor),
),
child: Text(
text ?? "",
text!,
textScaler: textScaleFactor != null
? TextScaler.linear(textScaleFactor!)
: null,
style: TextStyle(
height: 1,
fontSize: fs ?? fontSize,
fontSize: fontSize,
color: color,
fontWeight: bold ? FontWeight.bold : null,
fontWeight: isBold ? FontWeight.bold : null,
),
strutStyle: StrutStyle(
leading: 0,
height: 1,
fontSize: fs ?? fontSize,
fontWeight: bold ? FontWeight.bold : null,
fontSize: fontSize,
fontWeight: isBold ? FontWeight.bold : null,
),
semanticsLabel: semanticsLabel,
),
);
if (stack == 'position') {
if (isStack) {
return Positioned(
top: top,
left: left,

View File

@@ -10,6 +10,7 @@ Widget iconButton({
Color? bgColor,
Color? iconColor,
}) {
late final theme = Theme.of(context);
return SizedBox(
width: size,
height: size,
@@ -19,12 +20,11 @@ Widget iconButton({
icon: Icon(
icon,
size: iconSize ?? size / 2,
color: iconColor ?? Theme.of(context).colorScheme.onSecondaryContainer,
color: iconColor ?? theme.colorScheme.onSecondaryContainer,
),
style: IconButton.styleFrom(
padding: EdgeInsets.all(0),
backgroundColor:
bgColor ?? Theme.of(context).colorScheme.secondaryContainer,
padding: EdgeInsets.zero,
backgroundColor: bgColor ?? theme.colorScheme.secondaryContainer,
),
),
);

View File

@@ -16,6 +16,7 @@ class ToolbarIconButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return SizedBox(
width: 36,
height: 36,
@@ -23,16 +24,14 @@ class ToolbarIconButton extends StatelessWidget {
tooltip: tooltip,
onPressed: onPressed,
icon: icon,
highlightColor: Theme.of(context).colorScheme.secondaryContainer,
highlightColor: theme.colorScheme.secondaryContainer,
color: selected
? Theme.of(context).colorScheme.onSecondaryContainer
: Theme.of(context).colorScheme.outline,
? theme.colorScheme.onSecondaryContainer
: theme.colorScheme.outline,
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
backgroundColor: WidgetStateProperty.resolveWith((states) {
return selected
? Theme.of(context).colorScheme.secondaryContainer
: null;
return selected ? theme.colorScheme.secondaryContainer : null;
}),
),
),

View File

@@ -1,47 +0,0 @@
import 'package:flutter/material.dart';
class ContentContainer extends StatelessWidget {
final Widget? contentWidget;
final Widget? bottomWidget;
final bool isScrollable;
final Clip? childClipBehavior;
const ContentContainer({
super.key,
this.contentWidget,
this.bottomWidget,
this.isScrollable = true,
this.childClipBehavior,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
clipBehavior: childClipBehavior ?? Clip.hardEdge,
physics: isScrollable ? null : const NeverScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: constraints.copyWith(
minHeight: constraints.maxHeight,
maxHeight: double.infinity,
),
child: IntrinsicHeight(
child: Column(
children: <Widget>[
if (contentWidget != null)
Expanded(
child: contentWidget!,
)
else
const Spacer(),
if (bottomWidget != null) bottomWidget!,
],
),
),
),
);
},
);
}
}

View File

@@ -0,0 +1,29 @@
// ignore_for_file: constant_identifier_names
import 'package:flutter/widgets.dart';
class CustomIcon {
static const _kFontFam = 'custom_icon';
static const IconData coin = IconData(0xe800, fontFamily: _kFontFam);
static const IconData dm_off = IconData(0xe801, fontFamily: _kFontFam);
static const IconData dm_on = IconData(0xe802, fontFamily: _kFontFam);
static const IconData dm_settings = IconData(0xe803, fontFamily: _kFontFam);
static const IconData dyn = IconData(0xe804, fontFamily: _kFontFam);
static const IconData fav = IconData(0xe805, fontFamily: _kFontFam);
static const IconData share = IconData(0xe806, fontFamily: _kFontFam);
static const IconData share_line = IconData(0xe807, fontFamily: _kFontFam);
static const IconData share_node = IconData(0xe808, fontFamily: _kFontFam);
static const IconData star_favorite_line =
IconData(0xe809, fontFamily: _kFontFam);
static const IconData thumbs_down = IconData(0xe80a, fontFamily: _kFontFam);
static const IconData thumbs_down_outline =
IconData(0xe80b, fontFamily: _kFontFam);
static const IconData thumbs_up = IconData(0xe80c, fontFamily: _kFontFam);
static const IconData thumbs_up_line =
IconData(0xe80d, fontFamily: _kFontFam);
static const IconData thumbs_up_outline =
IconData(0xe80e, fontFamily: _kFontFam);
static const IconData topic_tag = IconData(0xe80f, fontFamily: _kFontFam);
static const IconData watch_later = IconData(0xe810, fontFamily: _kFontFam);
}

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
class CustomToast extends StatelessWidget {
const CustomToast({super.key, required this.msg});
@@ -8,6 +8,7 @@ class CustomToast extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final double toastOpacity = GStorage.setting
.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0) as double;
return Container(
@@ -15,19 +16,50 @@ class CustomToast extends StatelessWidget {
EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 30),
padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 10),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(toastOpacity),
borderRadius: BorderRadius.circular(20),
color:
theme.colorScheme.primaryContainer.withValues(alpha: toastOpacity),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Text(
msg,
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.onPrimaryContainer,
color: theme.colorScheme.onPrimaryContainer,
),
),
);
}
}
class LoadingWidget extends StatelessWidget {
const LoadingWidget({super.key, required this.msg});
///loading msg
final String msg;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final onSurfaceVariant = theme.colorScheme.onSurfaceVariant;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
decoration: BoxDecoration(
color: theme.dialogBackgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(15)),
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
//loading animation
CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation(onSurfaceVariant),
),
//msg
Container(
margin: const EdgeInsets.only(top: 20),
child: Text(msg, style: TextStyle(color: onSurfaceVariant)),
),
]),
);
}
}

View File

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

View File

@@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void showConfirmDialog({
required BuildContext context,
required String title,
dynamic content,
required VoidCallback onConfirm,
}) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(title),
content: content is String
? Text(content)
: content is Widget
? content
: null,
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () {
Get.back();
onConfirm();
},
child: const Text('确认'),
),
],
);
},
);
}
void showPgcFollowDialog({
required BuildContext context,
required String type,
required int followStatus,
required ValueChanged<int> onUpdateStatus,
}) {
Widget statusItem({
required bool enabled,
required String text,
required VoidCallback onTap,
}) {
return ListTile(
dense: true,
enabled: enabled,
title: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
'标记为 $text',
style: const TextStyle(fontSize: 14),
),
),
trailing: !enabled ? const Icon(size: 22, Icons.check) : null,
onTap: onTap,
);
}
showDialog(
context: context,
builder: (context) => AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
...[
{'followStatus': 3, 'title': '看过'},
{'followStatus': 2, 'title': '在看'},
{'followStatus': 1, 'title': '想看'},
].map(
(Map item) => statusItem(
enabled: followStatus != item['followStatus'],
text: item['title'],
onTap: () {
Get.back();
onUpdateStatus(item['followStatus']);
},
),
),
ListTile(
dense: true,
title: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
'取消$type',
style: const TextStyle(fontSize: 14),
),
),
onTap: () {
Get.back();
onUpdateStatus(-1);
},
)
],
),
));
}

View File

@@ -0,0 +1,245 @@
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
void autoWrapReportDialog(
BuildContext context,
Map<String, Map<int, String>> options,
Future<Map> Function(int reasonType, String? reasonDesc, bool banUid)
onSuccess,
) {
int? reasonType;
String? reasonDesc;
bool banUid = false;
late final key = GlobalKey<FormState>();
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: const Text('举报'),
titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22),
contentPadding: const EdgeInsets.symmetric(vertical: 5),
actionsPadding:
const EdgeInsets.only(left: 16, right: 16, bottom: 10),
content: Form(
key: key,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: SingleChildScrollView(
child: AnimatedSize(
duration: const Duration(milliseconds: 200),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(
left: 22,
right: 22,
bottom: 5,
),
child: Text('请选择举报的理由:'),
),
...options.entries.map(
(entry) => WrapRadioOptionsGroup<int>(
groupTitle: entry.key,
options: entry.value,
selectedValue: reasonType,
onChanged: (value) =>
setState(() => reasonType = value),
),
),
if (reasonType == 0)
ReasonField(
onChanged: (value) => reasonDesc = value),
],
),
),
),
),
Padding(
padding: const EdgeInsets.only(left: 14, top: 6),
child: CheckBoxText(
text: '拉黑该用户', onChanged: (value) => banUid = value),
),
],
),
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
if (reasonType == null ||
(reasonType == 0 && key.currentState?.validate() != true)) {
return;
}
SmartDialog.showLoading();
try {
final data = await onSuccess(reasonType!, reasonDesc, banUid);
SmartDialog.dismiss();
if (data['code'] == 0) {
Get.back();
SmartDialog.showToast('举报成功');
} else {
SmartDialog.showToast(data['message']);
}
} catch (e) {
SmartDialog.dismiss();
SmartDialog.showToast('提交失败:$e');
}
},
child: const Text('确定'),
),
],
);
},
),
);
}
class ReasonField extends StatefulWidget {
final ValueChanged<String> onChanged;
String? _validator(String? value) => value.isNullOrEmpty ? '理由不能为空' : null;
const ReasonField({super.key, required this.onChanged});
@override
State<ReasonField> createState() => _ReasonFieldState();
}
class _ReasonFieldState extends State<ReasonField> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 22, top: 5, right: 22),
child: TextFormField(
controller: _controller,
autofocus: true,
minLines: 4,
maxLines: 4,
decoration: const InputDecoration(
labelText: '为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.all(10),
),
onChanged: (value) {
widget.onChanged(value);
},
validator: widget._validator,
),
);
}
}
class CheckBoxText extends StatefulWidget {
final String text;
final ValueChanged<bool> onChanged;
final bool selected;
const CheckBoxText({
super.key,
required this.text,
required this.onChanged,
this.selected = false,
});
@override
State<CheckBoxText> createState() => _CheckBoxTextState();
}
class _CheckBoxTextState extends State<CheckBoxText> {
late bool _selected;
@override
void initState() {
super.initState();
_selected = widget.selected;
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return InkWell(
onTap: () {
setState(() {
_selected = !_selected;
});
widget.onChanged(_selected);
},
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
size: 22,
_selected
? Icons.check_box_outlined
: Icons.check_box_outline_blank,
color: _selected
? colorScheme.primary
: colorScheme.onSurfaceVariant,
),
Text(
' ${widget.text}',
style: TextStyle(color: _selected ? colorScheme.primary : null),
),
],
),
),
);
}
}
class ReportOptions {
// from https://s1.hdslb.com/bfs/seed/jinkela/comment-h5/static/js/605.chunks.js
static Map<String, Map<int, String>> get commentReport => const {
'违反法律法规': {9: '违法违规', 2: '色情', 10: '低俗', 12: '赌博诈骗', 23: '违法信息外链'},
'谣言类不实信息': {19: '涉政谣言', 22: '虚假不实信息', 20: '涉社会事件谣言'},
'侵犯个人权益': {7: '人身攻击', 15: '侵犯隐私'},
'有害社区环境': {
1: '垃圾广告',
4: '引战',
5: '剧透',
3: '刷屏',
8: '视频不相关',
18: '违规抽奖',
17: '青少年不良信息',
},
'其他': {0: '其他'},
};
static Map<String, Map<int, String>> get dynamicReport => const {
'': {
4: '垃圾广告',
8: '引战',
1: '色情',
5: '人身攻击',
3: '违法信息',
9: '涉政谣言',
10: '涉社会事件谣言',
12: '虚假不实信息',
13: '违法信息外链',
0: '其他',
},
};
}

View File

@@ -0,0 +1,126 @@
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class MemberReportPanel extends StatefulWidget {
const MemberReportPanel({
super.key,
required this.name,
required this.mid,
});
final dynamic name;
final dynamic mid;
@override
State<MemberReportPanel> createState() => _MemberReportPanelState();
}
class _MemberReportPanelState extends State<MemberReportPanel> {
final List<bool> _reasonList = List.generate(3, (_) => false).toList();
final Set<int> _reason = {};
int? _reasonV2;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'举报: ${widget.name}',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 4),
Text('uid: ${widget.mid}'),
const SizedBox(height: 10),
const Text('举报内容(必选,可多选)'),
...List.generate(
3,
(index) => _checkBoxWidget(
_reasonList[index],
(value) {
setState(() => _reasonList[index] = value);
if (value) {
_reason.add(index + 1);
} else {
_reason.remove(index + 1);
}
},
['头像违规', '昵称违规', '签名违规'][index],
),
),
const Text('举报理由(单选,非必选)'),
...List.generate(
5,
(index) => RadioWidget<int>(
value: index,
groupValue: _reasonV2,
onChanged: (value) {
setState(() => _reasonV2 = value);
},
title: const ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗'][index],
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: theme.colorScheme.outline),
),
),
TextButton(
onPressed: () async {
if (_reason.isEmpty) {
SmartDialog.showToast('至少选择一项作为举报内容');
} else {
Get.back();
dynamic result = await MemberHttp.reportMember(
widget.mid,
reason: _reason.join(','),
reasonV2: _reasonV2 != null ? _reasonV2! + 1 : null,
);
if (result['msg'] is String && result['msg'].isNotEmpty) {
SmartDialog.showToast(result['msg']);
} else {
SmartDialog.showToast('举报失败');
}
}
},
child: const Text('确定'),
),
],
),
],
),
);
}
}
Widget _checkBoxWidget(
bool defValue,
ValueChanged onChanged,
String title,
) {
return InkWell(
onTap: () => onChanged(!defValue),
child: Row(
children: [
Checkbox(
value: defValue,
onChanged: onChanged,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
Text(title),
],
),
);
}

View File

@@ -0,0 +1,101 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class DisabledIcon<T extends Widget> extends SingleChildRenderObjectWidget {
final Color? color;
final double lineLengthScale;
final StrokeCap strokeCap;
const DisabledIcon({
super.key,
required T child,
this.color,
double? lineLengthScale,
StrokeCap? strokeCap,
}) : lineLengthScale = lineLengthScale ?? 0.9,
strokeCap = strokeCap ?? StrokeCap.butt,
super(child: child);
@override
RenderObject createRenderObject(BuildContext context) {
return RenderMaskedIcon(
color ??
(child is Icon
? (child as Icon).color ?? IconTheme.of(context).color!
: IconTheme.of(context).color!),
lineLengthScale,
strokeCap,
);
}
T enable() => child as T;
}
class RenderMaskedIcon extends RenderProxyBox {
final Color color;
final double lineLengthScale;
final StrokeCap strokeCap;
RenderMaskedIcon(this.color, this.lineLengthScale, this.strokeCap);
@override
void paint(PaintingContext context, Offset offset) {
final strokeWidth = size.width / 12;
final canvas = context.canvas;
var rect = offset & size;
final sqrt2Width = strokeWidth * sqrt2; // rotate pi / 4
// final path = Path.combine(
// PathOperation.difference,
// Path()..addRect(rect),
// Path()..moveTo(rect.left, rect.top)
// ..relativeLineTo(sqrt2Width, 0)
// ..lineTo(rect.right, rect.bottom - sqrt2Width)
// ..lineTo(rect.right, rect.bottom)
// ..close(),
// );
final path = Path.combine(
PathOperation.union,
Path() // bottom
..moveTo(rect.left, rect.bottom)
..lineTo(rect.left, rect.top + sqrt2Width)
..lineTo(rect.right - sqrt2Width, rect.bottom)
..close(),
Path() // top
..moveTo(rect.right, rect.top)
..lineTo(rect.right, rect.bottom - sqrt2Width)
..lineTo(rect.left + sqrt2Width, rect.top));
canvas
..save()
..clipPath(path, doAntiAlias: false);
super.paint(context, offset);
context.canvas.restore();
final linePaint = Paint()
..color = color
..strokeWidth = strokeWidth
..strokeCap = strokeCap;
final strokeOffset = strokeWidth * sqrt1_2 / 2;
rect = rect
.translate(-strokeOffset, strokeOffset)
.deflate(size.width * lineLengthScale);
canvas.drawLine(
rect.topLeft,
rect.bottomRight,
linePaint,
);
}
}
extension DisabledIconExt on Icon {
DisabledIcon<Icon> disable([double? lineLengthScale]) =>
DisabledIcon(lineLengthScale: lineLengthScale, child: this);
}

View File

@@ -35,7 +35,7 @@ class DynamicSliverAppBar extends StatefulWidget {
this.stretchTriggerOffset = 100.0,
this.onStretchTrigger,
this.shape,
this.toolbarHeight = kToolbarHeight + 20,
this.toolbarHeight = kToolbarHeight,
this.leadingWidth,
this.toolbarTextStyle,
this.titleTextStyle,
@@ -43,8 +43,10 @@ class DynamicSliverAppBar extends StatefulWidget {
this.forceMaterialTransparency = false,
this.clipBehavior,
this.appBarClipper,
this.callback,
});
final ValueChanged<double>? callback;
final Widget? flexibleSpace;
final Widget? leading;
final bool automaticallyImplyLeading;
@@ -95,7 +97,6 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
// As long as the height is 0 instead of the sliver app bar a sliver to box adapter will be used
// to calculate dynamically the size for the sliver app bar
double _height = 0;
Orientation? _orientation;
@override
void initState() {
@@ -103,13 +104,6 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
_updateHeight();
}
@override
void didUpdateWidget(covariant DynamicSliverAppBar oldWidget) {
super.didUpdateWidget(oldWidget);
_updateHeight();
}
void _updateHeight() {
// Gets the new height and updates the sliver app bar. Needs to be called after the last frame has been rebuild
// otherwise this will throw an error
@@ -119,27 +113,32 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
_height = (_childKey.currentContext!.findRenderObject()! as RenderBox)
.size
.height;
widget.callback?.call(_height);
});
});
}
@override
void didChangeDependencies() {
_height = 0;
_updateHeight();
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
//Needed to lay out the flexibleSpace the first time, so we can calculate its intrinsic height
Orientation orientation = MediaQuery.orientationOf(context);
if (_orientation != orientation) {
_orientation = orientation;
_height = 0;
}
if (_height == 0) {
return SliverToBoxAdapter(
child: Container(
child: SizedBox(
key: _childKey,
child: widget.flexibleSpace ?? SizedBox(height: kToolbarHeight),
child: widget.flexibleSpace ?? const SizedBox(height: kToolbarHeight),
),
);
}
MediaQuery.orientationOf(context);
return SliverAppBar(
leading: widget.leading,
automaticallyImplyLeading: widget.automaticallyImplyLeading,

View File

@@ -0,0 +1,179 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// https://github.com/flutter/flutter/issues/18345#issuecomment-1627644396
class DynamicSliverAppBarMedium extends StatefulWidget {
const DynamicSliverAppBarMedium({
this.flexibleSpace,
super.key,
this.leading,
this.automaticallyImplyLeading = true,
this.title,
this.actions,
this.bottom,
this.elevation,
this.scrolledUnderElevation,
this.shadowColor,
this.surfaceTintColor,
this.forceElevated = false,
this.backgroundColor,
this.backgroundGradient,
this.foregroundColor,
this.iconTheme,
this.actionsIconTheme,
this.primary = true,
this.centerTitle,
this.excludeHeaderSemantics = false,
this.titleSpacing,
this.collapsedHeight,
this.expandedHeight,
this.floating = false,
this.pinned = false,
this.snap = false,
this.stretch = false,
this.stretchTriggerOffset = 100.0,
this.onStretchTrigger,
this.shape,
this.toolbarHeight = kToolbarHeight,
this.leadingWidth,
this.toolbarTextStyle,
this.titleTextStyle,
this.systemOverlayStyle,
this.forceMaterialTransparency = false,
this.clipBehavior,
this.appBarClipper,
this.callback,
});
final ValueChanged<double>? callback;
final Widget? flexibleSpace;
final Widget? leading;
final bool automaticallyImplyLeading;
final Widget? title;
final List<Widget>? actions;
final PreferredSizeWidget? bottom;
final double? elevation;
final double? scrolledUnderElevation;
final Color? shadowColor;
final Color? surfaceTintColor;
final bool forceElevated;
final Color? backgroundColor;
/// If backgroundGradient is non null, backgroundColor will be ignored
final LinearGradient? backgroundGradient;
final Color? foregroundColor;
final IconThemeData? iconTheme;
final IconThemeData? actionsIconTheme;
final bool primary;
final bool? centerTitle;
final bool excludeHeaderSemantics;
final double? titleSpacing;
final double? expandedHeight;
final double? collapsedHeight;
final bool floating;
final bool pinned;
final ShapeBorder? shape;
final double toolbarHeight;
final double? leadingWidth;
final TextStyle? toolbarTextStyle;
final TextStyle? titleTextStyle;
final SystemUiOverlayStyle? systemOverlayStyle;
final bool forceMaterialTransparency;
final Clip? clipBehavior;
final bool snap;
final bool stretch;
final double stretchTriggerOffset;
final AsyncCallback? onStretchTrigger;
final CustomClipper<Path>? appBarClipper;
@override
State<DynamicSliverAppBarMedium> createState() =>
_DynamicSliverAppBarMediumState();
}
class _DynamicSliverAppBarMediumState extends State<DynamicSliverAppBarMedium> {
final GlobalKey _childKey = GlobalKey();
// As long as the height is 0 instead of the sliver app bar a sliver to box adapter will be used
// to calculate dynamically the size for the sliver app bar
double _height = 0;
@override
void initState() {
super.initState();
_updateHeight();
}
void _updateHeight() {
// Gets the new height and updates the sliver app bar. Needs to be called after the last frame has been rebuild
// otherwise this will throw an error
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (_childKey.currentContext == null) return;
setState(() {
_height = (_childKey.currentContext!.findRenderObject()! as RenderBox)
.size
.height;
widget.callback?.call(_height);
});
});
}
@override
void didChangeDependencies() {
_height = 0;
_updateHeight();
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
//Needed to lay out the flexibleSpace the first time, so we can calculate its intrinsic height
if (_height == 0) {
return SliverToBoxAdapter(
child: SizedBox(
key: _childKey,
child: widget.flexibleSpace ?? const SizedBox(height: kToolbarHeight),
),
);
}
return SliverAppBar.medium(
leading: widget.leading,
automaticallyImplyLeading: widget.automaticallyImplyLeading,
title: widget.title,
actions: widget.actions,
bottom: widget.bottom,
elevation: widget.elevation,
scrolledUnderElevation: widget.scrolledUnderElevation,
shadowColor: widget.shadowColor,
surfaceTintColor: widget.surfaceTintColor,
forceElevated: widget.forceElevated,
backgroundColor: widget.backgroundColor,
foregroundColor: widget.foregroundColor,
iconTheme: widget.iconTheme,
actionsIconTheme: widget.actionsIconTheme,
primary: widget.primary,
centerTitle: widget.centerTitle,
excludeHeaderSemantics: widget.excludeHeaderSemantics,
titleSpacing: widget.titleSpacing,
collapsedHeight: widget.collapsedHeight,
floating: widget.floating,
pinned: widget.pinned,
snap: widget.snap,
stretch: widget.stretch,
stretchTriggerOffset: widget.stretchTriggerOffset,
onStretchTrigger: widget.onStretchTrigger,
shape: widget.shape,
toolbarHeight: widget.toolbarHeight,
expandedHeight: _height - MediaQuery.paddingOf(context).top,
leadingWidth: widget.leadingWidth,
toolbarTextStyle: widget.toolbarTextStyle,
titleTextStyle: widget.titleTextStyle,
systemOverlayStyle: widget.systemOverlayStyle,
forceMaterialTransparency: widget.forceMaterialTransparency,
clipBehavior: widget.clipBehavior,
flexibleSpace: FlexibleSpaceBar(background: widget.flexibleSpace),
);
}
}

View File

@@ -1,131 +0,0 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'network_img_layer.dart';
Widget htmlRender({
required BuildContext context,
String? htmlContent,
int? imgCount,
List<String>? imgList,
required double constrainedWidth,
Function(List<String>, int)? callback,
}) {
debugPrint('htmlRender');
return SelectionArea(
child: Html(
data: htmlContent,
onLinkTap: (String? url, Map<String, String> buildContext, attributes) {},
extensions: [
TagExtension(
tagsToExtend: <String>{'img'},
builder: (ExtensionContext extensionContext) {
try {
final Map<String, dynamic> attributes = extensionContext.attributes;
final List<dynamic> key = attributes.keys.toList();
String imgUrl = key.contains('src')
? attributes['src'] as String
: attributes['data-src'] as String;
if (imgUrl.startsWith('//')) {
imgUrl = 'https:$imgUrl';
}
if (imgUrl.startsWith('http://')) {
imgUrl = imgUrl.replaceAll('http://', 'https://');
}
imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl;
final bool isEmote = imgUrl.contains('/emote/');
final bool isMall = imgUrl.contains('/mall/');
if (isMall) {
return const SizedBox();
}
// bool inTable =
// extensionContext.element!.previousElementSibling == null ||
// extensionContext.element!.nextElementSibling == null;
// imgUrl = Utils().imageUrl(imgUrl!);
// return CachedNetworkImage(
// imageUrl: imgUrl,
// width: isEmote ? 22 : null,
// height: isEmote ? 22 : null,
// );
return Hero(
tag: imgUrl,
child: GestureDetector(
onTap: () {
if (callback != null) {
callback([imgUrl], 0);
} else {
context.imageView(
imgList: [SourceModel(url: imgUrl)],
);
}
},
child: NetworkImgLayer(
width: isEmote ? 22 : constrainedWidth,
height: isEmote ? 22 : 200,
src: imgUrl,
ignoreHeight: !isEmote,
),
),
);
} catch (err) {
return const SizedBox();
}
},
),
],
style: {
'html': Style(
fontSize: FontSize(16),
lineHeight: LineHeight.percent(160),
letterSpacing: 0.3,
),
// 'br': Style(margin: Margins.zero, padding: HtmlPaddings.zero),
'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero),
'a': Style(
color: Theme.of(context).colorScheme.primary,
textDecoration: TextDecoration.none,
),
'br': Style(
lineHeight: LineHeight.percent(-1),
),
'p': Style(
margin: Margins.only(bottom: 4),
// margin: Margins.zero,
),
'span': Style(
fontSize: FontSize.large,
height: Height(1.8),
),
'div': Style(height: Height.auto()),
'li > p': Style(
display: Display.inline,
),
'li': Style(
padding: HtmlPaddings.only(bottom: 4),
textAlign: TextAlign.justify,
),
'img': Style(margin: Margins.only(top: 4, bottom: 4)),
'h1,h2': Style(
fontSize: FontSize.xLarge,
fontWeight: FontWeight.bold,
margin: Margins.only(bottom: 8),
),
'h3,h4,h5': Style(
fontSize: FontSize(16),
fontWeight: FontWeight.bold,
margin: Margins.only(bottom: 4),
),
'figcaption': Style(
fontSize: FontSize.large,
textAlign: TextAlign.center,
// margin: Margins.only(top: 4),
),
'strong': Style(fontWeight: FontWeight.bold),
'figure': Style(
margin: Margins.zero,
),
},
));
}

View File

@@ -1,72 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class HttpError extends StatelessWidget {
const HttpError({
this.isSliver = true,
this.errMsg,
this.callback,
this.btnText,
this.extraWidget,
super.key,
});
final bool isSliver;
final String? errMsg;
final Function()? callback;
final String? btnText;
final Widget? extraWidget;
@override
Widget build(BuildContext context) {
return isSliver
? SliverToBoxAdapter(child: content(context))
: SizedBox(
width: double.infinity,
child: content(context),
);
}
Widget content(BuildContext context) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 40),
SvgPicture.asset(
"assets/images/error.svg",
height: 200,
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SelectableText(
errMsg ?? '没有数据',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall,
),
),
if (extraWidget != null) ...[
const SizedBox(height: 10),
extraWidget!,
const SizedBox(height: 5),
],
if (callback != null) ...[
if (extraWidget == null) const SizedBox(height: 20),
FilledButton.tonal(
onPressed: callback,
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith((states) {
return Theme.of(context).colorScheme.primary.withAlpha(20);
}),
),
child: Text(
btnText ?? '点击重试',
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
),
],
SizedBox(height: 40 + MediaQuery.paddingOf(context).bottom),
],
);
}

View File

@@ -0,0 +1,117 @@
import 'dart:math';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/utils/download.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
void imageSaveDialog({
required String? title,
required String? cover,
}) {
final double imgWidth = min(Get.width, Get.height) - 8 * 2;
SmartDialog.show(
animationType: SmartAnimationType.centerScale_otherSlide,
builder: (context) {
final theme = Theme.of(context);
return Container(
width: imgWidth,
margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
decoration: BoxDecoration(
color: theme.colorScheme.surface,
borderRadius: StyleString.mdRadius,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Stack(
clipBehavior: Clip.none,
children: [
GestureDetector(
onTap: SmartDialog.dismiss,
child: NetworkImgLayer(
width: imgWidth,
height: imgWidth / StyleString.aspectRatio,
src: cover,
quality: 100,
),
),
Positioned(
right: 8,
top: 8,
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.3),
shape: BoxShape.circle,
),
child: IconButton(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: SmartDialog.dismiss,
icon: const Icon(
Icons.close,
size: 18,
color: Colors.white,
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 8, 10),
child: Row(
children: [
Expanded(
child: SelectableText(
title ?? '',
style: theme.textTheme.titleSmall,
),
),
if (cover?.isNotEmpty == true) ...[
const SizedBox(width: 4),
iconButton(
context: context,
tooltip: '分享',
onPressed: () {
SmartDialog.dismiss();
DownloadUtils.onShareImg(cover!);
},
iconSize: 20,
icon: Icons.share,
bgColor: Colors.transparent,
iconColor: theme.colorScheme.onSurfaceVariant,
),
iconButton(
context: context,
tooltip: '保存封面图',
onPressed: () async {
bool saveStatus = await DownloadUtils.downloadImg(
context,
[cover!],
);
if (saveStatus) {
SmartDialog.dismiss();
}
},
iconSize: 20,
icon: Icons.download,
bgColor: Colors.transparent,
iconColor: theme.colorScheme.onSurfaceVariant,
),
],
],
),
),
],
),
);
},
);
}

View File

@@ -2,10 +2,10 @@ import 'dart:math';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel, SourceType;
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/nine_grid_view.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/image/nine_grid_view.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
@@ -31,7 +31,7 @@ class ImageModel {
bool get isLivePhoto => _isLivePhoto ??= liveUrl?.isNotEmpty == true;
}
Widget imageview(
Widget imageView(
double maxWidth,
List<ImageModel> picArr, {
VoidCallback? onViewImage,
@@ -61,32 +61,22 @@ Widget imageview(
}
final int row = picArr.length == 4 ? 2 : 3;
return BorderRadius.only(
topLeft: Radius.circular(
(index - row >= 0 ||
((index - 1) >= 0 && (index - 1) % row < index % row))
? 0
: 10,
),
topRight: Radius.circular(
(index - row >= 0 ||
((index + 1) < picArr.length &&
(index + 1) % row > index % row))
? 0
: 10,
),
bottomLeft: Radius.circular(
(index + row < picArr.length ||
((index - 1) >= 0 && (index - 1) % row < index % row))
? 0
: 10,
),
bottomRight: Radius.circular(
(index + row < picArr.length ||
((index + 1) < picArr.length &&
(index + 1) % row > index % row))
? 0
: 10,
),
topLeft: index - row >= 0 ||
((index - 1) >= 0 && (index - 1) % row < index % row)
? Radius.zero
: StyleString.imgRadius,
topRight: index - row >= 0 ||
((index + 1) < picArr.length && (index + 1) % row > index % row)
? Radius.zero
: StyleString.imgRadius,
bottomLeft: index + row < picArr.length ||
((index - 1) >= 0 && (index - 1) % row < index % row)
? Radius.zero
: StyleString.imgRadius,
bottomRight: index + row < picArr.length ||
((index + 1) < picArr.length && (index + 1) % row > index % row)
? Radius.zero
: StyleString.imgRadius,
);
}
@@ -138,6 +128,7 @@ Widget imageview(
}
},
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
ClipRRect(
@@ -158,7 +149,7 @@ Widget imageview(
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.4),
.withValues(alpha: 0.4),
borderRadius: borderRadius(index),
),
child: Center(
@@ -178,7 +169,7 @@ Widget imageview(
text: 'Live',
right: 8,
bottom: 8,
type: 'gray',
type: PBadgeType.gray,
)
else if (picArr[index].isLongPic)
const PBadge(

View File

@@ -1,124 +1,114 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/models/common/image_type.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';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/global_data.dart';
import '../constants.dart';
class NetworkImgLayer extends StatelessWidget {
const NetworkImgLayer({
super.key,
this.src,
required this.width,
required this.height,
this.type,
this.height,
this.type = ImageType.def,
this.fadeOutDuration,
this.fadeInDuration,
// 1%
this.quality,
this.semanticsLabel,
this.ignoreHeight,
this.radius,
this.imageBuilder,
this.isLongPic,
this.callback,
this.getPlaceHolder,
this.boxFit,
});
final String? src;
final double width;
final double height;
final String? type;
final double? height;
final ImageType type;
final Duration? fadeOutDuration;
final Duration? fadeInDuration;
final int? quality;
final String? semanticsLabel;
final bool? ignoreHeight;
final double? radius;
final ImageWidgetBuilder? imageBuilder;
final Function? isLongPic;
final Function? callback;
final Function? getPlaceHolder;
final BoxFit? boxFit;
@override
Widget build(BuildContext context) {
return src.isNullOrEmpty.not
? type == 'avatar'
? type == ImageType.avatar
? ClipOval(child: _buildImage(context))
: radius == 0 || type == 'emote'
: radius == 0 || type == ImageType.emote
? _buildImage(context)
: ClipRRect(
borderRadius: BorderRadius.circular(
radius ?? StyleString.imgRadius.x,
),
borderRadius: radius != null
? BorderRadius.circular(radius!)
: StyleString.mdRadius,
child: _buildImage(context),
)
: getPlaceHolder?.call() ?? placeholder(context);
}
Widget _buildImage(context) {
late final int defaultImgQuality = GlobalData().imgQuality;
bool thumbnail = true;
int? memCacheWidth, memCacheHeight;
if (callback?.call() == true || width <= height) {
if (height == null || callback?.call() == true || width <= height!) {
memCacheWidth = width.cacheSize(context);
} else {
memCacheHeight = height.cacheSize(context);
}
return CachedNetworkImage(
imageUrl:
'${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${type != 'emote' && type != 'cover' && thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}',
imageUrl: Utils.thumbnailImgUrl(src, quality),
width: width,
height: ignoreHeight == null || ignoreHeight == false ? height : null,
height: height,
memCacheWidth: memCacheWidth,
memCacheHeight: memCacheHeight,
fit: BoxFit.cover,
fit: boxFit ?? BoxFit.cover,
alignment:
isLongPic?.call() == true ? Alignment.topCenter : Alignment.center,
fadeOutDuration: fadeOutDuration ?? const Duration(milliseconds: 120),
fadeInDuration: fadeInDuration ?? const Duration(milliseconds: 120),
filterQuality: FilterQuality.low,
// errorWidget: (BuildContext context, String url, Object error) =>
// placeholder(context),
placeholder: (BuildContext context, String url) =>
getPlaceHolder?.call() ?? placeholder(context),
imageBuilder: imageBuilder,
// errorListener: (value) {
// thumbnail = false;
// if (context.mounted) {
// (context as Element).markNeedsBuild();
// }
// },
);
}
Widget placeholder(BuildContext context) {
int cacheWidth = width.cacheSize(context);
return Container(
width: width,
height: height,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
shape: type == 'avatar' ? BoxShape.circle : BoxShape.rectangle,
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
borderRadius: type == 'avatar' || type == 'emote' || radius == 0
? null
: BorderRadius.circular(
radius ?? StyleString.imgRadius.x,
),
shape: type == ImageType.avatar ? BoxShape.circle : BoxShape.rectangle,
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withValues(alpha: 0.4),
borderRadius:
type == ImageType.avatar || type == ImageType.emote || radius == 0
? null
: radius != null
? BorderRadius.circular(radius!)
: StyleString.mdRadius,
),
child: Center(
child: Image.asset(
type == ImageType.avatar
? 'assets/images/noface.jpeg'
: 'assets/images/loading.png',
width: width,
height: height,
cacheWidth: width.cacheSize(context),
),
),
child: type == 'bg'
? const SizedBox()
: Center(
child: Image.asset(
type == 'avatar'
? 'assets/images/noface.jpeg'
: 'assets/images/loading.png',
width: width,
height: height,
cacheWidth: cacheWidth == 0 ? null : cacheWidth,
// cacheHeight: height.cacheSize(context),
),
),
);
}
}

View File

@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:collection';
import 'dart:math' as math;
import 'package:flutter/material.dart';
/**
* @Author: Sky24n
* @GitHub: https://github.com/Sky24n
@@ -172,6 +172,7 @@ class _NineGridViewState extends State<NineGridView> {
)));
}
return Stack(
clipBehavior: Clip.none,
children: list,
);
}
@@ -260,6 +261,7 @@ class _NineGridViewState extends State<NineGridView> {
)));
}
return Stack(
clipBehavior: Clip.none,
children: list,
);
}
@@ -286,6 +288,7 @@ class _NineGridViewState extends State<NineGridView> {
}
return ClipOval(
child: Stack(
clipBehavior: Clip.none,
children: children,
),
);
@@ -372,7 +375,10 @@ class _NineGridViewState extends State<NineGridView> {
children.add(child);
}
return Stack(children: children);
return Stack(
clipBehavior: Clip.none,
children: children,
);
}
/// double is zero.
@@ -480,7 +486,7 @@ class _ImageUtil {
}
},
);
imageStream = image.image.resolve(const ImageConfiguration());
imageStream = image.image.resolve(ImageConfiguration.empty);
imageStream.addListener(listener);
return completer.future;
}

View File

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

View File

@@ -771,14 +771,16 @@ class _InteractiveViewerState extends State<InteractiveViewer>
widget.onInteractionStart?.call(details);
if (_controller.isAnimating) {
_controller.stop();
_controller.reset();
_controller
..stop()
..reset();
_animation?.removeListener(_onAnimate);
_animation = null;
}
if (_scaleController.isAnimating) {
_scaleController.stop();
_scaleController.reset();
_scaleController
..stop()
..reset();
_scaleAnimation?.removeListener(_onScaleAnimate);
_scaleAnimation = null;
}

View File

@@ -1,4 +1,5 @@
import 'interactive_viewer.dart' as custom;
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactive_viewer.dart'
as custom;
import 'package:flutter/material.dart';
/// https://github.com/qq326646683/interactiveviewer_gallery

View File

@@ -1,22 +1,20 @@
import 'dart:io';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactive_viewer.dart'
as custom;
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactive_viewer_boundary.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/utils/download.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dio/dio.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:status_bar_control/status_bar_control.dart';
import 'interactive_viewer_boundary.dart';
import 'interactive_viewer.dart' as custom;
/// https://github.com/qq326646683/interactiveviewer_gallery
@@ -35,24 +33,6 @@ typedef IndexedFocusedWidgetBuilder = Widget Function(
typedef IndexedTagStringBuilder = String Function(int index);
enum SourceType { fileImage, networkImage, livePhoto }
class SourceModel {
final SourceType sourceType;
final String url;
final String? liveUrl;
final int? width;
final int? height;
const SourceModel({
this.sourceType = SourceType.networkImage,
required this.url,
this.liveUrl,
this.width,
this.height,
});
}
class InteractiveviewerGallery<T> extends StatefulWidget {
const InteractiveviewerGallery({
super.key,
@@ -67,7 +47,7 @@ class InteractiveviewerGallery<T> extends StatefulWidget {
this.onClose,
});
final VoidCallback? onClose;
final ValueChanged? onClose;
final bool? setStatusBar;
@@ -111,15 +91,12 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
late final RxInt currentIndex = widget.initIndex.obs;
late List<bool> _thumbList;
late final int _quality = GStorage.previewQ;
@override
void initState() {
super.initState();
_thumbList = List.generate(widget.sources.length, (_) => true);
_pageController = PageController(initialPage: widget.initIndex);
_transformationController = custom.TransformationController();
@@ -127,10 +104,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
)..addListener(() {
_transformationController!.value =
_animation?.value ?? Matrix4.identity();
});
)..addListener(listener);
if (widget.setStatusBar != false) {
setStatusBar();
@@ -141,24 +115,37 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
}
setStatusBar() async {
void listener() {
_transformationController!.value = _animation?.value ?? Matrix4.identity();
}
SystemUiMode? mode;
Future<void> setStatusBar() async {
if (Platform.isIOS || Platform.isAndroid) {
await StatusBarControl.setHidden(
true,
animation: StatusBarAnimation.FADE,
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.immersiveSticky,
);
}
if (Platform.isAndroid &&
(await DeviceInfoPlugin().androidInfo).version.sdkInt < 29) {
mode = SystemUiMode.manual;
}
}
@override
void dispose() async {
void dispose() {
widget.onClose?.call(true);
_player?.dispose();
_pageController?.dispose();
_animationController.removeListener(() {});
_animationController.dispose();
_animationController
..removeListener(listener)
..dispose();
if (widget.setStatusBar != false) {
if (Platform.isIOS || Platform.isAndroid) {
StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE);
SystemChrome.setEnabledSystemUIMode(
mode ?? SystemUiMode.edgeToEdge,
overlays: SystemUiOverlay.values,
);
}
}
for (int index = 0; index < widget.sources.length; index++) {
@@ -253,13 +240,15 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
}
String _getActualUrl(int index) => _thumbList[index] && _quality != 100
? '${widget.sources[index].url}@${_quality}q.webp'.http2https
: widget.sources[index].url.http2https;
String _getActualUrl(int index) {
return _quality != 100
? Utils.thumbnailImgUrl(widget.sources[index].url, _quality)
: widget.sources[index].url.http2https;
}
void onClose() {
if (widget.onClose != null) {
widget.onClose!();
widget.onClose!(false);
} else {
Get.back();
widget.onDismissed?.call(_pageController!.page!.floor());
@@ -272,6 +261,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
InteractiveViewerBoundary(
controller: _transformationController,
@@ -325,12 +315,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
left: 0,
right: 0,
child: Container(
padding: EdgeInsets.fromLTRB(
12,
8,
20,
MediaQuery.of(context).padding.bottom + 8,
),
padding: MediaQuery.paddingOf(context) +
const EdgeInsets.fromLTRB(12, 8, 20, 8),
decoration: _enablePageView
? BoxDecoration(
gradient: LinearGradient(
@@ -338,12 +324,13 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black.withOpacity(0.3)
Colors.black.withValues(alpha: 0.3)
],
),
)
: null,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Align(
@@ -371,7 +358,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
itemBuilder: (context) {
return [
PopupMenuItem(
onTap: () => onShareImg(
onTap: () => DownloadUtils.onShareImg(
widget.sources[currentIndex.value].url),
child: const Text("分享图片"),
),
@@ -385,7 +372,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
PopupMenuItem(
onTap: () {
DownloadUtils.downloadImg(
context,
this.context,
[widget.sources[currentIndex.value].url],
);
},
@@ -395,20 +382,20 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
PopupMenuItem(
onTap: () {
DownloadUtils.downloadImg(
context,
this.context,
widget.sources
.map((item) => item.url)
.toList(),
);
},
child: const Text("保存全部图片"),
child: const Text("保存全部"),
),
if (widget.sources[currentIndex.value].sourceType ==
SourceType.livePhoto)
PopupMenuItem(
onTap: () {
DownloadUtils.downloadLivePhoto(
context: context,
context: this.context,
url: widget.sources[currentIndex.value].url,
liveUrl: widget
.sources[currentIndex.value].liveUrl!,
@@ -433,20 +420,6 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
);
}
// 图片分享
void onShareImg(String imgUrl) async {
SmartDialog.showLoading();
var response = await Request()
.get(imgUrl, options: Options(responseType: ResponseType.bytes));
final temp = await getTemporaryDirectory();
SmartDialog.dismiss();
String imgName =
"plpl_pic_${DateTime.now().toString().split('-').join()}.jpg";
var path = '${temp.path}/$imgName';
File(path).writeAsBytesSync(response.data);
Share.shareXFiles([XFile(path)], subject: imgUrl);
}
Widget _itemBuilder(index) {
return Center(
child: Hero(
@@ -457,26 +430,17 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
image: FileImage(File(widget.sources[index].url)),
),
SourceType.networkImage => CachedNetworkImage(
fadeInDuration: const Duration(milliseconds: 0),
fadeOutDuration: const Duration(milliseconds: 0),
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
imageUrl: _getActualUrl(index),
// fit: BoxFit.contain,
progressIndicatorBuilder: (context, url, progress) {
return Center(
child: SizedBox(
width: 150.0,
child:
LinearProgressIndicator(value: progress.progress ?? 0),
),
placeholderFadeInDuration: Duration.zero,
placeholder: (context, url) {
return CachedNetworkImage(
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
imageUrl: Utils.thumbnailImgUrl(widget.sources[index].url),
);
},
// errorListener: (value) {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// setState(() {
// _thumbList[index] = false;
// });
// });
// },
),
SourceType.livePhoto => Obx(() => currentIndex.value == index
? IgnorePointer(
@@ -491,7 +455,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
);
}
onDoubleTap() {
void onDoubleTap() {
Matrix4 matrix = _transformationController!.value.clone();
double currentScale = matrix.row0.x;
@@ -538,7 +502,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
.whenComplete(() => _onScaleChanged(targetScale));
}
onLongPress() {
void onLongPress() {
showDialog(
context: context,
builder: (context) {
@@ -550,7 +514,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
children: [
ListTile(
onTap: () {
onShareImg(widget.sources[currentIndex.value].url);
DownloadUtils.onShareImg(
widget.sources[currentIndex.value].url);
Get.back();
},
dense: true,
@@ -568,7 +533,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
onTap: () {
Get.back();
DownloadUtils.downloadImg(
context,
this.context,
[widget.sources[currentIndex.value].url],
);
},
@@ -580,7 +545,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
onTap: () {
Get.back();
DownloadUtils.downloadImg(
context,
this.context,
widget.sources.map((item) => item.url).toList(),
);
},
@@ -593,7 +558,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
onTap: () {
Get.back();
DownloadUtils.downloadLivePhoto(
context: context,
context: this.context,
url: widget.sources[currentIndex.value].url,
liveUrl: widget.sources[currentIndex.value].liveUrl!,
width: widget.sources[currentIndex.value].width!,

View File

@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
class KeepAliveWrapper extends StatefulWidget {
const KeepAliveWrapper({
super.key,
required this.builder,
this.wantKeepAlive = true,
});
final WidgetBuilder builder;
final bool wantKeepAlive;
@override
State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}
class _KeepAliveWrapperState extends State<KeepAliveWrapper>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return widget.builder(context);
}
@override
bool get wantKeepAlive => widget.wantKeepAlive;
}

View File

@@ -1,497 +0,0 @@
import 'dart:async';
import 'dart:math';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/bangumi/info.dart' as bangumi;
import 'package:PiliPlus/models/video_detail_res.dart' as video;
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../../utils/storage.dart';
import '../../utils/utils.dart';
class ListSheetContent extends StatefulWidget {
const ListSheetContent({
super.key,
this.index, // tab index
this.season,
this.episodes,
this.bvid,
this.aid,
required this.currentCid,
required this.changeFucCall,
this.onClose,
this.onReverse,
this.showTitle,
this.isSupportReverse,
this.isReversed,
});
final dynamic index;
final dynamic season;
final dynamic episodes;
final String? bvid;
final int? aid;
final int currentCid;
final Function changeFucCall;
final VoidCallback? onClose;
final VoidCallback? onReverse;
final bool? showTitle;
final bool? isSupportReverse;
final bool? isReversed;
@override
State<ListSheetContent> createState() => _ListSheetContentState();
}
class _ListSheetContentState extends State<ListSheetContent>
with TickerProviderStateMixin {
late List<ItemScrollController> itemScrollController = [];
late int currentIndex = _currentIndex;
late List<bool> reverse;
int get _index => widget.index ?? 0;
late final bool _isList = widget.season != null &&
widget.season?.sections is List &&
widget.season.sections.length > 1;
dynamic get episodes =>
widget.episodes ?? widget.season?.sections[_index].episodes;
TabController? _ctr;
StreamController? _indexStream;
int? _seasonFav;
StreamController? _favStream;
@override
void didUpdateWidget(ListSheetContent oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.showTitle != false) {
return;
}
int currentIndex = _currentIndex;
void jumpToCurrent() {
if (this.currentIndex != currentIndex) {
this.currentIndex = currentIndex;
try {
itemScrollController[_index].jumpTo(
index: currentIndex,
);
} catch (_) {}
}
}
// jump to current
if (_ctr != null && widget.index != _ctr?.index) {
_ctr?.animateTo(_index, duration: const Duration(milliseconds: 200));
Future.delayed(const Duration(milliseconds: 300)).then((_) {
jumpToCurrent();
});
} else {
jumpToCurrent();
}
}
int get _currentIndex => max(
0,
_isList
? widget.season.sections[_index].episodes
.indexWhere((e) => e.cid == widget.currentCid)
: episodes.indexWhere((e) => e.cid == widget.currentCid));
@override
void initState() {
super.initState();
if (_isList) {
_indexStream ??= StreamController<int>.broadcast();
_ctr = TabController(
vsync: this,
length: widget.season.sections.length,
initialIndex: _index,
)..addListener(() {
_indexStream?.add(_ctr?.index);
});
}
itemScrollController = _isList
? List.generate(
widget.season.sections.length, (_) => ItemScrollController())
: [ItemScrollController()];
reverse = _isList
? List.generate(widget.season.sections.length, (_) => false)
: [false];
if (widget.bvid != null && widget.season != null) {
_favStream ??= StreamController<int>();
() async {
dynamic result = await VideoHttp.videoRelation(bvid: widget.bvid);
if (result['status']) {
_seasonFav = result['data']['season_fav'] ? 1 : 0;
_favStream?.add(_seasonFav);
}
}();
}
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
itemScrollController[_index].jumpTo(index: currentIndex);
} catch (_) {}
});
}
@override
void dispose() {
_favStream?.close();
_favStream = null;
_indexStream?.close();
_indexStream = null;
_ctr?.removeListener(() {});
_ctr?.dispose();
super.dispose();
}
Widget buildEpisodeListItem(
dynamic episode,
int index,
int length,
bool isCurrentIndex,
) {
Color primary = Theme.of(context).colorScheme.primary;
late String title;
if (episode.runtimeType.toString() == "EpisodeItem") {
if (episode.longTitle != null && episode.longTitle != "") {
dynamic leading = episode.title ?? index + 1;
title =
"${Utils.isStringNumeric(leading) ? '$leading话' : leading} ${episode.longTitle!}";
} else {
title = episode.title!;
}
} else if (episode.runtimeType.toString() == "PageItem") {
title = episode.pagePart!;
} else if (episode.runtimeType.toString() == "Part") {
title = episode.pagePart!;
// debugPrint("未知类型:${episode.runtimeType}");
}
return ListTile(
onTap: () {
if (episode.badge != null && episode.badge == "会员") {
dynamic userInfo = GStorage.userInfo.get('userInfoCache');
int vipStatus = 0;
if (userInfo != null) {
vipStatus = userInfo.vipStatus;
}
if (vipStatus != 1) {
SmartDialog.showToast('需要大会员');
// return;
}
}
SmartDialog.showToast('切换到:$title');
widget.onClose?.call();
currentIndex = index;
widget.changeFucCall(
episode is bangumi.EpisodeItem ? episode.epId : null,
episode.runtimeType.toString() == "EpisodeItem"
? episode.bvid
: widget.bvid,
episode.cid,
episode.runtimeType.toString() == "EpisodeItem"
? episode.aid
: widget.aid,
episode is video.EpisodeItem
? episode.arc?.pic
: episode is bangumi.EpisodeItem
? episode.cover
: null,
);
},
dense: false,
leading: (episode is video.EpisodeItem && episode.arc?.pic != null) ||
(episode is video.Part && episode.firstFrame != null) ||
(episode is bangumi.EpisodeItem && episode.cover != null)
? Container(
margin: const EdgeInsets.symmetric(vertical: 6),
decoration: isCurrentIndex
? BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(
width: 1.8,
strokeAlign: BorderSide.strokeAlignOutside,
color: Theme.of(context).colorScheme.primary,
),
)
: null,
child: LayoutBuilder(
builder: (context, constraints) => NetworkImgLayer(
radius: 6,
src: episode is video.EpisodeItem
? episode.arc?.pic
: episode is bangumi.EpisodeItem
? episode.cover
: episode.firstFrame,
width: constraints.maxHeight * StyleString.aspectRatio,
height: constraints.maxHeight,
),
),
)
: isCurrentIndex
? Image.asset(
'assets/images/live.png',
color: primary,
height: 12,
semanticLabel: "正在播放:",
)
: null,
title: Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: isCurrentIndex ? FontWeight.bold : null,
color: isCurrentIndex
? primary
: Theme.of(context).colorScheme.onSurface,
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (episode.badge != null) ...[
if (episode.badge == '会员')
Image.asset(
'assets/images/big-vip.png',
height: 20,
semanticLabel: "大会员",
)
else
Text(episode.badge),
const SizedBox(width: 10),
],
if (episode is! bangumi.EpisodeItem) Text('${index + 1}/$length'),
],
),
);
}
@override
Widget build(BuildContext context) {
return ColoredBox(
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
Container(
height: 45,
padding: EdgeInsets.symmetric(
horizontal: widget.showTitle != false ? 14 : 6),
child: Row(
children: [
if (widget.showTitle != false)
Text(
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
style: Theme.of(context).textTheme.titleMedium,
),
StreamBuilder(
stream: _favStream?.stream,
builder: (context, snapshot) => snapshot.hasData
? mediumButton(
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
icon: _seasonFav == 1
? Icons.notifications_off_outlined
: Icons.notifications_active_outlined,
onPressed: () async {
dynamic result = await VideoHttp.seasonFav(
isFav: _seasonFav == 1,
seasonId: widget.season.id,
);
if (result['status']) {
SmartDialog.showToast(
'${_seasonFav == 1 ? '取消' : ''}订阅成功');
_seasonFav = _seasonFav == 1 ? 0 : 1;
_favStream?.add(_seasonFav);
} else {
SmartDialog.showToast(result['msg']);
}
},
)
: const SizedBox.shrink(),
),
mediumButton(
tooltip: '跳至顶部',
icon: Icons.vertical_align_top,
onPressed: () {
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0]
? 0
: _isList
? widget.season.sections[_ctr?.index].episodes
.length -
1
: episodes.length - 1,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
mediumButton(
tooltip: '跳至底部',
icon: Icons.vertical_align_bottom,
onPressed: () {
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0]
? _isList
? widget.season.sections[_ctr?.index].episodes
.length -
1
: episodes.length - 1
: 0,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
mediumButton(
tooltip: '跳至当前',
icon: Icons.my_location,
onPressed: () async {
if (_ctr != null && _ctr?.index != (_index)) {
_ctr?.animateTo(_index);
await Future.delayed(const Duration(milliseconds: 225));
}
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: currentIndex,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
if (widget.isSupportReverse == true)
if (!_isList)
_reverseButton
else
StreamBuilder(
stream: _indexStream?.stream,
initialData: _index,
builder: (context, snapshot) {
return snapshot.data == _index
? _reverseButton
: const SizedBox.shrink();
},
),
const Spacer(),
StreamBuilder(
stream: _indexStream?.stream,
initialData: _index,
builder: (context, snapshot) => mediumButton(
tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
icon: !reverse[snapshot.data]
? MdiIcons.sortNumericAscending
: MdiIcons.sortNumericDescending,
onPressed: () {
setState(() {
reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0];
});
},
),
),
if (widget.onClose != null)
mediumButton(
tooltip: '关闭',
icon: Icons.close,
onPressed: widget.onClose,
),
],
),
),
Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
if (_isList)
Material(
color: Theme.of(context).colorScheme.surface,
child: TabBar(
controller: _ctr,
padding: const EdgeInsets.only(right: 60),
isScrollable: true,
tabs: (widget.season.sections as List)
.map((item) => Tab(text: item.title))
.toList(),
dividerHeight: 1,
dividerColor: Theme.of(context).dividerColor.withOpacity(0.1),
),
),
Expanded(
child: _isList
? Material(
color: Theme.of(context).colorScheme.surface,
child: TabBarView(
controller: _ctr,
children: List.generate(
widget.season.sections.length,
(index) => _buildBody(
index, widget.season.sections[index].episodes),
),
),
)
: Material(
color: Theme.of(context).colorScheme.surface,
child: _buildBody(null, episodes),
),
),
],
),
);
}
Widget get _reverseButton => mediumButton(
tooltip: widget.isReversed == true ? '正序播放' : '倒序播放',
icon: widget.isReversed == true
? MdiIcons.sortDescending
: MdiIcons.sortAscending,
onPressed: () async {
if (widget.showTitle == false) {
// jump to current
if (_ctr != null && _ctr?.index != (_index)) {
_ctr?.animateTo(_index);
await Future.delayed(const Duration(milliseconds: 225));
}
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: currentIndex,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
}
widget.onReverse?.call();
},
);
Widget _buildBody(i, episodes) => ScrollablePositionedList.separated(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
reverse: reverse[i ?? 0],
itemCount: episodes.length,
itemBuilder: (BuildContext context, int index) {
return buildEpisodeListItem(
episodes[index],
index,
episodes.length,
i != null
? i == (_index)
? currentIndex == index
: false
: currentIndex == index,
);
},
itemScrollController: itemScrollController[i ?? 0],
separatorBuilder: (context, index) => Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
);
}

View File

@@ -1,142 +0,0 @@
import 'package:flutter/material.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'network_img_layer.dart';
class LiveCard extends StatelessWidget {
final dynamic liveItem;
const LiveCard({
super.key,
required this.liveItem,
});
@override
Widget build(BuildContext context) {
final String heroTag = Utils.makeHeroTag(liveItem.roomid);
return Card(
elevation: 0,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
side: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(0.08),
),
),
margin: EdgeInsets.zero,
child: InkWell(
onTap: () {},
child: Column(
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder:
(BuildContext context, BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: liveItem.cover as String,
type: 'emote',
width: maxWidth,
height: maxHeight,
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: AnimatedOpacity(
opacity: 1,
duration: const Duration(milliseconds: 200),
child: liveStat(context),
),
),
],
);
}),
),
liveContent(context)
],
),
),
);
}
Widget liveContent(context) {
return Padding(
// 多列
padding: const EdgeInsets.fromLTRB(8, 8, 6, 7),
// 单列
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
liveItem.title as String,
textAlign: TextAlign.start,
style: const TextStyle(fontSize: 13),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(
width: double.infinity,
child: Text(
liveItem.uname as String,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
),
],
),
);
}
Widget liveStat(context) {
return Container(
height: 45,
padding: const EdgeInsets.only(top: 22, left: 8, right: 8),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
Colors.transparent,
Colors.black54,
],
tileMode: TileMode.mirror,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
// Row(
// children: [
// StatView(
// theme: 'white',
// view: view,
// ),
// const SizedBox(width: 8),
// StatDanMu(
// theme: 'white',
// danmu: danmaku,
// ),
// ],
// ),
Text(
liveItem.online.toString(),
style: const TextStyle(fontSize: 11, color: Colors.white),
)
],
),
);
}
}

View File

@@ -1,44 +0,0 @@
import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/utils/global_data.dart';
import 'package:flutter/material.dart';
Widget get loadingWidget => Center(child: CircularProgressIndicator());
Widget errorWidget({errMsg, callback}) => HttpError(
isSliver: false,
errMsg: errMsg,
callback: callback,
);
Widget scrollErrorWidget({errMsg, callback}) => CustomScrollView(
slivers: [
HttpError(
errMsg: errMsg,
callback: callback,
)
],
);
Widget replyErrorWidget(context, isSliver, errMsg, onReload) => HttpError(
isSliver: isSliver,
errMsg:
'${errMsg.startsWith('gRPC Error') ? '如无法加载评论:\n关闭代理\n或设置中关闭使用gRPC加载评论\n\n' : ''}$errMsg',
callback: onReload,
extraWidget: errMsg.startsWith('gRPC Error') && GlobalData().grpcReply
? FilledButton.tonal(
onPressed: () {
GlobalData().grpcReply = false;
onReload();
},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith((states) {
return Theme.of(context).colorScheme.primary.withAlpha(20);
}),
),
child: Text(
'暂时关闭gRPC加载评论',
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
)
: null,
);

View File

@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class HttpError extends StatelessWidget {
const HttpError({
this.isSliver = true,
this.errMsg,
this.onReload,
this.btnText,
super.key,
});
final bool isSliver;
final String? errMsg;
final VoidCallback? onReload;
final String? btnText;
@override
Widget build(BuildContext context) {
return isSliver
? SliverToBoxAdapter(child: content(context))
: SizedBox(
width: double.infinity,
child: content(context),
);
}
Widget content(BuildContext context) {
final theme = Theme.of(context);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 40),
SvgPicture.asset(
"assets/images/error.svg",
height: 200,
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 5),
child: SelectableText(
errMsg ?? '没有数据',
textAlign: TextAlign.center,
style: theme.textTheme.titleSmall,
scrollPhysics: const NeverScrollableScrollPhysics(),
),
),
if (onReload != null)
FilledButton.tonal(
onPressed: onReload,
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith((states) {
return theme.colorScheme.primary.withAlpha(20);
}),
),
child: Text(
btnText ?? '点击重试',
style: TextStyle(color: theme.colorScheme.primary),
),
),
SizedBox(height: 40 + MediaQuery.paddingOf(context).bottom),
],
);
}
}

View File

@@ -0,0 +1,20 @@
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:flutter/material.dart';
Widget get loadingWidget => const Center(child: CircularProgressIndicator());
Widget errorWidget({errMsg, onReload}) => HttpError(
isSliver: false,
errMsg: errMsg,
onReload: onReload,
);
Widget scrollErrorWidget({errMsg, onReload, controller}) => CustomScrollView(
controller: controller,
slivers: [
HttpError(
errMsg: errMsg,
onReload: onReload,
)
],
);

View File

@@ -1,34 +0,0 @@
import 'package:flutter/material.dart';
class NoSplashFactory extends InteractiveInkFeatureFactory {
@override
InteractiveInkFeature create(
{required MaterialInkController controller,
required RenderBox referenceBox,
required Offset position,
required Color color,
required TextDirection textDirection,
bool containedInkWell = false,
RectCallback? rectCallback,
BorderRadius? borderRadius,
ShapeBorder? customBorder,
double? radius,
VoidCallback? onRemoved}) {
return _NoInteractiveInkFeature(
controller: controller,
referenceBox: referenceBox,
color: color,
onRemoved: onRemoved);
}
}
class _NoInteractiveInkFeature extends InteractiveInkFeature {
@override
void paintFeature(Canvas canvas, Matrix4 transform) {}
_NoInteractiveInkFeature({
required super.controller,
required super.referenceBox,
required super.color,
super.onRemoved,
});
}

View File

@@ -1,99 +0,0 @@
import 'dart:math';
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../utils/download.dart';
import '../constants.dart';
import 'network_img_layer.dart';
class OverlayPop extends StatelessWidget {
const OverlayPop({super.key, this.videoItem, this.closeFn});
final dynamic videoItem;
final Function? closeFn;
@override
Widget build(BuildContext context) {
final double imgWidth = min(Get.height, Get.width) - 8 * 2;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8),
width: imgWidth,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(10.0),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
NetworkImgLayer(
width: imgWidth,
height: imgWidth / StyleString.aspectRatio,
src: videoItem is card.Card
? (videoItem as card.Card).smallCoverV5.base.cover
: videoItem.pic,
quality: 100,
),
Positioned(
right: 8,
top: 8,
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
borderRadius:
const BorderRadius.all(Radius.circular(20))),
child: IconButton(
tooltip: '关闭',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => closeFn?.call(),
icon: const Icon(
Icons.close,
size: 18,
color: Colors.white,
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 8, 10),
child: Row(
children: [
Expanded(
child: SelectableText(
videoItem is card.Card
? (videoItem as card.Card).smallCoverV5.base.title
: videoItem.title,
),
),
const SizedBox(width: 4),
IconButton(
tooltip: '保存封面图',
onPressed: () async {
await DownloadUtils.downloadImg(
context,
[
videoItem is card.Card
? (videoItem as card.Card).smallCoverV5.base.cover
: videoItem.pic ?? videoItem.cover
],
);
closeFn?.call();
},
icon: const Icon(Icons.download, size: 20),
)
],
)),
],
),
);
}
}

View File

@@ -6,3 +6,14 @@ class Pair<T, R> {
T first;
R second;
}
class Triple<T, R, S> {
Triple({
required this.first,
required this.second,
required this.third,
});
T first;
R second;
S third;
}

View File

@@ -0,0 +1,161 @@
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/models/common/avatar_badge_type.dart';
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class PendantAvatar extends StatelessWidget {
final BadgeType _badgeType;
final String? avatar;
final double size;
final double badgeSize;
final String? garbPendantImage;
final dynamic roomId;
final VoidCallback? onTap;
const PendantAvatar({
super.key,
required this.avatar,
this.size = 80,
double? badgeSize,
bool? isVip,
int? officialType,
this.garbPendantImage,
this.roomId,
this.onTap,
}) : _badgeType = officialType == null || officialType < 0
? isVip == true
? BadgeType.vip
: BadgeType.none
: officialType == 0
? BadgeType.person
: officialType == 1
? BadgeType.institution
: BadgeType.none,
badgeSize = badgeSize ?? size / 3;
static bool showDynDecorate = GStorage.showDynDecorate;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Stack(
alignment: Alignment.bottomCenter,
clipBehavior: Clip.none,
children: [
onTap == null
? _buildAvatar(colorScheme)
: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: _buildAvatar(colorScheme),
),
if (showDynDecorate && !garbPendantImage.isNullOrEmpty)
Positioned(
top: -0.375 *
(size == 80 ? size - 4 : size), // -(size * 1.75 - size) / 2
child: IgnorePointer(
child: CachedNetworkImage(
width: size * 1.75,
height: size * 1.75,
imageUrl: Utils.thumbnailImgUrl(garbPendantImage),
),
),
),
if (roomId != null)
Positioned(
bottom: 0,
child: InkWell(
onTap: () {
Get.toNamed('/liveRoom?roomid=$roomId');
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
decoration: BoxDecoration(
color: colorScheme.secondaryContainer,
borderRadius: const BorderRadius.all(Radius.circular(36)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.equalizer_rounded,
size: MediaQuery.textScalerOf(context).scale(16),
color: colorScheme.onSecondaryContainer,
),
Text(
'直播中',
style: TextStyle(
height: 1,
fontSize: 13,
color: colorScheme.onSecondaryContainer,
),
),
],
),
),
),
)
else if (_badgeType != BadgeType.none)
_buildBadge(colorScheme),
],
);
}
Widget _buildAvatar(ColorScheme colorScheme) => size == 80
? DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
width: 2,
color: colorScheme.surface,
),
shape: BoxShape.circle,
),
child: Padding(
padding: const EdgeInsets.all(2),
child: NetworkImgLayer(
src: avatar,
width: size,
height: size,
type: ImageType.avatar,
),
),
)
: NetworkImgLayer(
src: avatar,
width: size,
height: size,
type: ImageType.avatar,
);
Widget _buildBadge(ColorScheme colorScheme) {
final child = switch (_badgeType) {
BadgeType.vip => Image.asset(
'assets/images/big-vip.png',
height: badgeSize,
semanticLabel: _badgeType.desc,
),
_ => Icon(
Icons.offline_bolt,
color: _badgeType.color,
size: badgeSize,
semanticLabel: _badgeType.desc,
),
};
return Positioned(
right: 0,
bottom: 0,
child: IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: colorScheme.surface,
),
child: child),
));
}
}

View File

@@ -267,9 +267,10 @@ class ProgressBar extends LeafRenderObjectWidget {
onDragUpdate: onDragUpdate,
onDragEnd: onDragEnd,
barHeight: barHeight,
baseBarColor: baseBarColor ?? primaryColor.withOpacity(0.24),
baseBarColor: baseBarColor ?? primaryColor.withValues(alpha: 0.24),
progressBarColor: progressBarColor ?? primaryColor,
bufferedBarColor: bufferedBarColor ?? primaryColor.withOpacity(0.24),
bufferedBarColor:
bufferedBarColor ?? primaryColor.withValues(alpha: 0.24),
barCapShape: barCapShape,
thumbRadius: thumbRadius,
thumbColor: thumbColor ?? primaryColor,
@@ -300,9 +301,10 @@ class ProgressBar extends LeafRenderObjectWidget {
..onDragUpdate = onDragUpdate
..onDragEnd = onDragEnd
..barHeight = barHeight
..baseBarColor = baseBarColor ?? primaryColor.withOpacity(0.24)
..baseBarColor = baseBarColor ?? primaryColor.withValues(alpha: 0.24)
..progressBarColor = progressBarColor ?? primaryColor
..bufferedBarColor = bufferedBarColor ?? primaryColor.withOpacity(0.24)
..bufferedBarColor =
bufferedBarColor ?? primaryColor.withValues(alpha: 0.24)
..barCapShape = barCapShape
..thumbRadius = thumbRadius
..thumbColor = thumbColor ?? primaryColor
@@ -320,43 +322,42 @@ class ProgressBar extends LeafRenderObjectWidget {
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('progress', progress.toString()));
properties.add(StringProperty('total', total.toString()));
properties.add(StringProperty('buffered', buffered.toString()));
properties.add(ObjectFlagProperty<ValueChanged<Duration>>('onSeek', onSeek,
ifNull: 'unimplemented'));
properties.add(ObjectFlagProperty<ThumbDragStartCallback>(
'onDragStart', onDragStart,
ifNull: 'unimplemented'));
properties.add(ObjectFlagProperty<ThumbDragUpdateCallback>(
'onDragUpdate', onDragUpdate,
ifNull: 'unimplemented'));
properties.add(ObjectFlagProperty<VoidCallback>('onDragEnd', onDragEnd,
ifNull: 'unimplemented'));
properties.add(DoubleProperty('barHeight', barHeight));
properties.add(ColorProperty('baseBarColor', baseBarColor));
properties.add(ColorProperty('progressBarColor', progressBarColor));
properties.add(ColorProperty('bufferedBarColor', bufferedBarColor));
properties.add(StringProperty('barCapShape', barCapShape.toString()));
properties.add(DoubleProperty('thumbRadius', thumbRadius));
properties.add(ColorProperty('thumbColor', thumbColor));
properties.add(ColorProperty('thumbGlowColor', thumbGlowColor));
properties.add(DoubleProperty('thumbGlowRadius', thumbGlowRadius));
properties.add(
FlagProperty(
'thumbCanPaintOutsideBar',
value: thumbCanPaintOutsideBar,
ifTrue: 'true',
ifFalse: 'false',
showName: true,
),
);
properties
.add(StringProperty('timeLabelLocation', timeLabelLocation.toString()));
properties.add(StringProperty('timeLabelType', timeLabelType.toString()));
properties
.add(DiagnosticsProperty('timeLabelTextStyle', timeLabelTextStyle));
properties.add(DoubleProperty('timeLabelPadding', timeLabelPadding));
..add(StringProperty('progress', progress.toString()))
..add(StringProperty('total', total.toString()))
..add(StringProperty('buffered', buffered.toString()))
..add(ObjectFlagProperty<ValueChanged<Duration>>('onSeek', onSeek,
ifNull: 'unimplemented'))
..add(ObjectFlagProperty<ThumbDragStartCallback>(
'onDragStart', onDragStart,
ifNull: 'unimplemented'))
..add(ObjectFlagProperty<ThumbDragUpdateCallback>(
'onDragUpdate', onDragUpdate,
ifNull: 'unimplemented'))
..add(ObjectFlagProperty<VoidCallback>('onDragEnd', onDragEnd,
ifNull: 'unimplemented'))
..add(DoubleProperty('barHeight', barHeight))
..add(ColorProperty('baseBarColor', baseBarColor))
..add(ColorProperty('progressBarColor', progressBarColor))
..add(ColorProperty('bufferedBarColor', bufferedBarColor))
..add(StringProperty('barCapShape', barCapShape.toString()))
..add(DoubleProperty('thumbRadius', thumbRadius))
..add(ColorProperty('thumbColor', thumbColor))
..add(ColorProperty('thumbGlowColor', thumbGlowColor))
..add(DoubleProperty('thumbGlowRadius', thumbGlowRadius))
..add(
FlagProperty(
'thumbCanPaintOutsideBar',
value: thumbCanPaintOutsideBar,
ifTrue: 'true',
ifFalse: 'false',
showName: true,
),
)
..add(StringProperty('timeLabelLocation', timeLabelLocation.toString()))
..add(StringProperty('timeLabelType', timeLabelType.toString()))
..add(DiagnosticsProperty('timeLabelTextStyle', timeLabelTextStyle))
..add(DoubleProperty('timeLabelPadding', timeLabelPadding));
}
}
@@ -484,6 +485,9 @@ class _RenderProgressBar extends RenderBox {
}
void _onDragStart(DragStartDetails details) {
if (onDragStart == null) {
return;
}
_userIsDraggingThumb = true;
_updateThumbPosition(details.localPosition);
onDragStart?.call(ThumbDragDetails(
@@ -494,6 +498,9 @@ class _RenderProgressBar extends RenderBox {
}
void _onDragUpdate(DragUpdateDetails details) {
if (onDragUpdate == null) {
return;
}
_updateThumbPosition(details.localPosition);
onDragUpdate?.call(ThumbDragDetails(
timeStamp: _currentThumbDuration(),
@@ -503,6 +510,9 @@ class _RenderProgressBar extends RenderBox {
}
void _onDragEnd(DragEndDetails details) {
if (onSeek == null) {
return;
}
onDragEnd?.call();
onSeek?.call(_currentThumbDuration());
_finishDrag();
@@ -610,11 +620,10 @@ class _RenderProgressBar extends RenderBox {
TextPainter _layoutText(String text) {
TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: _timeLabelTextStyle),
textDirection: TextDirection.ltr,
textScaleFactor: textScaleFactor,
);
textPainter.layout(minWidth: 0, maxWidth: double.infinity);
text: TextSpan(text: text, style: _timeLabelTextStyle),
textDirection: TextDirection.ltr,
textScaler: TextScaler.linear(textScaleFactor))
..layout(minWidth: 0, maxWidth: double.infinity);
return textPainter;
}
@@ -910,9 +919,9 @@ class _RenderProgressBar extends RenderBox {
@override
void paint(PaintingContext context, Offset offset) {
final canvas = context.canvas;
canvas.save();
canvas.translate(offset.dx, offset.dy);
final canvas = context.canvas
..save()
..translate(offset.dx, offset.dy);
switch (_timeLabelLocation) {
case TimeLabelLocation.above:
@@ -1004,8 +1013,9 @@ class _RenderProgressBar extends RenderBox {
}
void _drawProgressBar(Canvas canvas, Offset offset, Size localSize) {
canvas.save();
canvas.translate(offset.dx, offset.dy);
canvas
..save()
..translate(offset.dx, offset.dy);
_drawBaseBar(canvas, localSize);
_drawBufferedBar(canvas, localSize);
_drawCurrentProgressBar(canvas, localSize);
@@ -1100,17 +1110,19 @@ class _RenderProgressBar extends RenderBox {
super.describeSemanticsConfiguration(config);
// description
config.textDirection = TextDirection.ltr;
config.label = '进度条'; //'Progress bar';
config.value = '${(_thumbValue * 100).round()}%';
config
..textDirection = TextDirection.ltr
..label = '进度条' //'Progress bar';
..value = '${(_thumbValue * 100).round()}%'
// increase action
config.onIncrease = increaseAction;
// increase action
..onIncrease = increaseAction;
final increased = _thumbValue + _semanticActionUnit;
config.increasedValue = '${((increased).clamp(0.0, 1.0) * 100).round()}%';
config
..increasedValue = '${((increased).clamp(0.0, 1.0) * 100).round()}%'
// decrease action
config.onDecrease = decreaseAction;
// decrease action
..onDecrease = decreaseAction;
final decreased = _thumbValue - _semanticActionUnit;
config.decreasedValue = '${((decreased).clamp(0.0, 1.0) * 100).round()}%';
}

View File

@@ -89,7 +89,7 @@ class SegmentProgressBar extends CustomPainter {
size.width,
0,
),
Paint()..color = Colors.grey[600]!.withOpacity(0.45),
Paint()..color = Colors.grey[600]!.withValues(alpha: 0.45),
);
}

View File

@@ -1,11 +1,12 @@
import 'package:PiliPlus/common/constants.dart';
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),
borderRadius: const BorderRadius.only(
bottomLeft: StyleString.imgRadius,
bottomRight: StyleString.imgRadius,
),
child: LinearProgressIndicator(
minHeight: 10,

View File

@@ -1,25 +1,93 @@
import 'package:flutter/material.dart';
Widget radioWidget<T>({
required T value,
T? groupValue,
required ValueChanged onChanged,
required String title,
double? paddingStart,
}) {
return InkWell(
onTap: () => onChanged(value),
child: Row(
children: [
if (paddingStart != null) SizedBox(width: paddingStart),
Radio(
value: value,
groupValue: groupValue,
onChanged: onChanged,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
Text(title),
],
),
);
class RadioWidget<T> extends StatelessWidget {
final T value;
final T? groupValue;
final ValueChanged<T?> onChanged;
final String title;
final EdgeInsetsGeometry? padding;
const RadioWidget({
super.key,
required this.value,
this.groupValue,
required this.onChanged,
required this.title,
this.padding,
});
Widget _child() => Row(
children: [
Radio<T>(
value: value,
groupValue: groupValue,
onChanged: onChanged,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
Text(title),
],
);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onChanged(value),
child: padding != null
? Padding(
padding: padding!,
child: _child(),
)
: _child(),
);
}
}
class WrapRadioOptionsGroup<T> extends StatelessWidget {
final String groupTitle;
final Map<T, String> options;
final T? selectedValue;
final ValueChanged<T?> onChanged;
final EdgeInsetsGeometry? itemPadding;
const WrapRadioOptionsGroup({
super.key,
required this.groupTitle,
required this.options,
required this.selectedValue,
required this.onChanged,
this.itemPadding,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (groupTitle.isNotEmpty)
Padding(
padding: const EdgeInsets.only(left: 22),
child: Text(
groupTitle,
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Wrap(
children: options.entries.map((entry) {
return IntrinsicWidth(
child: RadioWidget<T>(
value: entry.key,
groupValue: selectedValue,
onChanged: onChanged,
title: entry.value,
padding: itemPadding ?? const EdgeInsets.only(right: 10),
),
);
}).toList(),
),
),
],
);
}
}

View File

@@ -46,15 +46,25 @@ const Duration _kIndicatorScaleDuration = Duration(milliseconds: 200);
/// Used by [RefreshIndicator.onRefresh].
typedef RefreshCallback = Future<void> Function();
// The state machine moves through these modes only when the scrollable
// identified by scrollableKey has been scrolled to its min or max limit.
enum _RefreshIndicatorMode {
drag, // Pointer is down.
armed, // Dragged far enough that an up event will run the onRefresh callback.
snap, // Animating to the indicator's final "displacement".
refresh, // Running the refresh callback.
done, // Animating the indicator's fade-out after refreshing.
canceled, // Animating the indicator's fade-out after not arming.
/// Indicates current status of Material `RefreshIndicator`.
enum RefreshIndicatorStatus {
/// Pointer is down.
drag,
/// Dragged far enough that an up event will run the onRefresh callback.
armed,
/// Animating to the indicator's final "displacement".
snap,
/// Running the refresh callback.
refresh,
/// Animating the indicator's fade-out after refreshing.
done,
/// Animating the indicator's fade-out after not arming.
canceled,
}
/// Used to configure how [RefreshIndicator] can be triggered.
@@ -68,7 +78,7 @@ enum RefreshIndicatorTriggerMode {
onEdge,
}
enum _IndicatorType { material, adaptive }
enum _IndicatorType { material, adaptive, noSpinner }
/// A widget that supports the Material "swipe to refresh" idiom.
///
@@ -96,6 +106,12 @@ enum _IndicatorType { material, adaptive }
/// ** See code in examples/api/lib/material/refresh_indicator/refresh_indicator.1.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// This example shows how to use [RefreshIndicator] without the spinner.
///
/// ** See code in examples/api/lib/material/refresh_indicator/refresh_indicator.2.dart **
/// {@end-tool}
///
/// ## Troubleshooting
///
/// ### Refresh indicator does not show up
@@ -149,7 +165,10 @@ class RefreshIndicator extends StatefulWidget {
this.semanticsValue,
this.strokeWidth = RefreshProgressIndicator.defaultStrokeWidth,
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
}) : _indicatorType = _IndicatorType.material;
this.elevation = 2.0,
}) : _indicatorType = _IndicatorType.material,
onStatusChange = null,
assert(elevation >= 0.0);
/// Creates an adaptive [RefreshIndicator] based on whether the target
/// platform is iOS or macOS, following Material design's
@@ -180,7 +199,35 @@ class RefreshIndicator extends StatefulWidget {
this.semanticsValue,
this.strokeWidth = RefreshProgressIndicator.defaultStrokeWidth,
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
}) : _indicatorType = _IndicatorType.adaptive;
this.elevation = 2.0,
}) : _indicatorType = _IndicatorType.adaptive,
onStatusChange = null,
assert(elevation >= 0.0);
/// Creates a [RefreshIndicator] with no spinner and calls `onRefresh` when
/// successfully armed by a drag event.
///
/// Events can be optionally listened by using the `onStatusChange` callback.
const RefreshIndicator.noSpinner({
super.key,
required this.child,
required this.onRefresh,
this.onStatusChange,
this.notificationPredicate = defaultScrollNotificationPredicate,
this.semanticsLabel,
this.semanticsValue,
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
this.elevation = 2.0,
}) : _indicatorType = _IndicatorType.noSpinner,
// The following parameters aren't used because [_IndicatorType.noSpinner] is being used,
// which involves showing no spinner, hence the following parameters are useless since
// their only use is to change the spinner's appearance.
displacement = 0.0,
edgeOffset = 0.0,
color = null,
backgroundColor = null,
strokeWidth = 0.0,
assert(elevation >= 0.0);
/// The widget below this widget in the tree.
///
@@ -220,6 +267,10 @@ class RefreshIndicator extends StatefulWidget {
/// [Future] must complete when the refresh operation is finished.
final RefreshCallback onRefresh;
/// Called to get the current status of the [RefreshIndicator] to update the UI as needed.
/// This is an optional parameter, used to fine tune app cases.
final ValueChanged<RefreshIndicatorStatus?>? onStatusChange;
/// The progress indicator's foreground color. The current theme's
/// [ColorScheme.primary] by default.
final Color? color;
@@ -266,6 +317,11 @@ class RefreshIndicator extends StatefulWidget {
/// Defaults to [RefreshIndicatorTriggerMode.onEdge].
final RefreshIndicatorTriggerMode triggerMode;
/// Defines the elevation of the underlying [RefreshIndicator].
///
/// Defaults to 2.0.
final double elevation;
@override
RefreshIndicatorState createState() => RefreshIndicatorState();
}
@@ -281,7 +337,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
late Animation<double> _value;
late Animation<Color?> _valueColor;
_RefreshIndicatorMode? _mode;
RefreshIndicatorStatus? _status;
late Future<void> _pendingRefreshFuture;
bool? _isIndicatorAtTop;
double? _dragOffset;
@@ -290,29 +346,37 @@ class RefreshIndicatorState extends State<RefreshIndicator>
static final Animatable<double> _threeQuarterTween =
Tween<double>(begin: 0.0, end: 0.75);
static final Animatable<double> _kDragSizeFactorLimitTween =
Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit);
static final Animatable<double> _kDragSizeFactorLimitTween = Tween<double>(
begin: 0.0,
end: _kDragSizeFactorLimit,
);
static final Animatable<double> _oneToZeroTween =
Tween<double>(begin: 1.0, end: 0.0);
@protected
@override
void initState() {
super.initState();
_positionController = AnimationController(vsync: this);
_positionFactor = _positionController.drive(_kDragSizeFactorLimitTween);
_value = _positionController.drive(
_threeQuarterTween); // The "value" of the circular progress indicator during a drag.
// The "value" of the circular progress indicator during a drag.
_value = _positionController.drive(_threeQuarterTween);
_scaleController = AnimationController(vsync: this);
_scaleFactor = _scaleController.drive(_oneToZeroTween);
}
@protected
@override
void didChangeDependencies() {
_setupColorTween();
super.didChangeDependencies();
}
@protected
@override
void didUpdateWidget(covariant RefreshIndicator oldWidget) {
super.didUpdateWidget(oldWidget);
@@ -321,6 +385,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
}
@protected
@override
void dispose() {
_positionController.dispose();
@@ -342,11 +407,8 @@ class RefreshIndicatorState extends State<RefreshIndicator>
ColorTween(
begin: color.withAlpha(0),
end: color.withAlpha(color.alpha),
).chain(
CurveTween(
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),
),
),
).chain(CurveTween(
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit))),
);
}
}
@@ -364,7 +426,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
notification.metrics.extentAfter == 0.0) ||
(notification.metrics.axisDirection == AxisDirection.down &&
notification.metrics.extentBefore == 0.0)) &&
_mode == null &&
_status == null &&
_start(notification.metrics.axisDirection);
}
@@ -374,7 +436,8 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
if (_shouldStart(notification)) {
setState(() {
_mode = _RefreshIndicatorMode.drag;
_status = RefreshIndicatorStatus.drag;
widget.onStatusChange?.call(_status);
});
return false;
}
@@ -384,13 +447,13 @@ class RefreshIndicatorState extends State<RefreshIndicator>
AxisDirection.left || AxisDirection.right => null,
};
if (indicatorAtTopNow != _isIndicatorAtTop) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed) {
_dismiss(_RefreshIndicatorMode.canceled);
if (_status == RefreshIndicatorStatus.drag ||
_status == RefreshIndicatorStatus.armed) {
_dismiss(RefreshIndicatorStatus.canceled);
}
} else if (notification is ScrollUpdateNotification) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed) {
if (_status == RefreshIndicatorStatus.drag ||
_status == RefreshIndicatorStatus.armed) {
if (notification.metrics.axisDirection == AxisDirection.down) {
_dragOffset = _dragOffset! - notification.scrollDelta!;
} else if (notification.metrics.axisDirection == AxisDirection.up) {
@@ -398,7 +461,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
_checkDragOffset(notification.metrics.viewportDimension);
}
if (_mode == _RefreshIndicatorMode.armed &&
if (_status == RefreshIndicatorStatus.armed &&
notification.dragDetails == null) {
// On iOS start the refresh when the Scrollable bounces back from the
// overscroll (ScrollNotification indicating this don't have dragDetails
@@ -406,8 +469,8 @@ class RefreshIndicatorState extends State<RefreshIndicator>
_show();
}
} else if (notification is OverscrollNotification) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed) {
if (_status == RefreshIndicatorStatus.drag ||
_status == RefreshIndicatorStatus.armed) {
if (notification.metrics.axisDirection == AxisDirection.down) {
_dragOffset = _dragOffset! - notification.overscroll;
} else if (notification.metrics.axisDirection == AxisDirection.up) {
@@ -416,19 +479,19 @@ class RefreshIndicatorState extends State<RefreshIndicator>
_checkDragOffset(notification.metrics.viewportDimension);
}
} else if (notification is ScrollEndNotification) {
switch (_mode) {
case _RefreshIndicatorMode.armed:
switch (_status) {
case RefreshIndicatorStatus.armed:
if (_positionController.value < 1.0) {
_dismiss(_RefreshIndicatorMode.canceled);
_dismiss(RefreshIndicatorStatus.canceled);
} else {
_show();
}
case _RefreshIndicatorMode.drag:
_dismiss(_RefreshIndicatorMode.canceled);
case _RefreshIndicatorMode.canceled:
case _RefreshIndicatorMode.done:
case _RefreshIndicatorMode.refresh:
case _RefreshIndicatorMode.snap:
case RefreshIndicatorStatus.drag:
_dismiss(RefreshIndicatorStatus.canceled);
case RefreshIndicatorStatus.canceled:
case RefreshIndicatorStatus.done:
case RefreshIndicatorStatus.refresh:
case RefreshIndicatorStatus.snap:
case null:
// do nothing
break;
@@ -442,7 +505,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
if (notification.depth != 0 || !notification.leading) {
return false;
}
if (_mode == _RefreshIndicatorMode.drag) {
if (_status == RefreshIndicatorStatus.drag) {
notification.disallowIndicator();
return true;
}
@@ -450,7 +513,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
bool _start(AxisDirection direction) {
assert(_mode == null);
assert(_status == null);
assert(_isIndicatorAtTop == null);
assert(_dragOffset == null);
switch (direction) {
@@ -470,75 +533,78 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
void _checkDragOffset(double containerExtent) {
assert(_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed);
assert(_status == RefreshIndicatorStatus.drag ||
_status == RefreshIndicatorStatus.armed);
double newValue =
_dragOffset! / (containerExtent * kDragContainerExtentPercentage);
if (_mode == _RefreshIndicatorMode.armed) {
if (_status == RefreshIndicatorStatus.armed) {
newValue = math.max(newValue, 1.0 / _kDragSizeFactorLimit);
}
_positionController.value =
clampDouble(newValue, 0.0, 1.0); // this triggers various rebuilds
if (_mode == _RefreshIndicatorMode.drag &&
clampDouble(newValue, 0.0, 1.0); // This triggers various rebuilds.
if (_status == RefreshIndicatorStatus.drag &&
_valueColor.value!.alpha == _effectiveValueColor.alpha) {
_mode = _RefreshIndicatorMode.armed;
_status = RefreshIndicatorStatus.armed;
widget.onStatusChange?.call(_status);
}
}
// Stop showing the refresh indicator.
Future<void> _dismiss(_RefreshIndicatorMode newMode) async {
Future<void> _dismiss(RefreshIndicatorStatus newMode) async {
await Future<void>.value();
// This can only be called from _show() when refreshing and
// _handleScrollNotification in response to a ScrollEndNotification or
// direction change.
assert(newMode == _RefreshIndicatorMode.canceled ||
newMode == _RefreshIndicatorMode.done);
assert(newMode == RefreshIndicatorStatus.canceled ||
newMode == RefreshIndicatorStatus.done);
setState(() {
_mode = newMode;
_status = newMode;
widget.onStatusChange?.call(_status);
});
switch (_mode!) {
case _RefreshIndicatorMode.done:
switch (_status!) {
case RefreshIndicatorStatus.done:
await _scaleController.animateTo(1.0,
duration: _kIndicatorScaleDuration);
case _RefreshIndicatorMode.canceled:
case RefreshIndicatorStatus.canceled:
await _positionController.animateTo(0.0,
duration: _kIndicatorScaleDuration);
case _RefreshIndicatorMode.armed:
case _RefreshIndicatorMode.drag:
case _RefreshIndicatorMode.refresh:
case _RefreshIndicatorMode.snap:
case RefreshIndicatorStatus.armed:
case RefreshIndicatorStatus.drag:
case RefreshIndicatorStatus.refresh:
case RefreshIndicatorStatus.snap:
assert(false);
}
if (mounted && _mode == newMode) {
if (mounted && _status == newMode) {
_dragOffset = null;
_isIndicatorAtTop = null;
setState(() {
_mode = null;
_status = null;
});
}
}
void _show() {
assert(_mode != _RefreshIndicatorMode.refresh);
assert(_mode != _RefreshIndicatorMode.snap);
assert(_status != RefreshIndicatorStatus.refresh);
assert(_status != RefreshIndicatorStatus.snap);
final Completer<void> completer = Completer<void>();
_pendingRefreshFuture = completer.future;
_mode = _RefreshIndicatorMode.snap;
_status = RefreshIndicatorStatus.snap;
widget.onStatusChange?.call(_status);
_positionController
.animateTo(1.0 / _kDragSizeFactorLimit,
duration: _kIndicatorSnapDuration)
.then<void>((void value) {
if (mounted && _mode == _RefreshIndicatorMode.snap) {
if (mounted && _status == RefreshIndicatorStatus.snap) {
setState(() {
// Show the indeterminate progress indicator.
_mode = _RefreshIndicatorMode.refresh;
_status = RefreshIndicatorStatus.refresh;
});
final Future<void> refreshResult = widget.onRefresh();
refreshResult.whenComplete(() {
if (mounted && _mode == _RefreshIndicatorMode.refresh) {
if (mounted && _status == RefreshIndicatorStatus.refresh) {
completer.complete();
_dismiss(_RefreshIndicatorMode.done);
_dismiss(RefreshIndicatorStatus.done);
}
});
}
@@ -562,9 +628,9 @@ class RefreshIndicatorState extends State<RefreshIndicator>
/// actual scroll view. It defaults to showing the indicator at the top. To
/// show it at the bottom, set `atTop` to false.
Future<void> show({bool atTop = true}) {
if (_mode != _RefreshIndicatorMode.refresh &&
_mode != _RefreshIndicatorMode.snap) {
if (_mode == null) {
if (_status != RefreshIndicatorStatus.refresh &&
_status != RefreshIndicatorStatus.snap) {
if (_status == null) {
_start(atTop ? AxisDirection.down : AxisDirection.up);
}
_show();
@@ -572,6 +638,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
return _pendingRefreshFuture;
}
@protected
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
@@ -583,7 +650,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
),
);
assert(() {
if (_mode == null) {
if (_status == null) {
assert(_dragOffset == null);
assert(_isIndicatorAtTop == null);
} else {
@@ -594,13 +661,14 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}());
final bool showIndeterminateIndicator =
_mode == _RefreshIndicatorMode.refresh ||
_mode == _RefreshIndicatorMode.done;
_status == RefreshIndicatorStatus.refresh ||
_status == RefreshIndicatorStatus.done;
return Stack(
clipBehavior: Clip.none,
children: <Widget>[
child,
if (_mode != null)
if (_status != null)
Positioned(
top: _isIndicatorAtTop! ? widget.edgeOffset : null,
bottom: !_isIndicatorAtTop! ? widget.edgeOffset : null,
@@ -608,41 +676,44 @@ class RefreshIndicatorState extends State<RefreshIndicator>
right: 0.0,
child: SizeTransition(
axisAlignment: _isIndicatorAtTop! ? 1.0 : -1.0,
sizeFactor: _positionFactor, // this is what brings it down
child: Container(
sizeFactor: _positionFactor, // This is what brings it down.
child: Padding(
padding: _isIndicatorAtTop!
? EdgeInsets.only(top: widget.displacement)
: EdgeInsets.only(bottom: widget.displacement),
alignment: _isIndicatorAtTop!
? Alignment.topCenter
: Alignment.bottomCenter,
child: ScaleTransition(
scale: _scaleFactor,
child: AnimatedBuilder(
animation: _positionController,
builder: (BuildContext context, Widget? child) {
final Widget materialIndicator = RefreshProgressIndicator(
semanticsLabel: widget.semanticsLabel ??
MaterialLocalizations.of(context)
.refreshIndicatorSemanticLabel,
semanticsValue: widget.semanticsValue,
value: showIndeterminateIndicator ? null : _value.value,
valueColor: _valueColor,
backgroundColor: widget.backgroundColor,
strokeWidth: widget.strokeWidth,
);
child: Align(
alignment: _isIndicatorAtTop!
? Alignment.topCenter
: Alignment.bottomCenter,
child: ScaleTransition(
scale: _scaleFactor,
child: AnimatedBuilder(
animation: _positionController,
builder: (BuildContext context, Widget? child) {
final Widget materialIndicator =
RefreshProgressIndicator(
semanticsLabel: widget.semanticsLabel ??
MaterialLocalizations.of(context)
.refreshIndicatorSemanticLabel,
semanticsValue: widget.semanticsValue,
value:
showIndeterminateIndicator ? null : _value.value,
valueColor: _valueColor,
backgroundColor: widget.backgroundColor,
strokeWidth: widget.strokeWidth,
elevation: widget.elevation,
);
final Widget cupertinoIndicator =
CupertinoActivityIndicator(
color: widget.color,
);
final Widget cupertinoIndicator =
CupertinoActivityIndicator(
color: widget.color,
);
switch (widget._indicatorType) {
case _IndicatorType.material:
return materialIndicator;
switch (widget._indicatorType) {
case _IndicatorType.material:
return materialIndicator;
case _IndicatorType.adaptive:
{
case _IndicatorType.adaptive:
final ThemeData theme = Theme.of(context);
switch (theme.platform) {
case TargetPlatform.android:
@@ -654,9 +725,12 @@ class RefreshIndicatorState extends State<RefreshIndicator>
case TargetPlatform.macOS:
return cupertinoIndicator;
}
}
}
},
case _IndicatorType.noSpinner:
return Container();
}
},
),
),
),
),

View File

@@ -0,0 +1,90 @@
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
Widget videoTabBarView({
required List<Widget> children,
TabController? controller,
}) =>
TabBarView(
physics: const CustomTabBarViewClampingScrollPhysics(),
controller: controller,
children: children,
);
Widget tabBarView({
required List<Widget> children,
TabController? controller,
}) =>
TabBarView(
physics: const CustomTabBarViewScrollPhysics(),
controller: controller,
children: children,
);
class CustomTabBarViewScrollPhysics extends ScrollPhysics {
const CustomTabBarViewScrollPhysics({super.parent});
@override
CustomTabBarViewScrollPhysics applyTo(ScrollPhysics? ancestor) {
return CustomTabBarViewScrollPhysics(parent: buildParent(ancestor));
}
@override
SpringDescription get spring => CustomSpringDescription();
}
class CustomTabBarViewClampingScrollPhysics extends ClampingScrollPhysics {
const CustomTabBarViewClampingScrollPhysics({super.parent});
@override
CustomTabBarViewClampingScrollPhysics applyTo(ScrollPhysics? ancestor) {
return CustomTabBarViewClampingScrollPhysics(parent: buildParent(ancestor));
}
@override
SpringDescription get spring => CustomSpringDescription();
}
class MemberVideoScrollPhysics extends AlwaysScrollableScrollPhysics {
const MemberVideoScrollPhysics({super.parent});
@override
MemberVideoScrollPhysics applyTo(ScrollPhysics? ancestor) {
return MemberVideoScrollPhysics(parent: buildParent(ancestor));
}
@override
double adjustPositionForNewDimensions({
required ScrollMetrics oldPosition,
required ScrollMetrics newPosition,
required bool isScrolling,
required double velocity,
}) {
if (newPosition.maxScrollExtent < oldPosition.maxScrollExtent) {
return 0;
}
return super.adjustPositionForNewDimensions(
oldPosition: oldPosition,
newPosition: newPosition,
isScrolling: isScrolling,
velocity: velocity,
);
}
}
class CustomSpringDescription implements SpringDescription {
@override
final mass = GStorage.springDescription[0];
@override
final stiffness = GStorage.springDescription[1];
@override
final damping = GStorage.springDescription[2];
CustomSpringDescription._();
static final _instance = CustomSpringDescription._();
factory CustomSpringDescription() => _instance;
}

View File

@@ -3,10 +3,11 @@ import 'package:flutter/material.dart';
/// https://stackoverflow.com/a/76605401
class SelfSizedHorizontalList extends StatefulWidget {
final Widget Function(int) childBuilder;
final Widget Function(int index) childBuilder;
final int itemCount;
final double gapSize;
final EdgeInsetsGeometry? padding;
final ScrollController? controller;
const SelfSizedHorizontalList({
super.key,
@@ -14,6 +15,7 @@ class SelfSizedHorizontalList extends StatefulWidget {
required this.itemCount,
this.gapSize = 5,
this.padding,
this.controller,
});
@override
@@ -33,23 +35,35 @@ class _SelfSizedHorizontalListState extends State<SelfSizedHorizontalList> {
bool get isInit => height == null;
// @override
// void didUpdateWidget(SelfSizedHorizontalList oldWidget) {
// super.didUpdateWidget(oldWidget);
// if (BuildConfig.isDebug) {
// prevHeight = null;
// }
// }
@override
Widget build(BuildContext context) {
if (height == null) {
WidgetsBinding.instance.addPostFrameCallback((v) => setState(() {}));
}
if (widget.itemCount == 0) return const SizedBox();
if (widget.itemCount == 0) return const SizedBox.shrink();
if (isInit) {
return Container(
key: infoKey,
padding: widget.padding,
child: widget.childBuilder(0),
return Align(
alignment: Alignment.centerLeft,
child: Padding(
key: infoKey,
padding: widget.padding ?? EdgeInsets.zero,
child: widget.childBuilder(0),
),
);
}
return SizedBox(
height: height,
child: ListView.separated(
controller: widget.controller,
padding: widget.padding,
scrollDirection: Axis.horizontal,
itemCount: widget.itemCount,

View File

@@ -1,24 +0,0 @@
import 'package:flutter/material.dart';
class SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
SliverHeaderDelegate({required this.height, required this.child});
final double height;
final Widget child;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return child;
}
@override
double get maxExtent => height;
@override
double get minExtent => height;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
true;
}

View File

@@ -1,36 +0,0 @@
import 'package:flutter/material.dart';
import 'package:PiliPlus/utils/utils.dart';
Widget statDanMu({
required BuildContext context,
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 = textColor ?? colorObject[theme]!;
return Row(
children: [
Icon(
Icons.subtitles_outlined,
size: 14,
color: color,
),
const SizedBox(width: 2),
Text(
Utils.numFormat(danmu!),
style: TextStyle(
fontSize: size == 'medium' ? 12 : 11,
color: color,
),
overflow: TextOverflow.clip,
semanticsLabel: '${Utils.numFormat(danmu!)}条弹幕',
)
],
);
}

View File

@@ -0,0 +1,92 @@
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
abstract class _StatItemBase extends StatelessWidget {
final BuildContext context;
final Object value;
final String? theme;
final Color? textColor;
final double iconSize;
const _StatItemBase({
required this.context,
required this.value,
this.theme,
this.textColor,
this.iconSize = 13,
});
IconData get iconData;
String get semanticsLabel;
Color get color {
return textColor ??
switch (theme) {
'gray' =>
Theme.of(context).colorScheme.outline.withValues(alpha: 0.8),
'black' =>
Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
_ => Colors.white,
};
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Icon(
iconData,
size: iconSize,
color: color,
),
const SizedBox(width: 2),
Text(
Utils.numFormat(value),
style: TextStyle(fontSize: 12, color: color),
overflow: TextOverflow.clip,
semanticsLabel: semanticsLabel,
)
],
);
}
}
class StatView extends _StatItemBase {
final String? goto;
const StatView({
required super.context,
required super.value,
this.goto,
super.theme,
super.textColor,
}) : super(iconSize: 13);
@override
IconData get iconData => switch (goto) {
'picture' => Icons.remove_red_eye_outlined,
'like' => Icons.thumb_up_outlined,
'reply' => Icons.comment_outlined,
'follow' => Icons.favorite_border,
_ => Icons.play_circle_outlined,
};
@override
String get semanticsLabel =>
'${Utils.numFormat(value)}${goto == "picture" ? "浏览" : "播放"}';
}
class StatDanMu extends _StatItemBase {
const StatDanMu({
required super.context,
required super.value,
super.theme,
super.textColor,
}) : super(iconSize: 14);
@override
IconData get iconData => Icons.subtitles_outlined;
@override
String get semanticsLabel => '${Utils.numFormat(value)}条弹幕';
}

View File

@@ -1,40 +0,0 @@
import 'package:flutter/material.dart';
import 'package:PiliPlus/utils/utils.dart';
Widget statView({
required BuildContext context,
String? theme,
dynamic view,
String? size,
String? goto,
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 = textColor ?? colorObject[theme]!;
return Row(
children: [
Icon(
goto == 'picture'
? Icons.remove_red_eye_outlined
: Icons.play_circle_outlined,
size: 13,
color: color,
),
const SizedBox(width: 2),
Text(
Utils.numFormat(view!),
style: TextStyle(
fontSize: size == 'medium' ? 12 : 11,
color: color,
),
overflow: TextOverflow.clip,
semanticsLabel:
'${Utils.numFormat(view!)}${goto == "picture" ? "浏览" : "播放"}',
),
],
);
}

View File

@@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'dart:ui' show SemanticsRole;
import 'package:flutter/foundation.dart' show clampDouble;
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/material.dart' hide TabBarView;
@@ -124,11 +126,8 @@ class _CustomTabBarViewState extends State<CustomTabBarView> {
_warpUnderwayCount -= 1;
}
Future<void> _animateToPage(
int page, {
required Duration duration,
required Curve curve,
}) async {
Future<void> _animateToPage(int page,
{required Duration duration, required Curve curve}) async {
_warpUnderwayCount += 1;
await _pageController!
.animateToPage(page, duration: duration, curve: curve);
@@ -190,7 +189,11 @@ class _CustomTabBarViewState extends State<CustomTabBarView> {
}
void _updateChildren() {
_childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children);
_childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(
widget.children.map<Widget>((Widget child) {
return Semantics(role: SemanticsRole.tabPanel, child: child);
}).toList(),
);
}
void _handleTabControllerAnimationTick() {

View File

@@ -0,0 +1,300 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/image/image_save.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/common/widgets/video_popup_menu.dart';
import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/models/search/result.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
// 视频卡片 - 水平布局
class VideoCardH extends StatelessWidget {
const VideoCardH({
super.key,
required this.videoItem,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
this.showDanmaku = true,
this.showPubdate = false,
this.onTap,
this.onLongPress,
this.onViewLater,
});
final BaseVideoItemModel videoItem;
final String source;
final bool showOwner;
final bool showView;
final bool showDanmaku;
final bool showPubdate;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final ValueChanged<int>? onViewLater;
@override
Widget build(BuildContext context) {
final int aid = videoItem.aid!;
final String bvid = videoItem.bvid!;
String type = 'video';
if (videoItem is SearchVideoItemModel) {
var typeOrNull = (videoItem as SearchVideoItemModel).type;
if (typeOrNull?.isNotEmpty == true) {
type = typeOrNull!;
}
}
return Material(
color: Colors.transparent,
child: Stack(
clipBehavior: Clip.none,
children: [
Semantics(
label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true,
child: InkWell(
onLongPress: () {
if (onLongPress != null) {
onLongPress!();
} else {
imageSaveDialog(
title: videoItem.title,
cover: videoItem.pic,
);
}
},
onTap: () async {
if (onTap != null) {
onTap!();
return;
}
if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放');
return;
} else if (type == 'live_room') {
if (videoItem is SearchVideoItemModel) {
int? roomId = (videoItem as SearchVideoItemModel).id;
if (roomId != null) {
Get.toNamed('/liveRoom?roomid=$roomId');
}
} else {
SmartDialog.showToast(
'err: live_room : ${videoItem.runtimeType}');
}
return;
}
if ((videoItem is HotVideoItemModel) &&
(videoItem as HotVideoItemModel).redirectUrl?.isNotEmpty ==
true) {
if (PageUtils.viewPgcFromUri(
(videoItem as HotVideoItemModel).redirectUrl!)) {
return;
}
}
try {
final int cid = videoItem.cid ??
await SearchHttp.ab2c(aid: aid, bvid: bvid);
if (source == 'later') {
onViewLater!(cid);
} else {
PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
arguments: {
'videoItem': videoItem,
'heroTag': Utils.makeHeroTag(aid)
},
);
}
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: 5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
num? progress;
if (videoItem is HotVideoItemModel) {
progress =
(videoItem as HotVideoItemModel).progress;
}
return Stack(
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
),
if (videoItem is HotVideoItemModel)
PBadge(
text:
(videoItem as HotVideoItemModel).pgcLabel,
top: 6.0,
right: 6.0,
),
if (progress != null && progress != 0) ...[
PBadge(
text: progress == -1
? '已看完'
: '${Utils.timeFormat(progress)}/${Utils.timeFormat(videoItem.duration)}',
right: 6,
bottom: 8,
type: PBadgeType.gray,
),
Positioned(
left: 0,
bottom: 0,
right: 0,
child: videoProgressIndicator(
progress == -1
? 1
: progress / videoItem.duration,
),
)
] else if (videoItem.duration > 0)
PBadge(
text: Utils.timeFormat(videoItem.duration),
right: 6.0,
bottom: 6.0,
type: PBadgeType.gray,
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: PBadgeType.primary,
),
],
);
},
),
),
const SizedBox(width: 10),
videoContent(context),
],
),
),
),
),
if (source == 'normal')
Positioned(
bottom: 0,
right: 12,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
videoItem: videoItem,
),
),
],
),
);
}
Widget videoContent(BuildContext context) {
final theme = Theme.of(context);
String pubdate = showPubdate
? Utils.dateFormat(videoItem.pubdate!, formatType: 'day')
: '';
if (pubdate != '') pubdate += ' ';
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if ((videoItem is SearchVideoItemModel) &&
(videoItem as SearchVideoItemModel).titleList?.isNotEmpty == true)
Expanded(
child: Text.rich(
overflow: TextOverflow.ellipsis,
maxLines: 2,
TextSpan(
children: [
for (var i
in (videoItem as SearchVideoItemModel).titleList!)
TextSpan(
text: i['text'],
style: TextStyle(
fontSize: theme.textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: i['type'] == 'em'
? theme.colorScheme.primary
: theme.colorScheme.onSurface,
),
),
],
),
),
)
else
Expanded(
child: Text(
videoItem.title,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: theme.textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
if (showOwner || showPubdate)
Text(
"$pubdate ${showOwner ? videoItem.owner.name : ''}",
maxLines: 1,
style: TextStyle(
fontSize: 12,
height: 1,
color: theme.colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const SizedBox(height: 3),
Row(
children: [
if (showView) ...[
StatView(
context: context,
theme: 'gray',
value: videoItem.stat.viewStr,
),
const SizedBox(width: 8),
],
if (showDanmaku)
StatDanMu(
context: context,
theme: 'gray',
value: videoItem.stat.danmuStr,
),
const Spacer(),
if (source == 'normal') const SizedBox(width: 24),
],
),
],
),
);
}
}

View File

@@ -1,16 +1,16 @@
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/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/image/image_save.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/common/widgets/video_popup_menu.dart';
import 'package:PiliPlus/common/widgets/video_progress_indicator.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'badge.dart';
import 'network_img_layer.dart';
// -
class VideoCardHMemberVideo extends StatelessWidget {
@@ -19,18 +19,21 @@ class VideoCardHMemberVideo extends StatelessWidget {
required this.videoItem,
this.onTap,
this.bvid,
this.fromViewAid,
});
final Item videoItem;
final SpaceArchiveItem videoItem;
final VoidCallback? onTap;
final dynamic bvid;
final String? fromViewAid;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Stack(
clipBehavior: Clip.none,
children: [
InkWell(
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.cover,
),
@@ -40,16 +43,16 @@ class VideoCardHMemberVideo extends StatelessWidget {
return;
}
if (videoItem.isPgc == true && videoItem.uri?.isNotEmpty == true) {
if (Utils.viewPgcFromUri(videoItem.uri!)) {
if (PageUtils.viewPgcFromUri(videoItem.uri!)) {
return;
}
}
if (videoItem.bvid == null || videoItem.firstCid == null) {
if (videoItem.bvid == null || videoItem.cid == null) {
return;
}
try {
Get.toNamed(
'/video?bvid=${videoItem.bvid}&cid=${videoItem.firstCid}',
PageUtils.toVideoPage(
'bvid=${videoItem.bvid}&cid=${videoItem.cid}',
arguments: {
'heroTag': Utils.makeHeroTag(videoItem.bvid),
},
@@ -77,13 +80,32 @@ class VideoCardHMemberVideo extends StatelessWidget {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
src: videoItem.cover,
// videoItem.season?['cover'] ?? videoItem.cover,
width: maxWidth,
height: maxHeight,
),
if (fromViewAid == videoItem.param)
const Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: StyleString.mdRadius,
color: Colors.black54,
),
child: Center(
child: Text(
'上次观看',
style: TextStyle(
color: Colors.white,
fontSize: 15,
letterSpacing: 5,
),
),
),
),
),
if (videoItem.badges?.isNotEmpty == true)
PBadge(
text: videoItem.badges!
@@ -91,15 +113,11 @@ class VideoCardHMemberVideo extends StatelessWidget {
.join('|'),
right: 6.0,
top: 6.0,
type: videoItem.badges!.first.text == '充电专属'
? PBadgeType.error
: PBadgeType.primary,
),
if (videoItem.duration != null)
PBadge(
text: Utils.timeFormat(videoItem.duration),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
if (videoItem.history != null)
if (videoItem.history != null) ...[
Builder(builder: (context) {
try {
return Positioned(
@@ -115,13 +133,41 @@ class VideoCardHMemberVideo extends StatelessWidget {
return const SizedBox.shrink();
}
}),
Builder(builder: (context) {
try {
return PBadge(
text: videoItem.history!['progress'] ==
videoItem.history!['duration']
? '已看完'
: '${Utils.timeFormat(videoItem.history!['progress'])}/${Utils.timeFormat(videoItem.history!['duration'])}',
right: 6.0,
bottom: 6.0,
type: PBadgeType.gray,
);
} catch (_) {
return PBadge(
text:
Utils.timeFormat(videoItem.duration),
right: 6.0,
bottom: 6.0,
type: PBadgeType.gray,
);
}
}),
] else if (videoItem.duration > 0)
PBadge(
text: Utils.timeFormat(videoItem.duration),
right: 6.0,
bottom: 6.0,
type: PBadgeType.gray,
),
],
);
},
),
),
const SizedBox(width: 10),
videoContent(context),
videoContent(context, theme),
],
);
},
@@ -141,26 +187,23 @@ class VideoCardHMemberVideo extends StatelessWidget {
);
}
Widget videoContent(context) {
Widget videoContent(BuildContext context, ThemeData theme) {
final isCurr = fromViewAid == videoItem.param ||
(videoItem.bvid != null && videoItem.bvid == bvid);
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
// videoItem.season?['title'] ?? videoItem.title ?? '',
videoItem.title ?? '',
videoItem.title,
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: videoItem.bvid != null && videoItem.bvid == bvid
? FontWeight.bold
: null,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
fontWeight: isCurr ? FontWeight.bold : null,
fontSize: theme.textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: videoItem.bvid != null && videoItem.bvid == bvid
? Theme.of(context).colorScheme.primary
: null,
color: isCurr ? theme.colorScheme.primary : null,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
@@ -172,28 +215,25 @@ class VideoCardHMemberVideo extends StatelessWidget {
: videoItem.publishTimeText ?? '',
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
fontSize: 12,
height: 1,
color: Theme.of(context).colorScheme.outline,
color: theme.colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const SizedBox(height: 3),
Row(
children: [
statView(
StatView(
context: context,
theme: 'gray',
// view: videoItem.season?['view_content'] ??
// videoItem.viewContent,
view: videoItem.viewContent,
value: videoItem.stat.viewStr,
),
const SizedBox(width: 8),
statDanMu(
StatDanMu(
context: context,
theme: 'gray',
// danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku,
danmu: videoItem.danmaku,
value: videoItem.stat.danmuStr,
),
],
),

View File

@@ -0,0 +1,288 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/image/image_save.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/common/widgets/video_popup_menu.dart';
import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/home/rcmd/result.dart';
import 'package:PiliPlus/models/model_rec_video_item.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
// 视频卡片 - 垂直布局
class VideoCardV extends StatelessWidget {
final BaseRecVideoItemModel videoItem;
final VoidCallback? onRemove;
const VideoCardV({
super.key,
required this.videoItem,
this.onRemove,
});
bool isStringNumeric(String str) {
RegExp numericRegex = RegExp(r'^\d+$');
return numericRegex.hasMatch(str);
}
Future<void> onPushDetail(heroTag) async {
String? goto = videoItem.goto;
switch (goto) {
case 'bangumi':
PageUtils.viewBangumi(epId: videoItem.param!);
break;
case 'av':
String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid!);
int? cid = videoItem.cid;
if (cid == null || cid == 0 || cid == -1) {
cid = await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid);
}
PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
arguments: {
'pic': videoItem.pic,
'heroTag': heroTag,
},
);
break;
// 动态
case 'picture':
try {
String type = 'picture';
String uri = videoItem.uri!;
String id = '';
if (uri.startsWith('bilibili://article/')) {
type = 'read';
RegExp regex = RegExp(r'\d+');
Match match = regex.firstMatch(uri)!;
String matchedNumber = match.group(0)!;
videoItem.param = int.parse(matchedNumber);
id = '${videoItem.param}';
}
if (uri.startsWith('http')) {
String id = Uri.parse(uri).path.split('/')[1];
if (isStringNumeric(id)) {
PageUtils.pushDynFromId(id: id);
return;
}
}
Get.toNamed(
'/articlePage',
parameters: {
'id': id,
'type': type,
},
);
} catch (err) {
SmartDialog.showToast(err.toString());
}
break;
default:
if (videoItem.uri?.isNotEmpty == true) {
PiliScheme.routePushFromUrl(videoItem.uri!);
}
}
}
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
Semantics(
label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true,
child: Card(
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: InkWell(
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.aid)),
onLongPress: () => imageSaveDialog(
title: videoItem.title,
cover: videoItem.pic,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return Stack(
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
),
if (videoItem.duration > 0)
PBadge(
bottom: 6,
right: 7,
size: PBadgeSize.small,
type: PBadgeType.gray,
text: Utils.timeFormat(videoItem.duration),
)
],
);
}),
),
videoContent(context)
],
),
),
),
),
if (videoItem.goto == 'av')
Positioned(
right: -5,
bottom: -2,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
videoItem: videoItem,
onRemove: onRemove,
),
),
],
);
}
Widget videoContent(context) {
final theme = Theme.of(context);
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(6, 5, 6, 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
"${videoItem.title}\n",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
height: 1.38,
),
),
),
videoStat(context, theme),
Row(
children: [
if (videoItem.goto == 'bangumi') ...[
PBadge(
text: videoItem.bangumiBadge,
isStack: false,
size: PBadgeSize.small,
type: PBadgeType.line_primary,
fontSize: 9,
),
const SizedBox(width: 2),
],
if (videoItem.rcmdReason != null) ...[
PBadge(
text: videoItem.rcmdReason,
isStack: false,
size: PBadgeSize.small,
type: PBadgeType.secondary,
),
const SizedBox(width: 2),
],
if (videoItem.goto == 'picture') ...[
const PBadge(
text: '动态',
isStack: false,
size: PBadgeSize.small,
type: PBadgeType.line_primary,
fontSize: 9,
),
const SizedBox(width: 2),
],
if (videoItem.isFollowed) ...[
const PBadge(
text: '已关注',
isStack: false,
size: PBadgeSize.small,
type: PBadgeType.secondary,
),
const SizedBox(width: 2),
],
Expanded(
flex: 1,
child: Text(
videoItem.owner.name.toString(),
maxLines: 1,
overflow: TextOverflow.clip,
style: TextStyle(
height: 1.5,
fontSize: theme.textTheme.labelMedium!.fontSize,
color: theme.colorScheme.outline,
),
),
),
if (videoItem.goto == 'av') const SizedBox(width: 10)
],
),
],
),
),
);
}
Widget videoStat(BuildContext context, ThemeData theme) {
return Row(
children: [
StatView(
context: context,
theme: 'gray',
value: videoItem.stat.viewStr,
goto: videoItem.goto,
),
const SizedBox(width: 4),
if (videoItem.goto != 'picture')
StatDanMu(
context: context,
theme: 'gray',
value: videoItem.stat.danmuStr,
),
if (videoItem is RecVideoItemModel) ...[
const Spacer(),
Text.rich(
maxLines: 1,
TextSpan(
style: TextStyle(
fontSize: theme.textTheme.labelSmall!.fontSize,
color: theme.colorScheme.outline.withValues(alpha: 0.8),
),
text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
),
const SizedBox(width: 2),
] else if (videoItem is RecVideoItemAppModel &&
videoItem.desc != null &&
videoItem.desc!.contains(' · ')) ...[
const Spacer(),
Text.rich(
maxLines: 1,
TextSpan(
style: TextStyle(
fontSize: theme.textTheme.labelSmall!.fontSize,
color: theme.colorScheme.outline.withValues(alpha: 0.8),
),
text: Utils.shortenChineseDateString(
videoItem.desc!.split(' · ').last)),
),
const SizedBox(width: 2),
]
],
);
}
}

View File

@@ -0,0 +1,120 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/image/image_save.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/space/item.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
// 视频卡片 - 垂直布局
class VideoCardVMemberHome extends StatelessWidget {
final SpaceItem videoItem;
const VideoCardVMemberHome({
super.key,
required this.videoItem,
});
Future<void> onPushDetail(heroTag) async {
String? goto = videoItem.goto;
switch (goto) {
case 'bangumi':
PageUtils.viewBangumi(epId: videoItem.param);
break;
case 'av':
if (videoItem.isPgc == true && videoItem.uri?.isNotEmpty == true) {
if (PageUtils.viewPgcFromUri(videoItem.uri!)) {
return;
}
}
String? aid = videoItem.param;
String? bvid = videoItem.bvid;
if (aid == null && bvid == null) {
return;
}
int? cid = videoItem.firstCid;
cid ??= await SearchHttp.ab2c(aid: aid, bvid: bvid);
PageUtils.toVideoPage(
'bvid=${bvid ?? IdUtils.av2bv(int.parse(aid!))}&cid=$cid',
arguments: {
'pic': videoItem.cover,
'heroTag': heroTag,
},
);
break;
default:
if (videoItem.uri?.isNotEmpty == true) {
PiliScheme.routePushFromUrl(videoItem.uri!);
}
}
}
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: InkWell(
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.bvid)),
onLongPress: () => imageSaveDialog(
title: videoItem.title,
cover: videoItem.cover,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return Stack(
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
src: videoItem.cover,
width: maxWidth,
height: maxHeight,
),
if ((videoItem.duration ?? -1) > 0)
PBadge(
bottom: 6,
right: 7,
size: PBadgeSize.small,
type: PBadgeType.gray,
text: Utils.timeFormat(videoItem.duration),
)
],
);
},
),
),
videoContent(context, videoItem)
],
),
),
);
}
}
Widget videoContent(BuildContext context, SpaceItem videoItem) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(6, 5, 6, 5),
child: Text(
'${videoItem.title}\n',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
height: 1.38,
),
),
),
);
}

View File

@@ -1,279 +0,0 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../http/search.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'badge.dart';
import 'network_img_layer.dart';
import 'stat/danmu.dart';
import 'stat/view.dart';
import 'video_popup_menu.dart';
// 视频卡片 - 水平布局
class VideoCardH extends StatelessWidget {
const VideoCardH({
super.key,
required this.videoItem,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
this.showDanmaku = true,
this.showPubdate = false,
this.onTap,
this.onLongPress,
});
final dynamic videoItem;
final String source;
final bool showOwner;
final bool showView;
final bool showDanmaku;
final bool showPubdate;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
@override
Widget build(BuildContext context) {
final int aid = videoItem.aid;
final String bvid = videoItem.bvid;
String type = 'video';
try {
type = videoItem.type;
} catch (_) {}
return Stack(children: [
Semantics(
label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true,
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
// for (var item in actions)
// CustomSemanticsAction(
// label: item.title.isEmpty ? 'label' : item.title): item.onTap!,
// },
child: InkWell(
onLongPress: () {
if (onLongPress != null) {
onLongPress!();
} else {
imageSaveDialog(
context: context,
title: videoItem.title is String
? videoItem.title
: videoItem.title is List
? (videoItem.title as List)
.map((item) => item['text'])
.join()
: '',
cover: videoItem.pic,
);
}
},
onTap: () async {
if (onTap != null) {
onTap?.call();
return;
}
if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放');
return;
}
if (videoItem is HotVideoItemModel &&
videoItem.redirectUrl?.isNotEmpty == true) {
if (Utils.viewPgcFromUri(videoItem.redirectUrl!)) {
return;
}
}
try {
final int cid =
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
Get.toNamed(
'/video?bvid=$bvid&cid=$cid',
arguments: {
'videoItem': videoItem,
'heroTag': Utils.makeHeroTag(aid)
},
);
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: 5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder:
(BuildContext context, BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
NetworkImgLayer(
src: videoItem.pic as String,
width: maxWidth,
height: maxHeight,
),
if (videoItem is HotVideoItemModel &&
videoItem.pgcLabel?.isNotEmpty == true)
PBadge(
text: videoItem.pgcLabel,
top: 6.0,
right: 6.0,
),
if (videoItem.duration != 0)
PBadge(
text: Utils.timeFormat(videoItem.duration!),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: 'primary',
),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
// 6.0, 6.0, null, null),
],
);
},
),
),
const SizedBox(width: 10),
videoContent(context),
],
),
),
),
),
if (source == 'normal')
Positioned(
bottom: 0,
right: 12,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
videoItem: videoItem,
),
),
]);
}
Widget videoContent(context) {
String pubdate = showPubdate
? Utils.dateFormat(videoItem.pubdate!, formatType: 'day')
: '';
if (pubdate != '') pubdate += ' ';
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (videoItem.title is String)
Expanded(
child: Text(
videoItem.title,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
)
else
Expanded(
child: Text.rich(
overflow: TextOverflow.ellipsis,
maxLines: 2,
TextSpan(
children: [
for (final i in videoItem.title) ...[
TextSpan(
text: i['text'] as String,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
),
]
],
),
),
),
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate)
Expanded(
flex: 0,
child: Text(
"$pubdate ${showOwner ? videoItem.owner.name : ''}",
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
),
const SizedBox(height: 3),
Row(
children: [
if (showView) ...[
statView(
context: context,
theme: 'gray',
view: videoItem.stat.view as int,
),
const SizedBox(width: 8),
],
if (showDanmaku)
statDanMu(
context: context,
theme: 'gray',
danmu: videoItem.stat.danmu as int,
),
const Spacer(),
if (source == 'normal') const SizedBox(width: 24),
],
),
],
),
);
}
}

View File

@@ -1,215 +0,0 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'badge.dart';
import 'network_img_layer.dart';
// 视频卡片 - 水平布局
class VideoCardHGrpc extends StatelessWidget {
const VideoCardHGrpc({
super.key,
required this.videoItem,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
this.showDanmaku = true,
this.showPubdate = false,
});
final card.Card videoItem;
final String source;
final bool showOwner;
final bool showView;
final bool showDanmaku;
final bool showPubdate;
@override
Widget build(BuildContext context) {
final int aid = videoItem.smallCoverV5.base.args.aid.toInt();
// final String bvid = IdUtils.av2bv(aid);
String type = 'video';
// try {
// type = videoItem.type;
// } catch (_) {}
// List<VideoCustomAction> actions =
// VideoCustomActions(videoItem, context).actions;
final String heroTag = Utils.makeHeroTag(aid);
return Stack(children: [
Semantics(
// label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true,
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
// for (var item in actions)
// CustomSemanticsAction(label: item.title): item.onTap!,
// },
child: InkWell(
borderRadius: BorderRadius.circular(12),
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.smallCoverV5.base.title,
cover: videoItem.smallCoverV5.base.cover,
),
onTap: () async {
if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放');
return;
}
try {
PiliScheme.routePushFromUrl(videoItem.smallCoverV5.base.uri);
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (BuildContext context,
BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.smallCoverV5.base.cover,
width: maxWidth,
height: maxHeight,
),
),
if (videoItem
.smallCoverV5.coverRightText1.isNotEmpty)
PBadge(
text: Utils.timeFormat(
videoItem.smallCoverV5.coverRightText1),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: 'primary',
),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
// 6.0, 6.0, null, null),
],
);
},
),
),
const SizedBox(width: 10),
videoContent(context),
],
);
},
),
),
),
// if (source == 'normal')
// Positioned(
// bottom: 0,
// right: 0,
// child: VideoPopupMenu(
// size: 29,
// iconSize: 17,
// actions: actions,
// ),
// ),
]);
}
Widget videoContent(context) {
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
videoItem.smallCoverV5.base.title,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate)
Text(
videoItem.smallCoverV5.rightDesc1,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const SizedBox(height: 3),
Text(
videoItem.smallCoverV5.rightDesc2,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
// Row(
// children: [
// if (showView) ...[
// StatView(
// theme: 'gray',
// view: videoItem.stat.view as int,
// ),
// const SizedBox(width: 8),
// ],
// if (showDanmaku)
// StatDanMu(
// theme: 'gray',
// danmu: videoItem.stat.danmu as int,
// ),
// const Spacer(),
// if (source == 'normal') const SizedBox(width: 24),
// ],
// ),
],
),
);
}
}

View File

@@ -1,321 +0,0 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/http/search.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../models/home/rcmd/result.dart';
import '../../models/model_rec_video_item.dart';
import 'stat/danmu.dart';
import 'stat/view.dart';
import '../../http/dynamics.dart';
import '../../utils/id_utils.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'badge.dart';
import 'network_img_layer.dart';
import 'video_popup_menu.dart';
// 视频卡片 - 垂直布局
class VideoCardV extends StatelessWidget {
final dynamic videoItem;
final VoidCallback? onRemove;
const VideoCardV({
super.key,
required this.videoItem,
this.onRemove,
});
bool isStringNumeric(String str) {
RegExp numericRegex = RegExp(r'^\d+$');
return numericRegex.hasMatch(str);
}
void onPushDetail(heroTag) async {
String goto = videoItem.goto;
switch (goto) {
case 'bangumi':
Utils.viewBangumi(epId: videoItem.param);
break;
case 'av':
String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid);
int cid = videoItem.cid;
if (cid == -1) {
cid = await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid);
}
Get.toNamed(
'/video?bvid=$bvid&cid=$cid',
arguments: {
// 'videoItem': videoItem,
'pic': videoItem.pic,
'heroTag': heroTag,
},
);
break;
// 动态
case 'picture':
try {
String dynamicType = 'picture';
String uri = videoItem.uri;
String id = '';
if (videoItem.uri.startsWith('bilibili://article/')) {
// https://www.bilibili.com/read/cv27063554
dynamicType = 'read';
RegExp regex = RegExp(r'\d+');
Match match = regex.firstMatch(videoItem.uri)!;
String matchedNumber = match.group(0)!;
videoItem.param = int.parse(matchedNumber);
id = 'cv${videoItem.param}';
}
if (uri.startsWith('http')) {
String path = Uri.parse(uri).path;
if (isStringNumeric(path.split('/')[1])) {
// 请求接口
var res =
await DynamicsHttp.dynamicDetail(id: path.split('/')[1]);
if (res['status']) {
Get.toNamed('/dynamicDetail', arguments: {
'item': res['data'],
'floor': 1,
'action': 'detail'
});
} else {
SmartDialog.showToast(res['msg']);
}
return;
}
}
Get.toNamed('/htmlRender', parameters: {
'url': uri,
'title': videoItem.title,
'id': id,
'dynamicType': dynamicType
});
} catch (err) {
SmartDialog.showToast(err.toString());
}
break;
default:
SmartDialog.showToast(videoItem.goto);
Utils.handleWebview(videoItem.uri);
}
}
@override
Widget build(BuildContext context) {
return Stack(children: [
Semantics(
label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true,
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
// for (var item in actions)
// CustomSemanticsAction(label: item.title): item.onTap!,
// },
child: Card(
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: InkWell(
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.id)),
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.pic,
),
child: Column(
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
),
if (videoItem.duration > 0)
PBadge(
bottom: 6,
right: 7,
size: 'small',
type: 'gray',
text: Utils.timeFormat(videoItem.duration),
// semanticsLabel:
// '时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}',
)
],
);
}),
),
videoContent(context)
],
),
),
),
),
if (videoItem.goto == 'av')
Positioned(
right: -5,
bottom: -2,
child: VideoPopupMenu(
size: 29,
iconSize: 17,
videoItem: videoItem,
onRemove: onRemove,
),
),
]);
}
Widget videoContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(6, 5, 6, 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Expanded(
child: Text(videoItem.title + "\n",
// semanticsLabel: "${videoItem.title}",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
height: 1.38,
)),
),
],
),
const Spacer(),
// const SizedBox(height: 2),
videoStat(context),
Row(
children: [
if (videoItem.goto == 'bangumi') ...[
PBadge(
text: videoItem.bangumiBadge,
stack: 'normal',
size: 'small',
type: 'line',
fs: 9,
),
const SizedBox(width: 2),
],
if (videoItem.rcmdReason != null) ...[
PBadge(
text: videoItem.rcmdReason,
stack: 'normal',
size: 'small',
type: 'color',
),
const SizedBox(width: 2),
],
if (videoItem.goto == 'picture') ...[
const PBadge(
text: '动态',
stack: 'normal',
size: 'small',
type: 'line',
fs: 9,
),
const SizedBox(width: 2),
],
if (videoItem.isFollowed == 1) ...[
const PBadge(
text: '已关注',
stack: 'normal',
size: 'small',
type: 'color',
),
const SizedBox(width: 2),
],
Expanded(
flex: 1,
child: Text(
videoItem.owner.name.toString(),
// semanticsLabel: "Up主${videoItem.owner.name}",
maxLines: 1,
overflow: TextOverflow.clip,
style: TextStyle(
height: 1.5,
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
),
if (videoItem.goto == 'av') const SizedBox(width: 10)
],
),
],
),
),
);
}
Widget videoStat(context) {
return Row(
children: [
statView(
context: context,
theme: 'gray',
view: videoItem.stat.view,
goto: videoItem.goto,
),
const SizedBox(width: 4),
if (videoItem.goto != 'picture')
statDanMu(
context: context,
theme: 'gray',
danmu: videoItem.stat.danmu,
),
if (videoItem is RecVideoItemModel) ...<Widget>[
const Spacer(),
Expanded(
flex: 0,
child: Text.rich(
maxLines: 1,
TextSpan(
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.8),
),
text:
Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
)),
const SizedBox(width: 2),
],
if (videoItem is RecVideoItemAppModel &&
videoItem.desc != null &&
videoItem.desc.contains(' · ')) ...<Widget>[
const Spacer(),
Expanded(
flex: 0,
child: Text.rich(
maxLines: 1,
TextSpan(
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.8),
),
text: Utils.shortenChineseDateString(
videoItem.desc.split(' · ').last)),
)),
const SizedBox(width: 2),
]
],
);
}
}

View File

@@ -1,296 +0,0 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/models/space/item.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'badge.dart';
import 'network_img_layer.dart';
// 视频卡片 - 垂直布局
class VideoCardVMemberHome extends StatelessWidget {
final Item videoItem;
const VideoCardVMemberHome({
super.key,
required this.videoItem,
});
void onPushDetail(heroTag) async {
String goto = videoItem.goto ?? '';
switch (goto) {
case 'bangumi':
Utils.viewBangumi(epId: videoItem.param);
break;
case 'av':
if (videoItem.isPgc == true && videoItem.uri?.isNotEmpty == true) {
if (Utils.viewPgcFromUri(videoItem.uri!)) {
return;
}
}
String bvid = videoItem.bvid ?? '';
Get.toNamed(
'/video?bvid=$bvid&cid=${videoItem.firstCid}',
arguments: {
// 'videoItem': videoItem,
'pic': videoItem.cover,
'heroTag': heroTag,
},
);
break;
// 动态
// case 'picture':
// try {
// String dynamicType = 'picture';
// String uri = videoItem.uri;
// String id = '';
// if (videoItem.uri.startsWith('bilibili://article/')) {
// // https://www.bilibili.com/read/cv27063554
// dynamicType = 'read';
// RegExp regex = RegExp(r'\d+');
// Match match = regex.firstMatch(videoItem.uri)!;
// String matchedNumber = match.group(0)!;
// videoItem.param = int.parse(matchedNumber);
// id = 'cv${videoItem.param}';
// }
// if (uri.startsWith('http')) {
// String path = Uri.parse(uri).path;
// if (isStringNumeric(path.split('/')[1])) {
// // 请求接口
// var res =
// await DynamicsHttp.dynamicDetail(id: path.split('/')[1]);
// if (res['status']) {
// Get.toNamed('/dynamicDetail', arguments: {
// 'item': res['data'],
// 'floor': 1,
// 'action': 'detail'
// });
// } else {
// SmartDialog.showToast(res['msg']);
// }
// return;
// }
// }
// Get.toNamed('/htmlRender', parameters: {
// 'url': uri,
// 'title': videoItem.title,
// 'id': id,
// 'dynamicType': dynamicType
// });
// } catch (err) {
// SmartDialog.showToast(err.toString());
// }
// break;
default:
SmartDialog.showToast(goto);
Utils.handleWebview(videoItem.uri ?? '');
}
}
@override
Widget build(BuildContext context) {
// List<VideoCustomAction> actions =
// VideoCustomActions(videoItem, context).actions;
return Stack(children: [
Semantics(
// label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true,
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
// for (var item in actions)
// CustomSemanticsAction(label: item.title): item.onTap!,
// },
child: Card(
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: InkWell(
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.bvid)),
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.cover,
),
child: Column(
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
NetworkImgLayer(
src: videoItem.cover,
width: maxWidth,
height: maxHeight,
),
if ((videoItem.duration ?? -1) > 0)
PBadge(
bottom: 6,
right: 7,
size: 'small',
type: 'gray',
text: Utils.timeFormat(videoItem.duration),
// semanticsLabel:
// '时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}',
)
],
);
}),
),
videoContent(context, videoItem)
],
),
),
),
),
// if (videoItem.goto == 'av')
// Positioned(
// right: -5,
// bottom: -2,
// child: VideoPopupMenu(
// size: 29,
// iconSize: 17,
// actions: actions,
// )),
]);
}
}
Widget videoContent(BuildContext context, Item videoItem) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(6, 5, 6, 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Expanded(
child: Text('${videoItem.title}\n',
// semanticsLabel: "${videoItem.title}",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
height: 1.38,
)),
),
],
),
// const Spacer(),
// const SizedBox(height: 2),
// VideoStat(
// videoItem: videoItem,
// ),
// Row(
// children: [
// if (videoItem.goto == 'bangumi') ...[
// PBadge(
// text: videoItem.bangumiBadge,
// stack: 'normal',
// size: 'small',
// type: 'line',
// fs: 9,
// )
// ],
// if (videoItem.rcmdReason != null) ...[
// PBadge(
// text: videoItem.rcmdReason,
// stack: 'normal',
// size: 'small',
// type: 'color',
// )
// ],
// if (videoItem.goto == 'picture') ...[
// const PBadge(
// text: '动态',
// stack: 'normal',
// size: 'small',
// type: 'line',
// fs: 9,
// )
// ],
// if (videoItem.isFollowed == 1) ...[
// const PBadge(
// text: '已关注',
// stack: 'normal',
// size: 'small',
// type: 'color',
// )
// ],
// Expanded(
// flex: 1,
// child: Text(
// videoItem.author ?? '',
// // semanticsLabel: "Up主${videoItem.owner.name}",
// maxLines: 1,
// overflow: TextOverflow.clip,
// style: TextStyle(
// height: 1.5,
// fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
// color: Theme.of(context).colorScheme.outline,
// ),
// ),
// ),
// if (videoItem.goto == 'av') const SizedBox(width: 10)
// ],
// ),
],
),
),
);
}
// Widget videoStat(BuildContext context, Item videoItem) {
// return Row(
// children: [
// StatView(
// theme: 'gray',
// view: videoItem.stat.view,
// goto: videoItem.goto,
// ),
// const SizedBox(width: 6),
// if (videoItem.goto != 'picture')
// StatDanMu(
// theme: 'gray',
// danmu: videoItem.stat.danmu,
// ),
// if (videoItem is RecVideoItemModel) ...<Widget>[
// const Spacer(),
// Expanded(
// flex: 0,
// child: RichText(
// maxLines: 1,
// text: TextSpan(
// style: TextStyle(
// fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
// color:
// Theme.of(context).colorScheme.outline.withOpacity(0.8),
// ),
// text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
// )),
// const SizedBox(width: 2),
// ],
// if (videoItem is RecVideoItemAppModel &&
// videoItem.desc != null &&
// videoItem.desc.contains(' · ')) ...<Widget>[
// const Spacer(),
// Expanded(
// flex: 0,
// child: RichText(
// maxLines: 1,
// text: TextSpan(
// style: TextStyle(
// fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
// color:
// Theme.of(context).colorScheme.outline.withOpacity(0.8),
// ),
// text: Utils.shortenChineseDateString(
// videoItem.desc.split(' · ').last)),
// )),
// const SizedBox(width: 2),
// ]
// ],
// );
// }

View File

@@ -1,17 +1,18 @@
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/common/account_type.dart';
import 'package:PiliPlus/models/home/rcmd/result.dart';
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import '../../http/user.dart';
import '../../http/video.dart';
import '../../models/home/rcmd/result.dart';
import '../../pages/mine/controller.dart';
import '../../utils/storage.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
class VideoCustomAction {
String title;
String value;
@@ -21,25 +22,26 @@ class VideoCustomAction {
}
class VideoCustomActions {
dynamic videoItem;
BaseSimpleVideoItemModel videoItem;
BuildContext context;
late List<VideoCustomAction> actions;
VoidCallback? onRemove;
VideoCustomActions(this.videoItem, this.context, [this.onRemove]) {
actions = [
if ((videoItem.bvid as String?)?.isNotEmpty == true) ...[
if (videoItem.bvid?.isNotEmpty == true) ...[
VideoCustomAction(
videoItem.bvid,
videoItem.bvid!,
'copy',
Stack(
clipBehavior: Clip.none,
children: [
Icon(MdiIcons.identifier, size: 16),
Icon(MdiIcons.circleOutline, size: 16),
],
),
() {
Utils.copyText(videoItem.bvid);
Utils.copyText(videoItem.bvid!);
},
),
VideoCustomAction(
@@ -52,240 +54,234 @@ class VideoCustomActions {
},
),
],
if (videoItem is! Item)
if (videoItem is! SpaceArchiveItem)
VideoCustomAction(
'访问:${videoItem.owner.name}',
'visit',
Icon(MdiIcons.accountCircleOutline, size: 16),
() async {
() {
Get.toNamed('/member?mid=${videoItem.owner.mid}', arguments: {
// 'face': videoItem.owner.face,
'heroTag': '${videoItem.owner.mid}',
});
},
),
if (videoItem is! Item)
if (videoItem is! SpaceArchiveItem)
VideoCustomAction(
'不感兴趣', 'dislike', Icon(MdiIcons.thumbDownOutline, size: 16),
() async {
String? accessKey = GStorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
if (accessKey == null || accessKey == "") {
SmartDialog.showToast("请退出账号后重新登录");
return;
}
if (videoItem is RecVideoItemAppModel) {
RecVideoItemAppModel v = videoItem as RecVideoItemAppModel;
ThreePoint? tp = v.threePoint;
if (tp == null) {
SmartDialog.showToast("未能获取threePoint");
'不感兴趣',
'dislike',
Icon(MdiIcons.thumbDownOutline, size: 16),
() {
String? accessKey = Accounts.get(AccountType.recommend).accessKey;
if (accessKey == null || accessKey == "") {
SmartDialog.showToast("请退出账号后重新登录");
return;
}
if (tp.dislikeReasons == null && tp.feedbacks == null) {
SmartDialog.showToast("未能获取dislikeReasons或feedbacks");
return;
}
Widget actionButton(DislikeReason? r, FeedbackReason? f) {
return SearchText(
text: r?.name ?? f?.name ?? '未知',
onTap: (_) async {
Get.back();
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.feedDislike(
reasonId: r?.id,
feedbackId: f?.id,
id: v.param!,
goto: v.goto!,
);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? (r?.toast ?? f?.toast) : res['msg'],
);
if (res['status']) {
onRemove?.call();
}
},
);
}
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (tp.dislikeReasons != null) ...[
Text('我不想看'),
const SizedBox(height: 5),
Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: tp.dislikeReasons!.map((item) {
return actionButton(item, null);
}).toList(),
),
],
if (tp.feedbacks != null) ...[
const SizedBox(height: 5),
Text('反馈'),
const SizedBox(height: 5),
Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: tp.feedbacks!.map((item) {
return actionButton(null, item);
}).toList(),
),
],
const Divider(),
Center(
child: FilledButton.tonal(
onPressed: () async {
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.feedDislikeCancel(
// reasonId: r?.id,
// feedbackId: f?.id,
id: v.param!,
goto: v.goto!,
);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? "成功" : res['msg']);
Get.back();
},
style: FilledButton.styleFrom(
visualDensity: VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: const Text("撤销"),
),
),
],
),
),
if (videoItem is RecVideoItemAppModel) {
RecVideoItemAppModel v = videoItem as RecVideoItemAppModel;
ThreePoint? tp = v.threePoint;
if (tp == null) {
SmartDialog.showToast("未能获取threePoint");
return;
}
if (tp.dislikeReasons == null && tp.feedbacks == null) {
SmartDialog.showToast("未能获取dislikeReasons或feedbacks");
return;
}
Widget actionButton(Reason? r, Reason? f) {
return SearchText(
text: r?.name ?? f?.name ?? '未知',
onTap: (_) async {
Get.back();
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.feedDislike(
reasonId: r?.id,
feedbackId: f?.id,
id: v.param!,
goto: v.goto!,
);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? (r?.toast ?? f?.toast) : res['msg'],
);
if (res['status']) {
onRemove?.call();
}
},
);
},
);
} else {
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 5),
const Text("web端暂不支持精细选择"),
const SizedBox(height: 5),
Wrap(
spacing: 5.0,
runSpacing: 2.0,
children: [
FilledButton.tonal(
onPressed: () async {
Get.back();
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.dislikeVideo(
bvid: videoItem.bvid as String, type: true);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? "点踩成功" : res['msg'],
);
if (res['status']) {
onRemove?.call();
}
},
style: FilledButton.styleFrom(
visualDensity: VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: const Text("点踩"),
}
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (tp.dislikeReasons != null) ...[
const Text('我不想看'),
const SizedBox(height: 5),
Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: tp.dislikeReasons!.map((item) {
return actionButton(item, null);
}).toList(),
),
FilledButton.tonal(
],
if (tp.feedbacks != null) ...[
const SizedBox(height: 5),
const Text('反馈'),
const SizedBox(height: 5),
Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: tp.feedbacks!.map((item) {
return actionButton(null, item);
}).toList(),
),
],
const Divider(),
Center(
child: FilledButton.tonal(
onPressed: () async {
Get.back();
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.dislikeVideo(
bvid: videoItem.bvid as String,
type: false);
var res = await VideoHttp.feedDislikeCancel(
id: v.param!,
goto: v.goto!,
);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? "取消踩" : res['msg']);
res['status'] ? "成功" : res['msg']);
Get.back();
},
style: FilledButton.styleFrom(
visualDensity: VisualDensity(
horizontal: -2,
vertical: -2,
),
visualDensity: VisualDensity.compact,
),
child: const Text("撤销"),
),
],
)
],
),
],
),
),
),
);
},
);
} else {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 5),
const Text("web端暂不支持精细选择"),
const SizedBox(height: 5),
Wrap(
spacing: 5.0,
runSpacing: 2.0,
children: [
FilledButton.tonal(
onPressed: () async {
Get.back();
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.dislikeVideo(
bvid: videoItem.bvid as String,
type: true);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? "点踩成功" : res['msg'],
);
if (res['status']) {
onRemove?.call();
}
},
style: FilledButton.styleFrom(
visualDensity: VisualDensity.compact,
),
child: const Text("点踩"),
),
FilledButton.tonal(
onPressed: () async {
Get.back();
SmartDialog.showLoading(msg: '正在提交');
var res = await VideoHttp.dislikeVideo(
bvid: videoItem.bvid as String,
type: false);
SmartDialog.dismiss();
SmartDialog.showToast(
res['status'] ? "取消踩" : res['msg']);
},
style: FilledButton.styleFrom(
visualDensity: VisualDensity.compact,
),
child: const Text("撤销"),
),
],
)
],
),
),
);
},
);
}
},
),
if (videoItem is! SpaceArchiveItem)
VideoCustomAction(
'拉黑:${videoItem.owner.name}',
'block',
Icon(MdiIcons.cancel, size: 16),
() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('提示'),
content: Text(
'确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?'
'\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'点错了',
style: TextStyle(
color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
var res = await VideoHttp.relationMod(
mid: videoItem.owner.mid!,
act: 5,
reSrc: 11,
);
GStorage.setBlackMid(videoItem.owner.mid!);
Get.back();
SmartDialog.showToast(res['msg'] ?? '成功');
},
child: const Text('确认'),
)
],
);
},
);
}
}),
if (videoItem is! Item)
VideoCustomAction('拉黑:${videoItem.owner.name}', 'block',
Icon(MdiIcons.cancel, size: 16), () async {
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('提示'),
content:
Text('确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?'
'\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'点错了',
style: TextStyle(
color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
var res = await VideoHttp.relationMod(
mid: videoItem.owner.mid,
act: 5,
reSrc: 11,
);
List<int> blackMidsList = GStorage.blackMidsList;
blackMidsList.insert(0, videoItem.owner.mid);
GStorage.setBlackMidsList(blackMidsList);
Get.back();
SmartDialog.showToast(res['msg'] ?? '成功');
},
child: const Text('确认'),
)
],
);
},
);
}),
},
),
VideoCustomAction(
"${MineController.anonymity.value ? '退出' : '进入'}无痕模式",
'anonymity',
Icon(
MineController.anonymity.value
? MdiIcons.incognitoOff
: MdiIcons.incognito,
size: 16,
),
() => MineController.onChangeAnonymity(context))
"${MineController.anonymity.value ? '退出' : '进入'}无痕模式",
'anonymity',
Icon(
MineController.anonymity.value
? MdiIcons.incognitoOff
: MdiIcons.incognito,
size: 16,
),
() => MineController.onChangeAnonymity(context),
)
];
}
}
@@ -293,7 +289,7 @@ class VideoCustomActions {
class VideoPopupMenu extends StatelessWidget {
final double? size;
final double? iconSize;
final double menuItemHeight = 45;
final double menuItemHeight;
final dynamic videoItem;
final VoidCallback? onRemove;
@@ -303,6 +299,7 @@ class VideoPopupMenu extends StatelessWidget {
required this.iconSize,
required this.videoItem,
this.onRemove,
this.menuItemHeight = 45,
});
@override

View File

@@ -1,11 +0,0 @@
//
// Generated code. Do not modify.
// source: bilibili/app/archive/middleware/v1/preload.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View File

@@ -1,471 +0,0 @@
//
// Generated code. Do not modify.
// source: bilibili/app/card/v1/ad.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:fixnum/fixnum.dart' as $fixnum;
import 'package:protobuf/protobuf.dart' as $pb;
class AdInfo extends $pb.GeneratedMessage {
factory AdInfo({
$fixnum.Int64? creativeId,
$core.int? creativeType,
$core.int? cardType,
CreativeContent? creativeContent,
$core.String? adCb,
$fixnum.Int64? resource,
$core.int? source,
$core.String? requestId,
$core.bool? isAd,
$fixnum.Int64? cmMark,
$core.int? index,
$core.bool? isAdLoc,
$core.int? cardIndex,
$core.String? clientIp,
$core.List<$core.int>? extra,
$core.int? creativeStyle,
}) {
final $result = create();
if (creativeId != null) {
$result.creativeId = creativeId;
}
if (creativeType != null) {
$result.creativeType = creativeType;
}
if (cardType != null) {
$result.cardType = cardType;
}
if (creativeContent != null) {
$result.creativeContent = creativeContent;
}
if (adCb != null) {
$result.adCb = adCb;
}
if (resource != null) {
$result.resource = resource;
}
if (source != null) {
$result.source = source;
}
if (requestId != null) {
$result.requestId = requestId;
}
if (isAd != null) {
$result.isAd = isAd;
}
if (cmMark != null) {
$result.cmMark = cmMark;
}
if (index != null) {
$result.index = index;
}
if (isAdLoc != null) {
$result.isAdLoc = isAdLoc;
}
if (cardIndex != null) {
$result.cardIndex = cardIndex;
}
if (clientIp != null) {
$result.clientIp = clientIp;
}
if (extra != null) {
$result.extra = extra;
}
if (creativeStyle != null) {
$result.creativeStyle = creativeStyle;
}
return $result;
}
AdInfo._() : super();
factory AdInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory AdInfo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AdInfo', package: const $pb.PackageName(_omitMessageNames ? '' : 'bilibili.app.card.v1'), createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'creativeId')
..a<$core.int>(2, _omitFieldNames ? '' : 'creativeType', $pb.PbFieldType.O3)
..a<$core.int>(3, _omitFieldNames ? '' : 'cardType', $pb.PbFieldType.O3)
..aOM<CreativeContent>(4, _omitFieldNames ? '' : 'creativeContent', subBuilder: CreativeContent.create)
..aOS(5, _omitFieldNames ? '' : 'adCb')
..aInt64(6, _omitFieldNames ? '' : 'resource')
..a<$core.int>(7, _omitFieldNames ? '' : 'source', $pb.PbFieldType.O3)
..aOS(8, _omitFieldNames ? '' : 'requestId')
..aOB(9, _omitFieldNames ? '' : 'isAd')
..aInt64(10, _omitFieldNames ? '' : 'cmMark')
..a<$core.int>(11, _omitFieldNames ? '' : 'index', $pb.PbFieldType.O3)
..aOB(12, _omitFieldNames ? '' : 'isAdLoc')
..a<$core.int>(13, _omitFieldNames ? '' : 'cardIndex', $pb.PbFieldType.O3)
..aOS(14, _omitFieldNames ? '' : 'clientIp')
..a<$core.List<$core.int>>(15, _omitFieldNames ? '' : 'extra', $pb.PbFieldType.OY)
..a<$core.int>(16, _omitFieldNames ? '' : 'creativeStyle', $pb.PbFieldType.O3)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
AdInfo clone() => AdInfo()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
AdInfo copyWith(void Function(AdInfo) updates) => super.copyWith((message) => updates(message as AdInfo)) as AdInfo;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static AdInfo create() => AdInfo._();
AdInfo createEmptyInstance() => create();
static $pb.PbList<AdInfo> createRepeated() => $pb.PbList<AdInfo>();
@$core.pragma('dart2js:noInline')
static AdInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AdInfo>(create);
static AdInfo? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get creativeId => $_getI64(0);
@$pb.TagNumber(1)
set creativeId($fixnum.Int64 v) { $_setInt64(0, v); }
@$pb.TagNumber(1)
$core.bool hasCreativeId() => $_has(0);
@$pb.TagNumber(1)
void clearCreativeId() => clearField(1);
@$pb.TagNumber(2)
$core.int get creativeType => $_getIZ(1);
@$pb.TagNumber(2)
set creativeType($core.int v) { $_setSignedInt32(1, v); }
@$pb.TagNumber(2)
$core.bool hasCreativeType() => $_has(1);
@$pb.TagNumber(2)
void clearCreativeType() => clearField(2);
@$pb.TagNumber(3)
$core.int get cardType => $_getIZ(2);
@$pb.TagNumber(3)
set cardType($core.int v) { $_setSignedInt32(2, v); }
@$pb.TagNumber(3)
$core.bool hasCardType() => $_has(2);
@$pb.TagNumber(3)
void clearCardType() => clearField(3);
@$pb.TagNumber(4)
CreativeContent get creativeContent => $_getN(3);
@$pb.TagNumber(4)
set creativeContent(CreativeContent v) { setField(4, v); }
@$pb.TagNumber(4)
$core.bool hasCreativeContent() => $_has(3);
@$pb.TagNumber(4)
void clearCreativeContent() => clearField(4);
@$pb.TagNumber(4)
CreativeContent ensureCreativeContent() => $_ensure(3);
@$pb.TagNumber(5)
$core.String get adCb => $_getSZ(4);
@$pb.TagNumber(5)
set adCb($core.String v) { $_setString(4, v); }
@$pb.TagNumber(5)
$core.bool hasAdCb() => $_has(4);
@$pb.TagNumber(5)
void clearAdCb() => clearField(5);
@$pb.TagNumber(6)
$fixnum.Int64 get resource => $_getI64(5);
@$pb.TagNumber(6)
set resource($fixnum.Int64 v) { $_setInt64(5, v); }
@$pb.TagNumber(6)
$core.bool hasResource() => $_has(5);
@$pb.TagNumber(6)
void clearResource() => clearField(6);
@$pb.TagNumber(7)
$core.int get source => $_getIZ(6);
@$pb.TagNumber(7)
set source($core.int v) { $_setSignedInt32(6, v); }
@$pb.TagNumber(7)
$core.bool hasSource() => $_has(6);
@$pb.TagNumber(7)
void clearSource() => clearField(7);
@$pb.TagNumber(8)
$core.String get requestId => $_getSZ(7);
@$pb.TagNumber(8)
set requestId($core.String v) { $_setString(7, v); }
@$pb.TagNumber(8)
$core.bool hasRequestId() => $_has(7);
@$pb.TagNumber(8)
void clearRequestId() => clearField(8);
@$pb.TagNumber(9)
$core.bool get isAd => $_getBF(8);
@$pb.TagNumber(9)
set isAd($core.bool v) { $_setBool(8, v); }
@$pb.TagNumber(9)
$core.bool hasIsAd() => $_has(8);
@$pb.TagNumber(9)
void clearIsAd() => clearField(9);
@$pb.TagNumber(10)
$fixnum.Int64 get cmMark => $_getI64(9);
@$pb.TagNumber(10)
set cmMark($fixnum.Int64 v) { $_setInt64(9, v); }
@$pb.TagNumber(10)
$core.bool hasCmMark() => $_has(9);
@$pb.TagNumber(10)
void clearCmMark() => clearField(10);
@$pb.TagNumber(11)
$core.int get index => $_getIZ(10);
@$pb.TagNumber(11)
set index($core.int v) { $_setSignedInt32(10, v); }
@$pb.TagNumber(11)
$core.bool hasIndex() => $_has(10);
@$pb.TagNumber(11)
void clearIndex() => clearField(11);
@$pb.TagNumber(12)
$core.bool get isAdLoc => $_getBF(11);
@$pb.TagNumber(12)
set isAdLoc($core.bool v) { $_setBool(11, v); }
@$pb.TagNumber(12)
$core.bool hasIsAdLoc() => $_has(11);
@$pb.TagNumber(12)
void clearIsAdLoc() => clearField(12);
@$pb.TagNumber(13)
$core.int get cardIndex => $_getIZ(12);
@$pb.TagNumber(13)
set cardIndex($core.int v) { $_setSignedInt32(12, v); }
@$pb.TagNumber(13)
$core.bool hasCardIndex() => $_has(12);
@$pb.TagNumber(13)
void clearCardIndex() => clearField(13);
@$pb.TagNumber(14)
$core.String get clientIp => $_getSZ(13);
@$pb.TagNumber(14)
set clientIp($core.String v) { $_setString(13, v); }
@$pb.TagNumber(14)
$core.bool hasClientIp() => $_has(13);
@$pb.TagNumber(14)
void clearClientIp() => clearField(14);
@$pb.TagNumber(15)
$core.List<$core.int> get extra => $_getN(14);
@$pb.TagNumber(15)
set extra($core.List<$core.int> v) { $_setBytes(14, v); }
@$pb.TagNumber(15)
$core.bool hasExtra() => $_has(14);
@$pb.TagNumber(15)
void clearExtra() => clearField(15);
@$pb.TagNumber(16)
$core.int get creativeStyle => $_getIZ(15);
@$pb.TagNumber(16)
set creativeStyle($core.int v) { $_setSignedInt32(15, v); }
@$pb.TagNumber(16)
$core.bool hasCreativeStyle() => $_has(15);
@$pb.TagNumber(16)
void clearCreativeStyle() => clearField(16);
}
class CreativeContent extends $pb.GeneratedMessage {
factory CreativeContent({
$core.String? title,
$core.String? description,
$fixnum.Int64? videoId,
$core.String? username,
$core.String? imageUrl,
$core.String? imageMd5,
$core.String? logUrl,
$core.String? logMd5,
$core.String? url,
$core.String? clickUrl,
$core.String? showUrl,
}) {
final $result = create();
if (title != null) {
$result.title = title;
}
if (description != null) {
$result.description = description;
}
if (videoId != null) {
$result.videoId = videoId;
}
if (username != null) {
$result.username = username;
}
if (imageUrl != null) {
$result.imageUrl = imageUrl;
}
if (imageMd5 != null) {
$result.imageMd5 = imageMd5;
}
if (logUrl != null) {
$result.logUrl = logUrl;
}
if (logMd5 != null) {
$result.logMd5 = logMd5;
}
if (url != null) {
$result.url = url;
}
if (clickUrl != null) {
$result.clickUrl = clickUrl;
}
if (showUrl != null) {
$result.showUrl = showUrl;
}
return $result;
}
CreativeContent._() : super();
factory CreativeContent.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory CreativeContent.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'CreativeContent', package: const $pb.PackageName(_omitMessageNames ? '' : 'bilibili.app.card.v1'), createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'title')
..aOS(2, _omitFieldNames ? '' : 'description')
..aInt64(3, _omitFieldNames ? '' : 'videoId')
..aOS(4, _omitFieldNames ? '' : 'username')
..aOS(5, _omitFieldNames ? '' : 'imageUrl')
..aOS(6, _omitFieldNames ? '' : 'imageMd5')
..aOS(7, _omitFieldNames ? '' : 'logUrl')
..aOS(8, _omitFieldNames ? '' : 'logMd5')
..aOS(9, _omitFieldNames ? '' : 'url')
..aOS(10, _omitFieldNames ? '' : 'clickUrl')
..aOS(11, _omitFieldNames ? '' : 'showUrl')
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
CreativeContent clone() => CreativeContent()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
CreativeContent copyWith(void Function(CreativeContent) updates) => super.copyWith((message) => updates(message as CreativeContent)) as CreativeContent;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static CreativeContent create() => CreativeContent._();
CreativeContent createEmptyInstance() => create();
static $pb.PbList<CreativeContent> createRepeated() => $pb.PbList<CreativeContent>();
@$core.pragma('dart2js:noInline')
static CreativeContent getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreativeContent>(create);
static CreativeContent? _defaultInstance;
@$pb.TagNumber(1)
$core.String get title => $_getSZ(0);
@$pb.TagNumber(1)
set title($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasTitle() => $_has(0);
@$pb.TagNumber(1)
void clearTitle() => clearField(1);
@$pb.TagNumber(2)
$core.String get description => $_getSZ(1);
@$pb.TagNumber(2)
set description($core.String v) { $_setString(1, v); }
@$pb.TagNumber(2)
$core.bool hasDescription() => $_has(1);
@$pb.TagNumber(2)
void clearDescription() => clearField(2);
@$pb.TagNumber(3)
$fixnum.Int64 get videoId => $_getI64(2);
@$pb.TagNumber(3)
set videoId($fixnum.Int64 v) { $_setInt64(2, v); }
@$pb.TagNumber(3)
$core.bool hasVideoId() => $_has(2);
@$pb.TagNumber(3)
void clearVideoId() => clearField(3);
@$pb.TagNumber(4)
$core.String get username => $_getSZ(3);
@$pb.TagNumber(4)
set username($core.String v) { $_setString(3, v); }
@$pb.TagNumber(4)
$core.bool hasUsername() => $_has(3);
@$pb.TagNumber(4)
void clearUsername() => clearField(4);
@$pb.TagNumber(5)
$core.String get imageUrl => $_getSZ(4);
@$pb.TagNumber(5)
set imageUrl($core.String v) { $_setString(4, v); }
@$pb.TagNumber(5)
$core.bool hasImageUrl() => $_has(4);
@$pb.TagNumber(5)
void clearImageUrl() => clearField(5);
@$pb.TagNumber(6)
$core.String get imageMd5 => $_getSZ(5);
@$pb.TagNumber(6)
set imageMd5($core.String v) { $_setString(5, v); }
@$pb.TagNumber(6)
$core.bool hasImageMd5() => $_has(5);
@$pb.TagNumber(6)
void clearImageMd5() => clearField(6);
@$pb.TagNumber(7)
$core.String get logUrl => $_getSZ(6);
@$pb.TagNumber(7)
set logUrl($core.String v) { $_setString(6, v); }
@$pb.TagNumber(7)
$core.bool hasLogUrl() => $_has(6);
@$pb.TagNumber(7)
void clearLogUrl() => clearField(7);
@$pb.TagNumber(8)
$core.String get logMd5 => $_getSZ(7);
@$pb.TagNumber(8)
set logMd5($core.String v) { $_setString(7, v); }
@$pb.TagNumber(8)
$core.bool hasLogMd5() => $_has(7);
@$pb.TagNumber(8)
void clearLogMd5() => clearField(8);
@$pb.TagNumber(9)
$core.String get url => $_getSZ(8);
@$pb.TagNumber(9)
set url($core.String v) { $_setString(8, v); }
@$pb.TagNumber(9)
$core.bool hasUrl() => $_has(8);
@$pb.TagNumber(9)
void clearUrl() => clearField(9);
@$pb.TagNumber(10)
$core.String get clickUrl => $_getSZ(9);
@$pb.TagNumber(10)
set clickUrl($core.String v) { $_setString(9, v); }
@$pb.TagNumber(10)
$core.bool hasClickUrl() => $_has(9);
@$pb.TagNumber(10)
void clearClickUrl() => clearField(10);
@$pb.TagNumber(11)
$core.String get showUrl => $_getSZ(10);
@$pb.TagNumber(11)
set showUrl($core.String v) { $_setString(10, v); }
@$pb.TagNumber(11)
$core.bool hasShowUrl() => $_has(10);
@$pb.TagNumber(11)
void clearShowUrl() => clearField(11);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');

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