Compare commits

..

517 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
ad4fba4f44 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-28 14:24:00 +08:00
bggRGjQaUbCoE
6092bab75c tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-28 14:17:21 +08:00
bggRGjQaUbCoE
365d9e1223 add android launcher
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-27 16:27:28 +08:00
bggRGjQaUbCoE
9c3b2717ac opt add reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-27 14:15:33 +08:00
bggRGjQaUbCoE
8b6320730c opt del reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-27 14:13:14 +08:00
bggRGjQaUbCoE
c34eeba859 fix get live dm token
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-27 11:39:24 +08:00
bggRGjQaUbCoE
d6914c42b3 opt create dyn panel drag
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-27 11:11:57 +08:00
bggRGjQaUbCoE
39778247f6 opt dyn repost
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-26 22:34:52 +08:00
bggRGjQaUbCoE
1d91b183fd opt custom widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-26 22:01:53 +08:00
bggRGjQaUbCoE
b2a4875ba7 opt topic scroll
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-26 21:35:17 +08:00
bggRGjQaUbCoE
077b31e4c9 opt topic scroll
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-26 21:22:48 +08:00
bggRGjQaUbCoE
dbcc19cac1 opt search topic
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-26 21:11:07 +08:00
bggRGjQaUbCoE
83de915e54 fix topic initialScrollOffset
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-26 18:44:53 +08:00
bggRGjQaUbCoE
8ce33736a0 opt select topic
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-26 18:41:42 +08:00
bggRGjQaUbCoE
3edac65ae8 opt pub panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-26 17:15:21 +08:00
My-Responsitories
db3b74e33f Revert "feat: cross row select (#867)" (#868)
This reverts commit 89a077be5c.
2025-05-25 13:02:44 +00:00
My-Responsitories
89a077be5c feat: cross row select (#867) 2025-05-25 11:59:56 +00:00
bggRGjQaUbCoE
76a5b6221d opt msg
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-25 16:16:17 +08:00
bggRGjQaUbCoE
18f8831b7e count format
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-25 15:54:08 +08:00
bggRGjQaUbCoE
b674d102e3 pgc review sort
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-25 15:38:22 +08:00
bggRGjQaUbCoE
86e52eec4c opt slider
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-25 15:38:22 +08:00
bggRGjQaUbCoE
fd55383778 opt handle res
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-25 11:45:20 +08:00
My-Responsitories
f29385ccef mod: isRedirect (#866) 2025-05-24 16:19:36 +00:00
bggRGjQaUbCoE
3993ff8a8e feat: slide dismiss tabbarview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 22:30:53 +08:00
bggRGjQaUbCoE
a130b5db98 opt pgc review
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 20:14:16 +08:00
bggRGjQaUbCoE
2d22501d08 opt pgc review input
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 18:51:36 +08:00
bggRGjQaUbCoE
b478427522 opt live count
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 18:43:19 +08:00
bggRGjQaUbCoE
70164fa3f7 feat: pgc review
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 18:39:04 +08:00
bggRGjQaUbCoE
8e1b2be073 opt repost dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 14:14:36 +08:00
bggRGjQaUbCoE
b6b67884f4 opt dyn auth
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 13:23:00 +08:00
bggRGjQaUbCoE
fe97a485c7 opt dyn panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 12:42:54 +08:00
bggRGjQaUbCoE
86c64fdd05 opt dyn panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 12:24:04 +08:00
bggRGjQaUbCoE
da56c66168 opt msg item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 11:10:57 +08:00
bggRGjQaUbCoE
5bd6b38908 opt img preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 11:10:57 +08:00
bggRGjQaUbCoE
81cfe3efe1 opt anim to top
opt refresh

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-24 11:09:48 +08:00
bggRGjQaUbCoE
0a9897f6a4 opt pub img panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-23 18:09:55 +08:00
bggRGjQaUbCoE
0b495f100f upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-23 17:57:35 +08:00
bggRGjQaUbCoE
70b55e5fdd opt article page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-23 15:30:07 +08:00
bggRGjQaUbCoE
9c2f3d3f86 opt slider color picker
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-23 13:53:17 +08:00
bggRGjQaUbCoE
5452b3de4f opt slider color picker
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-23 12:31:50 +08:00
bggRGjQaUbCoE
b1666095a6 enable new slider/progress
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-23 12:00:14 +08:00
bggRGjQaUbCoE
7fa6d81dc8 opt search trending header
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-22 21:07:25 +08:00
bggRGjQaUbCoE
04a10e62d6 change msg badge pos
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-22 20:31:40 +08:00
bggRGjQaUbCoE
ecce23589a dyn sub card
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-22 20:21:27 +08:00
bggRGjQaUbCoE
b6aa6aebb9 dyn addcard
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-22 17:01:15 +08:00
bggRGjQaUbCoE
4bd4178cbf revert navbar temply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-22 11:47:19 +08:00
bggRGjQaUbCoE
04a157c64a bump flutter
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-21 16:55:50 +08:00
bggRGjQaUbCoE
ac60ac417b tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-21 16:55:44 +08:00
My-Responsitories
1efd62803a opt: replace SizedBox with spacing (#863) 2025-05-20 18:16:01 +00:00
My-Responsitories
218e829fd4 opt: bar set (#862)
* opt: bar set

* opt: navbar

* fix: type
2025-05-20 18:14:08 +00:00
My-Responsitories
acb3784071 opt: up panel (#861) 2025-05-20 18:11:31 +00:00
bggRGjQaUbCoE
f87957b170 opt dyn author panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-20 23:36:35 +08:00
bggRGjQaUbCoE
043310ca00 opt dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-20 23:25:27 +08:00
bggRGjQaUbCoE
43d71bb368 opt dyn detail
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-20 22:44:40 +08:00
bggRGjQaUbCoE
12eb430d8c deprecate replytype
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-20 21:54:22 +08:00
bggRGjQaUbCoE
cfb42075dc fix push dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-20 21:31:43 +08:00
bggRGjQaUbCoE
9b5457ffc0 opt icon
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-20 17:15:45 +08:00
bggRGjQaUbCoE
3099bd6ca1 tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-20 16:33:08 +08:00
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
1030 changed files with 209639 additions and 120708 deletions

View File

@@ -11,9 +11,18 @@ body:
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:

View File

@@ -11,6 +11,8 @@ body:
options:
- label: 之前没有人提交过类似或相同的功能请求。
required: true
- label: 无视上一条 => block
required: true
- label: 正在使用最新版本。
required: true

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,9 +47,24 @@
## 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
- [x] 多账号支持 by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] 屏蔽带货动态/评论
- [x] 互动视频
- [x] 发评/动态反诈
@@ -74,7 +89,6 @@
- [x] 评论楼中楼定位点击查看的评论
- [x] 评论楼中楼按热度/时间排序
- [x] 评论点踩
- [x] 显示ops专栏
- [x] 私信发图
- [x] 投币动画
- [x] 取消/追番,更新追番状态
@@ -90,7 +104,7 @@
- [x] 合集图片
- [x] 删除/置顶/撤回私信
- [x] 举报用户/评论/视频/动态
- [x] 删除/发布文本/图片动态
- [x] 删除/发布/置顶文本/图片动态
- [x] 其他
## opt
@@ -159,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"
@@ -172,7 +175,7 @@
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
android:theme="@style/Ucrop.CropTheme"/>
<receiver
android:name="com.ryanheise.audioservice.MediaButtonReceiver"

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

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

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Ucrop.CropTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>

View File

@@ -21,4 +21,6 @@
<item name="android:windowBackground">?android:colorBackground</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
</style>
<style name="Ucrop.CropTheme" parent="Theme.AppCompat.Light.NoActionBar"/>
</resources>

View File

@@ -1,22 +1,33 @@
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) {
if (pluginCompileSdk < 31) {
@@ -34,18 +45,6 @@ subprojects {
compileSdk 31
}
}
if (pluginCompileSdk > 34) {
project.logger.error(
"Warning: Overriding compileSdk version in Flutter plugin: "
+ project.name
+ " from "
+ pluginCompileSdk
+ " to 34"
)
project.android {
compileSdk 34
}
}
}
}
}

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.

Before

Width:  |  Height:  |  Size: 45 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;
}
@@ -26,31 +26,37 @@ class Constants {
'{"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 get goodsUrlPrefix => "https://gaoneng.bilibili.com/tetris";
static const goodsUrlPrefix = "https://gaoneng.bilibili.com/tetris";
// 超分辨率滤镜
static List<String> get mpvAnime4KShaders => [
'Anime4K_Clamp_Highlights.glsl',
'Anime4K_Restore_CNN_VL.glsl',
'Anime4K_Upscale_CNN_x2_VL.glsl',
'Anime4K_AutoDownscalePre_x2.glsl',
'Anime4K_AutoDownscalePre_x4.glsl',
'Anime4K_Upscale_CNN_x2_M.glsl'
];
static const List<String> mpvAnime4KShaders = [
'Anime4K_Clamp_Highlights.glsl',
'Anime4K_Restore_CNN_VL.glsl',
'Anime4K_Upscale_CNN_x2_VL.glsl',
'Anime4K_AutoDownscalePre_x2.glsl',
'Anime4K_AutoDownscalePre_x4.glsl',
'Anime4K_Upscale_CNN_x2_M.glsl'
];
// 超分辨率滤镜 (轻量)
static List<String> get mpvAnime4KShadersLite => [
'Anime4K_Clamp_Highlights.glsl',
'Anime4K_Restore_CNN_M.glsl',
'Anime4K_Restore_CNN_S.glsl',
'Anime4K_Upscale_CNN_x2_M.glsl',
'Anime4K_AutoDownscalePre_x2.glsl',
'Anime4K_AutoDownscalePre_x4.glsl',
'Anime4K_Upscale_CNN_x2_S.glsl'
];
static const mpvAnime4KShadersLite = [
'Anime4K_Clamp_Highlights.glsl',
'Anime4K_Restore_CNN_M.glsl',
'Anime4K_Restore_CNN_S.glsl',
'Anime4K_Upscale_CNN_x2_M.glsl',
'Anime4K_AutoDownscalePre_x2.glsl',
'Anime4K_AutoDownscalePre_x4.glsl',
'Anime4K_Upscale_CNN_x2_S.glsl'
];
//内容来自 https://passport.bilibili.com/web/generic/country/list
static List<Map<String, dynamic>> get internationalDialingPrefix => [

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,79 +0,0 @@
import 'package:PiliPlus/common/widgets/no_splash_factory.dart';
import 'package:PiliPlus/common/widgets/overlay_pop.dart';
import 'package:PiliPlus/models/model_video.dart';
import 'package:flutter/material.dart';
class AnimatedDialog extends StatefulWidget {
const AnimatedDialog({
super.key,
required this.videoItem,
required this.closeFn,
});
final BaseVideoItemModel 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(listener);
controller.forward();
}
void listener() {
setState(() {});
}
@override
void dispose() {
controller.removeListener(listener);
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,71 +1,85 @@
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 double? textScaleFactor;
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;
} else if (type == 'color') {
bgColor = t.secondaryContainer.withOpacity(0.5);
color = t.onSecondaryContainer;
} else if (type == 'line') {
bgColor = Colors.transparent;
color = t.primary;
borderColor = t.primary;
} else if (type == 'error') {
bgColor = t.error;
color = t.onError;
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;
}
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: padding ?? paddingStyle,
@@ -75,26 +89,25 @@ class PBadge extends StatelessWidget {
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.zero,
backgroundColor:
bgColor ?? Theme.of(context).colorScheme.secondaryContainer,
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 IconData coin = _CustomIconData(0xe800);
static const IconData dm_off = _CustomIconData(0xe801);
static const IconData dm_on = _CustomIconData(0xe802);
static const IconData dm_settings = _CustomIconData(0xe803);
static const IconData dyn = _CustomIconData(0xe804);
static const IconData fav = _CustomIconData(0xe805);
static const IconData share = _CustomIconData(0xe806);
static const IconData share_line = _CustomIconData(0xe807);
static const IconData share_node = _CustomIconData(0xe808);
static const IconData star_favorite_line = _CustomIconData(0xe809);
static const IconData star_favorite_solid = _CustomIconData(0xe80a);
static const IconData thumbs_down = _CustomIconData(0xe80b);
static const IconData thumbs_down_outline = _CustomIconData(0xe80c);
static const IconData thumbs_up = _CustomIconData(0xe80d);
static const IconData thumbs_up_fill = _CustomIconData(0xe80e);
static const IconData thumbs_up_line = _CustomIconData(0xe80f);
static const IconData thumbs_up_outline = _CustomIconData(0xe810);
static const IconData topic_tag = _CustomIconData(0xe811);
static const IconData watch_later = _CustomIconData(0xe812);
}
class _CustomIconData extends IconData {
const _CustomIconData(super.codePoint) : super(fontFamily: 'custom_icon');
}

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,17 +16,15 @@ 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,
),
),
);
@@ -40,26 +39,25 @@ class LoadingWidget extends StatelessWidget {
@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.of(context).dialogBackgroundColor,
borderRadius: BorderRadius.circular(15),
color: theme.dialogBackgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(15)),
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
//loading animation
CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation(
Theme.of(context).colorScheme.onSurfaceVariant),
valueColor: AlwaysStoppedAnimation(onSurfaceVariant),
),
//msg
Container(
margin: const EdgeInsets.only(top: 20),
child: Text(msg,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant)),
child: Text(msg, style: TextStyle(color: onSurfaceVariant)),
),
]),
);

