Compare commits

..

181 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
646888c06f fix import
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-17 18:15:20 +08:00
bggRGjQaUbCoE
332f6f1bb4 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-17 18:04:30 +08:00
bggRGjQaUbCoE
aaab5371b2 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-17 17:35:29 +08:00
JianGuo Wang
ad931d7ea2 add media notification handling for offline videos (#1748)
* feat: add media notification handling for offline videos

* update

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

* update

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-17 17:35:00 +08:00
My-Responsitories
377e430d74 refa: report error (#1747)
* refa: report error

* remove some reports [skip ci]

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-17 17:20:29 +08:00
My-Responsitories
a797467606 upgrade dep (#1746) 2025-11-16 15:02:20 +00:00
My-Responsitories
5ee83d902d opt: exclude analysis flutter widget (#1745) 2025-11-16 14:33:40 +00:00
My-Responsitories
27ae296b28 refa: cdn (#1743)
* refa: cdn

* feat: live cdn (WIP)

* tweaks

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

* add live quality [skip ci]

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

* mod: replace durl host

* tweak [skip ci]

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-15 20:12:21 +08:00
My-Responsitories
e589f27195 fix; set subtitle before init (#1742)
* tweka

* fix; set subtitle before init
2025-11-15 07:34:23 +00:00
bggRGjQaUbCoE
c89d6a5a59 fix wakelock
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-15 11:50:17 +08:00
bggRGjQaUbCoE
861365930d reformat
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-14 09:22:43 +08:00
bggRGjQaUbCoE
0d4d92a202 bump flutter
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-14 09:22:43 +08:00
bggRGjQaUbCoE
4c6ad0e385 increase webdav timeout
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-14 09:22:43 +08:00
iKirby
ad45e995e2 fix preferred cdn & Add more PCDN url patterns (#1739)
* Fix preferred cdn not used after changing quality

* Add more PCDN url patterns
2025-11-14 09:21:51 +08:00
bggRGjQaUbCoE
50a035a479 opt get season status
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-13 14:27:52 +08:00
bggRGjQaUbCoE
c0dbd6cbb2 migration
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-13 11:04:46 +08:00
bggRGjQaUbCoE
686af4a330 bump flutter
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-13 10:07:44 +08:00
bggRGjQaUbCoE
46aad06e34 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-13 10:05:27 +08:00
bggRGjQaUbCoE
3921b2304d opt download task
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-13 09:38:23 +08:00
My-Responsitories
bca5b0419c tweaks (#1738)
* feat: edit dm filter

* opt: browser

* feat: sb userInfo

* mod: tvPlayUrl
2025-11-13 09:36:50 +08:00
bggRGjQaUbCoE
9754b061dd check dm state
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-12 20:21:11 +08:00
My-Responsitories
407b31c5c1 refa: download video (#1737)
* opt: save pb danmaku

* refa: download video

* opt: replaceAll

* fix: wait delete

* opt: remove completer

* fix: index.json

* tweaks

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-12 19:12:17 +08:00
bggRGjQaUbCoE
37b1228552 feat: dlna
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-12 19:03:01 +08:00
bggRGjQaUbCoE
0acd9ca767 upgrade dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-12 10:50:29 +08:00
My-Responsitories
8f3c9f029c opt: const page (#1736) 2025-11-12 07:33:28 +08:00
BH8GUQ
9310732343 Mark offline cache as completed in README (#1735) 2025-11-11 17:46:11 +08:00
bggRGjQaUbCoE
e767e506f3 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-11 17:43:50 +08:00
bggRGjQaUbCoE
ef3a612338 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-11 09:32:08 +08:00
bggRGjQaUbCoE
d66a42a0aa opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-11 09:32:03 +08:00
bggRGjQaUbCoE
0f06de0047 fix save subtitle on win
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-10 12:09:59 +08:00
bggRGjQaUbCoE
963181fef2 feat: load file sub
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-10 11:31:43 +08:00
bggRGjQaUbCoE
ffd4f9ee73 feat: video download
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-09 22:06:19 +08:00
bggRGjQaUbCoE
976622df89 fix pm send btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-09 16:13:42 +08:00
bggRGjQaUbCoE
13c220338c set system brightness
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-09 15:48:52 +08:00
bggRGjQaUbCoE
1291dc77c8 stop audio service
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-09 11:34:00 +08:00
bggRGjQaUbCoE
08e5477e74 enable audio cdn by def
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-08 16:55:28 +08:00
bggRGjQaUbCoE
c4c6a2243e disable win single alt key event
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-08 16:55:28 +08:00
bggRGjQaUbCoE
58791e3e91 show vote option pic
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-08 16:55:28 +08:00
bggRGjQaUbCoE
d5bb4bc149 fix fan/follow params
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-07 19:55:59 +08:00
bggRGjQaUbCoE
3d1199363b Release 1.1.5
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-07 16:45:13 +08:00
bggRGjQaUbCoE
f225fa33e1 update proto
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-06 15:08:19 +08:00
bggRGjQaUbCoE
e85c8b3dde tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-02 12:37:20 +08:00
bggRGjQaUbCoE
737be8dcac del cache
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-01 18:26:58 +08:00
bggRGjQaUbCoE
77dd939172 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-01 15:57:17 +08:00
bggRGjQaUbCoE
0a5965a423 fix parse dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-31 18:57:31 +08:00
bggRGjQaUbCoE
a53be6814c opt tooltip style
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-31 15:12:28 +08:00
bggRGjQaUbCoE
415b8e9da3 fix audio action
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-31 12:28:59 +08:00
bggRGjQaUbCoE
f034c24d13 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-30 17:32:18 +08:00
bggRGjQaUbCoE
1ac93d6269 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-30 17:32:02 +08:00
bggRGjQaUbCoE
906c8f7999 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-29 18:28:10 +08:00
bggRGjQaUbCoE
c904a5ded8 del
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-29 18:28:04 +08:00
bggRGjQaUbCoE
0c9486f6b4 build
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-28 23:15:48 +08:00
bggRGjQaUbCoE
576740a502 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-28 21:53:07 +08:00
bggRGjQaUbCoE
b3f9f43b57 fix #1729
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-28 21:52:52 +08:00
bggRGjQaUbCoE
e7424bcc66 fix buvidActive
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-28 17:21:41 +08:00
bggRGjQaUbCoE
209ec70ea9 opt icon
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-28 17:21:00 +08:00
bggRGjQaUbCoE
3b4e251034 opt desktop pip
Closes #1478

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-28 13:49:09 +08:00
bggRGjQaUbCoE
86beb879a2 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-28 13:17:16 +08:00
bggRGjQaUbCoE
321d434141 build
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-28 11:04:12 +08:00
Axiaobo
b9d17e27b1 fix #1718 (#1723) 2025-10-28 10:45:15 +08:00
My-Responsitories
2f6f6da6c0 opt: tap (#1719)
* opt: tap

* revert: remove suspendedDm
2025-10-27 21:32:39 +08:00
bggRGjQaUbCoE
c3d3fa67f7 fix #1716
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-27 18:16:09 +08:00
bggRGjQaUbCoE
032dfd69be fix #1712
fix #1641

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-27 18:16:09 +08:00
bggRGjQaUbCoE
e9dc154642 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-27 18:16:03 +08:00
My-Responsitories
b43840b636 mod: save panel (#1706) 2025-10-25 10:23:42 +00:00
My-Responsitories
1a9d8e35ba feat: show aiConclusion (#1698) 2025-10-25 06:54:21 +00:00
My-Responsitories
ccb61415f5 tweaks (#1697)
* opt: play status

* opt: comment
2025-10-25 06:45:19 +00:00
My-Responsitories
08944241bb fix: danmaku (#1696)
* fix: post danmaku

* mod: tap danmaku

* mod: delete danmaku
2025-10-25 06:41:47 +00:00
bggRGjQaUbCoE
63030147ea bump flutter
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-25 12:16:11 +08:00
bggRGjQaUbCoE
8ff71c44ca opt dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-25 12:16:11 +08:00
bggRGjQaUbCoE
4eaf16f500 disable search default by def
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-24 20:22:06 +08:00
Axiaobo
1a9c8a62f2 package linux appimage (#1688)
* modified:   .github/workflows/linux_x64.yml

* modified:   .github/workflows/linux_arm64.yml

* modified:   .github/workflows/linux_arm64.yml
	modified:   lib/plugin/pl_player/controller.dart

* modified:   lib/plugin/pl_player/controller.dart

* modified:   lib/plugin/pl_player/controller.dart

* modified:   lib/plugin/pl_player/controller.dart

---------

Co-authored-by: Xiaobo Ch. <Axiaobo7788@163.com>
2025-10-24 18:36:46 +08:00
bggRGjQaUbCoE
4256c2b023 opt ui
Closes #1680

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-24 14:34:13 +08:00
bggRGjQaUbCoE
bbcf0dec1b upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-23 23:21:33 +08:00
bggRGjQaUbCoE
da52cac2c6 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-23 23:21:28 +08:00
bggRGjQaUbCoE
e8a32a6149 custom audio order
Closes #1636

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-21 17:22:33 +08:00
bggRGjQaUbCoE
a71a7b66f8 opt req
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-21 17:01:18 +08:00
bggRGjQaUbCoE
9808f50816 show audio playlist parts
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-21 11:35:41 +08:00
bggRGjQaUbCoE
cf86bb7e13 add match entrance
Closes #1628

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-21 10:20:42 +08:00
bggRGjQaUbCoE
ff065254ae fix get window pos
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-21 10:20:00 +08:00
bggRGjQaUbCoE
39b4c1a59b show rank fold items
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-21 10:19:17 +08:00
bggRGjQaUbCoE
28f10e0a4b check uploadPictureIconState
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-20 19:34:27 +08:00
bggRGjQaUbCoE
12c0ed5baf fix #1616
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-20 15:41:20 +08:00
bggRGjQaUbCoE
23272d285b tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-20 15:39:48 +08:00
bggRGjQaUbCoE
67b4ed65ab opt audio playlist
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-20 13:15:26 +08:00
My-Responsitories
7524b3d168 fix: hide danmaku (#1654) 2025-10-20 12:17:59 +08:00
bggRGjQaUbCoE
340a933e70 fix #1648
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-20 10:23:13 +08:00
bggRGjQaUbCoE
488ca29fc1 opt dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-19 21:31:27 +08:00
bggRGjQaUbCoE
cc00b2cc39 feat: dyn show more
Closes #1629

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-19 20:27:42 +08:00
bggRGjQaUbCoE
287cea4d6c tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-19 20:11:44 +08:00
bggRGjQaUbCoE
39e556891a upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-19 15:33:02 +08:00
bggRGjQaUbCoE
0ae4157384 create release
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-19 14:40:40 +08:00
My-Responsitories
6e1ceb1277 feat: like count (#1640) 2025-10-19 05:45:29 +00:00
bggRGjQaUbCoE
71a170deb5 audio notification
Closes #1635

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-19 11:24:14 +08:00
bggRGjQaUbCoE
9482a706da fix #1613
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 21:02:05 +08:00
bggRGjQaUbCoE
0804484a49 check cmd key
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 19:28:14 +08:00
bggRGjQaUbCoE
cdb9bb3dbc check cmd key
Closes #1630

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 19:16:55 +08:00
bggRGjQaUbCoE
6ca0de96f4 opt pip
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 17:21:14 +08:00
bggRGjQaUbCoE
d908f58528 opt pip
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 16:29:14 +08:00
bggRGjQaUbCoE
1368733a24 fix check pip
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 16:17:32 +08:00
bggRGjQaUbCoE
32e71dbf65 fix #1625
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 15:57:41 +08:00
bggRGjQaUbCoE
c9ce1af2c6 Update build
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 14:23:17 +08:00
bggRGjQaUbCoE
416f9e6a8d upgrade dm dep
Closes #1619

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 13:25:10 +08:00
bggRGjQaUbCoE
3ae3955f53 rename release files
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 13:14:05 +08:00
bggRGjQaUbCoE
464f008023 fix #1615
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 11:29:31 +08:00
bggRGjQaUbCoE
52498b3e34 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-18 11:29:31 +08:00
Axiaobo
57c57b02a5 build linux arm64 (#1610)
* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* new file:   .github/workflows/linux_arm64.yml

* modified:   .github/workflows/linux.yml
	deleted:    .github/workflows/linux_arm64.yml
	modified:   .github/workflows/win.yml

* new file:   .github/workflows/linux_arm64.yml

* modified:   .github/workflows/linux_arm64.yml

* modified:   .github/workflows/linux_arm64.yml

* modified:   .github/workflows/linux_arm64.yml
	renamed:    .github/workflows/linux.yml -> .github/workflows/linux_x64.yml

* renamed:    .github/workflows/win.yml -> .github/workflows/win_x64.yml

* modified:   .github/workflows/linux_x64.yml

---------

Co-authored-by: Zhang Yujie <Axiaobo7788@163.com>
2025-10-17 23:23:33 +08:00
My-Responsitories
b8c6868043 fix: cross import 2025-10-17 17:24:13 +08:00
My-Responsitories
8200fbf512 tweak 2025-10-17 14:38:56 +08:00
My-Responsitories
8650c96b7b feat: danmaku seekTo (#1603) 2025-10-17 06:37:25 +00:00
bggRGjQaUbCoE
15fe7787ba pause
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-17 13:28:21 +08:00
dom
d83076cb07 Update android.yml 2025-10-17 12:07:07 +08:00
bggRGjQaUbCoE
8b3b4c28a5 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-17 10:43:43 +08:00
bggRGjQaUbCoE
740c001e2f bump flutter
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-17 10:34:13 +08:00
bggRGjQaUbCoE
096b057f81 fix #1598
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-17 10:34:13 +08:00
bggRGjQaUbCoE
a161fa5e58 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-17 10:34:08 +08:00
bggRGjQaUbCoE
bebf34db23 disable windows thread merging
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-16 19:09:47 +08:00
bggRGjQaUbCoE
b95061434a upgrade danmaku dep
Closes #1583

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-16 18:56:23 +08:00
bggRGjQaUbCoE
f2a05bb970 fix #1593
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-16 18:55:47 +08:00
bggRGjQaUbCoE
6c361a047b live dm action
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-16 17:37:49 +08:00
bggRGjQaUbCoE
3fb9e22378 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-16 13:44:13 +08:00
bggRGjQaUbCoE
b2fb4c9afe opt dm action
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-16 13:21:15 +08:00
bggRGjQaUbCoE
0862c0fc87 show backbtn on mine page if needed
Closes #1580

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-15 21:34:21 +08:00
My-Responsitories
77ec78e3fe opt: singleton FollowTypeController (#1578) 2025-10-15 21:13:56 +08:00
Axiaobo
fb59c208e3 package linux rpm (#1575)
* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml

* modified:   .github/workflows/linux.yml
2025-10-15 20:17:46 +08:00
bggRGjQaUbCoE
112a06f92a custom show tray icon
Closes #1569

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-15 19:21:11 +08:00
bggRGjQaUbCoE
c10c4a6f89 windows single instance
Closes #1574

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-15 19:06:28 +08:00
bggRGjQaUbCoE
669c807b23 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-15 18:40:54 +08:00
bggRGjQaUbCoE
c9de79532a handle relation url
Closes #1566

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-15 18:28:50 +08:00
bggRGjQaUbCoE
32ce2b87db enable tap dm by def
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-15 18:28:46 +08:00
bggRGjQaUbCoE
4cfcf18bc9 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-15 18:06:51 +08:00
bggRGjQaUbCoE
14ae61f891 fix typo
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-15 18:06:51 +08:00
My-Responsitories
a2d5ecc51e feat: ImmediateTapGestureRecognizer (#1572) 2025-10-15 18:06:10 +08:00
My-Responsitories
84f972a3ab fix: report 2025-10-15 16:03:10 +08:00
bggRGjQaUbCoE
5249ceccdb opt audio playlist
Closes #1547

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-14 21:39:30 +08:00
bggRGjQaUbCoE
5035495043 pause
Closes #1559

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-14 21:39:30 +08:00
My-Responsitories
25483d71e9 fix: decompress 2025-10-14 21:23:26 +08:00
My-Responsitories
c3fa976b26 tweaks (#1562)
* opt: downloadImg use cache

* opt: uin8 cast

* non null ext
2025-10-14 11:36:43 +00:00
My-Responsitories
43beb518f4 feat: right click fullscreen (#1561) 2025-10-14 11:27:15 +00:00
bggRGjQaUbCoE
11edabb890 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-14 17:09:06 +08:00
bggRGjQaUbCoE
019cd9fda0 opt search
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-14 17:08:42 +08:00
My-Responsitories
9d747c8e2c refa: video (#1555)
* refa: video [skip ci]

* fix: scroll [skip ci]

* mod: only left click

* downgrade

* refa: background play & wakelock [skip ci]

* fix: subtitle [skip ci]

* upgrade deps

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

* mod: long press

* tweak

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

* fix [skip ci]

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

* use right pos

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

* delay showing

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

* fix: null danmaku

* remove

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-14 17:05:31 +08:00
bggRGjQaUbCoE
4cf1c25b36 fix #1545
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-13 20:35:40 +08:00
bggRGjQaUbCoE
6c6ed46aea fix #1541
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-13 19:32:29 +08:00
bggRGjQaUbCoE
e1473a453e tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-13 17:03:36 +08:00
bggRGjQaUbCoE
9f6ef0281a tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-13 14:36:38 +08:00
bggRGjQaUbCoE
84d5a24bc3 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-13 13:44:32 +08:00
bggRGjQaUbCoE
ed8c39aa76 opt player gesture
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-13 12:52:50 +08:00
bggRGjQaUbCoE
23d235b8f4 opt player gesture
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-13 12:39:22 +08:00
bggRGjQaUbCoE
8bea09b78a tap dm when debug
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-13 11:49:20 +08:00
bggRGjQaUbCoE
897fda875a opt dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-13 10:55:56 +08:00
bggRGjQaUbCoE
510bfe01be opt btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-12 21:55:39 +08:00
My-Responsitories
f6ca007815 feat: tap danmaku (#1534) 2025-10-12 21:51:59 +08:00
bggRGjQaUbCoE
35b34cb2d4 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-12 21:51:14 +08:00
My-Responsitories
5197cca69c tweaks 2025-10-12 18:56:09 +08:00
My-Responsitories
e5f0742bf6 feat: danmaku api (#1530) 2025-10-12 18:41:40 +08:00
bggRGjQaUbCoE
88d207cc24 upgrade danmaku dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-12 18:32:01 +08:00
bggRGjQaUbCoE
931fcb6f8f opt fab
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-12 17:09:50 +08:00
bggRGjQaUbCoE
e4a960ecf9 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-12 16:13:23 +08:00
My-Responsitories
e44419e088 mod: ui (#1521)
* mod: ui

* fix: -400

* tweaks

* update

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

* tweak [skip ci]

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

* tweak [skip ci]

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-12 12:12:44 +08:00
dom
16f577f3fd feat: audio page (#1518)
* feat: audio page

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

* opt ui

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

* impl intro, share, fav

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

* tweaks

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

* load prev/next

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

---------

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-11 22:16:16 +08:00
My-Responsitories
a65edab7d1 opt: env (#1510)
* opt: env

* fix

* fix: regex

* fix: android

* fix

* fix

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

* fastforge define

* fix

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-10 15:52:26 +08:00
bggRGjQaUbCoE
c0bbf8400a upgrade dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-10 13:20:53 +08:00
bggRGjQaUbCoE
1dc2da68ac opt msgNotifyMsg
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-10 13:20:47 +08:00
bggRGjQaUbCoE
3d49529272 show user name
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-09 16:48:42 +08:00
bggRGjQaUbCoE
41768656b4 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-09 11:24:44 +08:00
bggRGjQaUbCoE
c7e7b3f9c5 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-09 11:24:39 +08:00
bggRGjQaUbCoE
e0b0a98f0f opt block
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-08 23:03:59 +08:00
bggRGjQaUbCoE
ca0eb1716f feat: pgc skip
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-08 22:50:08 +08:00
bggRGjQaUbCoE
06d8296939 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-08 16:11:24 +08:00
bggRGjQaUbCoE
322885f284 opt slide
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-07 21:25:47 +08:00
bggRGjQaUbCoE
4553b86cb4 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-07 17:47:31 +08:00
bggRGjQaUbCoE
904756b6ea opt gesture
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-07 14:10:48 +08:00
bggRGjQaUbCoE
2bfa1bb6c2 tweaks
Closes #1505

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-07 14:10:29 +08:00
bggRGjQaUbCoE
8439a3d85c opt fs
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-07 12:32:36 +08:00
bggRGjQaUbCoE
454d6b9de1 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-07 11:52:29 +08:00
bggRGjQaUbCoE
44c7c44a27 tweaks
Closes #1354

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-10-07 11:52:24 +08:00
dom
40e5e2f372 Update 功能请求.yml 2025-10-06 14:15:34 +08:00
dom
138739781c Update bug-反馈.yml 2025-10-06 14:14:23 +08:00
dom
355d897ef0 Update 功能请求.yml 2025-10-06 14:10:04 +08:00
dom
a06aef2b25 Update bug-反馈.yml 2025-10-06 14:08:19 +08:00
524 changed files with 63255 additions and 9563 deletions

4
.fvmrc
View File

@@ -1,3 +1,3 @@
{
"flutter": "3.35.5"
}
"flutter": "3.38.1"
}

View File

@@ -9,10 +9,16 @@ body:
attributes:
label: 检查清单
options:
- label: 之前没有人提交过类似或相同的 bug report。
- label: 搜索了 [历史 issue](https://github.com/bggRGjQaUbCoE/PiliPlus/issues?q=is%3Aissue) ,并未发现相同问题
required: true
- label: 正在使用最新版本。
required: true
- label: 已排除网络问题
required: true
- label: 已排除账号问题
required: true
- label: 已排除设置问题
required: true
- type: checkboxes
id: assign

View File

@@ -9,10 +9,12 @@ body:
attributes:
label: 检查清单
options:
- label: 之前没有人提交过类似或相同功能请求
- label: 搜索了 [历史 issue](https://github.com/bggRGjQaUbCoE/PiliPlus/issues?q=is%3Aissue) ,并未发现相同功能请求
required: true
- label: 正在使用最新版本。
required: true
- label: 设置中未搜索到该功能
required: true
- type: checkboxes
id: assign

View File

@@ -1,84 +0,0 @@
name: Android Release
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
paths-ignore:
- "**.md"
workflow_dispatch:
jobs:
android:
runs-on: ubuntu-latest
steps:
- name: 代码迁出
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: 构建Java环境
uses: actions/setup-java@v5
with:
distribution: "zulu"
java-version: "17"
cache: "gradle"
cache-dependency-path: |
android/*.gradle*
android/**/gradle-wrapper.properties
- name: 安装Flutter
uses: subosito/flutter-action@v2
id: flutter-action
with:
channel: stable
flutter-version-file: pubspec.yaml
cache: true
- name: apply bottom sheet patch
working-directory: ${{ env.FLUTTER_ROOT }}
run: git apply $GITHUB_WORKSPACE/lib/scripts/bottom_sheet_patch.diff
# - name: 下载项目依赖
# run: flutter pub get
- name: Write key
if: github.event_name != 'pull_request'
run: |
if [ ! -z "${{ secrets.SIGN_KEYSTORE_BASE64 }}" ]; then
echo "${{ secrets.SIGN_KEYSTORE_BASE64 }}" | base64 --decode > android/app/key.jks
echo storeFile='key.jks' >> android/key.properties
echo storePassword='${{ secrets.KEYSTORE_PASSWORD }}' >> android/key.properties
echo keyAlias='${{ secrets.KEY_ALIAS }}' >> android/key.properties
echo keyPassword='${{ secrets.KEY_PASSWORD }}' >> android/key.properties
fi
- name: flutter build apk
run: |
dart lib/scripts/build.dart "android"
flutter build apk --release --split-per-abi --pub
- name: 上传
uses: actions/upload-artifact@v4
with:
name: app-arm64-v8a
path: |
build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
- name: 上传
uses: actions/upload-artifact@v4
with:
name: app-armeabi-v7a
path: |
build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk
- name: 上传
uses: actions/upload-artifact@v4
with:
name: app-x86_64
path: |
build/app/outputs/flutter-apk/app-x86_64-release.apk

168
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,168 @@
name: Build
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
paths-ignore:
- "**.md"
workflow_dispatch:
inputs:
build_android:
description: "Build Android"
required: false
default: true
type: boolean
build_ios:
description: "Build iOS"
required: false
default: true
type: boolean
build_mac:
description: "Build Mac"
required: false
default: true
type: boolean
build_win_x64:
description: "Build Win-x64"
required: false
default: true
type: boolean
build_linux_x64:
description: "Build Linux-x64"
required: false
default: true
type: boolean
tag:
description: "tag"
required: false
default: ""
type: string
jobs:
android:
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_android == 'true' }}
name: Release Android
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: 代码迁出
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: 构建Java环境
uses: actions/setup-java@v5
with:
distribution: "zulu"
java-version: "17"
cache: "gradle"
cache-dependency-path: |
android/*.gradle*
android/**/gradle-wrapper.properties
- name: 安装Flutter
uses: subosito/flutter-action@v2
id: flutter-action
with:
channel: stable
flutter-version-file: pubspec.yaml
cache: true
- name: apply bottom sheet patch
working-directory: ${{ env.FLUTTER_ROOT }}
run: git apply $GITHUB_WORKSPACE/lib/scripts/bottom_sheet_patch.diff
continue-on-error: true
- name: Write key
if: github.event_name == 'workflow_dispatch'
run: |
if [ ! -z "${{ secrets.SIGN_KEYSTORE_BASE64 }}" ]; then
echo "${{ secrets.SIGN_KEYSTORE_BASE64 }}" | base64 --decode > android/app/key.jks
echo storeFile='key.jks' >> android/key.properties
echo storePassword='${{ secrets.KEYSTORE_PASSWORD }}' >> android/key.properties
echo keyAlias='${{ secrets.KEY_ALIAS }}' >> android/key.properties
echo keyPassword='${{ secrets.KEY_PASSWORD }}' >> android/key.properties
fi
- name: Set and Extract version
shell: pwsh
run: lib/scripts/build.ps1 android
- name: flutter build apk
run: flutter build apk --release --split-per-abi --dart-define-from-file=pili_release.json --pub
- name: rename
run: |
for file in build/app/outputs/flutter-apk/app-*-release.apk; do
abi=$(echo "$file" | sed -E 's|.*app-(.*)-release\.apk|\1|')
mv "$file" "PiliPlus_android_${{ env.version }}_${abi}.apk"
done
shell: bash
- name: Release
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.inputs.tag }}
name: ${{ github.event.inputs.tag }}
files: |
PiliPlus_android_*.apk
- name: 上传
uses: actions/upload-artifact@v4
with:
name: Android_arm64-v8a
path: |
PiliPlus_android_*_arm64-v8a.apk
- name: 上传
uses: actions/upload-artifact@v4
with:
name: Android_armeabi-v7a
path: |
PiliPlus_android_*_armeabi-v7a.apk
- name: 上传
uses: actions/upload-artifact@v4
with:
name: Android_x86_64
path: |
PiliPlus_android_*_x86_64.apk
ios:
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_ios == 'true' }}
uses: ./.github/workflows/ios.yml
permissions: write-all
with:
tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
mac:
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_mac == 'true' }}
uses: ./.github/workflows/mac.yml
permissions: write-all
with:
tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
win_x64:
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_win_x64 == 'true' }}
uses: ./.github/workflows/win_x64.yml
permissions: write-all
with:
tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
linux_x64:
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_linux_x64 == 'true' }}
uses: ./.github/workflows/linux_x64.yml
permissions: write-all
with:
tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}

View File

@@ -1,19 +1,14 @@
name: Build for iOS
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
paths-ignore:
- "**.md"
workflow_dispatch:
workflow_call:
inputs:
branch:
tag:
description: "tag"
required: false
default: "main"
default: ""
type: string
workflow_dispatch:
jobs:
build-macos-app:
@@ -23,7 +18,6 @@ jobs:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{ github.event.inputs.branch }}
fetch-depth: 0
- name: Setup flutter
@@ -32,16 +26,27 @@ jobs:
channel: stable
flutter-version-file: pubspec.yaml
- name: Set and Extract version
shell: pwsh
run: lib/scripts/build.ps1
- name: Build iOS
run: |
chmod +x lib/scripts/build.dart
dart lib/scripts/build.dart
flutter build ios --release --no-codesign
flutter build ios --release --no-codesign --dart-define-from-file=pili_release.json
ln -sf ./build/ios/iphoneos Payload
zip -r9 ios-release-no-sign.ipa Payload/runner.app
zip -r9 PiliPlus_ios_${{env.version}}.ipa Payload/runner.app
- name: Release
if: ${{ github.event.inputs.tag != '' }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.inputs.tag }}
name: ${{ github.event.inputs.tag }}
files: |
PiliPlus_ios_*.ipa
- name: Upload ios release
uses: actions/upload-artifact@v4
with:
name: ios-release
path: ios-release-no-sign.ipa
name: iOS-release
path: PiliPlus_ios_*.ipa

View File

@@ -1,123 +0,0 @@
name: Build for Linux
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
paths-ignore:
- "**.md"
workflow_dispatch:
inputs:
branch:
required: false
default: "main"
jobs:
build-linux-app:
name: Release Linux
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
ref: ${{ github.event.inputs.branch }}
fetch-depth: 0
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang cmake libgtk-3-dev ninja-build libayatana-appindicator3-dev unzip webkit2gtk-4.1 libasound2-dev
sudo apt-get install -y gcc g++ autoconf automake debhelper glslang-dev ladspa-sdk xutils-dev libasound2-dev \
libarchive-dev libbluray-dev libbs2b-dev libcaca-dev libcdio-paranoia-dev libdrm-dev \
libdav1d-dev libdvdnav-dev libegl1-mesa-dev libepoxy-dev libfontconfig-dev libfreetype6-dev \
libfribidi-dev libgl1-mesa-dev libgbm-dev libgme-dev libgsm1-dev libharfbuzz-dev libjpeg-dev \
libbrotli-dev liblcms2-dev libmodplug-dev libmp3lame-dev libopenal-dev \
libopus-dev libopencore-amrnb-dev libopencore-amrwb-dev libpulse-dev librtmp-dev \
libsdl2-dev libsixel-dev libssh-dev libsoxr-dev libspeex-dev libtool \
libv4l-dev libva-dev libvdpau-dev libvorbis-dev libvo-amrwbenc-dev \
libunwind-dev libvpx-dev libwayland-dev libx11-dev libxext-dev \
libxkbcommon-dev libxrandr-dev libxss-dev libxv-dev libxvidcore-dev \
linux-libc-dev nasm ninja-build pkg-config python3 python3-docutils wayland-protocols \
x11proto-core-dev zlib1g-dev libfdk-aac-dev libtheora-dev libwebp-dev \
unixodbc-dev libpq-dev libxxhash-dev libaom-dev \
libgtk-3-0 libblkid1 liblzma5 libmpv-dev
shell: bash
- name: Setup flutter
uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version-file: pubspec.yaml
cache: true
- name: Set and Extract version
run: |
dart lib/scripts/build.dart
VERSION=$(cat pubspec.yaml | grep 'version:' | sed 's/version: //g' | tr -d '[:space:]')
echo "version=$VERSION" >> $GITHUB_ENV
shell: bash
#TODO: deb and rpm packages need to be build
- name: Build Linux
run: flutter build linux --release -v --pub
- name: Package .tar.gz
run: tar -zcvf PiliPlus_linux_${{ env.version }}_amd64.tar.gz -C build/linux/x64/release/bundle .
- name: Packege deb
run: |
printf "建立构建目录...\n"
mkdir "PiliPlus_linux_${{ env.version }}_amd64"
pushd "PiliPlus_linux_${{ env.version }}_amd64"
mkdir -p opt/PiliPlus
mkdir -p usr/share/applications
mkdir -p usr/share/icons/hicolor/512x512/apps
printf "复制文件...\n"
cp -r ../build/linux/x64/release/bundle/* opt/PiliPlus
cp -r ../assets/linux/DEBIAN .
cp ../assets/linux/piliplus.desktop usr/share/applications
cp ../assets/images/logo/logo.png usr/share/icons/hicolor/512x512/apps/piliplus.png
printf "修改控制文件...\n"
# 替换版本号
sed -i "2s/version_need_change/${{ env.version }}/g" DEBIAN/control
# 计算安装大小并替换
SIZE_KB=$(du -s -b --apparent-size . | awk '{print int($1)}')
SIZE_KB=$(($SIZE_KB - $(du -s -b --apparent-size DEBIAN | awk '{print int($1)}')))
SIZE_KB=$(echo $SIZE_KB | awk '{print int($1/1024 + 0.999)}')
printf "\t安装大小: %s KB\n" "$SIZE_KB"
sed -i "9s/size_need_change/${SIZE_KB}/g" DEBIAN/control
printf "生成并写入 md5sums ...\n"
md5sum opt/PiliPlus/piliplus >> DEBIAN/md5sums
md5sum opt/PiliPlus/lib/* >> DEBIAN/md5sums
md5sum opt/PiliPlus/data/icudtl.dat >> DEBIAN/md5sums
printf "设置权限...\n"
chmod 0644 DEBIAN/control
chmod 0644 DEBIAN/md5sums
chmod 0755 DEBIAN/postinst
chmod 0755 DEBIAN/postrm
chmod 0755 DEBIAN/prerm
printf "打包 deb 文件...\n"
popd
dpkg-deb --build --verbose --root-owner-group "PiliPlus_linux_${{ env.version }}_amd64"
printf "完成: PiliPlus_linux_%s_amd64.deb\n" "${{ env.version }}"
shell: bash
- name: Upload linux targz package
uses: actions/upload-artifact@v4
with:
name: Linux_targz_packege
path: PiliPlus_linux_*.tar.gz
- name: Upload linux deb package
uses: actions/upload-artifact@v4
with:
name: Linux_deb_package
path: PiliPlus_linux_*.deb

260
.github/workflows/linux_x64.yml vendored Normal file
View File

@@ -0,0 +1,260 @@
name: Build for Linux x64
on:
workflow_call:
inputs:
tag:
description: "tag"
required: false
default: ""
type: string
workflow_dispatch:
jobs:
build-linux-app:
name: Release Linux x64
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang cmake libgtk-3-dev ninja-build libayatana-appindicator3-dev unzip webkit2gtk-4.1 libasound2-dev rpm patchelf
sudo apt-get install -y gcc g++ autoconf automake debhelper glslang-dev ladspa-sdk xutils-dev libasound2-dev \
libarchive-dev libbluray-dev libbs2b-dev libcaca-dev libcdio-paranoia-dev libdrm-dev \
libdav1d-dev libdvdnav-dev libegl1-mesa-dev libepoxy-dev libfontconfig-dev libfreetype6-dev \
libfribidi-dev libgl1-mesa-dev libgbm-dev libgme-dev libgsm1-dev libharfbuzz-dev libjpeg-dev \
libbrotli-dev liblcms2-dev libmodplug-dev libmp3lame-dev libopenal-dev \
libopus-dev libopencore-amrnb-dev libopencore-amrwb-dev libpulse-dev librtmp-dev \
libsdl2-dev libsixel-dev libssh-dev libsoxr-dev libspeex-dev libtool \
libv4l-dev libva-dev libvdpau-dev libvorbis-dev libvo-amrwbenc-dev \
libunwind-dev libvpx-dev libwayland-dev libx11-dev libxext-dev \
libxkbcommon-dev libxrandr-dev libxss-dev libxv-dev libxvidcore-dev \
linux-libc-dev nasm ninja-build pkg-config python3 python3-docutils wayland-protocols \
x11proto-core-dev zlib1g-dev libfdk-aac-dev libtheora-dev libwebp-dev \
unixodbc-dev libpq-dev libxxhash-dev libaom-dev \
libgtk-3-0 libblkid1 liblzma5 libmpv-dev
shell: bash
- name: Setup flutter
uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version-file: pubspec.yaml
cache: true
- name: Set and Extract version
shell: pwsh
run: lib/scripts/build.ps1
#TODO: deb and rpm packages need to be build
- name: Build Linux
run: flutter build linux --release -v --pub --dart-define-from-file=pili_release.json
- name: Package .tar.gz
run: tar -zcvf PiliPlus_linux_${{ env.version }}_amd64.tar.gz -C build/linux/x64/release/bundle .
- name: Packege deb
run: |
printf "建立构建目录...\n"
mkdir "PiliPlus_linux_${{ env.version }}_amd64"
pushd "PiliPlus_linux_${{ env.version }}_amd64"
mkdir -p opt/PiliPlus
mkdir -p usr/share/applications
mkdir -p usr/share/icons/hicolor/512x512/apps
printf "复制文件...\n"
cp -r ../build/linux/x64/release/bundle/* opt/PiliPlus
cp -r ../assets/linux/DEBIAN .
cp ../assets/linux/piliplus.desktop usr/share/applications
cp ../assets/images/logo/logo.png usr/share/icons/hicolor/512x512/apps/piliplus.png
printf "修改控制文件...\n"
# 替换版本号
sed -i "2s/version_need_change/${{ env.version }}/g" DEBIAN/control
# 计算安装大小并替换
SIZE_KB=$(du -s -b --apparent-size . | awk '{print int($1)}')
SIZE_KB=$(($SIZE_KB - $(du -s -b --apparent-size DEBIAN | awk '{print int($1)}')))
SIZE_KB=$(echo $SIZE_KB | awk '{print int($1/1024 + 0.999)}')
printf "\t安装大小: %s KB\n" "$SIZE_KB"
sed -i "9s/size_need_change/${SIZE_KB}/g" DEBIAN/control
printf "生成并写入 md5sums ...\n"
md5sum opt/PiliPlus/piliplus >> DEBIAN/md5sums
md5sum opt/PiliPlus/lib/* >> DEBIAN/md5sums
md5sum opt/PiliPlus/data/icudtl.dat >> DEBIAN/md5sums
printf "设置权限...\n"
chmod 0644 DEBIAN/control
chmod 0644 DEBIAN/md5sums
chmod 0755 DEBIAN/postinst
chmod 0755 DEBIAN/postrm
chmod 0755 DEBIAN/prerm
printf "打包 deb 文件...\n"
popd
dpkg-deb --build --verbose --root-owner-group "PiliPlus_linux_${{ env.version }}_amd64"
printf "完成: PiliPlus_linux_%s_amd64.deb\n" "${{ env.version }}"
shell: bash
- name: Packege rpm
run: |
printf "建立 RPM 构建目录...\n"
RPM_BUILD_ROOT="$PWD/rpm_build"
mkdir -p "$RPM_BUILD_ROOT/BUILD" "$RPM_BUILD_ROOT/RPMS" "$RPM_BUILD_ROOT/SOURCES" "$RPM_BUILD_ROOT/SPECS" "$RPM_BUILD_ROOT/SRPMS"
printf "准备源码归档(仅包含运行时与元数据)...\n"
DATE="$(date '+%a %b %d %Y')"
SRC_DIR="$PWD/piliplus-${{ env.version }}"
mkdir -p "$SRC_DIR/bundle" "$SRC_DIR/assets"
cp -r build/linux/x64/release/bundle/* "$SRC_DIR/bundle/"
cp assets/linux/piliplus.desktop "$SRC_DIR/assets/piliplus.desktop"
cp assets/images/logo/logo.png "$SRC_DIR/assets/piliplus.png"
tar -zcvf "$RPM_BUILD_ROOT/SOURCES/piliplus-${{ env.version }}.tar.gz" -C "$PWD" "piliplus-${{ env.version }}"
printf "生成 spec 文件...\n"
cat > "$RPM_BUILD_ROOT/SPECS/piliplus.spec" <<EOF
Name: piliplus
Version: ${{ env.version }}
Release: 1%{?dist}
Summary: PiliPlus Linux Version
License: GPL-3.0
Source0: piliplus-${{ env.version }}.tar.gz
Requires: desktop-file-utils, hicolor-icon-theme
%description
使用 Flutter 开发的 BiliBili 第三方客户端
%prep
%setup -q -n piliplus-${{ env.version }}
%build
%install
mkdir -p %{buildroot}/opt/PiliPlus
cp -r bundle/* %{buildroot}/opt/PiliPlus/
# 二进制权限与命令行入口
chmod 755 %{buildroot}/opt/PiliPlus/piliplus
mkdir -p %{buildroot}/usr/bin
ln -sf /opt/PiliPlus/piliplus %{buildroot}/usr/bin/piliplus
# 桌面集成
mkdir -p %{buildroot}/usr/share/applications
install -m 644 assets/piliplus.desktop %{buildroot}/usr/share/applications/piliplus.desktop
mkdir -p %{buildroot}/usr/share/icons/hicolor/512x512/apps
install -m 644 assets/piliplus.png %{buildroot}/usr/share/icons/hicolor/512x512/apps/piliplus.png
%post
update-desktop-database -q || true
gtk-update-icon-cache -q -t -f %{_datadir}/icons/hicolor || true
%postun
update-desktop-database -q || true
gtk-update-icon-cache -q -t -f %{_datadir}/icons/hicolor || true
%files
/opt/PiliPlus
/usr/bin/piliplus
/usr/share/applications/piliplus.desktop
/usr/share/icons/hicolor/512x512/apps/piliplus.png
%changelog
* DATE - ${{ env.version }}-1
- Initial RPM release
EOF
sed -i "s/DATE/${DATE}/g" "$RPM_BUILD_ROOT/SPECS/piliplus.spec"
printf "构建 RPM 包...\n"
rpmbuild --define "_topdir $RPM_BUILD_ROOT" -bb "$RPM_BUILD_ROOT/SPECS/piliplus.spec"
printf "移动生成的 RPM...\n"
find "$RPM_BUILD_ROOT/RPMS" -name "*.rpm" -exec mv {} "PiliPlus_linux_${{ env.version }}_amd64.rpm" \;
printf "完成: PiliPlus_linux_%s_amd64.rpm\n" "${{ env.version }}"
shell: bash
- name: Package AppImage
run: |
printf "下载 appimagetool...\n"
wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
printf "建立 AppDir 目录结构...\n"
APPDIR="PiliPlus.AppDir"
mkdir -p "$APPDIR/usr/bin"
mkdir -p "$APPDIR/usr/lib"
mkdir -p "$APPDIR/usr/share/applications"
mkdir -p "$APPDIR/usr/share/icons/hicolor/512x512/apps"
printf "复制应用文件...\n"
cp -r build/linux/x64/release/bundle/* "$APPDIR/usr/bin/"
printf "复制桌面文件和图标...\n"
cp assets/linux/piliplus.desktop "$APPDIR/piliplus.desktop"
cp assets/linux/piliplus.desktop "$APPDIR/usr/share/applications/piliplus.desktop"
cp assets/images/logo/logo.png "$APPDIR/piliplus.png"
cp assets/images/logo/logo.png "$APPDIR/usr/share/icons/hicolor/512x512/apps/piliplus.png"
printf "创建 AppRun 启动脚本...\n"
cat > "$APPDIR/AppRun" <<'APPRUN_EOF'
#!/bin/bash
SELF=$(readlink -f "$0")
HERE=${SELF%/*}
export PATH="${HERE}/usr/bin:${PATH}"
export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}"
exec "${HERE}/usr/bin/piliplus" "$@"
APPRUN_EOF
chmod +x "$APPDIR/AppRun"
printf "修改桌面文件中的 Exec 路径...\n"
sed -i 's|Exec=piliplus|Exec=piliplus|g' "$APPDIR/piliplus.desktop"
sed -i 's|Icon=piliplus|Icon=piliplus|g' "$APPDIR/piliplus.desktop"
printf "打包 AppImage...\n"
ARCH=x86_64 ./appimagetool-x86_64.AppImage "$APPDIR" "PiliPlus_linux_${{ env.version }}_amd64.AppImage"
printf "完成: PiliPlus_linux_%s_amd64.AppImage\n" "${{ env.version }}"
shell: bash
- name: Release
if: ${{ github.event.inputs.tag != '' }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.inputs.tag }}
name: ${{ github.event.inputs.tag }}
files: |
PiliPlus_linux_*.tar.gz
PiliPlus_linux_*.deb
PiliPlus_linux_*.rpm
PiliPlus_linux_*.AppImage
- name: Upload linux targz package
uses: actions/upload-artifact@v4
with:
name: Linux_targz_amd64_packege
path: PiliPlus_linux_*.tar.gz
- name: Upload linux deb package
uses: actions/upload-artifact@v4
with:
name: Linux_deb_amd64_package
path: PiliPlus_linux_*.deb
- name: Upload linux rpm package
uses: actions/upload-artifact@v4
with:
name: Linux_rpm_amd64_package
path: PiliPlus_linux_*.rpm
- name: Upload linux AppImage package
uses: actions/upload-artifact@v4
with:
name: Linux_AppImage_amd64_package
path: PiliPlus_linux_*.AppImage

View File

@@ -1,19 +1,14 @@
name: Build for Mac
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
paths-ignore:
- "**.md"
workflow_dispatch:
workflow_call:
inputs:
branch:
tag:
description: "tag"
required: false
default: "main"
default: ""
type: string
workflow_dispatch:
jobs:
build-mac-app:
@@ -23,7 +18,6 @@ jobs:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{ github.event.inputs.branch }}
fetch-depth: 0
- name: Setup flutter
@@ -32,12 +26,12 @@ jobs:
channel: stable
flutter-version-file: pubspec.yaml
- name: Set and Extract version
shell: pwsh
run: lib/scripts/build.ps1
- name: Build Mac
run: |
dart lib/scripts/build.dart
VERSION=$(cat pubspec.yaml | grep 'version:' | sed 's/version: //g' | tr -d '[:space:]')
echo "version=$VERSION" >> $GITHUB_ENV
flutter build macos --release
run: flutter build macos --release --dart-define-from-file=pili_release.json
- name: Prepare Upload
run: |
@@ -48,8 +42,17 @@ jobs:
- name: Rename DMG
run: mv PiliPlus*.dmg PiliPlus_macos_${{ env.version }}.dmg
- name: Release
if: ${{ github.event.inputs.tag != '' }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.inputs.tag }}
name: ${{ github.event.inputs.tag }}
files: |
PiliPlus_macos_*.dmg
- name: Upload macos release
uses: actions/upload-artifact@v4
with:
name: macos-release
path: PiliPlus*.dmg
name: macOS-release
path: PiliPlus_macos_*.dmg

View File

@@ -1,29 +1,23 @@
name: Build for Windows
name: Build for Windows x64
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
paths-ignore:
- "**.md"
workflow_dispatch:
workflow_call:
inputs:
branch:
tag:
description: "tag"
required: false
default: "main"
default: ""
type: string
workflow_dispatch:
jobs:
build-windows-app:
name: Release Windows
name: Release Windows x64
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{ github.event.inputs.branch }}
fetch-depth: 0
- name: Setup flutter
@@ -40,29 +34,47 @@ jobs:
- name: Add Chinese language file for Inno Setup
run: |
Copy-Item "windows/packaging/exe/ChineseSimplified.isl" "C:\Program Files (x86)\Inno Setup 6\Languages\ChineseSimplified.isl"
shell: powershell
shell: pwsh
- name: Set and Extract version
shell: pwsh
run: lib/scripts/build.ps1
- name: Build Windows
run: |
dart lib/scripts/build.dart
flutter build windows --release
fastforge package --platform windows --targets exe
fastforge package --platform windows --targets exe --flutter-build-args="dart-define-from-file=pili_release.json"
- name: Prepare Upload
run: |
mkdir -p Release/PiliPlus-Win
mkdir -p PiliPlus-Win-Setup
mv build/windows/x64/runner/Release/* Release/PiliPlus-Win/
mv dist/**/*.exe PiliPlus-Win-Setup/
mv dist/**/*.exe PiliPlus-Win-Setup/PiliPlus_windows_${{env.version}}_x64_setup.exe
- name: Compress
if: ${{ github.event.inputs.tag != '' }}
run: |
Compress-Archive -Path "Release/PiliPlus-Win" -DestinationPath "PiliPlus_windows_${{env.version}}_x64.zip"
shell: pwsh
- name: Release
if: ${{ github.event.inputs.tag != '' }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.inputs.tag }}
name: ${{ github.event.inputs.tag }}
files: |
PiliPlus_windows_*.zip
PiliPlus-Win-Setup/PiliPlus_windows_*.exe
- name: Upload windows file release
uses: actions/upload-artifact@v4
with:
name: windows-release
name: Windows-file-x64-release
path: Release
- name: Upload windows setup release
uses: actions/upload-artifact@v4
with:
name: windows-setup-release
name: Windows-setup-x64-release
path: PiliPlus-Win-Setup

10
.gitignore vendored
View File

@@ -137,9 +137,13 @@ app.*.symbols
!.vscode/launch.json
!.vscode/tasks.json
/lib/build_config.dart
devtools_options.yaml
# FVM Version Cache
.fvm/
.fvm/
pili_release.json
dist
test.dart

37
.vscode/launch.json vendored
View File

@@ -1,48 +1,25 @@
{
// 使用 IntelliSense 了解相关属性。
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug",
"name": "PiliPlus",
"request": "launch",
"type": "dart",
"preLaunchTask": "Update build_config"
"type": "dart"
},
{
"name": "Profile",
"name": "PiliPlus (profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile",
"preLaunchTask": "Update build_config"
"flutterMode": "profile"
},
{
"name": "Release",
"name": "PiliPlus (release mode)",
"request": "launch",
"type": "dart",
"flutterMode": "release",
"preLaunchTask": "Update build_config"
},
{
"name": "Debug (FVM)",
"request": "launch",
"type": "dart",
"preLaunchTask": "Update build_config (FVM)"
},
{
"name": "Profile (FVM)",
"request": "launch",
"type": "dart",
"flutterMode": "profile",
"preLaunchTask": "Update build_config (FVM)"
},
{
"name": "Release (FVM)",
"request": "launch",
"type": "dart",
"flutterMode": "release",
"preLaunchTask": "Update build_config (FVM)"
"flutterMode": "release"
}
]
}

25
.vscode/tasks.json vendored
View File

@@ -1,25 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Update build_config",
"command": "dart lib/scripts/build.dart dev",
"type": "shell",
"problemMatcher": [],
"presentation": {
"reveal": "always"
},
"group": "build"
},
{
"label": "Update build_config (FVM)",
"command": "fvm dart lib/scripts/build.dart dev",
"type": "shell",
"problemMatcher": [],
"presentation": {
"reveal": "always"
},
"group": "build"
}
]
}

View File

@@ -43,8 +43,14 @@
## feat
- [x] DLNA 投屏
- [x] 离线缓存/播放
- [x] 移动端支持点击弹幕悬停,点赞、复制、举报 by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] 播放音频
- [x] 跳过番剧片头/片尾
- [x] 安卓端 `loudnorm` 适配 by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] Win/Mac 支持极验、短信登录 by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] 视频截取 GIF by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] 视频截取动图 by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] AI 原声翻译
- [x] SuperChat
- [x] 播放课堂视频
@@ -147,7 +153,7 @@
- [x] 粉丝、关注用户、拉黑用户查看
- [x] 用户主页查看
- [x] 关注/取关用户
- [ ] 离线缓存
- [x] 离线缓存
- [x] 稍后再看
- [x] 观看记录
- [x] 我的收藏

View File

@@ -13,6 +13,7 @@ analyzer:
exclude:
- lib/grpc/bilibili/**
- lib/grpc/google/**
- lib/common/widgets/flutter/**
formatter:
trailing_commas: preserve

View File

@@ -210,4 +210,5 @@
-->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
</manifest>

View File

@@ -1,5 +1,6 @@
package com.example.piliplus
import android.app.PictureInPictureParams
import android.app.SearchManager
import android.content.ComponentName
import android.content.Intent
@@ -42,7 +43,10 @@ class MainActivity : AudioServiceActivity() {
val cookies = call.argument<List<String>>("cookies") ?: emptyList<String>()
val intent = Intent().apply {
component = ComponentName("icu.freedomIntrovert.biliSendCommAntifraud", "icu.freedomIntrovert.biliSendCommAntifraud.ByXposedLaunchedActivity")
component = ComponentName(
"icu.freedomIntrovert.biliSendCommAntifraud",
"icu.freedomIntrovert.biliSendCommAntifraud.ByXposedLaunchedActivity"
)
putExtra("action", action)
putExtra("oid", oid.toLong())
putExtra("type", type)
@@ -51,23 +55,27 @@ class MainActivity : AudioServiceActivity() {
putExtra("parent", parent.toLong())
putExtra("ctime", ctime.toLong())
putExtra("comment_text", commentText)
if(pictures != null)
if (pictures != null)
putExtra("pictures", pictures)
putExtra("source_id", sourceId)
putExtra("uid", uid.toLong())
putStringArrayListExtra("cookies", ArrayList(cookies))
}
startActivity(intent)
} catch (_: Exception) {}
} catch (_: Exception) {
}
}
"linkVerifySettings" -> {
val uri = ("package:" + context.packageName).toUri()
try {
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, uri)
} else {
Intent("android.intent.action.MAIN", uri).setClassName("com.android.settings",
"com.android.settings.applications.InstalledAppOpenByDefaultActivity")
Intent("android.intent.action.MAIN", uri).setClassName(
"com.android.settings",
"com.android.settings.applications.InstalledAppOpenByDefaultActivity"
)
}
context.startActivity(intent)
} catch (_: Throwable) {
@@ -75,33 +83,56 @@ class MainActivity : AudioServiceActivity() {
context.startActivity(intent)
}
}
"music" -> {
val title = call.argument<String>("title")
val intent = Intent(MediaStore.INTENT_ACTION_MEDIA_SEARCH).apply {
putExtra(SearchManager.QUERY, title)
putExtra(MediaStore.EXTRA_MEDIA_TITLE, title)
call.argument<String?>("artist")?.let { putExtra(MediaStore.EXTRA_MEDIA_ARTIST, it) }
call.argument<String?>("album")?.let { putExtra(MediaStore.EXTRA_MEDIA_ALBUM, it) }
call.argument<String?>("artist")
?.let { putExtra(MediaStore.EXTRA_MEDIA_ARTIST, it) }
call.argument<String?>("album")
?.let { putExtra(MediaStore.EXTRA_MEDIA_ALBUM, it) }
addCategory(Intent.CATEGORY_DEFAULT)
}
try {
if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
if (packageManager.resolveActivity(
intent,
PackageManager.MATCH_DEFAULT_ONLY
) != null
) {
startActivity(intent)
result.success(true)
return@setMethodCallHandler
}
} catch (_: Throwable) {}
} catch (_: Throwable) {
}
try {
intent.action = MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH
if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
if (packageManager.resolveActivity(
intent,
PackageManager.MATCH_DEFAULT_ONLY
) != null
) {
startActivity(intent)
result.success(true)
return@setMethodCallHandler
}
} catch (_: Throwable) {}
} catch (_: Throwable) {
}
result.success(false)
}
"setPipAutoEnterEnabled" -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val params = PictureInPictureParams.Builder()
.setAutoEnterEnabled(call.argument<Boolean>("autoEnable") ?: false)
.build()
setPictureInPictureParams(params)
}
}
else -> result.notImplemented()
}
}
@@ -124,6 +155,7 @@ class MainActivity : AudioServiceActivity() {
}
override fun onDestroy() {
stopService(Intent(this, com.ryanheise.audioservice.AudioService::class.java))
super.onDestroy()
android.os.Process.killProcess(android.os.Process.myPid())
exitProcess(0)
@@ -134,7 +166,10 @@ class MainActivity : AudioServiceActivity() {
methodChannel.invokeMethod("onUserLeaveHint", null)
}
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) {
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration?
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
MethodChannel(
flutterEngine!!.dartExecutor.binaryMessenger,

Binary file not shown.

View File

@@ -1,11 +0,0 @@
## 1.0.0
### 初始版本
+ 直播、推荐、动态功能
+ 投稿、番剧播放功能
+ 播放器手势支持
+ 画质、音质、解码格式支持
+ 点赞、投币、收藏功能
+ 关注/取关、用户主页功能
+ 评论功能
+ 历史记录、稍后再看功能

View File

@@ -1,7 +0,0 @@
## 1.0.1
### 修复
+ 升级播放器依赖
+ android平台 AV1格式视频支持
+ 视频全屏功能

View File

@@ -1,4 +0,0 @@
## 1.0.10
### 修复
+ 长按倍速抬起后未恢复默认倍速

View File

@@ -1,26 +0,0 @@
## 1.0.11
### 新功能
+ 适配了原生媒体通知栏 @Daydreamer-riri
+ 视频主题图标 @Daydreamer-riri
+ 关闭软件后自动画中画播放
+ UP主分组管理
+ md2样式底栏
+
### 修复
+ 历史记录记忆播放
+ 部分类型视频连播
+ 播放速度选择框不支持返回手势
+ 播放速度选择框不支持返回手势
+ 视频播放速度总是显示1.0X
+ 评论页面计数错误
+ 退出视频还有声音
### 优化
+ 视频加载速度
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,11 +0,0 @@
## 1.0.12
### 修复
+ iOS端视频播放时没有声音
+ 超过6分钟弹幕不显示
+ 视频详情页网络异常
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,22 +0,0 @@
## 1.0.13
### 新功能
+ 视频详情页稍后再看
+ 发送弹幕 感谢@orz12
+ 消息展示
+ up主页显示获赞数
+ up主页显示合集
+ 视频详情页「ai总结」增加开关
### 修复
+ 首页推荐问题(需要重新登录)
+ 长按倍速逻辑
+ 视频详情页网络异常
### 优化
+ 设置面板样式 感谢@GuMengYu @KoolShow
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,28 +0,0 @@
## 1.0.14
圣诞节快乐~ 🎉
大部分内容由@orz12提供,感谢👏
### 修复
+ 全屏弹幕消失
+ iOS全屏/退出全屏视频暂停
+ 个人主页关注状态
+ 视频合集向下滑动UI问题
+ 媒体库滑动底栏不隐藏
+ 个人主页动态加载问题 * 2
+ 未登录状态访问个人主页异常
+ 视频搜索标题特殊字符转义
+ iOS闪退
+ 消息页面夜间模式异常
+ 消息页面含有撤回消息时异常
+ 弹幕速度
### 优化
+ 全屏播放方案优化
+ 弹幕加载逻辑优化
+ 点赞、投币逻辑优化
+ 进度条及播放时间渲染优化
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,22 +0,0 @@
## 1.0.15
元旦快乐~ 🎉
### 功能
+ 转发动态评论展示
+ 推荐、最热、收藏视频增肌日期显示
### 修复
+ 全屏播放相关问题
+ 评论区@用户展示问题
+ 登录状态闪退问题
+ pip意外触发问题
+ 动态页tab切换样式问题
### 优化
+ 首页默认使用web端推荐
+ 取消iOS路由切换效果
+ 视频分享中添加Up主
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,15 +0,0 @@
## 1.0.16
### 功能
+ toast 背景支持透明度调节
### 修复
+ web端推荐未展示【已关注】
+ up主动态页异常
+ 未打开自动播放时,视频详情页异常
+ 视频暂停状态取消自动ip
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,39 +0,0 @@
## 1.0.17
### 功能
+ 视频全屏时隐藏进度条
+ 动态内容增加投稿跳转
+ 未开启自动播放时点击封面播放
+ 弹幕发送标识
+ 定时关闭
+ 推荐视频卡片拉黑up功能
+ 首页tabbar编辑排序
### 修复
+ 连续跳转搜索页未刷新
+ 搜索结果为空时页面异常
+ 评论区链接解析
+ 视频全屏状态栏背景色
+ 私信对话气泡位置
+ 设置up关注分组样式
+ 每次推荐请求数据相同
+ iOS代理网络异常
+ 双击切换播放状态无声
+ 设置自定义倍速白屏
+ 免登录查看1080p
### 优化
+ 首页web端推荐观看数展示
+ 首页web端推荐接口更新
+ 首页样式
+ 搜索页跳转
+ 弹幕资源优化
+ 图片渲染占用内存优化(部分)
+ 两次返回退出应用
+ schame 补充
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,16 +0,0 @@
## 1.0.18
### 功能
### 修复
### 优化
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,15 +0,0 @@
## 1.0.19
### 修复
+ 视频404、评论加载错误
+ bvav转换
### 优化
+ 视频详情页内存占用
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,19 +0,0 @@
## 1.0.2
### 新功能
+ 自动检查更新
+ 封面图片保存
+ 动态跳转番剧
+ 历史记录番剧记忆播放
+ 一键清空稍后再看
### 修复
+ 切换分P cid未切换
+ cookie存储问题
+ 登录/退出登录问题
### 优化
+ 页面空/异常状态样式
+ 退出登录提示
+ 请求节流
+ 全屏播放

View File

@@ -1,19 +0,0 @@
## 1.0.3
建议卸载1.0.2版本,重新安装
### 新功能
+ 底部播放进度条设置
+ 复制图片链接
### 修复
+ 用户数据格式修改
+ video Fit
+ 没有audio 资源的视频异常
+ 评论区域图片无法点击
+ 视频进度条拖动问题
### 优化
+ 页面空/异常状态样式
+ 部分页面样式
+ 图片预览页面样式

View File

@@ -1,21 +0,0 @@
## 1.0.4
### 新功能
+ 热搜刷新
+ 视频搜索排序、筛选
+ app字体大小自定义
+ app主题色自定义
+ 「课堂」类动态渲染
### 修复
+ 搜索词联想richText渲染异常
+ 部分动态点赞异常
+ 默认视频解码格式
+ 搜索页面返回搜索词未清空
+ 动态详情评论加载异常
+ 动态页面下拉刷新数据异常
### 优化
+ 一些样式修改
+ 取消热搜词缓存

View File

@@ -1,30 +0,0 @@
## 1.0.5
主要是bug修复跟一部分小功能弹幕功能需要下一版。
问题反馈请前往QQ频道或提交issues。
感谢🙏酷友「无力感*」「斤斤计较呀」「Pseudopamine」
### 新功能
+ 高帧率支持
+ 默认评论排序设置
+ 默认动态类别设置
+ 动态合集查看
+ 同时观看人数
+ iOS路由切换效果
### 修复
+ 收藏夹翻页
+ 首页搜索框频繁点击消失
+ 评论排序切换空白
+ 快速返回首页
+ 重复进入个人中心页面数据未刷新
+ 动态goods数据异常
+ 大会员切换番剧
+ 高画质codes匹配
### 优化
+ 倍速选择
+ 播放器亮度记忆
+ 下载对应版本apk

View File

@@ -1,34 +0,0 @@
## 1.0.6
问题反馈、功能建议请查看「关于」页面。
### 新功能
+ 首页单列布局
+ 首页推荐展示播放量、弹幕数
+ 简单弹幕功能实现(持续开发中...
+ 评论区搜索关键词开关 issues#46
+ 热搜榜隐藏功能 issues#35
+ 自动全屏 issues#37
+ 快速收藏功能
+ 双击快进/快退开关
+ 评论链接跳转视频
+ 支持移除单个稍后再看
+ app scheme外链跳转
### 修复
+ 杜比、无损音频切换
+ 收藏夹展示 issues#42
+ 搜索建议次 issues#47
### 优化
+ 倍速选择优化
+ 导航条沉浸
+ 取消Hero动画
+ 视频锁定逻辑
+ 登录逻辑优化
+ 图片预览样式
+ +评论区用户点击范围
+ 关注、粉丝页面优化
+ 关闭自动播放时播放器初始化逻辑

View File

@@ -1,22 +0,0 @@
## 1.0.7
默认倍速、直播弹幕、专栏等功能开发中
### 新功能
+ 弹幕设置、屏蔽功能
+ 不是很完美的后台播放功能
+ 不是很完美的画中画(pip)功能Android端
### 修复
+ 动态页面加载异常
+ 网络异常时页面空白
+ 竖屏全屏状态栏问题
+ iOS端代理请求异常
### 优化
+ 图片预览
+ 全屏播放时自动旋转
+ 转发内容增加视频标题
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,24 +0,0 @@
## 1.0.8
直播弹幕、循环播放等功能开发中
### 新功能
+ 用户拉黑功能
+ gif图片保存
+ 删除已看历史记录
### 修复
+ 弹幕数量较少
+ 弹幕屏蔽设置自动记忆
+ 动态页面渲染
+ 用户主页数据错乱
+ 大家都在搜空白
+ 默认自动全屏,顶部操作栏丢失
### 优化
+ 全屏状态栏区域显示优化
+ 图片保存至PiliPala文件夹
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,28 +0,0 @@
## 1.0.9
### 新功能
+ 自定义倍速、默认倍速
+ 历史记录搜索
+ 收藏夹搜索
+ 历史记录多选删除
+ 视频循环播放
+ 免登录看1080P
+ 评论区视频链接跳转
+ up主分组
+ up主投稿搜索
### 修复
+ 搜索视频标题乱码
+ 屏幕帧率
+ 动态页面渲染
### 优化
+ 快进手势
+ 视频简介链接匹配
+ 视频全屏时安全区域
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -1,10 +0,0 @@
PiliPlus is a third-party Bilibili client developed in Flutter,
fork from PiliPalaX (https://github.com/orz12/PiliPalaX).
Top Features:
* List of recommended videos
* List of hottest videos
* Popular live streams
* List of bangumis
* Block videos from blacklisted users

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -1 +0,0 @@
A third-party Bilibili client developed in Flutter

View File

@@ -1 +0,0 @@
PiliPlus

View File

@@ -1,10 +0,0 @@
PiliPlus 是使用 Flutter 开发的 BiliBili 第三方客户端,
是由PiliPalaX仓库fork并进行了差异化开发的版本
主要功能:
* 推荐视频列表
* 最热视频列表
* 热门直播
* 番剧列表
* 屏蔽黑名单内用户视频

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -1 +0,0 @@
使用 Flutter 开发的 BiliBili 第三方客户端

View File

@@ -1 +0,0 @@
PiliPlus

View File

@@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<string>13.0</string>
</dict>
</plist>

View File

@@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View File

@@ -4,7 +4,4 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -1,5 +1,5 @@
import UIKit
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {

16
lib/build_config.dart Normal file
View File

@@ -0,0 +1,16 @@
class BuildConfig {
static const int versionCode = int.fromEnvironment(
'pili.code',
defaultValue: 1,
);
static const String versionName = String.fromEnvironment(
'pili.name',
defaultValue: 'SNAPSHOT',
);
static const int buildTime = int.fromEnvironment('pili.time');
static const String commitHash = String.fromEnvironment(
'pili.hash',
defaultValue: 'N/A',
);
}

View File

@@ -1,4 +1,3 @@
import 'package:PiliPlus/http/constants.dart';
import 'package:flutter/material.dart';
class StyleString {
@@ -43,7 +42,7 @@ class Constants {
static const baseHeaders = {
'connection': 'keep-alive',
'accept-encoding': 'br,gzip',
'referer': HttpString.baseUrl,
// 'referer': HttpString.baseUrl,
'env': 'prod',
'app-key': 'android64',
'x-bili-aurora-zone': 'sh001',

View File

@@ -1,50 +1,35 @@
import 'package:flutter/material.dart';
Widget iconButton({
required BuildContext context,
BuildContext? context,
String? tooltip,
required IconData icon,
required Widget icon,
required VoidCallback? onPressed,
double size = 36,
double? iconSize,
Color? bgColor,
Color? iconColor,
}) {
late final theme = Theme.of(context);
Color? backgroundColor = bgColor;
Color? foregroundColor = iconColor;
if (context != null) {
final colorScheme = ColorScheme.of(context);
backgroundColor = colorScheme.secondaryContainer;
foregroundColor = colorScheme.onSecondaryContainer;
}
return SizedBox(
width: size,
height: size,
child: IconButton(
icon: icon,
tooltip: tooltip,
onPressed: onPressed,
icon: Icon(
icon,
size: iconSize ?? size / 2,
color: iconColor ?? theme.colorScheme.onSecondaryContainer,
),
style: IconButton.styleFrom(
padding: EdgeInsets.zero,
backgroundColor: bgColor ?? theme.colorScheme.secondaryContainer,
iconSize: iconSize ?? size / 2,
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
),
),
);
}
Widget mediumButton({
String? tooltip,
IconData? icon,
VoidCallback? onPressed,
}) {
return SizedBox(
width: 34,
height: 34,
child: IconButton(
tooltip: tooltip,
icon: Icon(icon),
style: const ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero),
),
onPressed: onPressed,
),
);
}

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
Widget moreTextButton({
String text = '查看更多',
required VoidCallback onTap,
EdgeInsets? padding,
Color? color,
}) {
Widget child = Text.rich(
style: TextStyle(color: color, height: 1),
strutStyle: const StrutStyle(leading: 0, height: 1),
TextSpan(
children: [
TextSpan(text: text),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Icon(
size: 22,
color: color,
Icons.keyboard_arrow_right,
),
),
],
),
);
if (padding != null) {
child = Padding(padding: padding, child: child);
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: child,
);
}

View File

@@ -10,19 +10,24 @@ class CustomIcons {
static const IconData dyn = _CustomIconData(0xe804);
static const IconData fav = _CustomIconData(0xe805);
static const IconData live_reserve = _CustomIconData(0xe806);
static const IconData share = _CustomIconData(0xe807);
static const IconData share_line = _CustomIconData(0xe808);
static const IconData share_node = _CustomIconData(0xe809);
static const IconData star_favorite_line = _CustomIconData(0xe80a);
static const IconData star_favorite_solid = _CustomIconData(0xe80b);
static const IconData thumbs_down = _CustomIconData(0xe80c);
static const IconData thumbs_down_outline = _CustomIconData(0xe80d);
static const IconData thumbs_up = _CustomIconData(0xe80e);
static const IconData thumbs_up_fill = _CustomIconData(0xe80f);
static const IconData thumbs_up_line = _CustomIconData(0xe810);
static const IconData thumbs_up_outline = _CustomIconData(0xe811);
static const IconData topic_tag = _CustomIconData(0xe812);
static const IconData watch_later = _CustomIconData(0xe813);
static const IconData player_dm_tip_back = _CustomIconData(0xe807);
static const IconData player_dm_tip_copy = _CustomIconData(0xe808);
static const IconData player_dm_tip_like = _CustomIconData(0xe809);
static const IconData player_dm_tip_like_solid = _CustomIconData(0xe80a);
static const IconData player_dm_tip_recall = _CustomIconData(0xe80b);
static const IconData share = _CustomIconData(0xe80c);
static const IconData share_line = _CustomIconData(0xe80d);
static const IconData share_node = _CustomIconData(0xe80e);
static const IconData star_favorite_line = _CustomIconData(0xe80f);
static const IconData star_favorite_solid = _CustomIconData(0xe810);
static const IconData thumbs_down = _CustomIconData(0xe811);
static const IconData thumbs_down_outline = _CustomIconData(0xe812);
static const IconData thumbs_up = _CustomIconData(0xe813);
static const IconData thumbs_up_fill = _CustomIconData(0xe814);
static const IconData thumbs_up_line = _CustomIconData(0xe815);
static const IconData thumbs_up_outline = _CustomIconData(0xe816);
static const IconData topic_tag = _CustomIconData(0xe817);
static const IconData watch_later = _CustomIconData(0xe818);
}
class _CustomIconData extends IconData {

View File

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
class CustomSliverPersistentHeaderDelegate
extends SliverPersistentHeaderDelegate {
CustomSliverPersistentHeaderDelegate({
const CustomSliverPersistentHeaderDelegate({
required this.child,
required this.bgColor,
double extent = 45,

View File

@@ -7,6 +7,7 @@ Future<void> showConfirmDialog({
dynamic content,
required VoidCallback onConfirm,
}) {
assert(content is String? || content is Widget);
return showDialog(
context: context,
builder: (context) {

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/utils/extension.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';
@@ -124,11 +125,12 @@ Future<void> autoWrapReportDialog(
Get.back();
SmartDialog.showToast('举报成功');
} else {
SmartDialog.showToast(data['message']);
SmartDialog.showToast(data['message'].toString());
}
} catch (e) {
} catch (e, s) {
SmartDialog.dismiss();
SmartDialog.showToast('提交失败:$e');
Utils.reportError(e, s);
}
},
child: const Text('确定'),
@@ -231,4 +233,34 @@ class ReportOptions {
0: '其他',
},
};
static Map<String, Map<int, String>> get danmakuReport => const {
'': {
1: '违法违禁',
2: '色情低俗',
3: '赌博诈骗',
4: '人身攻击',
5: '侵犯隐私',
6: '垃圾广告',
7: '引战',
8: '剧透',
9: '恶意刷屏',
10: '视频无关',
12: '青少年不良信息',
13: '违法信息外链',
0: '其它', // 11
},
};
static Map<String, Map<int, String>> get liveDanmakuReport => const {
'': {
1: '违法违规',
2: '低俗色情',
3: '垃圾广告',
4: '辱骂引战',
5: '政治敏感',
6: '青少年不良信息',
7: '其他', // avoid show form
},
};
}

View File

@@ -13,7 +13,7 @@ library;
import 'dart:math' as math;
import 'package:PiliPlus/common/widgets/dyn/ink_well.dart';
import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide InkWell;
import 'package:flutter/rendering.dart';

View File

@@ -12,7 +12,7 @@ library;
import 'dart:ui' show lerpDouble;
import 'package:PiliPlus/common/widgets/dyn/button.dart';
import 'package:PiliPlus/common/widgets/flutter/dyn/button.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide InkWell, ButtonStyleButton;

View File

@@ -316,6 +316,7 @@ class ListTile extends StatelessWidget {
/// Requires one of its ancestors to be a [Material] widget.
const ListTile({
super.key,
this.safeArea = false,
this.leading,
this.title,
this.subtitle,
@@ -335,6 +336,7 @@ class ListTile extends StatelessWidget {
this.enabled = true,
this.onTap,
this.onLongPress,
this.onSecondaryTap,
this.onFocusChange,
this.mouseCursor,
this.selected = false,
@@ -355,6 +357,8 @@ class ListTile extends StatelessWidget {
this.statesController,
}) : assert(isThreeLine != true || subtitle != null);
final bool safeArea;
/// A widget to display before the title.
///
/// Typically an [Icon] or a [CircleAvatar] widget.
@@ -563,6 +567,8 @@ class ListTile extends StatelessWidget {
/// Inoperative if [enabled] is false.
final GestureLongPressCallback? onLongPress;
final GestureTapCallback? onSecondaryTap;
/// {@macro flutter.material.inkwell.onFocusChange}
final ValueChanged<bool>? onFocusChange;
@@ -922,10 +928,64 @@ class ListTile extends StatelessWidget {
? ListTileTitleAlignment.threeLine
: ListTileTitleAlignment.titleHeight);
Widget child = IconTheme.merge(
data: iconThemeData,
child: IconButtonTheme(
data: iconButtonThemeData,
child: _ListTile(
leading: leadingIcon,
title: titleText,
subtitle: subtitleText,
trailing: trailingIcon,
isDense: _isDenseLayout(theme, tileTheme),
visualDensity:
visualDensity ?? tileTheme.visualDensity ?? theme.visualDensity,
isThreeLine:
isThreeLine ??
tileTheme.isThreeLine ??
theme.listTileTheme.isThreeLine ??
false,
textDirection: textDirection,
titleBaselineType:
titleStyle.textBaseline ?? defaults.titleTextStyle!.textBaseline!,
subtitleBaselineType:
subtitleStyle?.textBaseline ??
defaults.subtitleTextStyle!.textBaseline!,
horizontalTitleGap:
horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
minVerticalPadding:
minVerticalPadding ??
tileTheme.minVerticalPadding ??
defaults.minVerticalPadding!,
minLeadingWidth:
minLeadingWidth ??
tileTheme.minLeadingWidth ??
defaults.minLeadingWidth!,
minTileHeight: minTileHeight ?? tileTheme.minTileHeight,
titleAlignment: effectiveTitleAlignment,
),
),
);
if (safeArea) {
child = SafeArea(
top: false,
bottom: false,
minimum: resolvedContentPadding,
child: child,
);
} else {
child = Padding(
padding: resolvedContentPadding,
child: child,
);
}
return InkWell(
customBorder: shape ?? tileTheme.shape,
onTap: enabled ? onTap : null,
onLongPress: enabled ? onLongPress : null,
onSecondaryTap: enabled ? onSecondaryTap : null,
onFocusChange: onFocusChange,
mouseCursor: effectiveMouseCursor,
canRequestFocus: enabled,
@@ -947,50 +1007,7 @@ class ListTile extends StatelessWidget {
shape: shape ?? tileTheme.shape ?? const Border(),
color: _tileBackgroundColor(theme, tileTheme, defaults),
),
child: Padding(
padding: resolvedContentPadding,
child: IconTheme.merge(
data: iconThemeData,
child: IconButtonTheme(
data: iconButtonThemeData,
child: _ListTile(
leading: leadingIcon,
title: titleText,
subtitle: subtitleText,
trailing: trailingIcon,
isDense: _isDenseLayout(theme, tileTheme),
visualDensity:
visualDensity ??
tileTheme.visualDensity ??
theme.visualDensity,
isThreeLine:
isThreeLine ??
tileTheme.isThreeLine ??
theme.listTileTheme.isThreeLine ??
false,
textDirection: textDirection,
titleBaselineType:
titleStyle.textBaseline ??
defaults.titleTextStyle!.textBaseline!,
subtitleBaselineType:
subtitleStyle?.textBaseline ??
defaults.subtitleTextStyle!.textBaseline!,
horizontalTitleGap:
horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
minVerticalPadding:
minVerticalPadding ??
tileTheme.minVerticalPadding ??
defaults.minVerticalPadding!,
minLeadingWidth:
minLeadingWidth ??
tileTheme.minLeadingWidth ??
defaults.minLeadingWidth!,
minTileHeight: minTileHeight ?? tileTheme.minTileHeight,
titleAlignment: effectiveTitleAlignment,
),
),
),
),
child: child,
),
),
);

View File

@@ -10,7 +10,7 @@
/// @docImport 'text.dart';
library;
import 'package:PiliPlus/common/widgets/page/scrollable.dart';
import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/material.dart' hide Scrollable, ScrollableState;
import 'package:flutter/rendering.dart';

View File

@@ -664,10 +664,6 @@ class CustomScrollableState extends State<CustomScrollable>
vsync: this,
reverseDuration: const Duration(milliseconds: 500),
);
_anim = Tween<Offset>(
begin: Offset.zero,
end: const Offset(0, 1),
).animate(_animController);
}
@protected
@@ -786,7 +782,6 @@ class CustomScrollableState extends State<CustomScrollable>
bool? _isSliding;
late AnimationController _animController;
late Animation<Offset> _anim;
@override
@protected
@@ -1185,8 +1180,15 @@ class CustomScrollableState extends State<CustomScrollable>
return LayoutBuilder(
builder: (context, constraints) {
_maxWidth = constraints.maxWidth;
return SlideTransition(
position: _anim,
return AnimatedBuilder(
animation: _animController,
builder: (context, child) {
return Align(
alignment: AlignmentDirectional.topStart,
heightFactor: 1 - _animController.value,
child: child,
);
},
child: Material(
color: widget.bgColor,
child: widget.header != null

View File

@@ -4,7 +4,7 @@
import 'dart:ui' show SemanticsRole;
import 'package:PiliPlus/common/widgets/page/page_view.dart';
import 'package:PiliPlus/common/widgets/flutter/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;

View File

@@ -78,6 +78,7 @@ class RenderParagraph extends RenderBox
Color? selectionColor,
SelectionRegistrar? registrar,
required Color primary,
VoidCallback? onShowMore,
}) : assert(text.debugAssertIsValid()),
assert(
maxLines == null ||
@@ -93,6 +94,7 @@ class RenderParagraph extends RenderBox
_softWrap = softWrap,
_overflow = overflow,
_selectionColor = selectionColor,
_onShowMore = onShowMore,
_textPainter = TextPainter(
text: text,
textAlign: textAlign,
@@ -294,6 +296,8 @@ class RenderParagraph extends RenderBox
_disposeSelectableFragments();
_textPainter.dispose();
_textIntrinsicsCache?.dispose();
_tapGestureRecognizer?.dispose();
_tapGestureRecognizer = null;
_morePainter?.dispose();
_morePainter = null;
super.dispose();
@@ -553,6 +557,18 @@ class RenderParagraph extends RenderBox
@override
@protected
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
if (_tapGestureRecognizer != null) {
if (_morePainter case final textPainter?) {
late final height = _textPainter.height;
if (position.dx < textPainter.width &&
position.dy > height &&
position.dy < height + textPainter.height) {
result.add(HitTestEntry(_moreTextSpan));
return true;
}
}
}
final GlyphInfo? glyph = _textPainter.getClosestGlyphForOffset(position);
// The hit-test can't fall through the horizontal gaps between visually
// adjacent characters on the same line, even with a large letter-spacing or
@@ -680,9 +696,20 @@ class RenderParagraph extends RenderBox
}
}
VoidCallback? _onShowMore;
set onShowMore(VoidCallback? onShowMore) {
if (_onShowMore != onShowMore) {
_onShowMore = onShowMore;
_tapGestureRecognizer?.onTap = onShowMore;
}
}
TapGestureRecognizer? _tapGestureRecognizer;
TextSpan get _moreTextSpan => TextSpan(
style: text.style!.copyWith(color: _primary),
text: '查看更多',
recognizer: _tapGestureRecognizer,
);
TextPainter? _morePainter;
@@ -709,6 +736,9 @@ class RenderParagraph extends RenderBox
size.height < textSize.height || _textPainter.didExceedMaxLines;
if (didOverflowHeight) {
if (_onShowMore != null) {
_tapGestureRecognizer ??= TapGestureRecognizer()..onTap = _onShowMore;
}
_morePainter ??= TextPainter(
text: _moreTextSpan,
textDirection: textDirection,

View File

@@ -1,6 +1,6 @@
import 'dart:ui' as ui;
import 'package:PiliPlus/common/widgets/text/paragraph.dart';
import 'package:PiliPlus/common/widgets/flutter/text/paragraph.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' hide RenderParagraph;
@@ -114,6 +114,8 @@ class RichText extends MultiChildRenderObjectWidget {
this.textHeightBehavior,
this.selectionRegistrar,
this.selectionColor,
this.onShowMore,
required this.primary,
}) : assert(maxLines == null || maxLines > 0),
assert(selectionRegistrar == null || selectionColor != null),
assert(
@@ -228,6 +230,10 @@ class RichText extends MultiChildRenderObjectWidget {
/// widgets.
final Color? selectionColor;
final Color primary;
final VoidCallback? onShowMore;
@override
RenderParagraph createRenderObject(BuildContext context) {
assert(textDirection != null || debugCheckHasDirectionality(context));
@@ -245,7 +251,8 @@ class RichText extends MultiChildRenderObjectWidget {
locale: locale ?? Localizations.maybeLocaleOf(context),
registrar: selectionRegistrar,
selectionColor: selectionColor,
primary: Theme.of(context).colorScheme.primary,
primary: primary,
onShowMore: onShowMore,
);
}
@@ -266,7 +273,8 @@ class RichText extends MultiChildRenderObjectWidget {
..locale = locale ?? Localizations.maybeLocaleOf(context)
..registrar = selectionRegistrar
..selectionColor = selectionColor
..primary = Theme.of(context).colorScheme.primary;
..primary = primary
..onShowMore = onShowMore;
}
@override

View File

@@ -17,8 +17,8 @@ library;
import 'dart:math';
import 'dart:ui' as ui show TextHeightBehavior;
import 'package:PiliPlus/common/widgets/text/paragraph.dart';
import 'package:PiliPlus/common/widgets/text/rich_text.dart';
import 'package:PiliPlus/common/widgets/flutter/text/paragraph.dart';
import 'package:PiliPlus/common/widgets/flutter/text/rich_text.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide RichText;
import 'package:flutter/rendering.dart' hide RenderParagraph;
@@ -174,6 +174,8 @@ class Text extends StatelessWidget {
this.textWidthBasis,
this.textHeightBehavior,
this.selectionColor,
this.onShowMore,
required this.primary,
}) : textSpan = null,
assert(
textScaler == null || textScaleFactor == null,
@@ -211,6 +213,8 @@ class Text extends StatelessWidget {
this.textWidthBasis,
this.textHeightBehavior,
this.selectionColor,
this.onShowMore,
required this.primary,
}) : data = null,
assert(
textScaler == null || textScaleFactor == null,
@@ -349,6 +353,10 @@ class Text extends StatelessWidget {
/// (semi-transparent grey).
final Color? selectionColor;
final Color primary;
final VoidCallback? onShowMore;
@override
Widget build(BuildContext context) {
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
@@ -404,6 +412,7 @@ class Text extends StatelessWidget {
text: data,
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
),
primary: primary,
),
);
} else {
@@ -435,6 +444,8 @@ class Text extends StatelessWidget {
text: data,
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
),
onShowMore: onShowMore,
primary: primary,
);
}
if (semanticsLabel != null || semanticsIdentifier != null) {
@@ -532,6 +543,7 @@ class _SelectableTextContainer extends StatefulWidget {
required this.textWidthBasis,
this.textHeightBehavior,
required this.selectionColor,
required this.primary,
});
final TextSpan text;
@@ -546,6 +558,7 @@ class _SelectableTextContainer extends StatefulWidget {
final TextWidthBasis textWidthBasis;
final ui.TextHeightBehavior? textHeightBehavior;
final Color selectionColor;
final Color primary;
@override
State<_SelectableTextContainer> createState() =>
@@ -588,6 +601,7 @@ class _SelectableTextContainerState extends State<_SelectableTextContainer> {
textHeightBehavior: widget.textHeightBehavior,
selectionColor: widget.selectionColor,
text: widget.text,
primary: widget.primary,
),
);
}
@@ -608,6 +622,7 @@ class _RichText extends StatelessWidget {
required this.textWidthBasis,
this.textHeightBehavior,
required this.selectionColor,
required this.primary,
});
final GlobalKey? textKey;
@@ -623,6 +638,7 @@ class _RichText extends StatelessWidget {
final TextWidthBasis textWidthBasis;
final ui.TextHeightBehavior? textHeightBehavior;
final Color selectionColor;
final Color primary;
@override
Widget build(BuildContext context) {
@@ -642,6 +658,7 @@ class _RichText extends StatelessWidget {
selectionRegistrar: registrar,
selectionColor: selectionColor,
text: text,
primary: primary,
);
}
}

View File

@@ -9,7 +9,7 @@
/// @docImport 'text_field.dart';
library;
import 'package:PiliPlus/common/widgets/text_field/editable_text.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart';
import 'package:flutter/cupertino.dart' hide EditableText, EditableTextState;
import 'package:flutter/material.dart' hide EditableText, EditableTextState;
import 'package:flutter/rendering.dart';

View File

@@ -5,7 +5,7 @@
/// @docImport 'package:flutter/material.dart';
library;
import 'package:PiliPlus/common/widgets/text_field/editable_text.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart';
import 'package:flutter/cupertino.dart' hide EditableText, EditableTextState;
import 'package:flutter/foundation.dart' show defaultTargetPlatform;
import 'package:flutter/rendering.dart';

View File

@@ -5,7 +5,7 @@
/// @docImport 'package:flutter/material.dart';
library;
import 'package:PiliPlus/common/widgets/text_field/editable_text.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart';
import 'package:flutter/cupertino.dart' hide EditableText, EditableTextState;
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'

View File

@@ -7,13 +7,13 @@ library;
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
import 'package:PiliPlus/common/widgets/text_field/controller.dart';
import 'package:PiliPlus/common/widgets/text_field/cupertino/cupertino_adaptive_text_selection_toolbar.dart';
import 'package:PiliPlus/common/widgets/text_field/cupertino/cupertino_spell_check_suggestions_toolbar.dart';
import 'package:PiliPlus/common/widgets/text_field/editable_text.dart';
import 'package:PiliPlus/common/widgets/text_field/spell_check.dart';
import 'package:PiliPlus/common/widgets/text_field/system_context_menu.dart';
import 'package:PiliPlus/common/widgets/text_field/text_selection.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/cupertino/cupertino_adaptive_text_selection_toolbar.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/cupertino/cupertino_spell_check_suggestions_toolbar.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/spell_check.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/system_context_menu.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/text_selection.dart';
import 'package:flutter/cupertino.dart'
hide
SpellCheckConfiguration,

View File

@@ -16,7 +16,7 @@ import 'dart:ui'
SemanticsInputType,
TextBox;
import 'package:PiliPlus/common/widgets/text_field/controller.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

View File

@@ -22,10 +22,10 @@ import 'dart:math' as math;
import 'dart:ui' as ui hide TextStyle;
import 'dart:ui';
import 'package:PiliPlus/common/widgets/text_field/controller.dart';
import 'package:PiliPlus/common/widgets/text_field/editable.dart';
import 'package:PiliPlus/common/widgets/text_field/spell_check.dart';
import 'package:PiliPlus/common/widgets/text_field/text_selection.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/editable.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/spell_check.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/text_selection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'

View File

@@ -12,7 +12,7 @@ import 'package:flutter/painting.dart';
import 'package:flutter/services.dart'
show SpellCheckResults, SpellCheckService, SuggestionSpan, TextEditingValue;
import 'package:PiliPlus/common/widgets/text_field/editable_text.dart'
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart'
show EditableTextContextMenuBuilder;
/// Controls how spell check is performed for text input.

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:PiliPlus/common/widgets/text_field/editable_text.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart';
import 'package:flutter/cupertino.dart' hide EditableText, EditableTextState;
import 'package:flutter/material.dart' hide EditableText, EditableTextState;
import 'package:flutter/scheduler.dart';

View File

@@ -5,7 +5,7 @@
/// @docImport 'package:flutter/material.dart';
library;
import 'package:PiliPlus/common/widgets/text_field/editable_text.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart';
import 'package:flutter/material.dart' hide EditableText, EditableTextState;
import 'package:flutter/services.dart';

View File

@@ -13,15 +13,15 @@ library;
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
import 'package:PiliPlus/common/widgets/text_field/adaptive_text_selection_toolbar.dart';
import 'package:PiliPlus/common/widgets/text_field/controller.dart';
import 'package:PiliPlus/common/widgets/text_field/cupertino/cupertino_spell_check_suggestions_toolbar.dart';
import 'package:PiliPlus/common/widgets/text_field/cupertino/cupertino_text_field.dart';
import 'package:PiliPlus/common/widgets/text_field/editable_text.dart';
import 'package:PiliPlus/common/widgets/text_field/spell_check.dart';
import 'package:PiliPlus/common/widgets/text_field/spell_check_suggestions_toolbar.dart';
import 'package:PiliPlus/common/widgets/text_field/system_context_menu.dart';
import 'package:PiliPlus/common/widgets/text_field/text_selection.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/adaptive_text_selection_toolbar.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/cupertino/cupertino_spell_check_suggestions_toolbar.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/cupertino/cupertino_text_field.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/spell_check.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/spell_check_suggestions_toolbar.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/system_context_menu.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/text_selection.dart';
import 'package:flutter/cupertino.dart'
hide
EditableText,

View File

@@ -1,9 +1,9 @@
import 'dart:math' as math;
import 'dart:ui';
import 'package:PiliPlus/common/widgets/text_field/controller.dart';
import 'package:PiliPlus/common/widgets/text_field/editable.dart';
import 'package:PiliPlus/common/widgets/text_field/editable_text.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/editable.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' show kMinInteractiveDimension;

View File

@@ -0,0 +1,174 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
class ImmediateTapGestureRecognizer extends OneSequenceGestureRecognizer {
ImmediateTapGestureRecognizer({
super.debugOwner,
super.supportedDevices,
super.allowedButtonsFilter,
this.onTapDown,
required this.onTapUp,
this.onTapCancel,
this.onTap,
});
GestureTapDownCallback? onTapDown;
final GestureTapUpCallback onTapUp;
final GestureTapCancelCallback? onTapCancel;
final GestureTapCallback? onTap;
PointerUpEvent? _up;
int _activePointer = 0;
bool _sentTapDown = false;
bool _wonArena = false;
@override
bool isPointerPanZoomAllowed(PointerPanZoomStartEvent event) => false;
@override
bool isPointerAllowed(PointerDownEvent event) =>
_activePointer == 0 && super.isPointerAllowed(event);
@override
void addAllowedPointer(PointerDownEvent event) {
super.addAllowedPointer(event);
_reset(event.pointer);
}
@override
void handleEvent(PointerEvent event) {
if (event.pointer != _activePointer) {
stopTrackingPointer(event.pointer);
return;
}
if (event is PointerDownEvent) {
_handleTapDown(event);
} else if (event is PointerMoveEvent) {
_handlePointerMove(event);
} else if (event is PointerUpEvent) {
_up = event;
_handlePointerUp(event);
}
stopTrackingIfPointerNoLongerDown(event);
}
void _handleTapDown(PointerDownEvent event) {
if (_sentTapDown) return;
if (onTapDown != null) {
_sentTapDown = true;
final details = TapDownDetails(
globalPosition: event.position,
localPosition: event.localPosition,
kind: event.kind,
);
invokeCallback<void>('onTapDown', () => onTapDown!(details));
}
}
void _handlePointerMove(PointerMoveEvent event) {
if (event.delta.distanceSquared > 2.0) {
_cancelGesture('pointer moved');
stopTrackingPointer(event.pointer);
}
}
void _handlePointerUp(PointerUpEvent event) {
if (_wonArena) {
_handleTapUp(event);
}
}
void _handleTapUp(PointerUpEvent event) {
final details = TapUpDetails(
globalPosition: event.position,
localPosition: event.localPosition,
kind: event.kind,
);
invokeCallback<void>('onTapUp', () => onTapUp(details));
if (onTap != null) {
invokeCallback<void>('onTap', onTap!);
}
_reset();
}
void _cancelGesture(String reason) {
if (_sentTapDown && onTapCancel != null) {
invokeCallback<void>('onTapCancel: $reason', onTapCancel!);
}
_reset();
}
void _reset([int pointer = 0]) {
_activePointer = pointer;
_up = null;
_sentTapDown = false;
_wonArena = false;
}
@override
void acceptGesture(int pointer) {
super.acceptGesture(pointer);
if (pointer == _activePointer) {
_wonArena = true;
if (_up != null) {
_handleTapUp(_up!);
}
}
}
@override
void rejectGesture(int pointer) {
super.rejectGesture(pointer);
if (pointer == _activePointer) {
_cancelGesture('gesture rejected by arena');
stopTrackingPointer(pointer);
}
}
@override
void didStopTrackingLastPointer(int pointer) {
// wait for arena
}
@override
void dispose() {
_cancelGesture('disposed');
super.dispose();
}
@override
String get debugDescription => 'immediate tap';
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(IntProperty('activePointer', _activePointer))
..add(
FlagProperty(
'sentTapDown',
value: _sentTapDown,
ifTrue: 'has sentTapDown',
),
)
..add(FlagProperty('wonArena', value: _wonArena, ifTrue: 'wonArena'))
..add(
DiagnosticsProperty<PointerUpEvent>(
'pointerUpEvent',
_up,
defaultValue: null,
),
);
}
}

View File

@@ -0,0 +1,905 @@
// 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:io' show Platform;
import 'dart:math' as math;
import 'package:flutter/foundation.dart' show clampDouble;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:vector_math/vector_math_64.dart' show Quad, Vector3;
class MouseInteractiveViewer extends StatefulWidget {
const MouseInteractiveViewer({
super.key,
this.clipBehavior = Clip.hardEdge,
this.panAxis = PanAxis.free,
this.boundaryMargin = EdgeInsets.zero,
this.constrained = true,
this.maxScale = 2.5,
this.minScale = 0.8,
this.interactionEndFrictionCoefficient = _kDrag,
this.pointerSignalFallback,
this.onPointerPanZoomUpdate,
this.onPointerPanZoomEnd,
this.onPointerDown,
this.onInteractionEnd,
this.onInteractionStart,
this.onInteractionUpdate,
this.panEnabled = true,
this.scaleEnabled = true,
this.scaleFactor = kDefaultMouseScrollToScaleFactor,
this.transformationController,
this.alignment,
this.trackpadScrollCausesScale = false,
required this.childKey,
required this.child,
}) : assert(minScale > 0),
assert(interactionEndFrictionCoefficient > 0),
assert(maxScale > 0),
assert(maxScale >= minScale);
final Alignment? alignment;
final Clip clipBehavior;
final PanAxis panAxis;
final EdgeInsets boundaryMargin;
final Widget child;
final bool constrained;
final bool panEnabled;
final bool scaleEnabled;
final bool trackpadScrollCausesScale;
final double scaleFactor;
final double maxScale;
final double minScale;
final double interactionEndFrictionCoefficient;
final PointerSignalEventListener? pointerSignalFallback;
final PointerPanZoomUpdateEventListener? onPointerPanZoomUpdate;
final PointerPanZoomEndEventListener? onPointerPanZoomEnd;
final PointerDownEventListener? onPointerDown;
final GestureScaleEndCallback? onInteractionEnd;
final GestureScaleStartCallback? onInteractionStart;
final GestureScaleUpdateCallback? onInteractionUpdate;
final TransformationController? transformationController;
final GlobalKey childKey;
static const double _kDrag = 0.0000135;
@override
State<MouseInteractiveViewer> createState() => _MouseInteractiveViewerState();
}
class _MouseInteractiveViewerState extends State<MouseInteractiveViewer>
with TickerProviderStateMixin {
late TransformationController _transformer =
widget.transformationController ?? TransformationController();
final GlobalKey _parentKey = GlobalKey();
Animation<Offset>? _animation;
Animation<double>? _scaleAnimation;
late Offset _scaleAnimationFocalPoint;
late AnimationController _controller;
late AnimationController _scaleController;
Axis? _currentAxis;
Offset? _referenceFocalPoint;
double? _scaleStart;
double? _rotationStart = 0.0;
double _currentRotation = 0.0;
_GestureType? _gestureType;
static final gestureSettings = DeviceGestureSettings(
touchSlop: Platform.isIOS ? 9 : 4,
);
late final _scaleGestureRecognizer =
ScaleGestureRecognizer(
debugOwner: this,
allowedButtonsFilter: (buttons) => buttons == kPrimaryButton,
trackpadScrollToScaleFactor: Offset(0, -1 / widget.scaleFactor),
trackpadScrollCausesScale: widget.trackpadScrollCausesScale,
)
..gestureSettings = gestureSettings
..onStart = _onScaleStart
..onUpdate = _onScaleUpdate
..onEnd = _onScaleEnd;
final bool _rotateEnabled = false;
Rect get _boundaryRect {
assert(widget.childKey.currentContext != null);
final RenderBox childRenderBox =
widget.childKey.currentContext!.findRenderObject()! as RenderBox;
final Size childSize = childRenderBox.size;
final Rect boundaryRect = widget.boundaryMargin.inflateRect(
Offset.zero & childSize,
);
assert(
!boundaryRect.isEmpty,
"InteractiveViewer's child must have nonzero dimensions.",
);
assert(
boundaryRect.isFinite ||
(boundaryRect.left.isInfinite &&
boundaryRect.top.isInfinite &&
boundaryRect.right.isInfinite &&
boundaryRect.bottom.isInfinite),
'boundaryRect must either be infinite in all directions or finite in all directions.',
);
return boundaryRect;
}
Rect get _viewport {
assert(_parentKey.currentContext != null);
final RenderBox parentRenderBox =
_parentKey.currentContext!.findRenderObject()! as RenderBox;
return Offset.zero & parentRenderBox.size;
}
Matrix4 _matrixTranslate(Matrix4 matrix, Offset translation) {
if (translation == Offset.zero) {
return matrix.clone();
}
final Offset alignedTranslation;
if (_currentAxis != null) {
alignedTranslation = switch (widget.panAxis) {
PanAxis.horizontal => _alignAxis(translation, Axis.horizontal),
PanAxis.vertical => _alignAxis(translation, Axis.vertical),
PanAxis.aligned => _alignAxis(translation, _currentAxis!),
PanAxis.free => translation,
};
} else {
alignedTranslation = translation;
}
final Matrix4 nextMatrix = matrix.clone()
..translateByDouble(alignedTranslation.dx, alignedTranslation.dy, 0, 1);
final Quad nextViewport = _transformViewport(nextMatrix, _viewport);
if (_boundaryRect.isInfinite) {
return nextMatrix;
}
final Quad boundariesAabbQuad = _getAxisAlignedBoundingBoxWithRotation(
_boundaryRect,
_currentRotation,
);
final Offset offendingDistance = _exceedsBy(
boundariesAabbQuad,
nextViewport,
);
if (offendingDistance == Offset.zero) {
return nextMatrix;
}
final Offset nextTotalTranslation = _getMatrixTranslation(nextMatrix);
final double currentScale = matrix.getMaxScaleOnAxis();
final Offset correctedTotalTranslation = Offset(
nextTotalTranslation.dx - offendingDistance.dx * currentScale,
nextTotalTranslation.dy - offendingDistance.dy * currentScale,
);
final Matrix4 correctedMatrix = matrix.clone()
..setTranslation(
Vector3(
correctedTotalTranslation.dx,
correctedTotalTranslation.dy,
0.0,
),
);
final Quad correctedViewport = _transformViewport(
correctedMatrix,
_viewport,
);
final Offset offendingCorrectedDistance = _exceedsBy(
boundariesAabbQuad,
correctedViewport,
);
if (offendingCorrectedDistance == Offset.zero) {
return correctedMatrix;
}
if (offendingCorrectedDistance.dx != 0.0 &&
offendingCorrectedDistance.dy != 0.0) {
return matrix.clone();
}
final Offset unidirectionalCorrectedTotalTranslation = Offset(
offendingCorrectedDistance.dx == 0.0 ? correctedTotalTranslation.dx : 0.0,
offendingCorrectedDistance.dy == 0.0 ? correctedTotalTranslation.dy : 0.0,
);
return matrix.clone()..setTranslation(
Vector3(
unidirectionalCorrectedTotalTranslation.dx,
unidirectionalCorrectedTotalTranslation.dy,
0.0,
),
);
}
Matrix4 _matrixScale(Matrix4 matrix, double scale) {
if (scale == 1.0) {
return matrix.clone();
}
assert(scale != 0.0);
final double currentScale = _transformer.value.getMaxScaleOnAxis();
final double totalScale = math.max(
currentScale * scale,
math.max(
_viewport.width / _boundaryRect.width,
_viewport.height / _boundaryRect.height,
),
);
final double clampedTotalScale = clampDouble(
totalScale,
widget.minScale,
widget.maxScale,
);
final double clampedScale = clampedTotalScale / currentScale;
return matrix.clone()
..scaleByDouble(clampedScale, clampedScale, clampedScale, 1);
}
Matrix4 _matrixRotate(Matrix4 matrix, double rotation, Offset focalPoint) {
if (rotation == 0) {
return matrix.clone();
}
final Offset focalPointScene = _transformer.toScene(focalPoint);
return matrix.clone()
..translateByDouble(focalPointScene.dx, focalPointScene.dy, 0, 1)
..rotateZ(-rotation)
..translateByDouble(-focalPointScene.dx, -focalPointScene.dy, 0, 1);
}
bool _gestureIsSupported(_GestureType? gestureType) {
return switch (gestureType) {
_GestureType.rotate => _rotateEnabled,
_GestureType.scale => widget.scaleEnabled,
_GestureType.pan || null => widget.panEnabled,
};
}
_GestureType _getGestureType(ScaleUpdateDetails details) {
final double scale = !widget.scaleEnabled ? 1.0 : details.scale;
final double rotation = !_rotateEnabled ? 0.0 : details.rotation;
if ((scale - 1).abs() > rotation.abs()) {
return _GestureType.scale;
} else if (rotation != 0.0) {
return _GestureType.rotate;
} else {
return _GestureType.pan;
}
}
// Handle the start of a gesture. All of pan, scale, and rotate are handled
// with GestureDetector's scale gesture.
void _onScaleStart(ScaleStartDetails details) {
widget.onInteractionStart?.call(details);
if (_controller.isAnimating) {
_controller
..stop()
..reset();
_animation?.removeListener(_handleInertiaAnimation);
_animation = null;
}
if (_scaleController.isAnimating) {
_scaleController
..stop()
..reset();
_scaleAnimation?.removeListener(_handleScaleAnimation);
_scaleAnimation = null;
}
_gestureType = null;
_currentAxis = null;
_scaleStart = _transformer.value.getMaxScaleOnAxis();
_referenceFocalPoint = _transformer.toScene(details.localFocalPoint);
_rotationStart = _currentRotation;
}
// Handle an update to an ongoing gesture. All of pan, scale, and rotate are
// handled with GestureDetector's scale gesture.
void _onScaleUpdate(ScaleUpdateDetails details) {
final double scale = _transformer.value.getMaxScaleOnAxis();
_scaleAnimationFocalPoint = details.localFocalPoint;
final Offset focalPointScene = _transformer.toScene(
details.localFocalPoint,
);
if (_gestureType == _GestureType.pan) {
// When a gesture first starts, it sometimes has no change in scale and
// rotation despite being a two-finger gesture. Here the gesture is
// allowed to be reinterpreted as its correct type after originally
// being marked as a pan.
_gestureType = _getGestureType(details);
} else {
_gestureType ??= _getGestureType(details);
}
if (!_gestureIsSupported(_gestureType)) {
widget.onInteractionUpdate?.call(details);
return;
}
switch (_gestureType!) {
case _GestureType.scale:
assert(_scaleStart != null);
// details.scale gives us the amount to change the scale as of the
// start of this gesture, so calculate the amount to scale as of the
// previous call to _onScaleUpdate.
final double desiredScale = _scaleStart! * details.scale;
final double scaleChange = desiredScale / scale;
_transformer.value = _matrixScale(_transformer.value, scaleChange);
// While scaling, translate such that the user's two fingers stay on
// the same places in the scene. That means that the focal point of
// the scale should be on the same place in the scene before and after
// the scale.
final Offset focalPointSceneScaled = _transformer.toScene(
details.localFocalPoint,
);
_transformer.value = _matrixTranslate(
_transformer.value,
focalPointSceneScaled - _referenceFocalPoint!,
);
// details.localFocalPoint should now be at the same location as the
// original _referenceFocalPoint point. If it's not, that's because
// the translate came in contact with a boundary. In that case, update
// _referenceFocalPoint so subsequent updates happen in relation to
// the new effective focal point.
final Offset focalPointSceneCheck = _transformer.toScene(
details.localFocalPoint,
);
if (_round(_referenceFocalPoint!) != _round(focalPointSceneCheck)) {
_referenceFocalPoint = focalPointSceneCheck;
}
case _GestureType.rotate:
if (details.rotation == 0.0) {
widget.onInteractionUpdate?.call(details);
return;
}
final double desiredRotation = _rotationStart! + details.rotation;
_transformer.value = _matrixRotate(
_transformer.value,
_currentRotation - desiredRotation,
details.localFocalPoint,
);
_currentRotation = desiredRotation;
case _GestureType.pan:
assert(_referenceFocalPoint != null);
// details may have a change in scale here when scaleEnabled is false.
// In an effort to keep the behavior similar whether or not scaleEnabled
// is true, these gestures are thrown away.
if (details.scale != 1.0) {
widget.onInteractionUpdate?.call(details);
return;
}
_currentAxis ??= _getPanAxis(_referenceFocalPoint!, focalPointScene);
// Translate so that the same point in the scene is underneath the
// focal point before and after the movement.
final Offset translationChange =
focalPointScene - _referenceFocalPoint!;
_transformer.value = _matrixTranslate(
_transformer.value,
translationChange,
);
_referenceFocalPoint = _transformer.toScene(details.localFocalPoint);
}
widget.onInteractionUpdate?.call(details);
}
// Handle the end of a gesture of _GestureType. All of pan, scale, and rotate
// are handled with GestureDetector's scale gesture.
void _onScaleEnd(ScaleEndDetails details) {
widget.onInteractionEnd?.call(details);
_scaleStart = null;
_rotationStart = null;
_referenceFocalPoint = null;
_animation?.removeListener(_handleInertiaAnimation);
_scaleAnimation?.removeListener(_handleScaleAnimation);
_controller.reset();
_scaleController.reset();
if (!_gestureIsSupported(_gestureType)) {
_currentAxis = null;
return;
}
switch (_gestureType) {
case _GestureType.pan:
if (details.velocity.pixelsPerSecond.distance < kMinFlingVelocity) {
_currentAxis = null;
return;
}
final Vector3 translationVector = _transformer.value.getTranslation();
final Offset translation = Offset(
translationVector.x,
translationVector.y,
);
final FrictionSimulation frictionSimulationX = FrictionSimulation(
widget.interactionEndFrictionCoefficient,
translation.dx,
details.velocity.pixelsPerSecond.dx,
);
final FrictionSimulation frictionSimulationY = FrictionSimulation(
widget.interactionEndFrictionCoefficient,
translation.dy,
details.velocity.pixelsPerSecond.dy,
);
final double tFinal = _getFinalTime(
details.velocity.pixelsPerSecond.distance,
widget.interactionEndFrictionCoefficient,
);
_animation =
Tween<Offset>(
begin: translation,
end: Offset(
frictionSimulationX.finalX,
frictionSimulationY.finalX,
),
).animate(
CurvedAnimation(parent: _controller, curve: Curves.decelerate),
)
..addListener(_handleInertiaAnimation);
_controller
..duration = Duration(milliseconds: (tFinal * 1000).round())
..forward();
case _GestureType.scale:
if (details.scaleVelocity.abs() < 0.1) {
_currentAxis = null;
return;
}
final double scale = _transformer.value.getMaxScaleOnAxis();
final FrictionSimulation frictionSimulation = FrictionSimulation(
widget.interactionEndFrictionCoefficient * widget.scaleFactor,
scale,
details.scaleVelocity / 10,
);
final double tFinal = _getFinalTime(
details.scaleVelocity.abs(),
widget.interactionEndFrictionCoefficient,
effectivelyMotionless: 0.1,
);
_scaleAnimation =
Tween<double>(
begin: scale,
end: frictionSimulation.x(tFinal),
).animate(
CurvedAnimation(
parent: _scaleController,
curve: Curves.decelerate,
),
)
..addListener(_handleScaleAnimation);
_scaleController
..duration = Duration(milliseconds: (tFinal * 1000).round())
..forward();
case _GestureType.rotate || null:
break;
}
}
void _receivedPointerSignal(PointerSignalEvent event) {
final Offset local = event.localPosition;
final Offset global = event.position;
final double scaleChange;
if (event is PointerScrollEvent) {
if (event.kind == PointerDeviceKind.trackpad) {
widget.onInteractionStart?.call(
ScaleStartDetails(focalPoint: global, localFocalPoint: local),
);
final Offset localDelta = PointerEvent.transformDeltaViaPositions(
untransformedEndPosition: global + event.scrollDelta,
untransformedDelta: event.scrollDelta,
transform: event.transform,
);
final Offset focalPointScene = _transformer.toScene(local);
final Offset newFocalPointScene = _transformer.toScene(
local - localDelta,
);
_transformer.value = _matrixTranslate(
_transformer.value,
newFocalPointScene - focalPointScene,
);
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
focalPoint: global - event.scrollDelta,
localFocalPoint: local - localDelta,
focalPointDelta: -localDelta,
),
);
widget.onInteractionEnd?.call(ScaleEndDetails());
return;
}
_handlePointerScrollEvent(event);
return;
} else if (event is PointerScaleEvent) {
scaleChange = event.scale;
} else {
return;
}
widget.onInteractionStart?.call(
ScaleStartDetails(focalPoint: global, localFocalPoint: local),
);
if (!_gestureIsSupported(_GestureType.scale)) {
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
focalPoint: global,
localFocalPoint: local,
scale: scaleChange,
),
);
widget.onInteractionEnd?.call(ScaleEndDetails());
return;
}
final Offset focalPointScene = _transformer.toScene(local);
_transformer.value = _matrixScale(_transformer.value, scaleChange);
// After scaling, translate such that the event's position is at the
// same scene point before and after the scale.
final Offset focalPointSceneScaled = _transformer.toScene(local);
_transformer.value = _matrixTranslate(
_transformer.value,
focalPointSceneScaled - focalPointScene,
);
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
focalPoint: global,
localFocalPoint: local,
scale: scaleChange,
),
);
widget.onInteractionEnd?.call(ScaleEndDetails());
}
void _handlePointerScrollEvent(PointerScrollEvent event) {
final Offset local = event.localPosition;
final Offset global = event.position;
if (_gestureIsSupported(_GestureType.scale)) {
if (HardwareKeyboard.instance.isControlPressed) {
_handleMouseWheelScale(event, local, global);
return;
}
final shift = HardwareKeyboard.instance.isShiftPressed;
if (shift || HardwareKeyboard.instance.isAltPressed) {
_handleMouseWheelPanAsScale(event, local, global, shift);
return;
}
widget.pointerSignalFallback?.call(event);
}
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
focalPoint: global,
localFocalPoint: local,
scale: math.exp(-event.scrollDelta.dy / widget.scaleFactor),
),
);
widget.onInteractionEnd?.call(ScaleEndDetails());
}
void _handleMouseWheelScale(
PointerScrollEvent event,
Offset local,
Offset global,
) {
final double scaleChange = math.exp(
-event.scrollDelta.dy / widget.scaleFactor,
);
final Offset focalPointScene = _transformer.toScene(local);
_transformer.value = _matrixScale(_transformer.value, scaleChange);
final Offset focalPointSceneScaled = _transformer.toScene(local);
_transformer.value = _matrixTranslate(
_transformer.value,
focalPointSceneScaled - focalPointScene,
);
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
focalPoint: global,
localFocalPoint: local,
scale: scaleChange,
),
);
widget.onInteractionEnd?.call(ScaleEndDetails());
}
void _handleMouseWheelPanAsScale(
PointerScrollEvent event,
Offset local,
Offset global,
bool flip,
) {
final Offset translation = flip
? event.scrollDelta.flip
: event.scrollDelta;
final Offset focalPointScene = _transformer.toScene(local);
final Offset newFocalPointScene = _transformer.toScene(local - translation);
_transformer.value = _matrixTranslate(
_transformer.value,
newFocalPointScene - focalPointScene,
);
}
void _handleInertiaAnimation() {
if (!_controller.isAnimating) {
_currentAxis = null;
_animation?.removeListener(_handleInertiaAnimation);
_animation = null;
_controller.reset();
return;
}
final Vector3 translationVector = _transformer.value.getTranslation();
final Offset translation = Offset(translationVector.x, translationVector.y);
_transformer.value = _matrixTranslate(
_transformer.value,
_transformer.toScene(_animation!.value) -
_transformer.toScene(translation),
);
}
void _handleScaleAnimation() {
if (!_scaleController.isAnimating) {
_currentAxis = null;
_scaleAnimation?.removeListener(_handleScaleAnimation);
_scaleAnimation = null;
_scaleController.reset();
return;
}
final double desiredScale = _scaleAnimation!.value;
final double scaleChange =
desiredScale / _transformer.value.getMaxScaleOnAxis();
final Offset referenceFocalPoint = _transformer.toScene(
_scaleAnimationFocalPoint,
);
_transformer.value = _matrixScale(_transformer.value, scaleChange);
final Offset focalPointSceneScaled = _transformer.toScene(
_scaleAnimationFocalPoint,
);
_transformer.value = _matrixTranslate(
_transformer.value,
focalPointSceneScaled - referenceFocalPoint,
);
}
void _handleTransformation() {
setState(() {});
}
void _onPointerDown(PointerDownEvent event) {
widget.onPointerDown?.call(event);
_scaleGestureRecognizer.addPointer(event);
}
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_scaleController = AnimationController(vsync: this);
_transformer.addListener(_handleTransformation);
}
@override
void didUpdateWidget(MouseInteractiveViewer oldWidget) {
super.didUpdateWidget(oldWidget);
final TransformationController? newController =
widget.transformationController;
if (newController == oldWidget.transformationController) {
return;
}
_transformer.removeListener(_handleTransformation);
if (oldWidget.transformationController == null) {
_transformer.dispose();
}
_transformer = newController ?? TransformationController();
_transformer.addListener(_handleTransformation);
}
@override
void dispose() {
_scaleGestureRecognizer.dispose();
_controller.dispose();
_scaleController.dispose();
_transformer.removeListener(_handleTransformation);
if (widget.transformationController == null) {
_transformer.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
assert(widget.child.key == widget.childKey);
return Listener(
key: _parentKey,
behavior: HitTestBehavior.opaque,
onPointerSignal: _receivedPointerSignal,
onPointerDown: _onPointerDown,
onPointerPanZoomStart: _scaleGestureRecognizer.addPointerPanZoom,
onPointerPanZoomUpdate: widget.onPointerPanZoomUpdate,
onPointerPanZoomEnd: widget.onPointerPanZoomEnd,
child: _InteractiveViewerBuilt(
childKey: widget.childKey,
clipBehavior: widget.clipBehavior,
constrained: widget.constrained,
matrix: _transformer.value,
alignment: widget.alignment,
child: widget.child,
),
);
}
}
class _InteractiveViewerBuilt extends StatelessWidget {
const _InteractiveViewerBuilt({
required this.child,
required this.childKey,
required this.clipBehavior,
required this.constrained,
required this.matrix,
required this.alignment,
});
final Widget child;
final GlobalKey childKey;
final Clip clipBehavior;
final bool constrained;
final Matrix4 matrix;
final Alignment? alignment;
@override
Widget build(BuildContext context) {
Widget child = Transform(
transform: matrix,
alignment: alignment,
child: this.child,
);
if (!constrained) {
child = OverflowBox(
alignment: Alignment.topLeft,
minWidth: 0.0,
minHeight: 0.0,
maxWidth: double.infinity,
maxHeight: double.infinity,
child: child,
);
}
if (clipBehavior != Clip.none) {
child = ClipRect(clipBehavior: clipBehavior, child: child);
}
return child;
}
}
enum _GestureType { pan, scale, rotate }
double _getFinalTime(
double velocity,
double drag, {
double effectivelyMotionless = 10,
}) {
return math.log(effectivelyMotionless / velocity) / math.log(drag / 100);
}
Offset _getMatrixTranslation(Matrix4 matrix) {
final Vector3 nextTranslation = matrix.getTranslation();
return Offset(nextTranslation.x, nextTranslation.y);
}
Quad _transformViewport(Matrix4 matrix, Rect viewport) {
final Matrix4 inverseMatrix = matrix.clone()..invert();
return Quad.points(
inverseMatrix.transform3(
Vector3(viewport.topLeft.dx, viewport.topLeft.dy, 0.0),
),
inverseMatrix.transform3(
Vector3(viewport.topRight.dx, viewport.topRight.dy, 0.0),
),
inverseMatrix.transform3(
Vector3(viewport.bottomRight.dx, viewport.bottomRight.dy, 0.0),
),
inverseMatrix.transform3(
Vector3(viewport.bottomLeft.dx, viewport.bottomLeft.dy, 0.0),
),
);
}
Quad _getAxisAlignedBoundingBoxWithRotation(Rect rect, double rotation) {
final Matrix4 rotationMatrix = Matrix4.identity()
..translateByDouble(rect.size.width / 2, rect.size.height / 2, 0, 1)
..rotateZ(rotation)
..translateByDouble(-rect.size.width / 2, -rect.size.height / 2, 0, 1);
final Quad boundariesRotated = Quad.points(
rotationMatrix.transform3(Vector3(rect.left, rect.top, 0.0)),
rotationMatrix.transform3(Vector3(rect.right, rect.top, 0.0)),
rotationMatrix.transform3(Vector3(rect.right, rect.bottom, 0.0)),
rotationMatrix.transform3(Vector3(rect.left, rect.bottom, 0.0)),
);
// ignore: invalid_use_of_visible_for_testing_member
return InteractiveViewer.getAxisAlignedBoundingBox(boundariesRotated);
}
Offset _exceedsBy(Quad boundary, Quad viewport) {
final List<Vector3> viewportPoints = <Vector3>[
viewport.point0,
viewport.point1,
viewport.point2,
viewport.point3,
];
Offset largestExcess = Offset.zero;
for (final Vector3 point in viewportPoints) {
// ignore: invalid_use_of_visible_for_testing_member
final Vector3 pointInside = InteractiveViewer.getNearestPointInside(
point,
boundary,
);
final Offset excess = Offset(
pointInside.x - point.x,
pointInside.y - point.y,
);
if (excess.dx.abs() > largestExcess.dx.abs()) {
largestExcess = Offset(excess.dx, largestExcess.dy);
}
if (excess.dy.abs() > largestExcess.dy.abs()) {
largestExcess = Offset(largestExcess.dx, excess.dy);
}
}
return _round(largestExcess);
}
Offset _round(Offset offset) {
return Offset(
double.parse(offset.dx.toStringAsFixed(9)),
double.parse(offset.dy.toStringAsFixed(9)),
);
}
Offset _alignAxis(Offset offset, Axis axis) {
return switch (axis) {
Axis.horizontal => Offset(offset.dx, 0.0),
Axis.vertical => Offset(0.0, offset.dy),
};
}
Axis? _getPanAxis(Offset point1, Offset point2) {
if (point1 == point2) {
return null;
}
final double x = point2.dx - point1.dx;
final double y = point2.dy - point1.dy;
return x.abs() > y.abs() ? Axis.horizontal : Axis.vertical;
}
extension on Offset {
Offset get flip => Offset(dy, dx);
}

View File

@@ -124,9 +124,7 @@ class _CachedNetworkSVGImageState extends State<CachedNetworkSVGImage> {
Future<void> _loadImage() async {
try {
var file = (await widget._cacheManager.getFileFromMemory(
_cacheKey,
))?.file;
var file = (await widget._cacheManager.getFileFromCache(_cacheKey))?.file;
file ??= await widget._cacheManager.getSingleFile(
widget._url,
@@ -173,15 +171,14 @@ class _CachedNetworkSVGImageState extends State<CachedNetworkSVGImage> {
if (mounted) {
setState(() {});
} else {
SchedulerBinding.instance.addPostFrameCallback((_) => setState(() {}));
SchedulerBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
}
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(

View File

@@ -19,9 +19,8 @@ import 'dart:math' show min;
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/custom_layout.dart';
import 'package:PiliPlus/common/widgets/flutter/custom_layout.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/marquee.dart' show ContextSingleTicker;
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/utils/context_ext.dart';
@@ -65,7 +64,6 @@ class CustomGridView extends StatelessWidget {
required this.maxWidth,
required this.picArr,
this.onViewImage,
this.onDismissed,
this.fullScreen = false,
});
@@ -73,7 +71,6 @@ class CustomGridView extends StatelessWidget {
final double space;
final List<ImageModel> picArr;
final VoidCallback? onViewImage;
final ValueChanged<int>? onDismissed;
final bool fullScreen;
static bool horizontalPreview = Pref.horizontalPreview;
@@ -96,20 +93,18 @@ class CustomGridView extends StatelessWidget {
!context.mediaQuerySize.isPortrait) {
final scaffoldState = Scaffold.maybeOf(context);
if (scaffoldState != null) {
onViewImage?.call();
PageUtils.onHorizontalPreviewState(
scaffoldState,
ContextSingleTicker(scaffoldState.context),
imgList,
index,
);
return;
}
}
onViewImage?.call();
PageUtils.imageView(
initialPage: index,
imgList: imgList,
onDismissed: onDismissed,
);
}

View File

@@ -19,20 +19,17 @@ void imageSaveDialog({
animationType: SmartAnimationType.centerScale_otherSlide,
builder: (context) {
final theme = Theme.of(context);
late final iconColor = theme.colorScheme.onSurfaceVariant;
Widget iconBtn({
String? tooltip,
required IconData icon,
required Icon icon,
required VoidCallback? onPressed,
}) {
return iconButton(
context: context,
onPressed: onPressed,
iconSize: 20,
icon: icon,
bgColor: Colors.transparent,
iconColor: iconColor,
iconSize: 20,
tooltip: tooltip,
onPressed: onPressed,
);
}
@@ -61,19 +58,19 @@ void imageSaveDialog({
Positioned(
right: 8,
top: 8,
child: Container(
child: SizedBox(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.3),
shape: BoxShape.circle,
),
child: const IconButton(
child: IconButton(
tooltip: '关闭',
style: ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero),
backgroundColor: WidgetStatePropertyAll(
Colors.black.withValues(alpha: 0.3),
),
padding: const WidgetStatePropertyAll(EdgeInsets.zero),
),
onPressed: SmartDialog.dismiss,
icon: Icon(
icon: const Icon(
Icons.close,
size: 18,
color: Colors.white,
@@ -105,7 +102,7 @@ void imageSaveDialog({
(res) => SmartDialog.showToast(res['msg']),
),
},
icon: Icons.watch_later_outlined,
icon: const Icon(Icons.watch_later_outlined),
),
if (cover?.isNotEmpty == true) ...[
if (Utils.isMobile)
@@ -115,7 +112,7 @@ void imageSaveDialog({
SmartDialog.dismiss();
ImageUtils.onShareImg(cover!);
},
icon: Icons.share,
icon: const Icon(Icons.share),
),
iconBtn(
tooltip: '保存封面图',
@@ -128,7 +125,7 @@ void imageSaveDialog({
SmartDialog.dismiss();
}
},
icon: Icons.download,
icon: const Icon(Icons.download),
),
],
],

View File

@@ -80,7 +80,7 @@ class NetworkImgLayer extends StatelessWidget {
if (height == null || forceUseCacheWidth || width <= height!) {
memCacheWidth = width.cacheSize(context);
} else {
memCacheHeight = height.cacheSize(context);
memCacheHeight = height?.cacheSize(context);
}
return CachedNetworkImage(
imageUrl: ImageUtils.thumbnailUrl(src, quality),

View File

@@ -8,10 +8,10 @@ import 'package:flutter/material.dart';
/// show a [Hero] animation.
class HeroDialogRoute<T> extends PageRoute<T> {
HeroDialogRoute({
required this.builder,
required this.pageBuilder,
});
final WidgetBuilder builder;
final RoutePageBuilder pageBuilder;
@override
bool get opaque => false;
@@ -50,12 +50,10 @@ class HeroDialogRoute<T> extends PageRoute<T> {
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
final Widget child = builder(context);
final Widget result = Semantics(
return Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: child,
child: pageBuilder(context, animation, secondaryAnimation),
);
return result;
}
}

View File

@@ -18,7 +18,7 @@ class InteractiveViewerBoundary extends StatefulWidget {
super.key,
required this.child,
required this.boundaryWidth,
this.controller,
required this.controller,
this.onScaleChanged,
this.onLeftBoundaryHit,
this.onRightBoundaryHit,
@@ -43,7 +43,7 @@ class InteractiveViewerBoundary extends StatefulWidget {
final double boundaryWidth;
/// The [TransformationController] for the [InteractiveViewer].
final TransformationController? controller;
final TransformationController controller;
/// Called when the scale changed after an interaction ended.
final ScaleChanged? onScaleChanged;
@@ -68,7 +68,7 @@ class InteractiveViewerBoundary extends StatefulWidget {
class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
with SingleTickerProviderStateMixin {
TransformationController? _controller;
late TransformationController _controller;
double? _scale;
@@ -85,8 +85,7 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
@override
void initState() {
super.initState();
_controller = widget.controller ?? TransformationController();
_controller = widget.controller;
_animateController = AnimationController(
duration: const Duration(milliseconds: 300),
@@ -98,9 +97,7 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
@override
void dispose() {
_controller!.dispose();
_animateController.dispose();
super.dispose();
}
@@ -183,7 +180,7 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
}
void _updateBoundaryDetection() {
final double scale = _controller!.value.row0[0];
final double scale = _controller.value.row0[0];
if (_scale != scale) {
// the scale changed
@@ -196,7 +193,7 @@ class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
return;
}
final double xOffset = _controller!.value.row0[3];
final double xOffset = _controller.value.row0[3];
final double boundaryWidth = widget.boundaryWidth;
final double boundaryEnd = boundaryWidth * scale;
final double xPos = boundaryEnd + xOffset;

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