View File

@@ -4,7 +4,7 @@ import 'package:get/get.dart';
void showConfirmDialog({
required BuildContext context,
required String title,
String? content,
dynamic content,
required VoidCallback onConfirm,
}) {
showDialog(
@@ -12,7 +12,11 @@ void showConfirmDialog({
builder: (context) {
return AlertDialog(
title: Text(title),
content: content == null ? null : Text(content),
content: content is String
? Text(content)
: content is Widget
? content
: null,
actions: [
TextButton(
onPressed: Get.back,
@@ -26,7 +30,7 @@ void showConfirmDialog({
Get.back();
onConfirm();
},
child: Text('确认'),
child: const Text('确认'),
),
],
);
@@ -85,10 +89,10 @@ void showPgcFollowDialog({
ListTile(
dense: true,
title: Padding(
padding: EdgeInsets.only(left: 10),
padding: const EdgeInsets.only(left: 10),
child: Text(
'取消$type',
style: TextStyle(fontSize: 14),
style: const TextStyle(fontSize: 14),
),
),
onTap: () {

View File

@@ -7,7 +7,8 @@ import 'package:get/get.dart';
void autoWrapReportDialog(
BuildContext context,
Map<String, Map<int, String>> options,
Future<Map> Function(int, String?, bool) onSuccess,
Future<Map> Function(int reasonType, String? reasonDesc, bool banUid)
onSuccess,
) {
int? reasonType;
String? reasonDesc;
@@ -61,7 +62,11 @@ void autoWrapReportDialog(
),
),
),
BanUserCheckbox(onChanged: (value) => banUid = value),
Padding(
padding: const EdgeInsets.only(left: 14, top: 6),
child: CheckBoxText(
text: '拉黑该用户', onChanged: (value) => banUid = value),
),
],
),
),
@@ -136,52 +141,65 @@ class _ReasonFieldState extends State<ReasonField> {
border: OutlineInputBorder(),
contentPadding: EdgeInsets.all(10),
),
onChanged: (value) {
widget.onChanged(value);
},
onChanged: widget.onChanged,
validator: widget._validator,
),
);
}
}
class BanUserCheckbox extends StatefulWidget {
class CheckBoxText extends StatefulWidget {
final String text;
final ValueChanged<bool> onChanged;
final bool selected;
const BanUserCheckbox({super.key, required this.onChanged});
const CheckBoxText({
super.key,
required this.text,
required this.onChanged,
this.selected = false,
});
@override
State<BanUserCheckbox> createState() => _BanUserCheckboxState();
State<CheckBoxText> createState() => _CheckBoxTextState();
}
class _BanUserCheckboxState extends State<BanUserCheckbox> {
bool _banUid = false;
class _CheckBoxTextState extends State<CheckBoxText> {
late bool _selected;
@override
void initState() {
super.initState();
_selected = widget.selected;
}
@override
Widget build(BuildContext context) {
return GestureDetector(
final colorScheme = Theme.of(context).colorScheme;
return InkWell(
onTap: () {
setState(() => _banUid = !_banUid);
widget.onChanged(_banUid);
setState(() {
_selected = !_selected;
});
widget.onChanged(_selected);
},
child: Padding(
padding: const EdgeInsets.only(left: 18, top: 10),
padding: const EdgeInsets.all(4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
size: 22,
_banUid
_selected
? Icons.check_box_outlined
: Icons.check_box_outline_blank,
color: _banUid
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant,
color: _selected
? colorScheme.primary
: colorScheme.onSurfaceVariant,
),
Text(
' 拉黑该用户',
style: TextStyle(
color: _banUid ? Theme.of(context).colorScheme.primary : null,
),
' ${widget.text}',
style: TextStyle(color: _selected ? colorScheme.primary : null),
),
],
),
@@ -192,7 +210,7 @@ class _BanUserCheckboxState extends State<BanUserCheckbox> {
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 => {
static Map<String, Map<int, String>> get commentReport => const {
'违反法律法规': {9: '违法违规', 2: '色情', 10: '低俗', 12: '赌博诈骗', 23: '违法信息外链'},
'谣言类不实信息': {19: '涉政谣言', 22: '虚假不实信息', 20: '涉社会事件谣言'},
'侵犯个人权益': {7: '人身攻击', 15: '侵犯隐私'},
@@ -208,7 +226,7 @@ class ReportOptions {
'其他': {0: '其他'},
};
static Map<String, Map<int, String>> get dynamicReport => {
static Map<String, Map<int, String>> get dynamicReport => const {
'': {
4: '垃圾广告',
8: '引战',

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);
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);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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,137 +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;
imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl;
final bool isEmote = imgUrl.contains('/emote/');
final bool isMall = imgUrl.contains('/mall/');
if (isMall) {
return const SizedBox.shrink();
}
// 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,
// );
String? height = RegExp(r'max-height:(\d+)px')
.firstMatch('${attributes['style']}')
?.group(1);
if (height != null) {
return NetworkImgLayer(
width: constrainedWidth,
height: double.parse(height),
src: imgUrl,
type: 'emote',
boxFit: BoxFit.contain,
);
}
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,115 @@
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 = Get.mediaQuery.size.shortestSide - 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,
);
}
@@ -101,6 +91,31 @@ Widget imageview(
};
}
void onTap(BuildContext context, int index) {
if (callback != null) {
callback(picArr.map((item) => item.url).toList(), index);
} else {
onViewImage?.call();
context.imageView(
initialPage: index,
imgList: picArr.map(
(item) {
bool isLive = item.isLivePhoto && enableLivePhoto;
return SourceModel(
sourceType:
isLive ? SourceType.livePhoto : SourceType.networkImage,
url: item.url,
liveUrl: isLive ? item.liveUrl : null,
width: isLive ? parseSize(item.width) : null,
height: isLive ? parseSize(item.height) : null,
);
},
).toList(),
onDismissed: onDismissed,
);
}
}
return NineGridView(
type: NineGridType.weiBo,
margin: const EdgeInsets.only(top: 6),
@@ -113,31 +128,9 @@ Widget imageview(
itemBuilder: (context, index) => Hero(
tag: picArr[index].url,
child: GestureDetector(
onTap: () {
if (callback != null) {
callback(picArr.map((item) => item.url).toList(), index);
} else {
onViewImage?.call();
context.imageView(
initialPage: index,
imgList: picArr.map(
(item) {
bool isLive = item.isLivePhoto && enableLivePhoto;
return SourceModel(
sourceType:
isLive ? SourceType.livePhoto : SourceType.networkImage,
url: item.url,
liveUrl: isLive ? item.liveUrl : null,
width: isLive ? parseSize(item.width) : null,
height: isLive ? parseSize(item.height) : null,
);
},
).toList(),
onDismissed: onDismissed,
);
}
},
onTap: () => onTap(context, index),
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
ClipRRect(
@@ -158,7 +151,7 @@ Widget imageview(
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.4),
.withValues(alpha: 0.4),
borderRadius: borderRadius(index),
),
child: Center(
@@ -178,7 +171,7 @@ Widget imageview(
text: 'Live',
right: 8,
bottom: 8,
type: 'gray',
type: PBadgeType.gray,
)
else if (picArr[index].isLongPic)
const PBadge(

View File

@@ -1,22 +1,22 @@
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 '../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,
@@ -27,13 +27,12 @@ class NetworkImgLayer extends StatelessWidget {
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;
@@ -44,14 +43,14 @@ class NetworkImgLayer extends StatelessWidget {
@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);
@@ -59,7 +58,7 @@ class NetworkImgLayer extends StatelessWidget {
Widget _buildImage(context) {
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);
@@ -67,7 +66,7 @@ class NetworkImgLayer extends StatelessWidget {
return CachedNetworkImage(
imageUrl: Utils.thumbnailImgUrl(src, quality),
width: width,
height: ignoreHeight == null || ignoreHeight == false ? height : null,
height: height,
memCacheWidth: memCacheWidth,
memCacheHeight: memCacheHeight,
fit: boxFit ?? BoxFit.cover,
@@ -83,33 +82,33 @@ class NetworkImgLayer extends StatelessWidget {
}
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,112 +0,0 @@
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/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: (_) => 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(
context: context,
tooltip: '分享',
onPressed: () {
DownloadUtils.onShareImg(cover ?? '');
},
iconSize: 20,
icon: Icons.share,
bgColor: Colors.transparent,
iconColor: Theme.of(context).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.of(context).colorScheme.onSurfaceVariant,
),
],
),
),
],
),
),
);
}

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,5 +1,9 @@
import 'dart:io';
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';
@@ -11,8 +15,6 @@ 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 'interactive_viewer_boundary.dart';
import 'interactive_viewer.dart' as custom;
/// https://github.com/qq326646683/interactiveviewer_gallery
@@ -31,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,
@@ -61,8 +45,11 @@ class InteractiveviewerGallery<T> extends StatefulWidget {
this.onDismissed,
this.setStatusBar,
this.onClose,
required this.quality,
});
final int quality;
final ValueChanged? onClose;
final bool? setStatusBar;
@@ -126,8 +113,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
setStatusBar();
}
if (widget.sources[currentIndex.value].sourceType == SourceType.livePhoto) {
_onPlay(currentIndex.value);
var item = widget.sources[currentIndex.value];
if (item.sourceType == SourceType.livePhoto) {
_onPlay(item.liveUrl!);
}
}
@@ -136,7 +124,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
SystemUiMode? mode;
setStatusBar() async {
Future<void> setStatusBar() async {
if (Platform.isIOS || Platform.isAndroid) {
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.immersiveSticky,
@@ -153,8 +141,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
widget.onClose?.call(true);
_player?.dispose();
_pageController?.dispose();
_animationController.removeListener(listener);
_animationController.dispose();
_animationController
..removeListener(listener)
..dispose();
if (widget.setStatusBar != false) {
if (Platform.isIOS || Platform.isAndroid) {
SystemChrome.setEnabledSystemUIMode(
@@ -163,9 +152,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
);
}
}
for (int index = 0; index < widget.sources.length; index++) {
if (widget.sources[index].sourceType == SourceType.networkImage) {
CachedNetworkImageProvider(_getActualUrl(index)).evict();
for (var item in widget.sources) {
if (item.sourceType == SourceType.networkImage) {
CachedNetworkImageProvider(_getActualUrl(item.url)).evict();
}
}
super.dispose();
@@ -224,10 +213,10 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
}
void _onPlay(int index) {
void _onPlay(String liveUrl) {
_player ??= Player();
_videoController ??= VideoController(_player!);
_player!.open(Media(widget.sources[index].liveUrl!));
_player!.open(Media(liveUrl));
}
/// When the page view changed its page, the source will animate back into the
@@ -237,8 +226,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
void _onPageChanged(int page) {
_player?.pause();
currentIndex.value = page;
if (widget.sources[page].sourceType == SourceType.livePhoto) {
_onPlay(page);
var item = widget.sources[page];
if (item.sourceType == SourceType.livePhoto) {
_onPlay(item.liveUrl!);
}
widget.onPageChanged?.call(page);
if (_transformationController!.value != Matrix4.identity()) {
@@ -255,10 +245,10 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
}
String _getActualUrl(int index) {
String _getActualUrl(String url) {
return _quality != 100
? Utils.thumbnailImgUrl(widget.sources[index].url, _quality)
: widget.sources[index].url.http2https;
? Utils.thumbnailImgUrl(url, _quality)
: url.http2https;
}
void onClose() {
@@ -276,6 +266,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
InteractiveViewerBoundary(
controller: _transformationController,
@@ -301,6 +292,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_enablePageView ? null : const NeverScrollableScrollPhysics(),
itemCount: widget.sources.length,
itemBuilder: (BuildContext context, int index) {
final item = widget.sources[index];
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onClose,
@@ -308,10 +300,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_doubleTapLocalPosition = details.localPosition;
},
onDoubleTap: onDoubleTap,
onLongPress:
widget.sources[index].sourceType == SourceType.fileImage
? null
: onLongPress,
onLongPress: item.sourceType == SourceType.fileImage
? null
: () => onLongPress(item),
child: widget.itemBuilder != null
? widget.itemBuilder!(
context,
@@ -319,7 +310,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
index == currentIndex.value,
_enablePageView,
)
: _itemBuilder(index),
: _itemBuilder(index, item),
);
},
),
@@ -329,12 +320,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(
@@ -342,12 +329,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(
@@ -373,53 +361,40 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
alignment: Alignment.centerRight,
child: PopupMenuButton(
itemBuilder: (context) {
final item = widget.sources[currentIndex.value];
return [
PopupMenuItem(
onTap: () => DownloadUtils.onShareImg(
widget.sources[currentIndex.value].url),
onTap: () => DownloadUtils.onShareImg(item.url),
child: const Text("分享图片"),
),
PopupMenuItem(
onTap: () {
Utils.copyText(
widget.sources[currentIndex.value].url);
},
onTap: () => Utils.copyText(item.url),
child: const Text("复制链接"),
),
PopupMenuItem(
onTap: () {
DownloadUtils.downloadImg(
context,
[widget.sources[currentIndex.value].url],
);
},
onTap: () => DownloadUtils.downloadImg(
this.context,
[item.url],
),
child: const Text("保存图片"),
),
if (widget.sources.length > 1)
PopupMenuItem(
onTap: () {
DownloadUtils.downloadImg(
context,
widget.sources
.map((item) => item.url)
.toList(),
);
},
child: const Text("保存全部图片"),
onTap: () => DownloadUtils.downloadImg(
this.context,
widget.sources.map((item) => item.url).toList(),
),
child: const Text("保存全部"),
),
if (widget.sources[currentIndex.value].sourceType ==
SourceType.livePhoto)
if (item.sourceType == SourceType.livePhoto)
PopupMenuItem(
onTap: () {
DownloadUtils.downloadLivePhoto(
context: context,
url: widget.sources[currentIndex.value].url,
liveUrl: widget
.sources[currentIndex.value].liveUrl!,
width:
widget.sources[currentIndex.value].width!,
height: widget
.sources[currentIndex.value].height!,
context: this.context,
url: item.url,
liveUrl: item.liveUrl!,
width: item.width!,
height: item.height!,
);
},
child: const Text("保存 Live Photo"),
@@ -437,44 +412,27 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
);
}
Widget _itemBuilder(index) {
Widget _itemBuilder(int index, SourceModel item) {
return Center(
child: Hero(
tag: widget.sources[index].url,
child: switch (widget.sources[index].sourceType) {
tag: item.url,
child: switch (item.sourceType) {
SourceType.fileImage => Image(
filterQuality: FilterQuality.low,
image: FileImage(File(widget.sources[index].url)),
image: FileImage(File(item.url)),
),
SourceType.networkImage => CachedNetworkImage(
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
imageUrl: _getActualUrl(index),
imageUrl: _getActualUrl(item.url),
placeholderFadeInDuration: Duration.zero,
placeholder: (context, url) {
return CachedNetworkImage(
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
imageUrl: Utils.thumbnailImgUrl(widget.sources[index].url),
imageUrl: Utils.thumbnailImgUrl(item.url, widget.quality),
);
},
// fit: BoxFit.contain,
// progressIndicatorBuilder: (context, url, progress) {
// return Center(
// child: SizedBox(
// width: 150.0,
// child:
// LinearProgressIndicator(value: progress.progress ?? 0),
// ),
// );
// },
// errorListener: (value) {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// setState(() {
// _thumbList[index] = false;
// });
// });
// },
),
SourceType.livePhoto => Obx(() => currentIndex.value == index
? IgnorePointer(
@@ -489,7 +447,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
);
}
onDoubleTap() {
void onDoubleTap() {
Matrix4 matrix = _transformationController!.value.clone();
double currentScale = matrix.row0.x;
@@ -536,7 +494,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
.whenComplete(() => _onScaleChanged(targetScale));
}
onLongPress() {
void onLongPress(SourceModel item) {
showDialog(
context: context,
builder: (context) {
@@ -548,8 +506,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
children: [
ListTile(
onTap: () {
DownloadUtils.onShareImg(
widget.sources[currentIndex.value].url);
DownloadUtils.onShareImg(item.url);
Get.back();
},
dense: true,
@@ -558,7 +515,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
ListTile(
onTap: () {
Get.back();
Utils.copyText(widget.sources[currentIndex.value].url);
Utils.copyText(item.url);
},
dense: true,
title: const Text('复制链接', style: TextStyle(fontSize: 14)),
@@ -567,8 +524,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
onTap: () {
Get.back();
DownloadUtils.downloadImg(
context,
[widget.sources[currentIndex.value].url],
this.context,
[item.url],
);
},
dense: true,
@@ -579,24 +536,23 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
onTap: () {
Get.back();
DownloadUtils.downloadImg(
context,
this.context,
widget.sources.map((item) => item.url).toList(),
);
},
dense: true,
title: const Text('保存全部图片', style: TextStyle(fontSize: 14)),
),
if (widget.sources[currentIndex.value].sourceType ==
SourceType.livePhoto)
if (item.sourceType == SourceType.livePhoto)
ListTile(
onTap: () {
Get.back();
DownloadUtils.downloadLivePhoto(
context: context,
url: widget.sources[currentIndex.value].url,
liveUrl: widget.sources[currentIndex.value].liveUrl!,
width: widget.sources[currentIndex.value].width!,
height: widget.sources[currentIndex.value].height!,
context: this.context,
url: item.url,
liveUrl: item.liveUrl!,
width: item.width!,
height: item.height!,
);
},
dense: true,

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,20 +0,0 @@
import 'package:PiliPlus/common/widgets/http_error.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(
controller: ScrollController(),
slivers: [
HttpError(
errMsg: errMsg,
callback: callback,
)
],
);

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

@@ -0,0 +1,444 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: uri_does_not_exist_in_doc_import
/// @docImport 'package:flutter/material.dart';
///
/// @docImport 'single_child_scroll_view.dart';
/// @docImport 'text.dart';
library;
import 'package:PiliPlus/common/widgets/page/scrollable.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/material.dart' hide Scrollable, ScrollableState;
import 'package:flutter/rendering.dart';
class _ForceImplicitScrollPhysics extends ScrollPhysics {
const _ForceImplicitScrollPhysics(
{required this.allowImplicitScrolling, super.parent});
@override
_ForceImplicitScrollPhysics applyTo(ScrollPhysics? ancestor) {
return _ForceImplicitScrollPhysics(
allowImplicitScrolling: allowImplicitScrolling,
parent: buildParent(ancestor),
);
}
@override
final bool allowImplicitScrolling;
}
const PageScrollPhysics _kPagePhysics = PageScrollPhysics();
/// A scrollable list that works page by page.
///
/// Each child of a page view is forced to be the same size as the viewport.
///
/// You can use a [PageController] to control which page is visible in the view.
/// In addition to being able to control the pixel offset of the content inside
/// the [CustomPageView], a [PageController] also lets you control the offset in terms
/// of pages, which are increments of the viewport size.
///
/// The [PageController] can also be used to control the
/// [PageController.initialPage], which determines which page is shown when the
/// [CustomPageView] is first constructed, and the [PageController.viewportFraction],
/// which determines the size of the pages as a fraction of the viewport size.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=J1gE9xvph-A}
///
/// {@tool dartpad}
/// Here is an example of [CustomPageView]. It creates a centered [Text] in each of the three pages
/// which scroll horizontally.
///
/// ** See code in examples/api/lib/widgets/page_view/page_view.0.dart **
/// {@end-tool}
///
/// ## Persisting the scroll position during a session
///
/// Scroll views attempt to persist their scroll position using [PageStorage].
/// For a [CustomPageView], this can be disabled by setting [PageController.keepPage]
/// to false on the [controller]. If it is enabled, using a [PageStorageKey] for
/// the [key] of this widget is recommended to help disambiguate different
/// scroll views from each other.
///
/// See also:
///
/// * [PageController], which controls which page is visible in the view.
/// * [SingleChildScrollView], when you need to make a single child scrollable.
/// * [ListView], for a scrollable list of boxes.
/// * [GridView], for a scrollable grid of boxes.
/// * [ScrollNotification] and [NotificationListener], which can be used to watch
/// the scroll position without using a [ScrollController].
class CustomPageView extends StatefulWidget {
/// Creates a scrollable list that works page by page from an explicit [List]
/// of widgets.
///
/// This constructor is appropriate for page views with a small number of
/// children because constructing the [List] requires doing work for every
/// child that could possibly be displayed in the page view, instead of just
/// those children that are actually visible.
///
/// Like other widgets in the framework, this widget expects that
/// the [children] list will not be mutated after it has been passed in here.
/// See the documentation at [SliverChildListDelegate.children] for more details.
///
/// {@template flutter.widgets.PageView.allowImplicitScrolling}
/// If [allowImplicitScrolling] is true, the [CustomPageView] will participate in
/// accessibility scrolling more like a [ListView], where implicit scroll
/// actions will move to the next page rather than into the contents of the
/// [CustomPageView].
/// {@endtemplate}
CustomPageView({
super.key,
this.scrollDirection = Axis.horizontal,
this.reverse = false,
this.controller,
this.physics,
this.pageSnapping = true,
this.onPageChanged,
List<Widget> children = const <Widget>[],
this.dragStartBehavior = DragStartBehavior.start,
this.allowImplicitScrolling = false,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
this.hitTestBehavior = HitTestBehavior.opaque,
this.scrollBehavior,
this.padEnds = true,
this.header,
this.bgColor = Colors.transparent,
}) : childrenDelegate = SliverChildListDelegate(children);
final Widget? header;
final Color bgColor;
/// Creates a scrollable list that works page by page using widgets that are
/// created on demand.
///
/// This constructor is appropriate for page views with a large (or infinite)
/// number of children because the builder is called only for those children
/// that are actually visible.
///
/// Providing a non-null [itemCount] lets the [CustomPageView] compute the maximum
/// scroll extent.
///
/// [itemBuilder] will be called only with indices greater than or equal to
/// zero and less than [itemCount].
///
/// {@macro flutter.widgets.ListView.builder.itemBuilder}
///
/// {@template flutter.widgets.PageView.findChildIndexCallback}
/// The [findChildIndexCallback] corresponds to the
/// [SliverChildBuilderDelegate.findChildIndexCallback] property. If null,
/// a child widget may not map to its existing [RenderObject] when the order
/// of children returned from the children builder changes.
/// This may result in state-loss. This callback needs to be implemented if
/// the order of the children may change at a later time.
/// {@endtemplate}
///
/// {@macro flutter.widgets.PageView.allowImplicitScrolling}
CustomPageView.builder({
super.key,
this.scrollDirection = Axis.horizontal,
this.reverse = false,
this.controller,
this.physics,
this.pageSnapping = true,
this.onPageChanged,
required NullableIndexedWidgetBuilder itemBuilder,
ChildIndexGetter? findChildIndexCallback,
int? itemCount,
this.dragStartBehavior = DragStartBehavior.start,
this.allowImplicitScrolling = false,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
this.hitTestBehavior = HitTestBehavior.opaque,
this.scrollBehavior,
this.padEnds = true,
this.header,
this.bgColor = Colors.transparent,
}) : childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
findChildIndexCallback: findChildIndexCallback,
childCount: itemCount,
);
/// Creates a scrollable list that works page by page with a custom child
/// model.
///
/// {@tool dartpad}
/// This example shows a [CustomPageView] that uses a custom [SliverChildBuilderDelegate] to support child
/// reordering.
///
/// ** See code in examples/api/lib/widgets/page_view/page_view.1.dart **
/// {@end-tool}
///
/// {@macro flutter.widgets.PageView.allowImplicitScrolling}
const CustomPageView.custom({
super.key,
this.scrollDirection = Axis.horizontal,
this.reverse = false,
this.controller,
this.physics,
this.pageSnapping = true,
this.onPageChanged,
required this.childrenDelegate,
this.dragStartBehavior = DragStartBehavior.start,
this.allowImplicitScrolling = false,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
this.hitTestBehavior = HitTestBehavior.opaque,
this.scrollBehavior,
this.padEnds = true,
this.header,
this.bgColor = Colors.transparent,
});
/// Controls whether the widget's pages will respond to
/// [RenderObject.showOnScreen], which will allow for implicit accessibility
/// scrolling.
///
/// With this flag set to false, when accessibility focus reaches the end of
/// the current page and the user attempts to move it to the next element, the
/// focus will traverse to the next widget outside of the page view.
///
/// With this flag set to true, when accessibility focus reaches the end of
/// the current page and user attempts to move it to the next element, focus
/// will traverse to the next page in the page view.
final bool allowImplicitScrolling;
/// {@macro flutter.widgets.scrollable.restorationId}
final String? restorationId;
/// The [Axis] along which the scroll view's offset increases with each page.
///
/// For the direction in which active scrolling may be occurring, see
/// [ScrollDirection].
///
/// Defaults to [Axis.horizontal].
final Axis scrollDirection;
/// Whether the page view scrolls in the reading direction.
///
/// For example, if the reading direction is left-to-right and
/// [scrollDirection] is [Axis.horizontal], then the page view scrolls from
/// left to right when [reverse] is false and from right to left when
/// [reverse] is true.
///
/// Similarly, if [scrollDirection] is [Axis.vertical], then the page view
/// scrolls from top to bottom when [reverse] is false and from bottom to top
/// when [reverse] is true.
///
/// Defaults to false.
final bool reverse;
/// An object that can be used to control the position to which this page
/// view is scrolled.
final PageController? controller;
/// How the page view should respond to user input.
///
/// For example, determines how the page view continues to animate after the
/// user stops dragging the page view.
///
/// The physics are modified to snap to page boundaries using
/// [PageScrollPhysics] prior to being used.
///
/// If an explicit [ScrollBehavior] is provided to [scrollBehavior], the
/// [ScrollPhysics] provided by that behavior will take precedence after
/// [physics].
///
/// Defaults to matching platform conventions.
final ScrollPhysics? physics;
/// Set to false to disable page snapping, useful for custom scroll behavior.
///
/// If the [padEnds] is false and [PageController.viewportFraction] < 1.0,
/// the page will snap to the beginning of the viewport; otherwise, the page
/// will snap to the center of the viewport.
final bool pageSnapping;
/// Called whenever the page in the center of the viewport changes.
final ValueChanged<int>? onPageChanged;
/// A delegate that provides the children for the [CustomPageView].
///
/// The [PageView.custom] constructor lets you specify this delegate
/// explicitly. The [CustomPageView] and [PageView.builder] constructors create a
/// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
/// respectively.
final SliverChildDelegate childrenDelegate;
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
final Clip clipBehavior;
/// {@macro flutter.widgets.scrollable.hitTestBehavior}
///
/// Defaults to [HitTestBehavior.opaque].
final HitTestBehavior hitTestBehavior;
/// {@macro flutter.widgets.scrollable.scrollBehavior}
///
/// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be
/// modified by default to not apply a [Scrollbar].
final ScrollBehavior? scrollBehavior;
/// Whether to add padding to both ends of the list.
///
/// If this is set to true and [PageController.viewportFraction] < 1.0, padding will be added
/// such that the first and last child slivers will be in the center of
/// the viewport when scrolled all the way to the start or end, respectively.
///
/// If [PageController.viewportFraction] >= 1.0, this property has no effect.
///
/// This property defaults to true.
final bool padEnds;
@override
State<CustomPageView> createState() => _CustomPageViewState();
}
class _CustomPageViewState extends State<CustomPageView> {
int _lastReportedPage = 0;
late PageController _controller;
@override
void initState() {
super.initState();
_initController();
_lastReportedPage = _controller.initialPage;
}
@override
void dispose() {
if (widget.controller == null) {
_controller.dispose();
}
super.dispose();
}
void _initController() {
_controller = widget.controller ?? PageController();
}
@override
void didUpdateWidget(CustomPageView oldWidget) {
if (oldWidget.controller != widget.controller) {
if (oldWidget.controller == null) {
_controller.dispose();
}
_initController();
}
super.didUpdateWidget(oldWidget);
}
AxisDirection _getDirection(BuildContext context) {
switch (widget.scrollDirection) {
case Axis.horizontal:
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
final AxisDirection axisDirection =
textDirectionToAxisDirection(textDirection);
return widget.reverse
? flipAxisDirection(axisDirection)
: axisDirection;
case Axis.vertical:
return widget.reverse ? AxisDirection.up : AxisDirection.down;
}
}
@override
Widget build(BuildContext context) {
final AxisDirection axisDirection = _getDirection(context);
final ScrollPhysics physics = _ForceImplicitScrollPhysics(
allowImplicitScrolling: widget.allowImplicitScrolling,
).applyTo(
widget.pageSnapping
? _kPagePhysics.applyTo(
widget.physics ??
widget.scrollBehavior?.getScrollPhysics(context),
)
: widget.physics ?? widget.scrollBehavior?.getScrollPhysics(context),
);
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
if (notification.depth == 0 &&
widget.onPageChanged != null &&
notification is ScrollUpdateNotification) {
final PageMetrics metrics = notification.metrics as PageMetrics;
final int currentPage = metrics.page!.round();
if (currentPage != _lastReportedPage) {
_lastReportedPage = currentPage;
widget.onPageChanged!(currentPage);
}
}
return false;
},
child: CustomScrollable(
header: widget.header,
bgColor: widget.bgColor,
dragStartBehavior: widget.dragStartBehavior,
axisDirection: axisDirection,
controller: _controller,
physics: physics,
restorationId: widget.restorationId,
hitTestBehavior: widget.hitTestBehavior,
scrollBehavior: widget.scrollBehavior ??
ScrollConfiguration.of(context).copyWith(scrollbars: false),
viewportBuilder: (BuildContext context, ViewportOffset position) {
return Viewport(
// TODO(dnfield): we should provide a way to set cacheExtent
// independent of implicit scrolling:
// https://github.com/flutter/flutter/issues/45632
cacheExtent: widget.allowImplicitScrolling ? 1.0 : 0.0,
cacheExtentStyle: CacheExtentStyle.viewport,
axisDirection: axisDirection,
offset: position,
clipBehavior: widget.clipBehavior,
slivers: <Widget>[
SliverFillViewport(
viewportFraction: _controller.viewportFraction,
delegate: widget.childrenDelegate,
padEnds: widget.padEnds,
),
],
);
},
),
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description
..add(EnumProperty<Axis>('scrollDirection', widget.scrollDirection))
..add(FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed'))
..add(
DiagnosticsProperty<PageController>('controller', _controller,
showName: false),
)
..add(DiagnosticsProperty<ScrollPhysics>('physics', widget.physics,
showName: false))
..add(
FlagProperty('pageSnapping',
value: widget.pageSnapping, ifFalse: 'snapping disabled'),
)
..add(
FlagProperty(
'allowImplicitScrolling',
value: widget.allowImplicitScrolling,
ifTrue: 'allow implicit scrolling',
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,367 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show SemanticsRole;
import 'package:PiliPlus/common/widgets/page/page_view.dart';
import 'package:flutter/foundation.dart' show clampDouble;
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/material.dart' hide TabBarView, PageView;
/// A page view that displays the widget which corresponds to the currently
/// selected tab.
///
/// This widget is typically used in conjunction with a [TabBar].
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=POtoEH-5l40}
///
/// If a [TabController] is not provided, then there must be a [DefaultTabController]
/// ancestor.
///
/// The tab controller's [TabController.length] must equal the length of the
/// [children] list and the length of the [TabBar.tabs] list.
///
/// To see a sample implementation, visit the [TabController] documentation.
class CustomTabBarView extends StatefulWidget {
/// Creates a page view with one child per tab.
///
/// The length of [children] must be the same as the [controller]'s length.
const CustomTabBarView({
super.key,
required this.children,
this.controller,
this.physics,
this.dragStartBehavior = DragStartBehavior.start,
this.viewportFraction = 1.0,
this.clipBehavior = Clip.hardEdge,
this.scrollDirection = Axis.horizontal,
this.header,
this.bgColor = Colors.transparent,
});
final Widget? header;
final Color bgColor;
/// This widget's selection and animation state.
///
/// If [TabController] is not provided, then the value of [DefaultTabController.of]
/// will be used.
final TabController? controller;
/// One widget per tab.
///
/// Its length must match the length of the [TabBar.tabs]
/// list, as well as the [controller]'s [TabController.length].
final List<Widget> children;
/// How the page view should respond to user input.
///
/// For example, determines how the page view continues to animate after the
/// user stops dragging the page view.
///
/// The physics are modified to snap to page boundaries using
/// [PageScrollPhysics] prior to being used.
///
/// Defaults to matching platform conventions.
final ScrollPhysics? physics;
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
/// {@macro flutter.widgets.pageview.viewportFraction}
final double viewportFraction;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
final Clip clipBehavior;
final Axis scrollDirection;
@override
State<CustomTabBarView> createState() => _CustomTabBarViewState();
}
class _CustomTabBarViewState extends State<CustomTabBarView> {
TabController? _controller;
PageController? _pageController;
late List<Widget> _childrenWithKey;
int? _currentIndex;
int _warpUnderwayCount = 0;
int _scrollUnderwayCount = 0;
bool _debugHasScheduledValidChildrenCountCheck = false;
// If the TabBarView is rebuilt with a new tab controller, the caller should
// dispose the old one. In that case the old controller's animation will be
// null and should not be accessed.
bool get _controllerIsValid => _controller?.animation != null;
void _updateTabController() {
final TabController? newController =
widget.controller ?? DefaultTabController.maybeOf(context);
assert(() {
if (newController == null) {
throw FlutterError(
'No TabController for ${widget.runtimeType}.\n'
'When creating a ${widget.runtimeType}, you must either provide an explicit '
'TabController using the "controller" property, or you must ensure that there '
'is a DefaultTabController above the ${widget.runtimeType}.\n'
'In this case, there was neither an explicit controller nor a default controller.',
);
}
return true;
}());
if (newController == _controller) {
return;
}
if (_controllerIsValid) {
_controller!.animation!.removeListener(_handleTabControllerAnimationTick);
}
_controller = newController;
if (_controller != null) {
_controller!.animation!.addListener(_handleTabControllerAnimationTick);
}
}
void _jumpToPage(int page) {
_warpUnderwayCount += 1;
_pageController!.jumpToPage(page);
_warpUnderwayCount -= 1;
}
Future<void> _animateToPage(int page,
{required Duration duration, required Curve curve}) async {
_warpUnderwayCount += 1;
await _pageController!
.animateToPage(page, duration: duration, curve: curve);
_warpUnderwayCount -= 1;
}
@override
void initState() {
super.initState();
_updateChildren();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_updateTabController();
_currentIndex = _controller!.index;
if (_pageController == null) {
_pageController = PageController(
initialPage: _currentIndex!,
viewportFraction: widget.viewportFraction,
);
} else {
_pageController!.jumpToPage(_currentIndex!);
}
}
@override
void didUpdateWidget(CustomTabBarView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != oldWidget.controller) {
_updateTabController();
_currentIndex = _controller!.index;
_jumpToPage(_currentIndex!);
}
if (widget.viewportFraction != oldWidget.viewportFraction) {
_pageController?.dispose();
_pageController = PageController(
initialPage: _currentIndex!,
viewportFraction: widget.viewportFraction,
);
}
// While a warp is under way, we stop updating the tab page contents.
// This is tracked in https://github.com/flutter/flutter/issues/31269.
if (widget.children != oldWidget.children && _warpUnderwayCount == 0) {
_updateChildren();
}
}
@override
void dispose() {
if (_controllerIsValid) {
_controller!.animation!.removeListener(_handleTabControllerAnimationTick);
}
_controller = null;
_pageController?.dispose();
// We don't own the _controller Animation, so it's not disposed here.
super.dispose();
}
void _updateChildren() {
_childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(
widget.children.map<Widget>((Widget child) {
return Semantics(role: SemanticsRole.tabPanel, child: child);
}).toList(),
);
}
void _handleTabControllerAnimationTick() {
if (_scrollUnderwayCount > 0 || !_controller!.indexIsChanging) {
return;
} // This widget is driving the controller's animation.
if (_controller!.index != _currentIndex) {
_currentIndex = _controller!.index;
_warpToCurrentIndex();
}
}
void _warpToCurrentIndex() {
if (!mounted || _pageController!.page == _currentIndex!.toDouble()) {
return;
}
final bool adjacentDestination =
(_currentIndex! - _controller!.previousIndex).abs() == 1;
if (adjacentDestination) {
_warpToAdjacentTab(_controller!.animationDuration);
} else {
_warpToNonAdjacentTab(_controller!.animationDuration);
}
}
Future<void> _warpToAdjacentTab(Duration duration) async {
if (duration == Duration.zero) {
_jumpToPage(_currentIndex!);
} else {
await _animateToPage(_currentIndex!,
duration: duration, curve: Curves.ease);
}
if (mounted) {
setState(() {
_updateChildren();
});
}
return Future<void>.value();
}
Future<void> _warpToNonAdjacentTab(Duration duration) async {
final int previousIndex = _controller!.previousIndex;
assert((_currentIndex! - previousIndex).abs() > 1);
// initialPage defines which page is shown when starting the animation.
// This page is adjacent to the destination page.
final int initialPage = _currentIndex! > previousIndex
? _currentIndex! - 1
: _currentIndex! + 1;
setState(() {
// Needed for `RenderSliverMultiBoxAdaptor.move` and kept alive children.
// For motivation, see https://github.com/flutter/flutter/pull/29188 and
// https://github.com/flutter/flutter/issues/27010#issuecomment-486475152.
_childrenWithKey = List<Widget>.of(_childrenWithKey, growable: false);
final Widget temp = _childrenWithKey[initialPage];
_childrenWithKey[initialPage] = _childrenWithKey[previousIndex];
_childrenWithKey[previousIndex] = temp;
});
// Make a first jump to the adjacent page.
_jumpToPage(initialPage);
// Jump or animate to the destination page.
if (duration == Duration.zero) {
_jumpToPage(_currentIndex!);
} else {
await _animateToPage(_currentIndex!,
duration: duration, curve: Curves.ease);
}
if (mounted) {
setState(() {
_updateChildren();
});
}
}
void _syncControllerOffset() {
_controller!.offset =
clampDouble(_pageController!.page! - _controller!.index, -1.0, 1.0);
}
// Called when the PageView scrolls
bool _handleScrollNotification(ScrollNotification notification) {
if (_warpUnderwayCount > 0 || _scrollUnderwayCount > 0) {
return false;
}
if (notification.depth != 0) {
return false;
}
if (!_controllerIsValid) {
return false;
}
_scrollUnderwayCount += 1;
final double page = _pageController!.page!;
if (notification is ScrollUpdateNotification &&
!_controller!.indexIsChanging) {
final bool pageChanged = (page - _controller!.index).abs() > 1.0;
if (pageChanged) {
_controller!.index = page.round();
_currentIndex = _controller!.index;
}
_syncControllerOffset();
} else if (notification is ScrollEndNotification) {
_controller!.index = page.round();
_currentIndex = _controller!.index;
if (!_controller!.indexIsChanging) {
_syncControllerOffset();
}
}
_scrollUnderwayCount -= 1;
return false;
}
bool _debugScheduleCheckHasValidChildrenCount() {
if (_debugHasScheduledValidChildrenCountCheck) {
return true;
}
WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
_debugHasScheduledValidChildrenCountCheck = false;
if (!mounted) {
return;
}
assert(() {
if (_controller!.length != widget.children.length) {
throw FlutterError(
"Controller's length property (${_controller!.length}) does not match the "
"number of children (${widget.children.length}) present in TabBarView's children property.",
);
}
return true;
}());
}, debugLabel: 'TabBarView.validChildrenCountCheck');
_debugHasScheduledValidChildrenCountCheck = true;
return true;
}
@override
Widget build(BuildContext context) {
assert(_debugScheduleCheckHasValidChildrenCount());
return NotificationListener<ScrollNotification>(
onNotification: _handleScrollNotification,
child: CustomPageView(
scrollDirection: widget.scrollDirection,
dragStartBehavior: widget.dragStartBehavior,
clipBehavior: widget.clipBehavior,
controller: _pageController,
physics: widget.physics == null
? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
: const PageScrollPhysics().applyTo(widget.physics),
header: widget.header,
bgColor: widget.bgColor,
children: _childrenWithKey,
),
);
}
}

View File

@@ -0,0 +1,159 @@
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));
}
}
@@ -619,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;
}
@@ -919,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:
@@ -1013,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);
@@ -1109,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

@@ -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,77 @@ 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) {
.whenComplete(() {
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) {
widget.onRefresh().whenComplete(() {
if (mounted && _status == RefreshIndicatorStatus.refresh) {
completer.complete();
_dismiss(_RefreshIndicatorMode.done);
_dismiss(RefreshIndicatorStatus.done);
}
});
}
@@ -562,9 +627,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 +637,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
return _pendingRefreshFuture;
}
@protected
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
@@ -583,7 +649,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
),
);
assert(() {
if (_mode == null) {
if (_status == null) {
assert(_dragOffset == null);
assert(_isIndicatorAtTop == null);
} else {
@@ -594,13 +660,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 +675,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 +724,12 @@ class RefreshIndicatorState extends State<RefreshIndicator>
case TargetPlatform.macOS:
return cupertinoIndicator;
}
}
}
},
case _IndicatorType.noSpinner:
return Container();
}
},
),
),
),
),

View File

@@ -0,0 +1,98 @@
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;
/// Defaults to 0.
@override
double bounce = 0.0;
/// Defaults to 0.5 seconds.
@override
Duration duration = const Duration(milliseconds: 500);
}

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,54 +0,0 @@
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 => SpringDescription(
mass: GStorage.springDescription[0],
stiffness: GStorage.springDescription[1],
damping: GStorage.springDescription[2],
);
}
class CustomTabBarViewClampingScrollPhysics extends ClampingScrollPhysics {
const CustomTabBarViewClampingScrollPhysics({super.parent});
@override
CustomTabBarViewClampingScrollPhysics applyTo(ScrollPhysics? ancestor) {
return CustomTabBarViewClampingScrollPhysics(parent: buildParent(ancestor));
}
@override
SpringDescription get spring => SpringDescription(
mass: GStorage.springDescription[0],
stiffness: GStorage.springDescription[1],
damping: GStorage.springDescription[2],
);
}

View File

@@ -5,7 +5,6 @@ abstract class _StatItemBase extends StatelessWidget {
final BuildContext context;
final Object value;
final String? theme;
final String? size;
final Color? textColor;
final double iconSize;
@@ -13,7 +12,6 @@ abstract class _StatItemBase extends StatelessWidget {
required this.context,
required this.value,
this.theme,
this.size,
this.textColor,
this.iconSize = 13,
});
@@ -24,8 +22,10 @@ abstract class _StatItemBase extends StatelessWidget {
Color get color {
return textColor ??
switch (theme) {
'gray' => Theme.of(context).colorScheme.outline.withOpacity(0.8),
'black' => Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
'gray' =>
Theme.of(context).colorScheme.outline.withValues(alpha: 0.8),
'black' =>
Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
_ => Colors.white,
};
}
@@ -42,7 +42,7 @@ abstract class _StatItemBase extends StatelessWidget {
const SizedBox(width: 2),
Text(
Utils.numFormat(value),
style: TextStyle(fontSize: size == 'medium' ? 12 : 11, color: color),
style: TextStyle(fontSize: 12, color: color),
overflow: TextOverflow.clip,
semanticsLabel: semanticsLabel,
)
@@ -59,7 +59,6 @@ class StatView extends _StatItemBase {
required super.value,
this.goto,
super.theme,
super.size,
super.textColor,
}) : super(iconSize: 13);
@@ -68,6 +67,7 @@ class StatView extends _StatItemBase {
'picture' => Icons.remove_red_eye_outlined,
'like' => Icons.thumb_up_outlined,
'reply' => Icons.comment_outlined,
'follow' => Icons.favorite_border,
_ => Icons.play_circle_outlined,
};
@@ -81,7 +81,6 @@ class StatDanMu extends _StatItemBase {
required super.context,
required super.value,
super.theme,
super.size,
super.textColor,
}) : super(iconSize: 14);

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

@@ -1,17 +1,20 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/common/widgets/video_progress_indicator.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/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 '../../http/search.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'badge.dart';
import 'network_img_layer.dart';
import 'stat/stat.dart';
import 'video_popup_menu.dart';
import 'package:get/get.dart';
// -
class VideoCardH extends StatelessWidget {
@@ -42,9 +45,6 @@ class VideoCardH extends StatelessWidget {
final int aid = videoItem.aid!;
final String bvid = videoItem.bvid!;
String type = 'video';
// try {
// type = videoItem.type;
// } catch (_) {}
if (videoItem is SearchVideoItemModel) {
var typeOrNull = (videoItem as SearchVideoItemModel).type;
if (typeOrNull?.isNotEmpty == true) {
@@ -59,18 +59,12 @@ class VideoCardH extends StatelessWidget {
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,
cover: videoItem.pic,
);
@@ -84,11 +78,22 @@ class VideoCardH extends StatelessWidget {
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 (Utils.viewPgcFromUri(
if (PageUtils.viewPgcFromUri(
(videoItem as HotVideoItemModel).redirectUrl!)) {
return;
}
@@ -99,7 +104,7 @@ class VideoCardH extends StatelessWidget {
if (source == 'later') {
onViewLater!(cid);
} else {
Utils.toViewPage(
PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
arguments: {
'videoItem': videoItem,
@@ -123,8 +128,7 @@ class VideoCardH extends StatelessWidget {
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (BuildContext context,
BoxConstraints boxConstraints) {
builder: (context, boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
num? progress;
@@ -141,11 +145,7 @@ class VideoCardH extends StatelessWidget {
width: maxWidth,
height: maxHeight,
),
if (videoItem is HotVideoItemModel &&
(videoItem as HotVideoItemModel)
.pgcLabel
?.isNotEmpty ==
true)
if (videoItem is HotVideoItemModel)
PBadge(
text:
(videoItem as HotVideoItemModel).pgcLabel,
@@ -159,7 +159,7 @@ class VideoCardH extends StatelessWidget {
: '${Utils.timeFormat(progress)}/${Utils.timeFormat(videoItem.duration)}',
right: 6,
bottom: 8,
type: 'gray',
type: PBadgeType.gray,
),
Positioned(
left: 0,
@@ -176,19 +176,15 @@ class VideoCardH extends StatelessWidget {
text: Utils.timeFormat(videoItem.duration),
right: 6.0,
bottom: 6.0,
type: 'gray',
type: PBadgeType.gray,
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: 'primary',
type: PBadgeType.primary,
),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
// 6.0, 6.0, null, null),
],
);
},
@@ -217,6 +213,7 @@ class VideoCardH extends StatelessWidget {
}
Widget videoContent(BuildContext context) {
final theme = Theme.of(context);
String pubdate = showPubdate
? Utils.dateFormat(videoItem.pubdate!, formatType: 'day')
: '';
@@ -238,13 +235,12 @@ class VideoCardH extends StatelessWidget {
TextSpan(
text: i['text'],
style: TextStyle(
fontSize:
Theme.of(context).textTheme.bodyMedium!.fontSize,
fontSize: theme.textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
? theme.colorScheme.primary
: theme.colorScheme.onSurface,
),
),
],
@@ -257,7 +253,7 @@ class VideoCardH extends StatelessWidget {
videoItem.title,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
fontSize: theme.textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
@@ -265,36 +261,15 @@ class VideoCardH extends StatelessWidget {
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)
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,
),
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),

View File

@@ -1,14 +1,16 @@
import 'package:PiliPlus/common/widgets/image_save.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 '../../utils/utils.dart';
import '../constants.dart';
import 'badge.dart';
import 'network_img_layer.dart';
// -
class VideoCardHMemberVideo extends StatelessWidget {
@@ -19,18 +21,19 @@ class VideoCardHMemberVideo extends StatelessWidget {
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,7 +43,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
return;
}
if (videoItem.isPgc == true && videoItem.uri?.isNotEmpty == true) {
if (Utils.viewPgcFromUri(videoItem.uri!)) {
if (PageUtils.viewPgcFromUri(videoItem.uri!)) {
return;
}
}
@@ -48,7 +51,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
return;
}
try {
Utils.toViewPage(
PageUtils.toVideoPage(
'bvid=${videoItem.bvid}&cid=${videoItem.cid}',
arguments: {
'heroTag': Utils.makeHeroTag(videoItem.bvid),
@@ -77,27 +80,27 @@ 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)
Positioned.fill(
const Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
borderRadius: StyleString.mdRadius,
color: Colors.black54,
),
child: Center(
child: const Text(
child: Text(
'上次观看',
style: TextStyle(
color: Colors.white,
fontSize: 15,
letterSpacing: 1.5,
letterSpacing: 5,
),
),
),
@@ -111,8 +114,8 @@ class VideoCardHMemberVideo extends StatelessWidget {
right: 6.0,
top: 6.0,
type: videoItem.badges!.first.text == '充电专属'
? 'error'
: 'primary',
? PBadgeType.error
: PBadgeType.primary,
),
if (videoItem.history != null) ...[
Builder(builder: (context) {
@@ -139,7 +142,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
: '${Utils.timeFormat(videoItem.history!['progress'])}/${Utils.timeFormat(videoItem.history!['duration'])}',
right: 6.0,
bottom: 6.0,
type: 'gray',
type: PBadgeType.gray,
);
} catch (_) {
return PBadge(
@@ -147,7 +150,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
Utils.timeFormat(videoItem.duration),
right: 6.0,
bottom: 6.0,
type: 'gray',
type: PBadgeType.gray,
);
}
}),
@@ -156,7 +159,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
text: Utils.timeFormat(videoItem.duration),
right: 6.0,
bottom: 6.0,
type: 'gray',
type: PBadgeType.gray,
),
],
);
@@ -164,7 +167,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
),
),
const SizedBox(width: 10),
videoContent(context),
videoContent(context, theme),
],
);
},
@@ -184,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,
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,
@@ -215,9 +215,9 @@ 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,
),
),
@@ -227,15 +227,12 @@ class VideoCardHMemberVideo extends StatelessWidget {
StatView(
context: context,
theme: 'gray',
// view: videoItem.season?['view_content'] ??
// videoItem.viewContent,
value: videoItem.stat.viewStr,
),
const SizedBox(width: 8),
StatDanMu(
context: context,
theme: 'gray',
// danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku,
value: videoItem.stat.danmuStr,
),
],

View File

@@ -0,0 +1,281 @@
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(
spacing: 2,
children: [
if (videoItem.goto == 'bangumi')
PBadge(
text: videoItem.bangumiBadge,
isStack: false,
size: PBadgeSize.small,
type: PBadgeType.line_primary,
fontSize: 9,
),
if (videoItem.rcmdReason != null)
PBadge(
text: videoItem.rcmdReason,
isStack: false,
size: PBadgeSize.small,
type: PBadgeType.secondary,
),
if (videoItem.goto == 'picture')
const PBadge(
text: '动态',
isStack: false,
size: PBadgeSize.small,
type: PBadgeType.line_primary,
fontSize: 9,
),
if (videoItem.isFollowed)
const PBadge(
text: '已关注',
isStack: false,
size: PBadgeSize.small,
type: PBadgeType.secondary,
),
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,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,320 +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/stat.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 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);
}
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);
}
Utils.toViewPage(
'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 (uri.startsWith('bilibili://article/')) {
// https://www.bilibili.com/read/cv27063554
dynamicType = 'read';
RegExp regex = RegExp(r'\d+');
Match match = regex.firstMatch(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) {
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.aid)),
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) ...[
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',
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) ...<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,303 +0,0 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/space/item.dart';
import 'package:PiliPlus/utils/id_utils.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 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? 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);
Utils.toViewPage(
'bvid=${bvid ?? IdUtils.av2bv(int.parse(aid!))}&cid=$cid',
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,18 +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;
@@ -33,213 +33,204 @@ class VideoCustomActions {
VideoCustomAction(
videoItem.bvid!,
'copy',
Stack(
const Stack(
clipBehavior: Clip.none,
children: [
Icon(MdiIcons.identifier, size: 16),
Icon(MdiIcons.circleOutline, size: 16),
],
),
() {
Utils.copyText(videoItem.bvid!);
},
() => Utils.copyText(videoItem.bvid!),
),
VideoCustomAction(
'稍后再看',
'pause',
Icon(MdiIcons.clockTimeEightOutline, size: 16),
const Icon(MdiIcons.clockTimeEightOutline, size: 16),
() async {
var res = await UserHttp.toViewLater(bvid: videoItem.bvid);
SmartDialog.showToast(res['msg']);
},
),
],
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}',
});
},
const Icon(MdiIcons.accountCircleOutline, size: 16),
() => Get.toNamed('/member?mid=${videoItem.owner.mid}', arguments: {
'heroTag': '${videoItem.owner.mid}',
}),
),
if (videoItem is! Item)
if (videoItem is! SpaceArchiveItem)
VideoCustomAction(
'不感兴趣', 'dislike', Icon(MdiIcons.thumbDownOutline, size: 16),
() async {
String? accessKey = Accounts.get(AccountType.recommend).accessKey;
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',
const 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(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();
}
},
);
}
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("撤销"),
),
],
)
],
),
],
),
),
),
);
},
);
}
}),
if (videoItem is! Item)
VideoCustomAction('拉黑:${videoItem.owner.name}', 'block',
Icon(MdiIcons.cancel, size: 16), () async {
await showDialog(
);
},
);
} 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',
const Icon(MdiIcons.cancel, size: 16),
() => showDialog(
context: context,
builder: (context) {
return AlertDialog(
@@ -249,7 +240,7 @@ class VideoCustomActions {
'\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'),
actions: [
TextButton(
onPressed: () => Get.back(),
onPressed: Get.back,
child: Text(
'点错了',
style: TextStyle(
@@ -272,18 +263,16 @@ class VideoCustomActions {
],
);
},
);
}),
VideoCustomAction(
"${MineController.anonymity.value ? '退出' : '进入'}无痕模式",
'anonymity',
Icon(
MineController.anonymity.value
? MdiIcons.incognitoOff
: MdiIcons.incognito,
size: 16,
),
() => MineController.onChangeAnonymity(context))
),
VideoCustomAction(
"${MineController.anonymity.value ? '退出' : '进入'}无痕模式",
'anonymity',
MineController.anonymity.value
? const Icon(MdiIcons.incognitoOff, size: 16)
: const Icon(MdiIcons.incognito, size: 16),
() => MineController.onChangeAnonymity(context),
)
];
}
}
@@ -291,7 +280,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;
@@ -301,6 +290,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