Compare commits

..

230 Commits

Author SHA1 Message Date
dom
de85e82bfa Update build.yml 2026-01-07 12:12:39 +08:00
bggRGjQaUbCoE
9855b35b65 opt ui
fix

report im msg

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-07 11:32:24 +08:00
bggRGjQaUbCoE
5a0b045a1f opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-06 13:10:02 +08:00
bggRGjQaUbCoE
c226f8f6df upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-06 13:10:02 +08:00
bggRGjQaUbCoE
fd06fa9cc4 report sc
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-06 13:09:58 +08:00
s
2b5f111fb1 fix: resolve Linux window close handler to prevent app hang (#1795)
- Add delete-event callback that properly quits the application when window is closed
2026-01-03 18:42:30 +08:00
bggRGjQaUbCoE
9f5ce5ae37 fix find sc index
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-03 15:28:19 +08:00
Vixb
3d95165d46 feat: support more dolby id (#1794) 2026-01-03 12:16:09 +08:00
bggRGjQaUbCoE
cfb72f27ac tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-03 11:29:47 +08:00
bggRGjQaUbCoE
bcacc41db3 live dm action
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-03 11:01:06 +08:00
bggRGjQaUbCoE
b2da99e334 fix dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-02 14:58:52 +08:00
bggRGjQaUbCoE
041af37bb0 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-02 12:06:09 +08:00
bggRGjQaUbCoE
80e007bac6 add static2Scroll option
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-02 12:06:05 +08:00
bggRGjQaUbCoE
87c7699324 fix dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-31 14:01:49 +08:00
bggRGjQaUbCoE
11912c5f62 fix level
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-31 12:44:03 +08:00
bggRGjQaUbCoE
236a8b3023 fix dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-31 12:14:01 +08:00
bggRGjQaUbCoE
63e4bac204 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-31 12:13:38 +08:00
My-Responsitories
2e11247af4 fix: font size 2025-12-30 14:13:45 +08:00
My-Responsitories
13f377f680 fix: font size 2025-12-30 14:07:18 +08:00
bggRGjQaUbCoE
b9d594bc8b tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-29 21:04:52 +08:00
bggRGjQaUbCoE
2a52157c3f show live rank
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-29 21:04:10 +08:00
bggRGjQaUbCoE
a037d8e793 opt dyn publish
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-29 21:04:10 +08:00
My-Responsitories
49b7ea14c3 refa: danmaku & feat: scroll fixed velocity (#1791) 2025-12-29 21:03:24 +08:00
bggRGjQaUbCoE
0a40d11133 opt SpringDescription
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-28 10:58:31 +08:00
bggRGjQaUbCoE
dff6b6486d do not check uploadPictureIconState
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-27 20:54:57 +08:00
bggRGjQaUbCoE
b51c646415 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-27 20:54:41 +08:00
My-Responsitories
25acf3a9bb fix: dynamic openInBrowser (#1790) 2025-12-27 20:51:40 +08:00
bggRGjQaUbCoE
7ec90e9a22 opt dyn more text
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-27 14:07:49 +08:00
bggRGjQaUbCoE
645ce0b7b3 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-27 13:52:36 +08:00
bggRGjQaUbCoE
864fef5881 fix check uploadPictureIconState
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-27 12:40:35 +08:00
bggRGjQaUbCoE
eea232c6db show dyn interaction
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-27 12:40:35 +08:00
bggRGjQaUbCoE
25fca498fc opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-27 12:40:30 +08:00
bggRGjQaUbCoE
c9a02f9c74 fix retry
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-26 10:43:27 +08:00
bggRGjQaUbCoE
99602eea95 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-26 10:43:27 +08:00
bggRGjQaUbCoE
b5fe0faeec Revert "opt view dyn reply"
This reverts commit 161bf2eedb.
2025-12-26 10:43:27 +08:00
bggRGjQaUbCoE
20a36e8f9a tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-25 13:46:21 +08:00
bggRGjQaUbCoE
161bf2eedb opt view dyn reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-24 18:36:17 +08:00
bggRGjQaUbCoE
fcf4e72d8e fix vote card
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-24 12:47:50 +08:00
bggRGjQaUbCoE
b46cb69df4 opt reload reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-24 12:47:30 +08:00
My-Responsitories
43c7620b4c fix: cacheIndex 2025-12-24 01:05:59 +08:00
bggRGjQaUbCoE
1a8f65b075 opt bar set
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-23 21:12:51 +08:00
bggRGjQaUbCoE
259e7080f8 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-23 17:31:05 +08:00
My-Responsitories
7da6f05a50 tweak 2025-12-23 14:17:22 +08:00
My-Responsitories
521ca3ad18 tweaks (#1788)
* tweak

* opt: show bar

* opt: crc32

* opt: appsign

* opt: Get

* opt: compress only if large

* opt: wbi

* tweak

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

---------

Signed-off-by: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com>
Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-23 12:57:19 +08:00
bggRGjQaUbCoE
31e5692dff upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-22 10:58:45 +08:00
bggRGjQaUbCoE
191bcbc525 fix parse dyn emoji
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-22 10:58:45 +08:00
bggRGjQaUbCoE
a0f3b3e442 tweaks
cache season fav state

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-22 10:58:40 +08:00
bggRGjQaUbCoE
5bcd822251 opt live follow list
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-22 10:43:52 +08:00
My-Responsitories
d80324655e opt: cache image (#1787) 2025-12-22 10:43:32 +08:00
bggRGjQaUbCoE
952d168022 fix grpc contentType
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-19 11:22:26 +08:00
bggRGjQaUbCoE
af723e161c tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-19 11:22:13 +08:00
dom
3ff521e103 Update build.yml 2025-12-18 22:29:19 +08:00
My-Responsitories
b4a5d985f5 opt: isolate parse danmaku & feat: grpc account (#1785)
* opt: isolate parse danmaku

* feat: grpc account
2025-12-18 22:27:40 +08:00
bggRGjQaUbCoE
1e0e2d2d6e tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-18 12:30:57 +08:00
My-Responsitories
d7f7611af4 opt: color (#1782)
* fixes

* opt: color

* fix
2025-12-18 11:08:03 +08:00
My-Responsitories
11cdb67050 feat: show network type (#1781) 2025-12-17 21:58:42 +08:00
bggRGjQaUbCoE
53cf9d54c4 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-17 21:39:34 +08:00
bggRGjQaUbCoE
2e73688688 add superChatType
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-17 19:54:30 +08:00
My-Responsitories
ce5e85e64b tweaks (#1780)
* opt: sized

* fix: self send

* feat: ctrl enter to send

* opt: checked

* opt: download notifier

* opt: Future.syncValue

* mod: account

* mod: loading state

* opt: DebounceStreamMixin

* opt: report

* opt: enum map

* opt: file handler

* opt: dyn color

* opt: Uint8List subview

* opt: FileExt

* opt: computeLuminance

* opt: isNullOrEmpty

* opt: Get context

* update [skip ci]

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

* opt dynamicColor [skip ci]

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

* fixes [skip ci]

* update

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

* update

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

---------

Signed-off-by: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com>
Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-17 17:01:10 +08:00
bggRGjQaUbCoE
02e0d34127 increase desktop max volume
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-17 14:05:30 +08:00
bggRGjQaUbCoE
830f3b60e0 opt theme
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-17 13:09:15 +08:00
bggRGjQaUbCoE
b4fb7d14d4 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-17 13:02:01 +08:00
lesetong
ab1e5cb62a Add multi-select support to pmshare panel (#1779)
* Add multi-select support to share panel

- Replace single selection index with per-user selected flag
- Allow sending to multiple selected users
- Add sending state to prevent multiple clicks
- Update default selection logic to mark first user as selected

* 简化代码逻辑

* update

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

---------

Signed-off-by: lesetong <oscarlbw@qq.com>
Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-17 12:46:36 +08:00
bggRGjQaUbCoE
348a9e014e opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-16 18:42:20 +08:00
bggRGjQaUbCoE
0baf3fcd36 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-16 15:56:54 +08:00
bggRGjQaUbCoE
13818533a7 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-16 14:13:40 +08:00
bggRGjQaUbCoE
0dd3689d65 opt opus text
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-16 11:21:03 +08:00
bggRGjQaUbCoE
23b6850778 opt dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-15 20:29:46 +08:00
bggRGjQaUbCoE
d8ca89ac8f upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-15 20:11:07 +08:00
bggRGjQaUbCoE
ae06d5f7f2 opt live header
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-14 17:02:01 +08:00
bggRGjQaUbCoE
62506d3eb5 disable alwaysOnTop on dispose
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-14 16:36:02 +08:00
bggRGjQaUbCoE
f7c61d63a0 remove deprecated pref keys
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-14 16:14:10 +08:00
bggRGjQaUbCoE
f46437f891 fix get block color
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-14 15:57:57 +08:00
bggRGjQaUbCoE
1cd949c365 use ValueGetter
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-14 14:14:27 +08:00
bggRGjQaUbCoE
bc5ce11449 fix PopupMenuText
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-14 14:04:00 +08:00
Vixb
cef4beaa0c feat: sync segment type with upstream (#1777)
* feat: sync segment type with upstream

* update

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

---------

Signed-off-by: Vixb <xzx8023@outlook.com>
Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-14 13:48:00 +08:00
bggRGjQaUbCoE
02bd68f697 opt desktop pip
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-14 12:31:51 +08:00
bggRGjQaUbCoE
2bc3275c1f opt reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-14 11:14:01 +08:00
bggRGjQaUbCoE
ec107063c3 opt pay coin
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-13 20:42:27 +08:00
bggRGjQaUbCoE
4c2fd38d6c upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-13 17:10:46 +08:00
bggRGjQaUbCoE
1a6653ba93 opt reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-13 15:53:14 +08:00
bggRGjQaUbCoE
74d5e03a34 show followee votes
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-13 15:53:14 +08:00
My-Responsitories
2b4b1debe6 tweak 2025-12-13 14:51:07 +08:00
My-Responsitories
17883eb77e opt: LoadingState (#1776) 2025-12-13 12:43:32 +08:00
bggRGjQaUbCoE
3741fe54ff upgrade dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-10 18:00:01 +08:00
bggRGjQaUbCoE
ec11af3827 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-10 17:57:39 +08:00
My-Responsitories
890dc58dc3 refa: settings model (#1773)
* opt: MediaQuery

* refa: settings model
2025-12-10 16:41:31 +08:00
bggRGjQaUbCoE
b12bdf2eb8 opt log page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-10 10:51:33 +08:00
bggRGjQaUbCoE
59c7f8a030 opt onChangeAccount
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-10 10:51:33 +08:00
bggRGjQaUbCoE
50cf74ccf7 fix play next audio
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-10 10:51:33 +08:00
LiPolymer
15b5c0a874 feat: modify recommend page's card width separately (#1771)
* feat: modify recommend card width setting separately
2025-12-10 10:51:16 +08:00
My-Responsitories
244ef22f54 feat: load config from text (#1772)
* feat: load config from text

* opt: login utils

* update

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-09 22:09:57 +08:00
bggRGjQaUbCoE
b4daf5fbd8 reduce log snackbar duration
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-08 23:07:30 +08:00
bggRGjQaUbCoE
0519ec0e4b build
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-08 23:07:30 +08:00
My-Responsitories
ff4f97de1a opt: parse sys msg (#1770) 2025-12-08 23:06:46 +08:00
My-Responsitories
773bdafec3 opt: more linter (#1765)
* opt: more linter

* fix [skip ci]

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-07 23:46:42 +08:00
bggRGjQaUbCoE
3787f99d35 opt download next
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-07 12:29:12 +08:00
bggRGjQaUbCoE
2cb8331528 cache follow order type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-07 11:48:47 +08:00
bggRGjQaUbCoE
5b6443cfa4 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-07 11:48:42 +08:00
bggRGjQaUbCoE
6fd8212d8b upgrade actions/checkout
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-07 10:42:36 +08:00
My-Responsitories
0d273f6909 refa: logfile (#1764)
* refa: logfile

* opt: log page

* opt: raf log file

* remove old log

* update

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-06 22:33:00 +08:00
bggRGjQaUbCoE
255e39b709 bump flutter
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-06 10:04:57 +08:00
bggRGjQaUbCoE
ea52dd4484 fix typos
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-06 10:04:52 +08:00
bggRGjQaUbCoE
b4a46133be opt set pageTransition
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-04 17:11:59 +08:00
bggRGjQaUbCoE
7c1644efc4 upgrade dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-04 11:26:17 +08:00
bggRGjQaUbCoE
775e1aa97d do not show others rank
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-04 11:19:50 +08:00
bggRGjQaUbCoE
2a55d4390a opt list
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-04 11:16:51 +08:00
bggRGjQaUbCoE
d57a34a4e1 fix member list jump
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-03 22:05:06 +08:00
bggRGjQaUbCoE
2785248615 opt up panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-03 21:47:12 +08:00
bggRGjQaUbCoE
c42468e2c8 opt update down progress
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-03 19:05:00 +08:00
bggRGjQaUbCoE
196ddf3f5f opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-03 17:36:47 +08:00
bggRGjQaUbCoE
27302435be specify window class name
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-03 17:36:47 +08:00
My-Responsitories
2b3ec77e92 opt: unnecessary_non_null_assertion (#1762) 2025-12-03 17:35:42 +08:00
bggRGjQaUbCoE
b7a277a57c refa: member fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-03 14:15:02 +08:00
bggRGjQaUbCoE
9c8e5b53e7 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-03 14:14:38 +08:00
bggRGjQaUbCoE
001b746f65 change dynamicColor def value
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-02 17:06:52 +08:00
bggRGjQaUbCoE
a78214de3c sort video language
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-02 16:58:26 +08:00
bggRGjQaUbCoE
d88ffb1127 tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-02 16:58:26 +08:00
dom
f05b901009 fix & opt appsign (#1761)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-02 16:58:13 +08:00
bggRGjQaUbCoE
430837eef6 opt live
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-02 11:46:38 +08:00
bggRGjQaUbCoE
fa583ebd0f tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-02 11:46:31 +08:00
bggRGjQaUbCoE
d2dcba5a59 upgrade dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-02 11:45:57 +08:00
bggRGjQaUbCoE
fb5116d525 opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-12-01 14:14:29 +08:00
bggRGjQaUbCoE
a48f6b1ca5 opt update block type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-30 11:41:34 +08:00
bggRGjQaUbCoE
fc0af3f284 remove seek announce
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-30 11:41:34 +08:00
bggRGjQaUbCoE
2288e11398 fix dm block type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-30 11:06:20 +08:00
bggRGjQaUbCoE
d95283c4ac upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-28 20:49:28 +08:00
bggRGjQaUbCoE
4b56bd5a87 fix download status
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-28 16:50:53 +08:00
My-Responsitories
62bb605ee8 tweak: danmaku (#1756)
* fix: danmaku like

* opt: danmaku merge

* remove: showSpecialDanmaku
2025-11-28 16:50:37 +08:00
bggRGjQaUbCoE
0f8da1999a opt multi select
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-28 11:59:12 +08:00
bggRGjQaUbCoE
21a2373a5c update dm
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-28 11:24:03 +08:00
bggRGjQaUbCoE
2ca5310825 reduce fullscreen sc duration
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-28 11:24:03 +08:00
dom
9ccaa3072b opt download (#1755)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-27 21:00:13 +08:00
bggRGjQaUbCoE
ded78e534f upgrade protobuf
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-26 17:11:35 +08:00
bggRGjQaUbCoE
9b0a43efc9 upgrade dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-26 11:50:08 +08:00
bggRGjQaUbCoE
10808c2a84 show live online count
update live title

update live watchedshow

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-26 11:47:00 +08:00
My-Responsitories
38a7afd63a opt: player controller (#1753)
* opt: player controller

* tweak [skip ci]

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-26 11:45:16 +08:00
bggRGjQaUbCoE
54b26d20fa upgrade dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-23 12:43:53 +08:00
bggRGjQaUbCoE
ad2bc78ebd opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-23 12:43:46 +08:00
bggRGjQaUbCoE
c4aca389a8 fixes
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-22 22:35:37 +08:00
bggRGjQaUbCoE
cb8333d4c0 show vote status
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-22 19:35:27 +08:00
bggRGjQaUbCoE
2f5eed6998 bump flutter
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-22 13:56:06 +08:00
bggRGjQaUbCoE
935c53e452 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-22 09:50:32 +08:00
bggRGjQaUbCoE
dd0ccb327b show battery level
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-22 09:50:27 +08:00
bggRGjQaUbCoE
919134759b tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-21 17:53:46 +08:00
bggRGjQaUbCoE
c1d42b498a opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-21 17:46:17 +08:00
bggRGjQaUbCoE
a7e67796f1 fix theme type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-21 14:07:22 +08:00
bggRGjQaUbCoE
6692c9e851 set dm for live
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-21 14:03:20 +08:00
bggRGjQaUbCoE
ace949aaa0 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-21 13:26:57 +08:00
bggRGjQaUbCoE
fbd9687432 upgrade kgp
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-21 13:26:47 +08:00
bggRGjQaUbCoE
460a8262c1 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-20 19:45:55 +08:00
bggRGjQaUbCoE
c8de503fae fix dyn showmore
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-20 15:27:52 +08:00
bggRGjQaUbCoE
a60cd51ff4 bump flutter
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-20 13:27:31 +08:00
bggRGjQaUbCoE
aad980ce23 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-20 13:27:15 +08:00
bggRGjQaUbCoE
e7cda7b9fa upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-19 18:34:54 +08:00
bggRGjQaUbCoE
1d368b7a8b update richtextfield
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-19 18:34:49 +08:00
bggRGjQaUbCoE
725d7055bf update flutter widgets
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-19 17:45:24 +08:00
bggRGjQaUbCoE
1fb798db4e opt ui
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-19 10:19:36 +08:00
bggRGjQaUbCoE
8e1d5e0dd5 opt live back btn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-19 09:35:14 +08:00
bggRGjQaUbCoE
2d9a1310b9 tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-19 09:35:09 +08:00
bggRGjQaUbCoE
588ec7babd upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-19 09:35:04 +08:00
My-Responsitories
2be13e7283 refa: sb & feat: sb portVideo (WIP) (#1751)
* refa: sb

* feat: sb portVideo (WIP)

* fix: keep-alive

* revert: ua version

* fix

* tweak [skip ci]

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

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-19 09:30:04 +08:00
bggRGjQaUbCoE
d5d95671ff ios uiscene migration
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-18 18:22:55 +08:00
bggRGjQaUbCoE
a0eccda6ff set player proxy
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-18 18:22:55 +08:00
bggRGjQaUbCoE
ec82c86210 upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-17 22:06:30 +08:00
bggRGjQaUbCoE
de03bef226 show video restore btn if translated
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-17 21:50:03 +08:00
My-Responsitories
0f8166620e opt: notify-debugger-on-exception (#1750) 2025-11-17 21:49:36 +08:00
bggRGjQaUbCoE
76c2de4394 fix THREE_DOT_ITEM_TYPE_UP_HELPER action
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-17 21:10:20 +08:00
bggRGjQaUbCoE
0d38ded981 show lock btn for live
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-11-17 18:51:40 +08:00
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
857 changed files with 24336 additions and 20058 deletions

2
.fvmrc
View File

@@ -1,3 +1,3 @@
{
"flutter": "3.35.7"
"flutter": "3.38.4"
}

View File

@@ -41,12 +41,6 @@ on:
default: true
type: boolean
build_linux_arm64:
description: "Build Linux-arm64"
required: false
default: true
type: boolean
tag:
description: "tag"
required: false
@@ -62,7 +56,7 @@ jobs:
steps:
- name: 代码迁出
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -116,7 +110,7 @@ jobs:
shell: bash
- name: Release
if: ${{ github.event.inputs.tag != '' }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.inputs.tag }}
@@ -125,21 +119,21 @@ jobs:
PiliPlus_android_*.apk
- name: 上传
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Android_arm64-v8a
path: |
PiliPlus_android_*_arm64-v8a.apk
- name: 上传
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Android_armeabi-v7a
path: |
PiliPlus_android_*_armeabi-v7a.apk
- name: 上传
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Android_x86_64
path: |
@@ -150,32 +144,25 @@ jobs:
uses: ./.github/workflows/ios.yml
permissions: write-all
with:
tag: github.event.inputs.tag
tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
mac:
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_mac == 'true' }}
if: ${{ github.event.inputs.build_mac == 'true' }}
uses: ./.github/workflows/mac.yml
permissions: write-all
with:
tag: github.event.inputs.tag
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.inputs.tag
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' }}
if: ${{ github.event.inputs.build_linux_x64 == 'true' }}
uses: ./.github/workflows/linux_x64.yml
permissions: write-all
with:
tag: github.event.inputs.tag
linux_arm64:
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_linux_arm64 == 'true' }}
uses: ./.github/workflows/linux_arm64.yml
permissions: write-all
with:
tag: github.event.inputs.tag
tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -46,7 +46,7 @@ jobs:
PiliPlus_ios_*.ipa
- name: Upload ios release
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: iOS-release
path: PiliPlus_ios_*.ipa

View File

@@ -1,260 +0,0 @@
name: Build for Linux Arm64
on:
workflow_call:
inputs:
tag:
description: "tag"
required: false
default: ""
type: string
workflow_dispatch:
jobs:
build-linux-app:
name: Release Linux Arm64
runs-on: ubuntu-24.04-arm
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: master
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 }}_arm64.tar.gz -C build/linux/arm64/release/bundle .
- name: Packege deb
run: |
printf "建立构建目录...\n"
mkdir "PiliPlus_linux_${{ env.version }}_arm64"
pushd "PiliPlus_linux_${{ env.version }}_arm64"
mkdir -p opt/PiliPlus
mkdir -p usr/share/applications
mkdir -p usr/share/icons/hicolor/512x512/apps
printf "复制文件...\n"
cp -r ../build/linux/arm64/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 }}_arm64"
printf "完成: PiliPlus_linux_%s_arm64.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/arm64/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 }}_arm64.rpm" \;
printf "完成: PiliPlus_linux_%s_arm64.rpm\n" "${{ env.version }}"
shell: bash
- name: Package AppImage
run: |
printf "下载 appimagetool (ARM64)...\n"
wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-aarch64.AppImage
chmod +x appimagetool-aarch64.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/arm64/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=aarch64 ./appimagetool-aarch64.AppImage "$APPDIR" "PiliPlus_linux_${{ env.version }}_arm64.AppImage"
printf "完成: PiliPlus_linux_%s_arm64.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_arm64_packege
path: PiliPlus_linux_*.tar.gz
- name: Upload linux deb package
uses: actions/upload-artifact@v4
with:
name: Linux_deb_arm64_package
path: PiliPlus_linux_*.deb
- name: Upload linux rpm package
uses: actions/upload-artifact@v4
with:
name: Linux_rpm_arm64_package
path: PiliPlus_linux_*.rpm
- name: Upload linux AppImage package
uses: actions/upload-artifact@v4
with:
name: Linux_AppImage_arm64_package
path: PiliPlus_linux_*.AppImage

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -179,7 +179,7 @@ jobs:
printf "完成: PiliPlus_linux_%s_amd64.rpm\n" "${{ env.version }}"
shell: bash
- name: Package AppImage
run: |
printf "下载 appimagetool...\n"
@@ -195,13 +195,13 @@ jobs:
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
@@ -219,7 +219,7 @@ jobs:
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
@@ -236,25 +236,25 @@ jobs:
PiliPlus_linux_*.AppImage
- name: Upload linux targz package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Linux_targz_amd64_packege
path: PiliPlus_linux_*.tar.gz
- name: Upload linux deb package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Linux_deb_amd64_package
path: PiliPlus_linux_*.deb
- name: Upload linux rpm package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Linux_rpm_amd64_package
path: PiliPlus_linux_*.rpm
- name: Upload linux AppImage package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Linux_AppImage_amd64_package
path: PiliPlus_linux_*.AppImage

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -52,7 +52,7 @@ jobs:
PiliPlus_macos_*.dmg
- name: Upload macos release
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: macOS-release
path: PiliPlus_macos_*.dmg

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -68,13 +68,13 @@ jobs:
PiliPlus-Win-Setup/PiliPlus_windows_*.exe
- name: Upload windows file release
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Windows-file-x64-release
path: Release
- name: Upload windows setup release
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Windows-setup-x64-release
path: PiliPlus-Win-Setup

View File

@@ -43,6 +43,8 @@
## feat
- [x] DLNA 投屏
- [x] 离线缓存/播放
- [x] 移动端支持点击弹幕悬停,点赞、复制、举报 by [@My-Responsitories](https://github.com/My-Responsitories)
- [x] 播放音频
- [x] 跳过番剧片头/片尾
@@ -151,7 +153,7 @@
- [x] 粉丝、关注用户、拉黑用户查看
- [x] 用户主页查看
- [x] 关注/取关用户
- [ ] 离线缓存
- [x] 离线缓存
- [x] 稍后再看
- [x] 观看记录
- [x] 我的收藏

View File

@@ -12,7 +12,8 @@ include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- lib/grpc/bilibili/**
- lib/grpc/google/**
# - lib/grpc/google/**
# - lib/common/widgets/flutter/**
formatter:
trailing_commas: preserve
@@ -63,5 +64,13 @@ linter:
- use_null_aware_elements
- unnecessary_lambdas
- use_is_even_rather_than_modulo
- unnecessary_async
- unnecessary_await_in_return
- unnecessary_getters_setters
- prefer_const_literals_to_create_immutables
- no_literal_bool_comparisons
- use_truncating_division
- use_string_buffers
- unnecessary_statements
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

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

@@ -43,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)
@@ -52,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) {
@@ -76,33 +83,47 @@ 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()
@@ -111,6 +132,7 @@ class MainActivity : AudioServiceActivity() {
setPictureInPictureParams(params)
}
}
else -> result.notImplemented()
}
}
@@ -133,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)
@@ -143,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,

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://downloads.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@@ -20,7 +20,7 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.12.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.0" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}
include(":app")

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,83 +1,135 @@
PODS:
- appscheme (1.0.4):
- app_links (7.0.0):
- Flutter
- audio_service (0.0.1):
- Flutter
- FlutterMacOS
- audio_session (0.0.1):
- Flutter
- auto_orientation (0.0.1):
- Flutter
- chat_bottom_container (0.0.1):
- Flutter
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.9)
- DKImagePickerController/PhotoGallery (4.3.9):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.9)
- DKPhotoGallery (0.0.19):
- DKPhotoGallery/Core (= 0.0.19)
- DKPhotoGallery/Model (= 0.0.19)
- DKPhotoGallery/Preview (= 0.0.19)
- DKPhotoGallery/Resource (= 0.0.19)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.19):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_inappwebview_ios (0.0.1):
- Flutter
- flutter_inappwebview_ios/Core (= 0.0.1)
- OrderedSet (~> 6.0.3)
- flutter_inappwebview_ios/Core (0.0.1):
- Flutter
- OrderedSet (~> 6.0.3)
- flutter_mailer (0.0.1):
- Flutter
- flutter_native_splash (2.4.3):
- Flutter
- flutter_volume_controller (0.0.1):
- Flutter
- fluttertoast (0.0.2):
- Flutter
- Toast
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- gt3_flutter_plugin (0.0.8):
- gt3_flutter_plugin (0.0.9):
- Flutter
- GT3Captcha-iOS
- GT3Captcha-iOS (0.15.8.3)
- image_cropper (0.0.4):
- Flutter
- TOCropViewController (~> 2.8.0)
- image_picker_ios (0.0.1):
- Flutter
- live_photo_maker (0.0.3):
- Flutter
- media_kit_libs_ios_video (1.0.4):
- Flutter
- media_kit_native_event_loop (1.0.0):
- Flutter
- media_kit_video (0.0.1):
- Flutter
- OrderedSet (6.0.3)
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.1.1):
- permission_handler_apple (9.3.0):
- Flutter
- ReachabilitySwift (5.0.0)
- saver_gallery (0.0.1):
- Flutter
- screen_brightness_ios (0.1.0):
- Flutter
- SDWebImage (5.21.3):
- SDWebImage/Core (= 5.21.3)
- SDWebImage/Core (5.21.3)
- share_plus (0.0.1):
- Flutter
- sqflite (0.0.3):
- shared_preferences_foundation (0.0.1):
- Flutter
- FMDB (>= 2.7.5)
- status_bar_control (3.2.1):
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- system_proxy (0.0.1):
- Flutter
- Toast (4.1.0)
- FlutterMacOS
- SwiftyGif (5.4.5)
- TOCropViewController (2.8.0)
- url_launcher_ios (0.0.1):
- Flutter
- volume_controller (0.0.1):
- Flutter
- wakelock_plus (0.0.1):
- Flutter
- webview_cookie_manager (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
DEPENDENCIES:
- appscheme (from `.symlinks/plugins/appscheme/ios`)
- audio_service (from `.symlinks/plugins/audio_service/ios`)
- app_links (from `.symlinks/plugins/app_links/ios`)
- audio_service (from `.symlinks/plugins/audio_service/darwin`)
- audio_session (from `.symlinks/plugins/audio_session/ios`)
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
- chat_bottom_container (from `.symlinks/plugins/chat_bottom_container/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- live_photo_maker (from `.symlinks/plugins/live_photo_maker/ios`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
@@ -87,45 +139,58 @@ DEPENDENCIES:
- saver_gallery (from `.symlinks/plugins/saver_gallery/ios`)
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- status_bar_control (from `.symlinks/plugins/status_bar_control/ios`)
- system_proxy (from `.symlinks/plugins/system_proxy/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
- webview_cookie_manager (from `.symlinks/plugins/webview_cookie_manager/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
SPEC REPOS:
trunk:
- FMDB
- DKImagePickerController
- DKPhotoGallery
- GT3Captcha-iOS
- ReachabilitySwift
- Toast
- OrderedSet
- SDWebImage
- SwiftyGif
- TOCropViewController
EXTERNAL SOURCES:
appscheme:
:path: ".symlinks/plugins/appscheme/ios"
app_links:
:path: ".symlinks/plugins/app_links/ios"
audio_service:
:path: ".symlinks/plugins/audio_service/ios"
:path: ".symlinks/plugins/audio_service/darwin"
audio_session:
:path: ".symlinks/plugins/audio_session/ios"
auto_orientation:
:path: ".symlinks/plugins/auto_orientation/ios"
chat_bottom_container:
:path: ".symlinks/plugins/chat_bottom_container/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_inappwebview_ios:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
flutter_mailer:
:path: ".symlinks/plugins/flutter_mailer/ios"
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_volume_controller:
:path: ".symlinks/plugins/flutter_volume_controller/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
gt3_flutter_plugin:
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
image_cropper:
:path: ".symlinks/plugins/image_cropper/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
live_photo_maker:
:path: ".symlinks/plugins/live_photo_maker/ios"
media_kit_libs_ios_video:
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
media_kit_native_event_loop:
@@ -144,57 +209,55 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/screen_brightness_ios/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
status_bar_control:
:path: ".symlinks/plugins/status_bar_control/ios"
system_proxy:
:path: ".symlinks/plugins/system_proxy/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
volume_controller:
:path: ".symlinks/plugins/volume_controller/ios"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
webview_cookie_manager:
:path: ".symlinks/plugins/webview_cookie_manager/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS:
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
app_links: 6d01271b3907b0ee7325c5297c75d697c4226c4d
audio_service: cab6c1a0eaf01b5a35b567e11fa67d3cc1956910
audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
chat_bottom_container: d8b077152c91b0ab90001e900748ea50353a5520
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
image_cropper: b8ef14d3fcff4040b0f9da2ca28d98219a5cba0e
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
live_photo_maker: 7d57bfc70a120b4673c10871f354f4b1b6fde5fd
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
saver_gallery: 76172dc4bf6b40e66d694948ada9ff402304dd87
screen_brightness_ios: 6a6f7794b67f07c4f1e24f6374b2d8ad367ffb39
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
TOCropViewController: 797deaf39c90e6e9ddd848d88817f6b9a8a09888
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be
PODFILE CHECKSUM: f62db4fb414ebdecb264109948f76dfef35fdc3d
COCOAPODS: 1.14.3
COCOAPODS: 1.16.2

View File

@@ -156,7 +156,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -48,6 +48,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@@ -1,14 +1,17 @@
import UIKit
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
application.applicationSupportsShakeToEdit = false // Disable shake to undo
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}

View File

@@ -2,6 +2,27 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>FlutterDeepLinkingEnabled</key>
<false/>
<key>CFBundleDevelopmentRegion</key>

View File

@@ -1,7 +1,6 @@
import 'package:PiliPlus/http/constants.dart';
import 'package:flutter/material.dart';
class StyleString {
abstract final class StyleString {
static const double cardSpace = 8;
static const double safeSpace = 12;
static const BorderRadius mdRadius = BorderRadius.all(imgRadius);
@@ -10,9 +9,13 @@ class StyleString {
static const bottomSheetRadius = BorderRadius.vertical(
top: Radius.circular(18),
);
static const dialogFixedConstraints = BoxConstraints(
minWidth: 420,
maxWidth: 420,
);
}
class Constants {
abstract final class Constants {
static const appName = 'PiliPlus';
static const sourceCodeUrl = 'https://github.com/bggRGjQaUbCoE/PiliPlus';
@@ -21,9 +24,9 @@ class Constants {
static const String appKey = 'dfca71928277209b';
// 59b43e04ad6965f34319062b478f83dd TV端
static const String appSec = 'b5475a8825547a4fc26c7d518eaaa02e';
static const String thirdSign = '04224646d1fea004e79606d3b038c84a';
static const String thirdApi =
'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png';
// static const String thirdSign = '04224646d1fea004e79606d3b038c84a';
// static const String thirdApi =
// 'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png';
static const String traceId =
'11111111111111111111111111111111:1111111111111111:0:0';
@@ -41,9 +44,7 @@ class Constants {
'{"appId":1,"platform":3,"version":"8.43.0","abtest":""}';
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

@@ -9,6 +9,13 @@ class DynamicCardSkeleton extends StatelessWidget {
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final color = theme.colorScheme.onInverseSurface;
final buttonStyle = TextButton.styleFrom(
tapTargetSize: .padded,
padding: const .symmetric(horizontal: 15),
foregroundColor: theme.colorScheme.outline.withValues(
alpha: 0.2,
),
);
return Skeleton(
child: Container(
padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
@@ -86,29 +93,19 @@ class DynamicCardSkeleton extends StatelessWidget {
if (GlobalData().dynamicsWaterfallFlow) const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
for (var i = 0; i < 3; i++)
TextButton.icon(
onPressed: () {},
icon: const Icon(
Icons.radio_button_unchecked_outlined,
size: 20,
),
style: TextButton.styleFrom(
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
foregroundColor: theme.colorScheme.outline.withValues(
alpha: 0.2,
children: const ['转发', '评论', '点赞']
.map(
(e) => TextButton.icon(
onPressed: () {},
icon: const Icon(
Icons.radio_button_unchecked_outlined,
size: 20,
),
style: buttonStyle,
label: Text(e),
),
label: Text(
i == 0
? '转发'
: i == 1
? '评论'
: '点赞',
),
),
],
)
.toList(),
),
],
),

View File

@@ -11,7 +11,7 @@ class Skeleton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.surface.withAlpha(10);
var shimmerGradient = LinearGradient(
final shimmerGradient = LinearGradient(
colors: [
Colors.transparent,
color,
@@ -62,7 +62,6 @@ class ShimmerState extends State<Shimmer> with SingleTickerProviderStateMixin {
@override
void initState() {
super.initState();
_shimmerController = AnimationController.unbounded(vsync: this)
..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000));
}

View File

@@ -7,19 +7,21 @@ class MultiSelectAppBarWidget extends StatelessWidget
final MultiSelectBase ctr;
final bool? visible;
final AppBar child;
final List<Widget>? children;
final List<Widget>? actions;
const MultiSelectAppBarWidget({
super.key,
required this.ctr,
this.visible,
this.children,
this.actions,
required this.child,
});
@override
Widget build(BuildContext context) {
if (visible ?? ctr.enableMultiSelect.value) {
final style = TextButton.styleFrom(visualDensity: VisualDensity.compact);
final colorScheme = ColorScheme.of(context);
return AppBar(
bottom: child.bottom,
leading: IconButton(
@@ -30,21 +32,22 @@ class MultiSelectAppBarWidget extends StatelessWidget
title: Obx(() => Text('已选: ${ctr.checkedCount}')),
actions: [
TextButton(
style: TextButton.styleFrom(
visualDensity: VisualDensity.compact,
),
style: style,
onPressed: () => ctr.handleSelect(checked: true),
child: const Text('全选'),
),
...?children,
...?actions,
TextButton(
style: TextButton.styleFrom(
visualDensity: VisualDensity.compact,
),
onPressed: ctr.onRemove,
style: style,
onPressed: () {
if (ctr.checkedCount == 0) {
return;
}
ctr.onRemove();
},
child: Text(
'移除',
style: TextStyle(color: Get.theme.colorScheme.error),
style: TextStyle(color: colorScheme.error),
),
),
const SizedBox(width: 6),

View File

@@ -0,0 +1,54 @@
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/models/model_owner.dart';
import 'package:flutter/material.dart';
Widget avatars({
required ColorScheme colorScheme,
required Iterable<Owner> users,
}) {
const gap = 6.0;
const size = 22.0;
const offset = size - gap;
if (users.length == 1) {
return NetworkImgLayer(
src: users.first.face,
width: size,
height: size,
type: .avatar,
);
} else {
final decoration = BoxDecoration(
shape: .circle,
border: Border.all(color: colorScheme.surface),
);
return SizedBox(
height: size,
width: offset * users.length + gap,
child: Stack(
clipBehavior: .none,
children: users.indexed
.map(
(e) => Positioned(
top: 0,
bottom: 0,
width: size,
left: e.$1 * offset,
child: DecoratedBox(
decoration: decoration,
child: Padding(
padding: const .all(.8),
child: NetworkImgLayer(
src: e.$2.face,
width: size - .8,
height: size - .8,
type: .avatar,
),
),
),
),
)
.toList(),
),
);
}
}

View File

@@ -1,7 +1,7 @@
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/extension/string_ext.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class PBadge extends StatelessWidget {
final String? text;
@@ -59,7 +59,7 @@ class PBadge extends StatelessWidget {
bgColor = Colors.black45;
color = Colors.white;
case PBadgeType.error:
if (Get.isDarkMode) {
if (theme.isDark) {
bgColor = theme.errorContainer;
color = theme.onErrorContainer;
} else {

View File

@@ -1,39 +1,32 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:flutter/material.dart';
import 'package:material_color_utilities/material_color_utilities.dart';
class ColorPalette extends StatelessWidget {
final Color color;
final ColorScheme colorScheme;
final bool selected;
final bool showBgColor;
const ColorPalette({
super.key,
required this.color,
required this.colorScheme,
required this.selected,
this.showBgColor = true,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final Hct hct = Hct.fromInt(color.toARGB32());
final primary = Color(Hct.from(hct.hue, 20.0, 90.0).toInt());
final tertiary = Color(Hct.from(hct.hue + 50, 20.0, 85.0).toInt());
final primaryContainer = Color(Hct.from(hct.hue, 30.0, 50.0).toInt());
Widget coloredBox(Color color) => Expanded(
child: ColoredBox(
color: color,
child: const SizedBox.expand(),
),
);
final primary = colorScheme.primary;
final tertiary = colorScheme.tertiary;
final primaryContainer = colorScheme.primaryContainer;
Widget child = ClipOval(
child: Column(
children: [
coloredBox(primary),
_coloredBox(primary),
Expanded(
child: Row(
children: [
coloredBox(tertiary),
coloredBox(primaryContainer),
_coloredBox(tertiary),
_coloredBox(primaryContainer),
],
),
),
@@ -50,7 +43,7 @@ class ColorPalette extends StatelessWidget {
width: 23,
height: 23,
decoration: BoxDecoration(
color: Color(Hct.from(hct.hue, 30.0, 40.0).toInt()),
color: colorScheme.surfaceContainer,
shape: BoxShape.circle,
),
child: Icon(
@@ -66,11 +59,20 @@ class ColorPalette extends StatelessWidget {
width: 50,
height: 50,
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: theme.colorScheme.onInverseSurface,
borderRadius: StyleString.mdRadius,
),
decoration: showBgColor
? BoxDecoration(
color: colorScheme.onInverseSurface,
borderRadius: StyleString.mdRadius,
)
: null,
child: child,
);
}
static Widget _coloredBox(Color color) => Expanded(
child: ColoredBox(
color: color,
child: const SizedBox.expand(),
),
);
}

View File

@@ -50,6 +50,7 @@ class LoadingWidget extends StatelessWidget {
borderRadius: const BorderRadius.all(Radius.circular(15)),
),
child: Column(
spacing: 20,
mainAxisSize: MainAxisSize.min,
children: [
//loading animation
@@ -59,10 +60,7 @@ class LoadingWidget extends StatelessWidget {
),
//msg
Container(
margin: const EdgeInsets.only(top: 20),
child: Text(msg, style: TextStyle(color: onSurfaceVariant)),
),
Text(msg, style: TextStyle(color: onSurfaceVariant)),
],
),
);

View File

@@ -1,7 +1,7 @@
import 'dart:math' as math;
import 'dart:ui' show clampDouble;
import 'package:PiliPlus/utils/utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@@ -13,99 +13,36 @@ class CustomTooltip extends StatefulWidget {
this.type = TooltipType.top,
required this.overlayWidget,
required this.child,
this.indicator,
required this.indicator,
});
final TooltipType type;
final Widget child;
final Widget Function() overlayWidget;
final Widget Function()? indicator;
static final List<CustomTooltipState> _openedTooltips =
<CustomTooltipState>[];
static bool dismissAllToolTips() {
if (_openedTooltips.isNotEmpty) {
final List<CustomTooltipState> openedTooltips = _openedTooltips.toList();
for (final CustomTooltipState state in openedTooltips) {
assert(state.mounted);
state._scheduleDismissTooltip();
}
return true;
}
return false;
}
final ValueGetter<Widget> overlayWidget;
final ValueGetter<Widget> indicator;
@override
State<CustomTooltip> createState() => CustomTooltipState();
State<CustomTooltip> createState() => _CustomTooltipState();
}
class CustomTooltipState extends State<CustomTooltip>
with SingleTickerProviderStateMixin {
static const Duration _fadeInDuration = Duration(milliseconds: 150);
static const Duration _fadeOutDuration = Duration(milliseconds: 75);
class _CustomTooltipState extends State<CustomTooltip> {
final OverlayPortalController _overlayController = OverlayPortalController();
AnimationController? _backingController;
AnimationController get _controller {
return _backingController ??= AnimationController(
duration: _fadeInDuration,
reverseDuration: _fadeOutDuration,
vsync: this,
)..addStatusListener(_handleStatusChanged);
}
CurvedAnimation? _backingOverlayAnimation;
CurvedAnimation get _overlayAnimation {
return _backingOverlayAnimation ??= CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
}
LongPressGestureRecognizer? _longPressRecognizer;
AnimationStatus _animationStatus = AnimationStatus.dismissed;
void _handleStatusChanged(AnimationStatus status) {
assert(mounted);
switch ((_animationStatus.isDismissed, status.isDismissed)) {
case (false, true):
CustomTooltip._openedTooltips.remove(this);
_overlayController.hide();
case (true, false):
_overlayController.show();
CustomTooltip._openedTooltips.add(this);
case (true, true) || (false, false):
break;
}
_animationStatus = status;
}
void _scheduleShowTooltip() {
_controller.forward();
_overlayController.show();
}
void _scheduleDismissTooltip() {
_controller.reverse();
_overlayController.hide();
}
void _handlePointerDown(PointerDownEvent event) {
assert(mounted);
const Set<PointerDeviceKind> triggerModeDeviceKinds = <PointerDeviceKind>{
PointerDeviceKind.invertedStylus,
PointerDeviceKind.stylus,
PointerDeviceKind.touch,
PointerDeviceKind.unknown,
PointerDeviceKind.trackpad,
};
_longPressRecognizer ??= LongPressGestureRecognizer(
(_longPressRecognizer ??= LongPressGestureRecognizer(
debugOwner: this,
supportedDevices: triggerModeDeviceKinds,
);
_longPressRecognizer!
..onLongPress = _scheduleShowTooltip
..addPointer(event);
)..onLongPress = _scheduleShowTooltip).addPointer(event);
}
Widget _buildCustomTooltipOverlay(BuildContext context) {
@@ -121,9 +58,8 @@ class CustomTooltipState extends State<CustomTooltip>
final _CustomTooltipOverlay overlayChild = _CustomTooltipOverlay(
verticalOffset: box.size.height / 2,
horizontslOffset: box.size.width / 2,
horizontalOffset: box.size.width / 2,
type: widget.type,
animation: _overlayAnimation,
target: target,
onDismiss: _scheduleDismissTooltip,
overlayWidget: widget.overlayWidget,
@@ -138,11 +74,8 @@ class CustomTooltipState extends State<CustomTooltip>
@protected
@override
void dispose() {
CustomTooltip._openedTooltips.remove(this);
_longPressRecognizer?.onLongPressCancel = null;
_longPressRecognizer?.dispose();
_backingController?.dispose();
_backingOverlayAnimation?.dispose();
super.dispose();
}
@@ -150,7 +83,7 @@ class CustomTooltipState extends State<CustomTooltip>
@override
Widget build(BuildContext context) {
Widget result;
if (Utils.isMobile) {
if (PlatformUtils.isMobile) {
result = Listener(
onPointerDown: _handlePointerDown,
behavior: HitTestBehavior.opaque,
@@ -172,26 +105,26 @@ class CustomTooltipState extends State<CustomTooltip>
}
}
enum _ChildType { overlay, indicator }
class _CustomTooltipOverlay extends StatelessWidget {
const _CustomTooltipOverlay({
required this.verticalOffset,
required this.horizontslOffset,
required this.horizontalOffset,
required this.type,
required this.animation,
required this.target,
required this.onDismiss,
required this.overlayWidget,
this.indicator,
required this.indicator,
});
final double verticalOffset;
final double horizontslOffset;
final double horizontalOffset;
final TooltipType type;
final Animation<double> animation;
final Offset target;
final VoidCallback onDismiss;
final Widget Function() overlayWidget;
final Widget Function()? indicator;
final ValueGetter<Widget> overlayWidget;
final ValueGetter<Widget> indicator;
@override
Widget build(BuildContext context) {
@@ -200,22 +133,21 @@ class _CustomTooltipOverlay extends StatelessWidget {
type: type,
target: target,
verticalOffset: verticalOffset,
horizontslOffset: horizontslOffset,
horizontalOffset: horizontalOffset,
preferBelow: false,
),
children: [
LayoutId(
id: 'overlay',
id: _ChildType.overlay,
child: overlayWidget(),
),
if (indicator != null)
LayoutId(
id: 'indicator',
child: indicator!(),
),
LayoutId(
id: _ChildType.indicator,
child: indicator(),
),
],
);
if (Utils.isMobile) {
if (PlatformUtils.isMobile) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onDismiss,
@@ -231,7 +163,7 @@ class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate {
required this.type,
required this.target,
required this.verticalOffset,
required this.horizontslOffset,
required this.horizontalOffset,
required this.preferBelow,
});
@@ -241,7 +173,7 @@ class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate {
final double verticalOffset;
final double horizontslOffset;
final double horizontalOffset;
final bool preferBelow;
@@ -250,13 +182,16 @@ class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate {
switch (type) {
case TooltipType.top:
Size? indicatorSize;
if (hasChild('indicator')) {
indicatorSize = layoutChild('indicator', BoxConstraints.loose(size));
if (hasChild(_ChildType.indicator)) {
indicatorSize = layoutChild(
_ChildType.indicator,
BoxConstraints.loose(size),
);
}
if (hasChild('overlay')) {
if (hasChild(_ChildType.overlay)) {
final overlaySize = layoutChild(
'overlay',
_ChildType.overlay,
BoxConstraints.loose(size),
);
Offset offset = positionDependentBox(
@@ -265,30 +200,33 @@ class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate {
childSize: overlaySize,
target: target,
verticalOffset: verticalOffset,
horizontslOffset: horizontslOffset,
horizontalOffset: horizontalOffset,
preferBelow: preferBelow,
);
if (indicatorSize != null) {
offset = Offset(offset.dx, offset.dy - indicatorSize.height + 1);
positionChild(
'indicator',
_ChildType.indicator,
Offset(
target.dx - indicatorSize.width / 2,
offset.dy + overlaySize.height - 1,
),
);
}
positionChild('overlay', offset);
positionChild(_ChildType.overlay, offset);
}
case TooltipType.right:
Size? indicatorSize;
if (hasChild('indicator')) {
indicatorSize = layoutChild('indicator', BoxConstraints.loose(size));
if (hasChild(_ChildType.indicator)) {
indicatorSize = layoutChild(
_ChildType.indicator,
BoxConstraints.loose(size),
);
}
if (hasChild('overlay')) {
if (hasChild(_ChildType.overlay)) {
final overlaySize = layoutChild(
'overlay',
_ChildType.overlay,
BoxConstraints.loose(size),
);
Offset offset = positionDependentBox(
@@ -297,20 +235,20 @@ class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate {
childSize: overlaySize,
target: target,
verticalOffset: verticalOffset,
horizontslOffset: horizontslOffset,
horizontalOffset: horizontalOffset,
preferBelow: preferBelow,
);
if (indicatorSize != null) {
offset = Offset(offset.dx + indicatorSize.height - 1, offset.dy);
positionChild(
'indicator',
_ChildType.indicator,
Offset(
offset.dx - indicatorSize.width + 1,
target.dy - indicatorSize.height / 2,
),
);
}
positionChild('overlay', offset);
positionChild(_ChildType.overlay, offset);
}
}
}
@@ -364,7 +302,7 @@ Offset positionDependentBox({
required Offset target,
required bool preferBelow,
double verticalOffset = 0.0,
double horizontslOffset = 0.0,
double horizontalOffset = 0.0,
double margin = 10.0,
}) {
switch (type) {
@@ -397,7 +335,7 @@ Offset positionDependentBox({
case TooltipType.right:
final double dy = math.max(margin, target.dy - childSize.height / 2);
final double dx = math.min(
target.dx + horizontslOffset,
target.dx + horizontalOffset,
size.width - childSize.width - margin,
);
return Offset(dx, dy);

View File

@@ -1,41 +1,46 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
Future<void> showConfirmDialog({
Future<bool> showConfirmDialog({
required BuildContext context,
required String title,
dynamic content,
required VoidCallback onConfirm,
}) {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(title),
content: content is String
? Text(content)
: content is Widget
? content
: null,
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () {
Get.back();
onConfirm();
},
child: const Text('确认'),
),
],
);
},
);
Object? content,
// @Deprecated('use `bool result = await showConfirmDialog()` instead')
VoidCallback? onConfirm,
}) async {
assert(content is String? || content is Widget);
return await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: Text(title),
content: content is String
? Text(content)
: content is Widget
? content
: null,
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
TextButton(
onPressed: () {
Get.back(result: true);
onConfirm?.call();
},
child: const Text('确认'),
),
],
);
},
) ??
false;
}
void showPgcFollowDialog({

View File

@@ -1,6 +1,7 @@
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/foundation.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/utils/extension/string_ext.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';
@@ -8,25 +9,22 @@ import 'package:get/get.dart';
Future<void> autoWrapReportDialog(
BuildContext context,
Map<String, Map<int, String>> options,
Future<Map> Function(int reasonType, String? reasonDesc, bool banUid)
onSuccess,
) {
Future<LoadingState> Function(int reasonType, String? reasonDesc, bool banUid)
onSuccess, {
bool ban = true,
}) {
int? reasonType;
String? reasonDesc;
bool banUid = false;
late final key = GlobalKey<FormState>();
late final key = GlobalKey<FormFieldState<String>>();
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('举报'),
titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22),
contentPadding: const EdgeInsets.symmetric(vertical: 5),
actionsPadding: const EdgeInsets.only(
left: 16,
right: 16,
bottom: 10,
),
titlePadding: const .only(left: 22, top: 16, right: 22),
contentPadding: const .symmetric(vertical: 5),
actionsPadding: const .only(left: 16, right: 16, bottom: 10),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -40,11 +38,7 @@ Future<void> autoWrapReportDialog(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(
left: 22,
right: 22,
bottom: 5,
),
padding: .only(left: 22, right: 22, bottom: 5),
child: Text('请选择举报的理由:'),
),
RadioGroup(
@@ -65,27 +59,23 @@ Future<void> autoWrapReportDialog(
),
if (reasonType == 0)
Padding(
padding: const EdgeInsets.only(
left: 22,
top: 5,
right: 22,
),
child: Form(
padding: const .only(left: 22, top: 5, right: 22),
child: TextFormField(
key: key,
child: TextFormField(
autofocus: true,
minLines: 2,
maxLines: 4,
initialValue: reasonDesc,
decoration: const InputDecoration(
labelText: '为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.all(10),
),
onChanged: (value) => reasonDesc = value,
validator: (value) =>
value.isNullOrEmpty ? '理由不能为空' : null,
autofocus: true,
minLines: 2,
maxLines: 4,
initialValue: reasonDesc,
decoration: const InputDecoration(
labelText: '为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息',
border: OutlineInputBorder(),
contentPadding: .all(10),
labelStyle: TextStyle(fontSize: 14),
floatingLabelStyle: TextStyle(fontSize: 14),
),
onChanged: (value) => reasonDesc = value,
validator: (value) =>
value.isNullOrEmpty ? '理由不能为空' : null,
),
),
],
@@ -94,13 +84,14 @@ Future<void> autoWrapReportDialog(
),
),
),
Padding(
padding: const EdgeInsets.only(left: 14, top: 6),
child: CheckBoxText(
text: '拉黑该用户',
onChanged: (value) => banUid = value,
if (ban)
Padding(
padding: const EdgeInsets.only(left: 14, top: 6),
child: CheckBoxText(
text: '拉黑该用户',
onChanged: (value) => banUid = value,
),
),
),
],
),
actions: [
@@ -108,7 +99,7 @@ Future<void> autoWrapReportDialog(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
style: TextStyle(color: ColorScheme.of(context).outline),
),
),
TextButton(
@@ -119,18 +110,18 @@ Future<void> autoWrapReportDialog(
}
SmartDialog.showLoading();
try {
final data = await onSuccess(reasonType!, reasonDesc, banUid);
final res = await onSuccess(reasonType!, reasonDesc, banUid);
SmartDialog.dismiss();
if (data['code'] == 0) {
if (res.isSuccess) {
Get.back();
SmartDialog.showToast('举报成功');
} else {
SmartDialog.showToast(data['message'].toString());
res.toast();
}
} catch (e) {
} catch (e, s) {
SmartDialog.dismiss();
SmartDialog.showToast('提交失败:$e');
if (kDebugMode) rethrow;
Utils.reportError(e, s);
}
},
child: const Text('确定'),
@@ -168,7 +159,7 @@ class _CheckBoxTextState extends State<CheckBoxText> {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final colorScheme = ColorScheme.of(context);
return InkWell(
onTap: () {
setState(() {
@@ -201,7 +192,7 @@ class _CheckBoxTextState extends State<CheckBoxText> {
}
}
class ReportOptions {
abstract final class ReportOptions {
// from https://s1.hdslb.com/bfs/seed/jinkela/comment-h5/static/js/605.chunks.js
static Map<String, Map<int, String>> get commentReport => const {
'违反法律法规': {9: '违法违规', 2: '色情', 10: '低俗', 12: '赌博诈骗', 23: '违法信息外链'},
@@ -263,4 +254,16 @@ class ReportOptions {
7: '其他', // avoid show form
},
};
static Map<String, Map<int, String>> get imMsgReport => const {
'': {
1: '色情低俗',
2: '政治敏感',
3: '违法有害',
4: '广告骚扰',
5: '人身攻击',
6: '诈骗',
0: '其他问题',
},
};
}

View File

@@ -3,6 +3,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
const _reason = ['头像违规', '昵称违规', '签名违规'];
const _reasonV2 = ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗', '违规引流外链'];
Future<void> showMemberReportDialog(
BuildContext context, {
required Object? name,
@@ -17,13 +21,11 @@ Future<void> showMemberReportDialog(
final theme = Theme.of(context);
return AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
contentPadding: const EdgeInsets.symmetric(vertical: 16),
titleTextStyle: theme.textTheme.bodyMedium,
title: Column(
spacing: 4,
crossAxisAlignment: .start,
children: [
Text(
'举报: $name',
@@ -34,53 +36,101 @@ Future<void> showMemberReportDialog(
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
const Text('举报内容(必选,可多选)'),
const Padding(
padding: .only(left: 18),
child: Text('举报内容(必选,可多选)'),
),
...List.generate(
3,
(index) => Builder(
builder: (context) => CheckboxListTile(
dense: true,
value: reason.contains(index + 1),
controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero,
onChanged: (value) {
if (value!) {
reason.add(index + 1);
} else {
reason.remove(index + 1);
}
(context as Element).markNeedsBuild();
},
title: Text(const ['头像违规', '昵称违规', '签名违规'][index]),
),
builder: (context) {
final checked = reason.contains(index + 1);
return ListTile(
dense: true,
minTileHeight: 40,
onTap: () {
if (!checked) {
reason.add(index + 1);
} else {
reason.remove(index + 1);
}
(context as Element).markNeedsBuild();
},
title: Row(
spacing: 8,
children: [
checked
? Icon(
size: 22,
Icons.check_box,
color: theme.colorScheme.primary,
)
: Icon(
size: 22,
Icons.check_box_outline_blank,
color: theme.colorScheme.onSurfaceVariant,
),
Expanded(
child: Text(
_reason[index],
style: const TextStyle(fontSize: 14),
),
),
],
),
);
},
),
),
const Text('举报理由(单选,非必选)'),
const Padding(
padding: .only(left: 18),
child: Text('举报理由(单选,非必选)'),
),
Builder(
builder: (context) => RadioGroup<int>(
onChanged: (v) {
reasonV2 = v;
(context as Element).markNeedsBuild();
},
groupValue: reasonV2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: List.generate(
5,
(index) => RadioListTile<int>(
toggleable: true,
controlAffinity: ListTileControlAffinity.leading,
contentPadding: const EdgeInsets.only(left: 4),
builder: (context) => Column(
crossAxisAlignment: .start,
children: List.generate(
_reasonV2.length,
(index) {
final checked = index == reasonV2;
return ListTile(
dense: true,
value: index,
title: Text(
const ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗'][index],
minTileHeight: 40,
onTap: () {
if (checked) {
reasonV2 = null;
} else {
reasonV2 = index;
}
(context as Element).markNeedsBuild();
},
title: Row(
spacing: 8,
children: [
checked
? Icon(
size: 22,
Icons.radio_button_checked,
color: theme.colorScheme.primary,
)
: Icon(
size: 22,
Icons.radio_button_off,
color: theme.colorScheme.onSurfaceVariant,
),
Expanded(
child: Text(
_reasonV2[index],
style: const TextStyle(fontSize: 14),
),
),
],
),
),
),
);
},
),
),
),
@@ -101,7 +151,7 @@ Future<void> showMemberReportDialog(
SmartDialog.showToast('至少选择一项作为举报内容');
} else {
Get.back();
var result = await MemberHttp.reportMember(
final result = await MemberHttp.reportMember(
mid,
reason: reason.join(','),
reasonV2: reasonV2 != null ? reasonV2! + 1 : null,

View File

@@ -43,10 +43,10 @@ class DynamicSliverAppBarMedium extends StatefulWidget {
this.forceMaterialTransparency = false,
this.clipBehavior,
this.appBarClipper,
this.callback,
this.afterCalc,
});
final ValueChanged<double>? callback;
final ValueChanged<double>? afterCalc;
final Widget? flexibleSpace;
final Widget? leading;
final bool automaticallyImplyLeading;
@@ -108,7 +108,7 @@ class _DynamicSliverAppBarMediumState extends State<DynamicSliverAppBarMedium> {
_height = (_childKey.currentContext!.findRenderObject()! as RenderBox)
.size
.height;
widget.callback?.call(_height);
widget.afterCalc?.call(_height);
});
});
}

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

@@ -21,9 +21,6 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
// Examples can assume:
// late BuildContext context;
abstract class _ParentInkResponseState {
void markChildInkResponsePressed(
_ParentInkResponseState childState,
@@ -144,6 +141,7 @@ class InkResponse extends StatelessWidget {
this.onTapCancel,
this.onDoubleTap,
this.onLongPress,
this.onLongPressUp,
this.onSecondaryTap,
this.onSecondaryTapUp,
this.onSecondaryTapDown,
@@ -197,6 +195,19 @@ class InkResponse extends StatelessWidget {
/// Called when the user long-presses on this part of the material.
final GestureLongPressCallback? onLongPress;
/// Called when the user lifts their finger after a long press on the button.
///
/// This callback is triggered at the end of a long press gesture, specifically
/// after the user holds a long press and then releases it. It does not include
/// position details.
///
/// Common use cases include performing an action only after the long press completes,
/// such as displaying a context menu or confirming a held gesture.
///
/// See also:
/// * [onLongPress], which is triggered when the long press gesture is first recognized.
final GestureLongPressUpCallback? onLongPressUp;
/// Called when the user taps this part of the material with a secondary button.
///
/// See also:
@@ -237,7 +248,7 @@ class InkResponse extends StatelessWidget {
/// become highlighted and false if this part of the material has stopped
/// being highlighted.
///
/// If all of [onTap], [onDoubleTap], and [onLongPress] become null while a
/// If all of [onTap], [onDoubleTap], [onLongPress], and [onLongPressUp] become null while a
/// gesture is ongoing, then [onTapCancel] will be fired and
/// [onHighlightChanged] will be fired with the value false _during the
/// build_. This means, for instance, that in that scenario [State.setState]
@@ -493,6 +504,7 @@ class InkResponse extends StatelessWidget {
onTapCancel: onTapCancel,
onDoubleTap: onDoubleTap,
onLongPress: onLongPress,
onLongPressUp: onLongPressUp,
onSecondaryTap: onSecondaryTap,
onSecondaryTapUp: onSecondaryTapUp,
onSecondaryTapDown: onSecondaryTapDown,
@@ -550,6 +562,7 @@ class _InkResponseStateWidget extends StatefulWidget {
this.onTapCancel,
this.onDoubleTap,
this.onLongPress,
this.onLongPressUp,
this.onSecondaryTap,
this.onSecondaryTapUp,
this.onSecondaryTapDown,
@@ -588,6 +601,7 @@ class _InkResponseStateWidget extends StatefulWidget {
final GestureTapCallback? onTapCancel;
final GestureTapCallback? onDoubleTap;
final GestureLongPressCallback? onLongPress;
final GestureLongPressUpCallback? onLongPressUp;
final GestureTapCallback? onSecondaryTap;
final GestureTapUpCallback? onSecondaryTapUp;
final GestureTapDownCallback? onSecondaryTapDown;
@@ -628,6 +642,7 @@ class _InkResponseStateWidget extends StatefulWidget {
if (onTap != null) 'tap',
if (onDoubleTap != null) 'double tap',
if (onLongPress != null) 'long press',
if (onLongPressUp != null) 'long press up',
if (onTapDown != null) 'tap down',
if (onTapUp != null) 'tap up',
if (onTapCancel != null) 'tap cancel',
@@ -1099,6 +1114,12 @@ class _InkResponseState extends State<_InkResponseStateWidget>
}
}
void handleLongPressUp() {
_currentSplash?.confirm();
_currentSplash = null;
widget.onLongPressUp?.call();
}
void handleSecondaryTap() {
_currentSplash?.confirm();
_currentSplash = null;
@@ -1140,6 +1161,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
return widget.onTap != null ||
widget.onDoubleTap != null ||
widget.onLongPress != null ||
widget.onLongPressUp != null ||
widget.onTapUp != null ||
widget.onTapDown != null;
}
@@ -1276,6 +1298,9 @@ class _InkResponseState extends State<_InkResponseStateWidget>
onLongPress: widget.onLongPress != null
? handleLongPress
: null,
onLongPressUp: widget.onLongPressUp != null
? handleLongPressUp
: null,
onSecondaryTapDown: _secondaryEnabled
? handleSecondaryTapDown
: null,
@@ -1387,6 +1412,7 @@ class InkWell extends InkResponse {
super.onTap,
super.onDoubleTap,
super.onLongPress,
super.onLongPressUp,
super.onTapDown,
super.onTapUp,
super.onTapCancel,

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

@@ -337,6 +337,7 @@ class ListTile extends StatelessWidget {
this.onTap,
this.onLongPress,
this.onSecondaryTap,
this.onSecondaryTapUp,
this.onFocusChange,
this.mouseCursor,
this.selected = false,
@@ -569,6 +570,8 @@ class ListTile extends StatelessWidget {
final GestureTapCallback? onSecondaryTap;
final GestureTapUpCallback? onSecondaryTapUp;
/// {@macro flutter.material.inkwell.onFocusChange}
final ValueChanged<bool>? onFocusChange;
@@ -914,10 +917,7 @@ class ListTile extends StatelessWidget {
WidgetState.disabled,
};
final MouseCursor effectiveMouseCursor =
WidgetStateProperty.resolveAs<MouseCursor?>(
mouseCursor,
mouseStates,
) ??
WidgetStateProperty.resolveAs<MouseCursor?>(mouseCursor, mouseStates) ??
tileTheme.mouseCursor?.resolve(mouseStates) ??
WidgetStateMouseCursor.clickable.resolve(mouseStates);
@@ -986,6 +986,7 @@ class ListTile extends StatelessWidget {
onTap: enabled ? onTap : null,
onLongPress: enabled ? onLongPress : null,
onSecondaryTap: enabled ? onSecondaryTap : null,
onSecondaryTapUp: enabled ? onSecondaryTapUp : null,
onFocusChange: onFocusChange,
mouseCursor: effectiveMouseCursor,
canRequestFocus: enabled,
@@ -1330,12 +1331,7 @@ class _RenderListTile extends RenderBox
@override
Iterable<RenderBox> get children {
final RenderBox? title = childForSlot(_ListTileSlot.title);
return <RenderBox>[
?leading,
?title,
?subtitle,
?trailing,
];
return <RenderBox>[?leading, ?title, ?subtitle, ?trailing];
}
bool get isDense => _isDense;

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

@@ -19,13 +19,14 @@ library;
import 'dart:async';
import 'dart:math' as math;
import 'package:PiliPlus/common/widgets/flutter/page/scrollable_helpers.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide Scrollable, ScrollableState;
import 'package:flutter/material.dart'
hide Scrollable, ScrollableState, EdgeDraggingAutoScroller;
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
export 'package:flutter/physics.dart' show Tolerance;
@@ -933,7 +934,7 @@ class CustomScrollableState extends State<CustomScrollable>
if (_animController.value * _maxWidth +
(_isRTL ? (_maxWidth - dx) : dx) >=
100) {
Get.back();
Navigator.pop(context);
} else {
_animController.reverse();
}
@@ -1937,7 +1938,7 @@ class _RenderScrollSemantics extends RenderProxyBox {
if (child.isTagged(RenderViewport.excludeFromScrolling)) {
excluded.add(child);
} else {
if (!child.hasFlag(SemanticsFlag.isHidden)) {
if (!child.flagsCollection.isHidden) {
firstVisibleIndex ??= child.indexInParent;
}
included.add(child);
@@ -1982,222 +1983,3 @@ class _RestorableScrollOffset extends RestorableValue<double?> {
@override
bool get enabled => value != null;
}
// 2D SCROLLING
/// Specifies how to configure the [DragGestureRecognizer]s of a
/// [TwoDimensionalScrollable].
// TODO(Piinks): Add sample code, https://github.com/flutter/flutter/issues/126298
enum DiagonalDragBehavior {
/// This behavior will not allow for any diagonal scrolling.
///
/// Drag gestures in one direction or the other will lock the input axis until
/// the gesture is released.
none,
/// This behavior will only allow diagonal scrolling on a weighted
/// scale per gesture event.
///
/// This means that after initially evaluating the drag gesture, the weighted
/// evaluation (based on [kTouchSlop]) stands until the gesture is released.
weightedEvent,
/// This behavior will only allow diagonal scrolling on a weighted
/// scale that is evaluated throughout a gesture event.
///
/// This means that during each update to the drag gesture, the scrolling
/// axis will be allowed to scroll diagonally if it exceeds the
/// [kTouchSlop].
weightedContinuous,
/// This behavior allows free movement in any and all directions when
/// dragging.
free,
}
/// An auto scroller that scrolls the [scrollable] if a drag gesture drags close
/// to its edge.
///
/// The scroll velocity is controlled by the [velocityScalar]:
///
/// velocity = (distance of overscroll) * [velocityScalar].
class EdgeDraggingAutoScroller {
/// Creates a auto scroller that scrolls the [scrollable].
EdgeDraggingAutoScroller(
this.scrollable, {
this.onScrollViewScrolled,
required this.velocityScalar,
});
/// The [CustomScrollable] this auto scroller is scrolling.
final CustomScrollableState scrollable;
/// Called when a scroll view is scrolled.
///
/// The scroll view may be scrolled multiple times in a row until the drag
/// target no longer triggers the auto scroll. This callback will be called
/// in between each scroll.
final VoidCallback? onScrollViewScrolled;
/// {@template flutter.widgets.EdgeDraggingAutoScroller.velocityScalar}
/// The velocity scalar per pixel over scroll.
///
/// It represents how the velocity scale with the over scroll distance. The
/// auto-scroll velocity = (distance of overscroll) * velocityScalar.
/// {@endtemplate}
final double velocityScalar;
late Rect _dragTargetRelatedToScrollOrigin;
/// Whether the auto scroll is in progress.
bool get scrolling => _scrolling;
bool _scrolling = false;
double _offsetExtent(Offset offset, Axis scrollDirection) {
return switch (scrollDirection) {
Axis.horizontal => offset.dx,
Axis.vertical => offset.dy,
};
}
double _sizeExtent(Size size, Axis scrollDirection) {
return switch (scrollDirection) {
Axis.horizontal => size.width,
Axis.vertical => size.height,
};
}
AxisDirection get _axisDirection => scrollable.axisDirection;
Axis get _scrollDirection => axisDirectionToAxis(_axisDirection);
/// Starts the auto scroll if the [dragTarget] is close to the edge.
///
/// The scroll starts to scroll the [scrollable] if the target rect is close
/// to the edge of the [scrollable]; otherwise, it remains stationary.
///
/// If the scrollable is already scrolling, calling this method updates the
/// previous dragTarget to the new value and continues scrolling if necessary.
void startAutoScrollIfNecessary(Rect dragTarget) {
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
_dragTargetRelatedToScrollOrigin = dragTarget.translate(
deltaToOrigin.dx,
deltaToOrigin.dy,
);
if (_scrolling) {
// The change will be picked up in the next scroll.
return;
}
assert(!_scrolling);
_scroll();
}
/// Stop any ongoing auto scrolling.
void stopAutoScroll() {
_scrolling = false;
}
Future<void> _scroll() async {
final RenderBox scrollRenderBox =
scrollable.context.findRenderObject()! as RenderBox;
final Rect globalRect = MatrixUtils.transformRect(
scrollRenderBox.getTransformTo(null),
Rect.fromLTWH(
0,
0,
scrollRenderBox.size.width,
scrollRenderBox.size.height,
),
);
assert(
globalRect.size.width >= _dragTargetRelatedToScrollOrigin.size.width &&
globalRect.size.height >=
_dragTargetRelatedToScrollOrigin.size.height,
'Drag target size is larger than scrollable size, which may cause bouncing',
);
_scrolling = true;
double? newOffset;
const double overDragMax = 20.0;
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
final Offset viewportOrigin = globalRect.topLeft.translate(
deltaToOrigin.dx,
deltaToOrigin.dy,
);
final double viewportStart = _offsetExtent(
viewportOrigin,
_scrollDirection,
);
final double viewportEnd =
viewportStart + _sizeExtent(globalRect.size, _scrollDirection);
final double proxyStart = _offsetExtent(
_dragTargetRelatedToScrollOrigin.topLeft,
_scrollDirection,
);
final double proxyEnd = _offsetExtent(
_dragTargetRelatedToScrollOrigin.bottomRight,
_scrollDirection,
);
switch (_axisDirection) {
case AxisDirection.up:
case AxisDirection.left:
if (proxyEnd > viewportEnd &&
scrollable.position.pixels > scrollable.position.minScrollExtent) {
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
newOffset = math.max(
scrollable.position.minScrollExtent,
scrollable.position.pixels - overDrag,
);
} else if (proxyStart < viewportStart &&
scrollable.position.pixels < scrollable.position.maxScrollExtent) {
final double overDrag = math.min(
viewportStart - proxyStart,
overDragMax,
);
newOffset = math.min(
scrollable.position.maxScrollExtent,
scrollable.position.pixels + overDrag,
);
}
case AxisDirection.right:
case AxisDirection.down:
if (proxyStart < viewportStart &&
scrollable.position.pixels > scrollable.position.minScrollExtent) {
final double overDrag = math.min(
viewportStart - proxyStart,
overDragMax,
);
newOffset = math.max(
scrollable.position.minScrollExtent,
scrollable.position.pixels - overDrag,
);
} else if (proxyEnd > viewportEnd &&
scrollable.position.pixels < scrollable.position.maxScrollExtent) {
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
newOffset = math.min(
scrollable.position.maxScrollExtent,
scrollable.position.pixels + overDrag,
);
}
}
if (newOffset == null ||
(newOffset - scrollable.position.pixels).abs() < 1.0) {
// Drag should not trigger scroll.
_scrolling = false;
return;
}
final Duration duration = Duration(
milliseconds: (1000 / velocityScalar).round(),
);
await scrollable.position.animateTo(
newOffset,
duration: duration,
curve: Curves.linear,
);
onScrollViewScrolled?.call();
if (_scrolling) {
await _scroll();
}
}
}

View File

@@ -0,0 +1,210 @@
// 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.
/// @docImport 'package:flutter/material.dart';
///
/// @docImport 'overscroll_indicator.dart';
/// @docImport 'viewport.dart';
// ignore_for_file: dangling_library_doc_comments
import 'dart:math' as math;
import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/// An auto scroller that scrolls the [scrollable] if a drag gesture drags close
/// to its edge.
///
/// The scroll velocity is controlled by the [velocityScalar]:
///
/// velocity = (distance of overscroll) * [velocityScalar].
class EdgeDraggingAutoScroller {
/// Creates a auto scroller that scrolls the [scrollable].
EdgeDraggingAutoScroller(
this.scrollable, {
this.onScrollViewScrolled,
required this.velocityScalar,
});
/// The [CustomScrollable] this auto scroller is scrolling.
final CustomScrollableState scrollable;
/// Called when a scroll view is scrolled.
///
/// The scroll view may be scrolled multiple times in a row until the drag
/// target no longer triggers the auto scroll. This callback will be called
/// in between each scroll.
final VoidCallback? onScrollViewScrolled;
/// {@template flutter.widgets.EdgeDraggingAutoScroller.velocityScalar}
/// The velocity scalar per pixel over scroll.
///
/// It represents how the velocity scale with the over scroll distance. The
/// auto-scroll velocity = (distance of overscroll) * velocityScalar.
/// {@endtemplate}
final double velocityScalar;
late Rect _dragTargetRelatedToScrollOrigin;
/// Whether the auto scroll is in progress.
bool get scrolling => _scrolling;
bool _scrolling = false;
double _offsetExtent(Offset offset, Axis scrollDirection) {
return switch (scrollDirection) {
Axis.horizontal => offset.dx,
Axis.vertical => offset.dy,
};
}
double _sizeExtent(Size size, Axis scrollDirection) {
return switch (scrollDirection) {
Axis.horizontal => size.width,
Axis.vertical => size.height,
};
}
AxisDirection get _axisDirection => scrollable.axisDirection;
Axis get _scrollDirection => axisDirectionToAxis(_axisDirection);
/// Starts the auto scroll if the [dragTarget] is close to the edge.
///
/// The scroll starts to scroll the [scrollable] if the target rect is close
/// to the edge of the [scrollable]; otherwise, it remains stationary.
///
/// If the scrollable is already scrolling, calling this method updates the
/// previous dragTarget to the new value and continues scrolling if necessary.
void startAutoScrollIfNecessary(Rect dragTarget) {
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
_dragTargetRelatedToScrollOrigin = dragTarget.translate(
deltaToOrigin.dx,
deltaToOrigin.dy,
);
if (_scrolling) {
// The change will be picked up in the next scroll.
return;
}
assert(!_scrolling);
_scroll();
}
/// Stop any ongoing auto scrolling.
void stopAutoScroll() {
_scrolling = false;
}
Future<void> _scroll() async {
final RenderBox scrollRenderBox =
scrollable.context.findRenderObject()! as RenderBox;
final Matrix4 transform = scrollRenderBox.getTransformTo(null);
final Rect globalRect = MatrixUtils.transformRect(
transform,
Rect.fromLTWH(
0,
0,
scrollRenderBox.size.width,
scrollRenderBox.size.height,
),
);
final Rect transformedDragTarget = MatrixUtils.transformRect(
transform,
_dragTargetRelatedToScrollOrigin,
);
assert(
(globalRect.size.width + precisionErrorTolerance) >=
transformedDragTarget.size.width &&
(globalRect.size.height + precisionErrorTolerance) >=
transformedDragTarget.size.height,
'Drag target size is larger than scrollable size, which may cause bouncing',
);
_scrolling = true;
double? newOffset;
const double overDragMax = 20.0;
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
final Offset viewportOrigin = globalRect.topLeft.translate(
deltaToOrigin.dx,
deltaToOrigin.dy,
);
final double viewportStart = _offsetExtent(
viewportOrigin,
_scrollDirection,
);
final double viewportEnd =
viewportStart + _sizeExtent(globalRect.size, _scrollDirection);
final double proxyStart = _offsetExtent(
_dragTargetRelatedToScrollOrigin.topLeft,
_scrollDirection,
);
final double proxyEnd = _offsetExtent(
_dragTargetRelatedToScrollOrigin.bottomRight,
_scrollDirection,
);
switch (_axisDirection) {
case AxisDirection.up:
case AxisDirection.left:
if (proxyEnd > viewportEnd &&
scrollable.position.pixels > scrollable.position.minScrollExtent) {
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
newOffset = math.max(
scrollable.position.minScrollExtent,
scrollable.position.pixels - overDrag,
);
} else if (proxyStart < viewportStart &&
scrollable.position.pixels < scrollable.position.maxScrollExtent) {
final double overDrag = math.min(
viewportStart - proxyStart,
overDragMax,
);
newOffset = math.min(
scrollable.position.maxScrollExtent,
scrollable.position.pixels + overDrag,
);
}
case AxisDirection.right:
case AxisDirection.down:
if (proxyStart < viewportStart &&
scrollable.position.pixels > scrollable.position.minScrollExtent) {
final double overDrag = math.min(
viewportStart - proxyStart,
overDragMax,
);
newOffset = math.max(
scrollable.position.minScrollExtent,
scrollable.position.pixels - overDrag,
);
} else if (proxyEnd > viewportEnd &&
scrollable.position.pixels < scrollable.position.maxScrollExtent) {
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
newOffset = math.min(
scrollable.position.maxScrollExtent,
scrollable.position.pixels + overDrag,
);
}
}
if (newOffset == null ||
(newOffset - scrollable.position.pixels).abs() < 1.0) {
// Drag should not trigger scroll.
_scrolling = false;
return;
}
final Duration duration = Duration(
milliseconds: (1000 / velocityScalar).round(),
);
await scrollable.position.animateTo(
newOffset,
duration: duration,
curve: Curves.linear,
);
onScrollViewScrolled?.call();
if (_scrolling) {
await _scroll();
}
}
}

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

@@ -0,0 +1,157 @@
// 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.
library;
import 'package:flutter/material.dart';
class CustomPopupMenuItem<T> extends PopupMenuEntry<T> {
const CustomPopupMenuItem({
super.key,
this.value,
this.height = kMinInteractiveDimension,
required this.child,
});
final T? value;
@override
final double height;
final Widget? child;
@override
bool represents(T? value) => value == this.value;
@override
CustomPopupMenuItemState<T, CustomPopupMenuItem<T>> createState() =>
CustomPopupMenuItemState<T, CustomPopupMenuItem<T>>();
}
class CustomPopupMenuItemState<T, W extends CustomPopupMenuItem<T>>
extends State<W> {
@protected
@override
Widget build(BuildContext context) {
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
const Set<WidgetState> states = <WidgetState>{};
final style =
popupMenuTheme.labelTextStyle?.resolve(states)! ??
_PopupMenuDefaultsM3(context).labelTextStyle!.resolve(states)!;
return ListTileTheme.merge(
contentPadding: .zero,
titleTextStyle: style,
child: AnimatedDefaultTextStyle(
style: style,
duration: kThemeChangeDuration,
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: widget.height),
child: Padding(
padding: _PopupMenuDefaultsM3.menuItemPadding,
child: Align(
alignment: AlignmentDirectional.centerStart,
child: widget.child,
),
),
),
),
);
}
}
class CustomPopupMenuDivider extends PopupMenuEntry<Never> {
const CustomPopupMenuDivider({
super.key,
required this.height,
this.thickness,
this.indent,
this.endIndent,
this.radius,
});
@override
final double height;
final double? thickness;
final double? indent;
final double? endIndent;
final BorderRadiusGeometry? radius;
@override
bool represents(void value) => false;
@override
State<CustomPopupMenuDivider> createState() => _CustomPopupMenuDividerState();
}
class _CustomPopupMenuDividerState extends State<CustomPopupMenuDivider> {
@override
Widget build(BuildContext context) {
return Divider(
height: widget.height,
thickness: widget.thickness,
indent: widget.indent,
color: ColorScheme.of(context).outline.withValues(alpha: 0.2),
endIndent: widget.endIndent,
radius: widget.radius,
);
}
}
// BEGIN GENERATED TOKEN PROPERTIES - PopupMenu
// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
// dev/tools/gen_defaults/bin/gen_defaults.dart.
// dart format off
class _PopupMenuDefaultsM3 extends PopupMenuThemeData {
_PopupMenuDefaultsM3(this.context)
: super(elevation: 3.0);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
late final TextTheme _textTheme = _theme.textTheme;
@override WidgetStateProperty<TextStyle?>? get labelTextStyle {
return WidgetStateProperty.resolveWith((Set<WidgetState> states) {
// TODO(quncheng): Update this hard-coded value to use the latest tokens.
final TextStyle style = _textTheme.labelLarge!;
if (states.contains(WidgetState.disabled)) {
return style.apply(color: _colors.onSurface.withValues(alpha: 0.38));
}
return style.apply(color: _colors.onSurface);
});
}
@override
Color? get color => _colors.surfaceContainer;
@override
Color? get shadowColor => _colors.shadow;
@override
Color? get surfaceTintColor => Colors.transparent;
@override
ShapeBorder? get shape => const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
// TODO(bleroux): This is taken from https://m3.material.io/components/menus/specs
// Update this when the token is available.
@override
EdgeInsets? get menuPadding => const EdgeInsets.symmetric(vertical: 8.0);
// TODO(tahatesser): This is taken from https://m3.material.io/components/menus/specs
// Update this when the token is available.
static EdgeInsets menuItemPadding = const EdgeInsets.symmetric(horizontal: 12.0);
}// dart format on
// END GENERATED TOKEN PROPERTIES - PopupMenu

View File

@@ -1,3 +1,12 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: uri_does_not_exist_in_doc_import
/// @docImport 'color_scheme.dart';
library;
import 'dart:async';
import 'dart:math' as math;
@@ -6,25 +15,10 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart' show clampDouble;
import 'package:flutter/material.dart' hide RefreshIndicator;
Widget refreshIndicator({
required RefreshCallback onRefresh,
required Widget child,
}) {
return RefreshIndicator(
displacement: displacement,
onRefresh: onRefresh,
child: child,
);
}
// 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.
double displacement = Pref.refreshDisplacement;
// The over-scroll distance that moves the indicator to its maximum
// displacement, as a percentage of the scrollable's container extent.
double displacement = Pref.refreshDisplacement;
double kDragContainerExtentPercentage = Pref.refreshDragPercentage;
// How much the scroll's drag gesture can overshoot the RefreshIndicator's
@@ -403,15 +397,15 @@ class RefreshIndicatorState extends State<RefreshIndicator>
_effectiveValueColor =
widget.color ?? Theme.of(context).colorScheme.primary;
final Color color = _effectiveValueColor;
if (color.alpha == 0x00) {
if (color.a == 0) {
// Set an always stopped animation instead of a driven tween.
_valueColor = AlwaysStoppedAnimation<Color>(color);
} else {
// Respect the alpha of the given color.
_valueColor = _positionController.drive(
ColorTween(
begin: color.withAlpha(0),
end: color.withAlpha(color.alpha),
begin: color.withValues(alpha: 0),
end: color,
).chain(
CurveTween(curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit)),
),
@@ -555,7 +549,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
1.0,
); // This triggers various rebuilds.
if (_status == RefreshIndicatorStatus.drag &&
_valueColor.value!.alpha == _effectiveValueColor.alpha) {
_valueColor.value!.a == _effectiveValueColor.a) {
_status = RefreshIndicatorStatus.armed;
widget.onStatusChange?.call(_status);
}
@@ -749,7 +743,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
case _IndicatorType.noSpinner:
return Container();
return const SizedBox.shrink();
}
},
),
@@ -762,3 +756,14 @@ class RefreshIndicatorState extends State<RefreshIndicator>
);
}
}
Widget refreshIndicator({
required RefreshCallback onRefresh,
required Widget child,
}) {
return RefreshIndicator(
displacement: displacement,
onRefresh: onRefresh,
child: child,
);
}

View File

@@ -9,7 +9,6 @@
/// @docImport 'editable.dart';
library;
import 'dart:collection';
import 'dart:math' as math;
import 'dart:ui'
as ui
@@ -46,6 +45,15 @@ typedef _TextBoundaryAtPositionInText =
const String _kEllipsis = '\u2026';
class _UnspecifiedTextScaler extends TextScaler {
const _UnspecifiedTextScaler();
@override
Never get textScaleFactor => throw UnimplementedError();
@override
Never scale(double fontSize) => throw UnimplementedError();
}
/// A render object that displays a paragraph of text.
class RenderParagraph extends RenderBox
with
@@ -68,7 +76,7 @@ class RenderParagraph extends RenderBox
'This feature was deprecated after v3.12.0-2.0.pre.',
)
double textScaleFactor = 1.0,
TextScaler textScaler = TextScaler.noScaling,
TextScaler textScaler = const _UnspecifiedTextScaler(),
int? maxLines,
Locale? locale,
StrutStyle? strutStyle,
@@ -80,26 +88,22 @@ class RenderParagraph extends RenderBox
required Color primary,
VoidCallback? onShowMore,
}) : assert(text.debugAssertIsValid()),
assert(maxLines == null || maxLines > 0),
assert(
maxLines == null ||
(maxLines > 0 &&
overflow != TextOverflow.ellipsis &&
overflow != TextOverflow.fade),
),
assert(
identical(textScaler, TextScaler.noScaling) || textScaleFactor == 1.0,
identical(textScaler, const _UnspecifiedTextScaler()) ||
textScaleFactor == 1.0,
'textScaleFactor is deprecated and cannot be specified when textScaler is specified.',
),
_primary = primary,
_onShowMore = onShowMore,
_softWrap = softWrap,
_overflow = overflow,
_selectionColor = selectionColor,
_onShowMore = onShowMore,
_textPainter = TextPainter(
text: text,
textAlign: textAlign,
textDirection: textDirection,
textScaler: textScaler == TextScaler.noScaling
textScaler: textScaler == const _UnspecifiedTextScaler()
? TextScaler.linear(textScaleFactor)
: textScaler,
maxLines: maxLines,
@@ -146,7 +150,22 @@ class RenderParagraph extends RenderBox
/// The text to display.
InlineSpan get text => _textPainter.text!;
set text(InlineSpan value) {
set text(({InlineSpan text, Color primary}) params) {
final value = params.text;
_primary = params.primary;
if (_morePainter case final textPainter?) {
final textSpan = _moreTextSpan(value.style);
switch (textPainter.text!.compareTo(textSpan)) {
case RenderComparison.paint:
textPainter.text = textSpan;
case RenderComparison.layout:
textPainter
..text = textSpan
..layout();
default:
}
}
switch (_textPainter.text!.compareTo(value)) {
case RenderComparison.identical:
return;
@@ -390,6 +409,9 @@ class RenderParagraph extends RenderBox
if (_textPainter.textScaler == value) {
return;
}
_morePainter
?..textScaler = value
..layout();
_textPainter.textScaler = value;
_overflowShader = null;
markNeedsLayout();
@@ -563,7 +585,7 @@ class RenderParagraph extends RenderBox
if (position.dx < textPainter.width &&
position.dy > height &&
position.dy < height + textPainter.height) {
result.add(HitTestEntry(_moreTextSpan));
result.add(HitTestEntry(textPainter.text as TextSpan));
return true;
}
}
@@ -689,13 +711,6 @@ class RenderParagraph extends RenderBox
Color _primary;
set primary(Color primary) {
if (_primary != primary) {
_primary = primary;
_morePainter?.text = _moreTextSpan;
}
}
VoidCallback? _onShowMore;
set onShowMore(VoidCallback? onShowMore) {
if (_onShowMore != onShowMore) {
@@ -706,8 +721,8 @@ class RenderParagraph extends RenderBox
TapGestureRecognizer? _tapGestureRecognizer;
TextSpan get _moreTextSpan => TextSpan(
style: text.style!.copyWith(color: _primary),
TextSpan _moreTextSpan([TextStyle? style]) => TextSpan(
style: (style ?? text.style!).copyWith(color: _primary),
text: '查看更多',
recognizer: _tapGestureRecognizer,
);
@@ -740,7 +755,7 @@ class RenderParagraph extends RenderBox
_tapGestureRecognizer ??= TapGestureRecognizer()..onTap = _onShowMore;
}
_morePainter ??= TextPainter(
text: _moreTextSpan,
text: _moreTextSpan(),
textDirection: textDirection,
textScaler: textScaler,
locale: locale,
@@ -842,6 +857,11 @@ class RenderParagraph extends RenderBox
}
}
assert(() {
_textPainter.debugPaintTextLayoutBoxes = debugPaintTextLayoutBoxes;
return true;
}());
_textPainter.paint(context.canvas, offset);
paintInlineChildren(context, offset);
@@ -1013,8 +1033,9 @@ class RenderParagraph extends RenderBox
}
if (needsAssembleSemanticsNode) {
config.explicitChildNodes = true;
config.isSemanticBoundary = true;
config
..explicitChildNodes = true
..isSemanticBoundary = true;
} else if (needsChildConfigurationsDelegate) {
config.childConfigurationsDelegate =
_childSemanticsConfigurationsDelegate;
@@ -1043,8 +1064,9 @@ class RenderParagraph extends RenderBox
AttributedString(buffer.toString(), attributes: attributes),
];
}
config.attributedLabel = _cachedAttributedLabels![0];
config.textDirection = textDirection;
config
..attributedLabel = _cachedAttributedLabels![0]
..textDirection = textDirection;
}
}
@@ -1126,7 +1148,7 @@ class RenderParagraph extends RenderBox
// can be re-used when [assembleSemanticsNode] is called again. This ensures
// stable ids for the [SemanticsNode]s of [TextSpan]s across
// [assembleSemanticsNode] invocations.
LinkedHashMap<Key, SemanticsNode>? _cachedChildNodes;
Map<Key, SemanticsNode>? _cachedChildNodes;
@override
void assembleSemanticsNode(
@@ -1143,8 +1165,7 @@ class RenderParagraph extends RenderBox
int placeholderIndex = 0;
int childIndex = 0;
RenderBox? child = firstChild;
final LinkedHashMap<Key, SemanticsNode> newChildCache =
LinkedHashMap<Key, SemanticsNode>();
final Map<Key, SemanticsNode> newChildCache = <Key, SemanticsNode>{};
_cachedCombinedSemanticsInfos ??= combineSemanticsInfo(_semanticsInfo!);
for (final InlineSpanSemanticsInformation info
in _cachedCombinedSemanticsInfos!) {
@@ -1214,8 +1235,9 @@ class RenderParagraph extends RenderBox
onDoubleTap: final VoidCallback? handler,
):
if (handler != null) {
configuration.onTap = handler;
configuration.isLink = true;
configuration
..onTap = handler
..isLink = true;
}
case LongPressGestureRecognizer(
onLongPress: final GestureLongPressCallback? onLongPress,
@@ -1285,29 +1307,30 @@ class RenderParagraph extends RenderBox
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(EnumProperty<TextAlign>('textAlign', textAlign));
properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
properties.add(
FlagProperty(
'softWrap',
value: softWrap,
ifTrue: 'wrapping at box width',
ifFalse: 'no wrapping except at line break characters',
showName: true,
),
);
properties.add(EnumProperty<TextOverflow>('overflow', overflow));
properties.add(
DiagnosticsProperty<TextScaler>(
'textScaler',
textScaler,
defaultValue: TextScaler.noScaling,
),
);
properties.add(
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
);
properties.add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
properties
..add(EnumProperty<TextAlign>('textAlign', textAlign))
..add(EnumProperty<TextDirection>('textDirection', textDirection))
..add(
FlagProperty(
'softWrap',
value: softWrap,
ifTrue: 'wrapping at box width',
ifFalse: 'no wrapping except at line break characters',
showName: true,
),
)
..add(EnumProperty<TextOverflow>('overflow', overflow))
..add(
DiagnosticsProperty<TextScaler>(
'textScaler',
textScaler,
defaultValue: TextScaler.noScaling,
),
)
..add(
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
)
..add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
}
}
@@ -1768,8 +1791,7 @@ class _SelectableFragment
final TextPosition? existingSelectionEnd = _textSelectionEnd;
_setSelectionPosition(null, isEnd: isEnd);
final Matrix4 transform = paragraph.getTransformTo(null);
transform.invert();
final Matrix4 transform = paragraph.getTransformTo(null)..invert();
final Offset localPosition = MatrixUtils.transformPoint(
transform,
globalPosition,
@@ -1842,8 +1864,7 @@ class _SelectableFragment
required bool isEnd,
}) {
_setSelectionPosition(null, isEnd: isEnd);
final Matrix4 transform = paragraph.getTransformTo(null);
transform.invert();
final Matrix4 transform = paragraph.getTransformTo(null)..invert();
final Offset localPosition = MatrixUtils.transformPoint(
transform,
globalPosition,
@@ -2348,8 +2369,8 @@ class _SelectableFragment
existingSelectionEnd,
);
}
final Matrix4 originTransform = originParagraph.getTransformTo(null);
originTransform.invert();
final Matrix4 originTransform = originParagraph.getTransformTo(null)
..invert();
final Offset originParagraphLocalPosition = MatrixUtils.transformPoint(
originTransform,
globalPosition,
@@ -2653,8 +2674,8 @@ class _SelectableFragment
existingSelectionEnd,
);
}
final Matrix4 originTransform = originParagraph.getTransformTo(null);
originTransform.invert();
final Matrix4 originTransform = originParagraph.getTransformTo(null)
..invert();
final Offset originParagraphLocalPosition = MatrixUtils.transformPoint(
originTransform,
globalPosition,
@@ -3116,8 +3137,7 @@ class _SelectableFragment
RenderObject? current = paragraph;
while (current != null) {
if (current is RenderParagraph) {
final Matrix4 currentTransform = current.getTransformTo(null);
currentTransform.invert();
final Matrix4 currentTransform = current.getTransformTo(null)..invert();
final Offset currentParagraphLocalPosition = MatrixUtils.transformPoint(
currentTransform,
globalPosition,
@@ -3809,13 +3829,14 @@ class _SelectableFragment
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<String>(
'textInsideRange',
range.textInside(fullText),
),
);
properties.add(DiagnosticsProperty<TextRange>('range', range));
properties.add(DiagnosticsProperty<String>('fullText', fullText));
properties
..add(
DiagnosticsProperty<String>(
'textInsideRange',
range.textInside(fullText),
),
)
..add(DiagnosticsProperty<TextRange>('range', range))
..add(DiagnosticsProperty<String>('fullText', fullText));
}
}

View File

@@ -1,6 +1,10 @@
import 'dart:ui' as ui;
// 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 'package:PiliPlus/common/widgets/text/paragraph.dart';
import 'dart:ui' as ui show TextHeightBehavior;
import 'package:PiliPlus/common/widgets/flutter/text/paragraph.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' hide RenderParagraph;
@@ -260,7 +264,7 @@ class RichText extends MultiChildRenderObjectWidget {
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
assert(textDirection != null || debugCheckHasDirectionality(context));
renderObject
..text = text
..text = (text: text, primary: primary)
..textAlign = textAlign
..textDirection = textDirection ?? Directionality.of(context)
..softWrap = softWrap
@@ -273,75 +277,75 @@ class RichText extends MultiChildRenderObjectWidget {
..locale = locale ?? Localizations.maybeLocaleOf(context)
..registrar = selectionRegistrar
..selectionColor = selectionColor
..primary = primary
..onShowMore = onShowMore;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
EnumProperty<TextAlign>(
'textAlign',
textAlign,
defaultValue: TextAlign.start,
),
);
properties.add(
EnumProperty<TextDirection>(
'textDirection',
textDirection,
defaultValue: null,
),
);
properties.add(
FlagProperty(
'softWrap',
value: softWrap,
ifTrue: 'wrapping at box width',
ifFalse: 'no wrapping except at line break characters',
showName: true,
),
);
properties.add(
EnumProperty<TextOverflow>(
'overflow',
overflow,
defaultValue: TextOverflow.clip,
),
);
properties.add(
DiagnosticsProperty<TextScaler>(
'textScaler',
textScaler,
defaultValue: TextScaler.noScaling,
),
);
properties.add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
properties.add(
EnumProperty<TextWidthBasis>(
'textWidthBasis',
textWidthBasis,
defaultValue: TextWidthBasis.parent,
),
);
properties.add(StringProperty('text', text.toPlainText()));
properties.add(
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
);
properties.add(
DiagnosticsProperty<StrutStyle>(
'strutStyle',
strutStyle,
defaultValue: null,
),
);
properties.add(
DiagnosticsProperty<TextHeightBehavior>(
'textHeightBehavior',
textHeightBehavior,
defaultValue: null,
),
);
properties
..add(
EnumProperty<TextAlign>(
'textAlign',
textAlign,
defaultValue: TextAlign.start,
),
)
..add(
EnumProperty<TextDirection>(
'textDirection',
textDirection,
defaultValue: null,
),
)
..add(
FlagProperty(
'softWrap',
value: softWrap,
ifTrue: 'wrapping at box width',
ifFalse: 'no wrapping except at line break characters',
showName: true,
),
)
..add(
EnumProperty<TextOverflow>(
'overflow',
overflow,
defaultValue: TextOverflow.clip,
),
)
..add(
DiagnosticsProperty<TextScaler>(
'textScaler',
textScaler,
defaultValue: TextScaler.noScaling,
),
)
..add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'))
..add(
EnumProperty<TextWidthBasis>(
'textWidthBasis',
textWidthBasis,
defaultValue: TextWidthBasis.parent,
),
)
..add(StringProperty('text', text.toPlainText()))
..add(
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
)
..add(
DiagnosticsProperty<StrutStyle>(
'strutStyle',
strutStyle,
defaultValue: null,
),
)
..add(
DiagnosticsProperty<TextHeightBehavior>(
'textHeightBehavior',
textHeightBehavior,
defaultValue: null,
),
);
}
}

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;
@@ -47,7 +47,11 @@ import 'package:flutter/rendering.dart' hide RenderParagraph;
/// Container(
/// width: 100,
/// decoration: BoxDecoration(border: Border.all()),
/// child: Text(overflow: TextOverflow.ellipsis, 'Hello $_name, how are you?'))
/// child: const Text(
/// 'Hello, how are you?',
/// overflow: TextOverflow.ellipsis,
/// ),
/// )
/// ```
/// {@end-tool}
///
@@ -60,10 +64,11 @@ import 'package:flutter/rendering.dart' hide RenderParagraph;
/// ![If a second line overflows the Text widget displays a horizontal fade](https://flutter.github.io/assets-for-api-docs/assets/widgets/text_fade_max_lines.png)
///
/// ```dart
/// Text(
/// const Text(
/// 'Hello, how are you?',
/// overflow: TextOverflow.fade,
/// maxLines: 1,
/// 'Hello $_name, how are you?')
/// )
/// ```
///
/// Here soft wrapping is enabled and the [Text] widget tries to wrap the words
@@ -74,10 +79,11 @@ import 'package:flutter/rendering.dart' hide RenderParagraph;
/// ![If a single line overflows the Text widget displays a horizontal fade](https://flutter.github.io/assets-for-api-docs/assets/widgets/text_fade_soft_wrap.png)
///
/// ```dart
/// Text(
/// const Text(
/// 'Hello, how are you?',
/// overflow: TextOverflow.fade,
/// softWrap: false,
/// 'Hello $_name, how are you?')
/// )
/// ```
///
/// Here soft wrapping is disabled with `softWrap: false` and the [Text] widget
@@ -410,6 +416,7 @@ class Text extends StatelessWidget {
text: TextSpan(
style: effectiveTextStyle,
text: data,
locale: locale,
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
),
primary: primary,
@@ -442,6 +449,7 @@ class Text extends StatelessWidget {
text: TextSpan(
style: effectiveTextStyle,
text: data,
locale: locale,
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
),
onShowMore: onShowMore,
@@ -1105,7 +1113,7 @@ class _SelectableTextContainerDelegate
bool forwardSelection =
currentSelectionEndIndex >= currentSelectionStartIndex;
if (currentSelectionEndIndex == currentSelectionStartIndex) {
// Determining selection direction is innacurate if currentSelectionStartIndex == currentSelectionEndIndex.
// Determining selection direction is inaccurate if currentSelectionStartIndex == currentSelectionEndIndex.
// Use the range from the selectable within the selection as the source of truth for selection direction.
final SelectedContentRange rangeAtSelectableInSelection =
selectables[currentSelectionStartIndex].getSelection()!;

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';
@@ -311,8 +311,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget {
@override
Widget build(BuildContext context) {
// If there aren't any buttons to build, build an empty toolbar.
if ((children != null && children!.isEmpty) ||
(buttonItems != null && buttonItems!.isEmpty)) {
if ((children ?? buttonItems)?.isEmpty ?? true) {
return const SizedBox.shrink();
}

View File

@@ -50,7 +50,7 @@ mixin RichTextTypeMixin {
extension TextEditingDeltaExt on TextEditingDelta {
({RichTextType type, String? rawText, Emote? emote, String? id}) get config {
if (this case RichTextTypeMixin e) {
if (this case final RichTextTypeMixin e) {
return (type: e.type, rawText: e.rawText, emote: e.emote, id: e.id);
}
return (
@@ -62,7 +62,7 @@ extension TextEditingDeltaExt on TextEditingDelta {
}
bool get isText {
if (this case RichTextTypeMixin e) {
if (this case final RichTextTypeMixin e) {
return e.type == RichTextType.text;
}
return !composing.isValid;
@@ -588,7 +588,7 @@ class RichTextEditingController extends TextEditingController {
return '';
}
final buffer = StringBuffer();
for (var e in items) {
for (final e in items) {
buffer.write(e.text);
}
return buffer.toString();
@@ -599,7 +599,7 @@ class RichTextEditingController extends TextEditingController {
return '';
}
final buffer = StringBuffer();
for (var e in items) {
for (final e in items) {
if (e.type == RichTextType.at) {
buffer.write(e.text);
} else {
@@ -704,11 +704,11 @@ class RichTextEditingController extends TextEditingController {
}
}
if (addIndex != null && toAdd?.isNotEmpty == true) {
items.insertAll(addIndex, toAdd!);
if (addIndex != null && toAdd != null && toAdd.isNotEmpty) {
items.insertAll(addIndex, toAdd);
}
if (toDel?.isNotEmpty == true) {
for (var item in toDel!) {
if (toDel != null && toDel.isNotEmpty) {
for (final item in toDel) {
items.remove(item);
}
}
@@ -736,7 +736,7 @@ class RichTextEditingController extends TextEditingController {
// bool isValid = true;
// int cursor = 0;
// for (var e in items) {
// for (final e in items) {
// final range = e.range;
// if (range.start == cursor) {
// cursor = range.end;
@@ -787,7 +787,7 @@ class RichTextEditingController extends TextEditingController {
width: 22, // emote.width,
height: 22, // emote.height,
type: ImageType.emote,
boxFit: BoxFit.contain,
fit: BoxFit.contain,
),
),
);
@@ -846,7 +846,7 @@ class RichTextEditingController extends TextEditingController {
TextPosition dragOffset(TextPosition position) {
final offset = position.offset;
for (var e in items) {
for (final e in items) {
final range = e.range;
if (offset >= range.end) {
continue;
@@ -866,7 +866,7 @@ class RichTextEditingController extends TextEditingController {
}
int tapOffsetSimple(int offset) {
for (var e in items) {
for (final e in items) {
final range = e.range;
if (offset >= range.end) {
continue;
@@ -891,7 +891,7 @@ class RichTextEditingController extends TextEditingController {
required Offset localPos,
required Offset lastTapDownPosition,
}) {
for (var e in items) {
for (final e in items) {
final range = e.range;
if (offset >= range.end) {
continue;
@@ -902,10 +902,10 @@ class RichTextEditingController extends TextEditingController {
// emoji tap
if (offset == range.start) {
if (e.emote != null) {
final cloestOffset = textPainter.getClosestGlyphForOffset(localPos);
if (cloestOffset != null) {
final offsetRect = cloestOffset.graphemeClusterLayoutBounds;
final offsetRange = cloestOffset.graphemeClusterCodeUnitRange;
final closestOffset = textPainter.getClosestGlyphForOffset(localPos);
if (closestOffset != null) {
final offsetRect = closestOffset.graphemeClusterLayoutBounds;
final offsetRange = closestOffset.graphemeClusterCodeUnitRange;
if (lastTapDownPosition.dx > offsetRect.right) {
return offsetRange.end;
} else {
@@ -930,7 +930,7 @@ class RichTextEditingController extends TextEditingController {
int startOffset,
int endOffset,
) {
for (var e in items) {
for (final e in items) {
final range = e.range;
if (startOffset >= range.end) {
continue;
@@ -963,7 +963,7 @@ class RichTextEditingController extends TextEditingController {
TextSelection keyboardOffset(TextSelection newSelection) {
final offset = newSelection.baseOffset;
for (var e in items) {
for (final e in items) {
final range = e.range;
if (offset >= range.end) {
continue;
@@ -994,7 +994,7 @@ class RichTextEditingController extends TextEditingController {
final startOffset = newSelection.start;
final endOffset = newSelection.end;
final isNormalized = newSelection.baseOffset < newSelection.extentOffset;
for (var e in items) {
for (final e in items) {
final range = e.range;
if (startOffset >= range.end) {
continue;
@@ -1046,7 +1046,7 @@ class RichTextEditingController extends TextEditingController {
String text = '';
final start = selection.start;
final end = selection.end;
for (var e in items) {
for (final e in items) {
final range = e.range;
if (start >= range.end) {
continue;

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';
@@ -209,7 +209,7 @@ class CupertinoAdaptiveTextSelectionToolbar extends StatelessWidget {
@override
Widget build(BuildContext context) {
// If there aren't any buttons to build, build an empty toolbar.
if ((children?.isEmpty ?? false) || (buttonItems?.isEmpty ?? false)) {
if ((children ?? buttonItems)?.isEmpty ?? true) {
return const SizedBox.shrink();
}

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

@@ -5,7 +5,6 @@
/// @docImport 'package:flutter/cupertino.dart';
library;
import 'dart:collection';
import 'dart:math' as math;
import 'dart:ui'
as ui
@@ -16,10 +15,10 @@ 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:characters/characters.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
@@ -153,7 +152,10 @@ class VerticalCaretMovementRun implements Iterator<TextPosition> {
final TextPosition closestPosition = _editable._textPainter
.getPositionForOffset(newOffset);
final MapEntry<Offset, TextPosition> position =
MapEntry<Offset, TextPosition>(newOffset, closestPosition);
MapEntry<Offset, TextPosition>(
newOffset,
closestPosition,
);
_positionCache[lineNumber] = position;
return position;
}
@@ -294,8 +296,8 @@ class RenderEditable extends RenderBox
bool paintCursorAboveText = false,
Offset cursorOffset = Offset.zero,
double devicePixelRatio = 1.0,
ui.BoxHeightStyle selectionHeightStyle = ui.BoxHeightStyle.tight,
ui.BoxWidthStyle selectionWidthStyle = ui.BoxWidthStyle.tight,
ui.BoxHeightStyle selectionHeightStyle = ui.BoxHeightStyle.max,
ui.BoxWidthStyle selectionWidthStyle = ui.BoxWidthStyle.max,
bool? enableInteractiveSelection,
this.floatingCursorAddedMargin = const EdgeInsets.fromLTRB(4, 4, 4, 5),
TextRange? promptRectRange,
@@ -1301,7 +1303,7 @@ class RenderEditable extends RenderBox
// can be re-used when [assembleSemanticsNode] is called again. This ensures
// stable ids for the [SemanticsNode]s of [TextSpan]s across
// [assembleSemanticsNode] invocations.
LinkedHashMap<Key, SemanticsNode>? _cachedChildNodes;
Map<Key, SemanticsNode>? _cachedChildNodes;
/// Returns a list of rects that bound the given selection, and the text
/// direction. The text direction is used by the engine to calculate
@@ -1311,7 +1313,11 @@ class RenderEditable extends RenderBox
List<TextBox> getBoxesForSelection(TextSelection selection) {
_computeTextMetricsIfNeeded();
return _textPainter
.getBoxesForSelection(selection)
.getBoxesForSelection(
selection,
boxHeightStyle: selectionHeightStyle,
boxWidthStyle: selectionWidthStyle,
)
.map(
(TextBox textBox) => TextBox.fromLTRBD(
textBox.left + _paintOffset.dx,
@@ -1381,6 +1387,7 @@ class RenderEditable extends RenderBox
..isMultiline = _isMultiline
..textDirection = textDirection
..isFocused = hasFocus
..isFocusable = true
..isTextField = true
..isReadOnly = readOnly
// This is the default for customer that uses RenderEditable directly.
@@ -1437,8 +1444,7 @@ class RenderEditable extends RenderBox
int placeholderIndex = 0;
int childIndex = 0;
RenderBox? child = firstChild;
final LinkedHashMap<Key, SemanticsNode> newChildCache =
LinkedHashMap<Key, SemanticsNode>();
final Map<Key, SemanticsNode> newChildCache = <Key, SemanticsNode>{};
_cachedCombinedSemanticsInfos ??= combineSemanticsInfo(_semanticsInfo!);
for (final InlineSpanSemanticsInformation info
in _cachedCombinedSemanticsInfos!) {
@@ -1507,8 +1513,9 @@ class RenderEditable extends RenderBox
onDoubleTap: final VoidCallback? handler,
):
if (handler != null) {
configuration.onTap = handler;
configuration.isLink = true;
configuration
..onTap = handler
..isLink = true;
}
case LongPressGestureRecognizer(
onLongPress: final GestureLongPressCallback? onLongPress,
@@ -2203,17 +2210,14 @@ class RenderEditable extends RenderBox
Offset? to,
required SelectionChangedCause cause,
}) {
final localFrom = globalToLocal(from);
_computeTextMetricsIfNeeded();
final localFrom = globalToLocal(from);
final TextPosition fromPosition = _textPainter.getPositionForOffset(
localFrom - _paintOffset,
);
final TextPosition? toPosition = to == null
? null
: _textPainter.getPositionForOffset(
globalToLocal(to) - _paintOffset,
);
: _textPainter.getPositionForOffset(globalToLocal(to) - _paintOffset);
int baseOffset = fromPosition.offset;
int extentOffset = toPosition?.offset ?? fromPosition.offset;
@@ -2265,7 +2269,6 @@ class RenderEditable extends RenderBox
/// beginning and end of a word respectively.
///
/// {@macro flutter.rendering.RenderEditable.selectPosition}
void selectWordsInRange({
required Offset from,
Offset? to,
@@ -2278,9 +2281,7 @@ class RenderEditable extends RenderBox
final TextSelection fromWord = getWordAtOffset(fromPosition);
final TextPosition toPosition = to == null
? fromPosition
: _textPainter.getPositionForOffset(
globalToLocal(to) - _paintOffset,
);
: _textPainter.getPositionForOffset(globalToLocal(to) - _paintOffset);
final TextSelection toWord = toPosition == fromPosition
? fromWord
: getWordAtOffset(toPosition);
@@ -2526,9 +2527,7 @@ class RenderEditable extends RenderBox
..layout(minWidth: minWidth, maxWidth: maxWidth);
final double width = forceLine
? constraints.maxWidth
: constraints.constrainWidth(
_textIntrinsics.size.width + _caretMargin,
);
: constraints.constrainWidth(_textIntrinsics.size.width + _caretMargin);
return Size(
width,
constraints.constrainHeight(_preferredHeight(constraints.maxWidth)),
@@ -2603,8 +2602,9 @@ class RenderEditable extends RenderBox
_backgroundRenderObject?.layout(painterConstraints);
_maxScrollExtent = _getMaxScrollExtent(contentSize);
offset.applyViewportDimension(_viewportExtent);
offset.applyContentDimensions(0.0, _maxScrollExtent);
offset
..applyViewportDimension(_viewportExtent)
..applyContentDimensions(0.0, _maxScrollExtent);
}
// The relative origin in relation to the distance the user has theoretically
@@ -2942,28 +2942,29 @@ class RenderEditable extends RenderBox
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(ColorProperty('cursorColor', cursorColor));
properties.add(
DiagnosticsProperty<ValueNotifier<bool>>('showCursor', showCursor),
);
properties.add(IntProperty('maxLines', maxLines));
properties.add(IntProperty('minLines', minLines));
properties.add(
DiagnosticsProperty<bool>('expands', expands, defaultValue: false),
);
properties.add(ColorProperty('selectionColor', selectionColor));
properties.add(
DiagnosticsProperty<TextScaler>(
'textScaler',
textScaler,
defaultValue: TextScaler.noScaling,
),
);
properties.add(
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
);
properties.add(DiagnosticsProperty<TextSelection>('selection', selection));
properties.add(DiagnosticsProperty<ViewportOffset>('offset', offset));
properties
..add(ColorProperty('cursorColor', cursorColor))
..add(
DiagnosticsProperty<ValueNotifier<bool>>('showCursor', showCursor),
)
..add(IntProperty('maxLines', maxLines))
..add(IntProperty('minLines', minLines))
..add(
DiagnosticsProperty<bool>('expands', expands, defaultValue: false),
)
..add(ColorProperty('selectionColor', selectionColor))
..add(
DiagnosticsProperty<TextScaler>(
'textScaler',
textScaler,
defaultValue: TextScaler.noScaling,
),
)
..add(
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
)
..add(DiagnosticsProperty<TextSelection>('selection', selection))
..add(DiagnosticsProperty<ViewportOffset>('offset', offset));
}
@override
@@ -3154,11 +3155,13 @@ class _TextHighlightPainter extends RenderEditablePainter {
highlightPaint.color = color;
final TextPainter textPainter = renderEditable._textPainter;
final List<TextBox> boxes = textPainter.getBoxesForSelection(
TextSelection(baseOffset: range.start, extentOffset: range.end),
boxHeightStyle: selectionHeightStyle,
boxWidthStyle: selectionWidthStyle,
);
final Set<TextBox> boxes = textPainter
.getBoxesForSelection(
TextSelection(baseOffset: range.start, extentOffset: range.end),
boxHeightStyle: selectionHeightStyle,
boxWidthStyle: selectionWidthStyle,
)
.toSet();
for (final TextBox box in boxes) {
canvas.drawRect(
@@ -3215,7 +3218,7 @@ class _CaretPainter extends RenderEditablePainter {
Color? get caretColor => _caretColor;
Color? _caretColor;
set caretColor(Color? value) {
if (caretColor?.value == value?.value) {
if (caretColor?.toARGB32() == value?.toARGB32()) {
return;
}
@@ -3246,7 +3249,7 @@ class _CaretPainter extends RenderEditablePainter {
Color? get backgroundCursorColor => _backgroundCursorColor;
Color? _backgroundCursorColor;
set backgroundCursorColor(Color? value) {
if (backgroundCursorColor?.value == value?.value) {
if (backgroundCursorColor?.toARGB32() == value?.toARGB32()) {
return;
}
@@ -3318,7 +3321,7 @@ class _CaretPainter extends RenderEditablePainter {
paintRegularCursor(canvas, renderEditable, caretColor, caretTextPosition);
}
final Color? floatingCursorColor = this.caretColor?.withOpacity(0.75);
final Color? floatingCursorColor = this.caretColor?.withValues(alpha: 0.75);
// Floating Cursor.
if (floatingCursorRect == null ||
floatingCursorColor == null ||

View File

@@ -20,44 +20,25 @@ import 'dart:async';
import 'dart:io' show Platform;
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'
hide
EditableText,
EditableTextState,
SpellCheckConfiguration,
buildTextSpanWithSpellCheckSuggestions,
TextSelectionOverlay,
TextSelectionGestureDetectorBuilder;
TextSelectionGestureDetectorBuilder,
TextSelectionOverlay;
import 'package:flutter/rendering.dart'
hide RenderEditable, VerticalCaretMovementRun;
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
export 'package:flutter/services.dart'
show
KeyboardInsertedContent,
SelectionChangedCause,
SmartDashesType,
SmartQuotesType,
TextEditingValue,
TextInputType,
TextSelection;
// Examples can assume:
// late BuildContext context;
// late WidgetTester tester;
/// Signature for the callback that reports when the user changes the selection
/// (including the cursor location).
typedef SelectionChangedCallback =
void Function(TextSelection selection, SelectionChangedCause? cause);
/// Signature for a widget builder that builds a context menu for the given
/// [EditableTextState].
///
@@ -135,54 +116,6 @@ class _RenderCompositionCallback extends RenderProxyBox {
}
}
/// A controller for an editable text field.
///
/// Whenever the user modifies a text field with an associated
/// [RichTextEditingController], the text field updates [value] and the controller
/// notifies its listeners. Listeners can then read the [text] and [selection]
/// properties to learn what the user has typed or how the selection has been
/// updated.
///
/// Similarly, if you modify the [text] or [selection] properties, the text
/// field will be notified and will update itself appropriately.
///
/// A [RichTextEditingController] can also be used to provide an initial value for a
/// text field. If you build a text field with a controller that already has
/// [text], the text field will use that text as its initial value.
///
/// The [value] (as well as [text] and [selection]) of this controller can be
/// updated from within a listener added to this controller. Be aware of
/// infinite loops since the listener will also be notified of the changes made
/// from within itself. Modifying the composing region from within a listener
/// can also have a bad interaction with some input methods. Gboard, for
/// example, will try to restore the composing region of the text if it was
/// modified programmatically, creating an infinite loop of communications
/// between the framework and the input method. Consider using
/// [TextInputFormatter]s instead for as-you-type text modification.
///
/// If both the [text] and [selection] properties need to be changed, set the
/// controller's [value] instead. Setting [text] will clear the selection
/// and composing range.
///
/// Remember to [dispose] of the [RichTextEditingController] when it is no longer
/// needed. This will ensure we discard any resources used by the object.
///
/// {@tool dartpad}
/// This example creates a [TextField] with a [RichTextEditingController] whose
/// change listener forces the entered text to be lower case and keeps the
/// cursor at the end of the input.
///
/// ** See code in examples/api/lib/widgets/editable_text/text_editing_controller.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [TextField], which is a Material Design text field that can be controlled
/// with a [RichTextEditingController].
/// * [EditableText], which is a raw region of editable text that can be
/// controlled with a [RichTextEditingController].
/// * Learn how to use a [RichTextEditingController] in one of our [cookbook recipes](https://docs.flutter.dev/cookbook/forms/text-field-changes#2-use-a-texteditingcontroller).
// A time-value pair that represents a key frame in an animation.
class _KeyFrame {
const _KeyFrame(this.time, this.value);
@@ -506,7 +439,7 @@ class EditableText extends StatefulWidget {
this.readOnly = false,
this.obscuringCharacter = '',
this.obscureText = false,
this.autocorrect = true,
bool? autocorrect,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
this.enableSuggestions = true,
@@ -556,12 +489,13 @@ class EditableText extends StatefulWidget {
this.cursorOpacityAnimates = false,
this.cursorOffset,
this.paintCursorAboveText = false,
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
ui.BoxHeightStyle? selectionHeightStyle,
ui.BoxWidthStyle? selectionWidthStyle,
this.scrollPadding = const EdgeInsets.all(20.0),
this.keyboardAppearance = Brightness.light,
this.dragStartBehavior = DragStartBehavior.start,
bool? enableInteractiveSelection,
bool? selectAllOnFocus,
this.scrollController,
this.scrollPhysics,
this.autocorrectionTextRectColor,
@@ -586,7 +520,10 @@ class EditableText extends StatefulWidget {
this.contextMenuBuilder,
this.spellCheckConfiguration,
this.magnifierConfiguration = TextMagnifierConfiguration.disabled,
this.hintLocales,
}) : assert(obscuringCharacter.length == 1),
autocorrect =
autocorrect ?? _inferAutocorrect(autofillHints: autofillHints),
smartDashesType =
smartDashesType ??
(obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
@@ -608,6 +545,7 @@ class EditableText extends StatefulWidget {
),
enableInteractiveSelection =
enableInteractiveSelection ?? (!readOnly || !obscureText),
selectAllOnFocus = selectAllOnFocus ?? _defaultSelectAllOnFocus,
toolbarOptions =
selectionControls is TextSelectionHandleControls &&
toolbarOptions == null
@@ -647,7 +585,10 @@ class EditableText extends StatefulWidget {
...inputFormatters ?? const Iterable<TextInputFormatter>.empty(),
]
: inputFormatters,
showCursor = showCursor ?? !readOnly;
showCursor = showCursor ?? !readOnly,
selectionHeightStyle =
selectionHeightStyle ?? defaultSelectionHeightStyle,
selectionWidthStyle = selectionWidthStyle ?? defaultSelectionWidthStyle;
/// Controls the text being edited.
final RichTextEditingController controller;
@@ -737,7 +678,7 @@ class EditableText extends StatefulWidget {
/// {@template flutter.widgets.editableText.autocorrect}
/// Whether to enable autocorrection.
///
/// Defaults to true.
/// False on iOS if [autofillHints] contains password-related hints, otherwise true.
/// {@endtemplate}
final bool autocorrect;
@@ -1302,7 +1243,7 @@ class EditableText extends StatefulWidget {
/// [TextSelectionGestureDetectorBuilder] to wrap the [EditableText], and set
/// [rendererIgnoresPointer] to true.
///
/// When [rendererIgnoresPointer] is true true, the [RenderEditable] created
/// When [rendererIgnoresPointer] is true, the [RenderEditable] created
/// by this widget will not handle pointer events.
///
/// This property is false by default.
@@ -1396,8 +1337,9 @@ class EditableText extends StatefulWidget {
/// cut/copy/paste menu, and tapping to move the text caret.
///
/// When this is false, the text selection cannot be adjusted by
/// the user, text cannot be copied, and the user cannot paste into
/// the text field from the clipboard.
/// the user, the cut/copy/paste menu is hidden, and the shortcuts to
/// cut/copy/paste text do nothing but stop propagation of the key event
/// to other key event handlers in the focus chain.
///
/// Defaults to true.
/// {@endtemplate}
@@ -1480,6 +1422,16 @@ class EditableText extends StatefulWidget {
/// {@endtemplate}
bool get selectionEnabled => enableInteractiveSelection;
/// {@template flutter.widgets.editableText.selectAllOnFocus}
/// Whether this field should select all text when gaining focus.
///
/// When false, focusing this text field will leave its
/// existing text selection unchanged.
///
/// Defaults to true on web and desktop platforms, and false on mobile platforms.
/// {@endtemplate}
final bool selectAllOnFocus;
/// {@template flutter.widgets.editableText.autofillHints}
/// A list of strings that helps the autofill service identify the type of this
/// text input.
@@ -1714,12 +1666,62 @@ class EditableText extends StatefulWidget {
/// {@macro flutter.widgets.magnifier.intro}
final TextMagnifierConfiguration magnifierConfiguration;
/// {@macro flutter.services.TextInputConfiguration.hintLocales}
final List<Locale>? hintLocales;
/// The default value for [selectionHeightStyle].
///
/// On web platforms, this defaults to [ui.BoxHeightStyle.max].
///
/// On native platforms, this defaults to [ui.BoxHeightStyle.includeLineSpacingMiddle] for all
/// platforms.
static ui.BoxHeightStyle get defaultSelectionHeightStyle {
if (kIsWeb) {
return ui.BoxHeightStyle.max;
}
return ui.BoxHeightStyle.includeLineSpacingMiddle;
}
/// The default value for [selectionWidthStyle].
///
/// On web platforms, this defaults to [ui.BoxWidthStyle.max] for Apple platforms running
/// Safari (webkit) based browsers and [ui.BoxWidthStyle.tight] for all others.
///
/// On non-web platforms, this defaults to [ui.BoxWidthStyle.max].
static ui.BoxWidthStyle get defaultSelectionWidthStyle {
// if (kIsWeb) {
// if (defaultTargetPlatform == TargetPlatform.iOS ||
// WebBrowserDetection.isSafari) {
// // On macOS web, the selection width behavior differs when running on
// // Chrom(e|ium) (blink) or Safari (webkit).
// return ui.BoxWidthStyle.max;
// }
// return ui.BoxWidthStyle.tight;
// }
return ui.BoxWidthStyle.max;
}
/// The default value for [stylusHandwritingEnabled].
static const bool defaultStylusHandwritingEnabled = true;
bool get _userSelectionEnabled =>
enableInteractiveSelection && (!readOnly || !obscureText);
/// The default value for [selectAllOnFocus].
static bool get _defaultSelectAllOnFocus {
if (kIsWeb) {
return true;
}
return switch (defaultTargetPlatform) {
TargetPlatform.android => false,
TargetPlatform.iOS => false,
TargetPlatform.fuchsia => false,
TargetPlatform.linux => true,
TargetPlatform.macOS => true,
TargetPlatform.windows => true,
};
}
/// Returns the [ContextMenuButtonItem]s representing the buttons in this
/// platform's default selection menu for an editable field.
///
@@ -1818,6 +1820,38 @@ class EditableText extends StatefulWidget {
return resultButtonItem;
}
// Infer the value of autocorrect from autofillHints.
static bool _inferAutocorrect({required Iterable<String>? autofillHints}) {
if (autofillHints == null || autofillHints.isEmpty || kIsWeb) {
return true;
}
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
// username, password and newPassword are password related hint.
// newUsername is not supported on iOS.
final bool passwordRelatedHint = autofillHints.any(
(String hint) =>
hint == AutofillHints.username ||
hint == AutofillHints.password ||
hint == AutofillHints.newPassword,
);
if (passwordRelatedHint) {
// https://github.com/flutter/flutter/issues/134723
// Set autocorrect to false to prevent password bar from flashing.
return false;
}
case TargetPlatform.macOS:
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
break;
}
return true;
}
// Infer the keyboard type of an `EditableText` if it's not specified.
static TextInputType _inferKeyboardType({
required Iterable<String>? autofillHints,
@@ -2000,7 +2034,7 @@ class EditableText extends StatefulWidget {
DiagnosticsProperty<bool>(
'autocorrect',
autocorrect,
defaultValue: true,
defaultValue: null,
),
)
..add(
@@ -2136,6 +2170,13 @@ class EditableText extends StatefulWidget {
? const <String>[]
: kDefaultContentInsertionMimeTypes,
),
)
..add(
DiagnosticsProperty<List<Locale>?>(
'hintLocales',
hintLocales,
defaultValue: null,
),
);
}
}
@@ -2438,6 +2479,7 @@ class EditableTextState extends State<EditableText>
if (selection.isCollapsed || widget.obscureText) {
return;
}
// bggRGjQaUbCoE copySelection
final String text =
widget.controller.getSelectionText(selection) ??
selection.textInside(textEditingValue.text);
@@ -2455,7 +2497,6 @@ class EditableTextState extends State<EditableText>
case TargetPlatform.android:
case TargetPlatform.fuchsia:
// Collapse the selection and hide the toolbar and handles.
userUpdateTextEditingValue(
TextEditingValue(
text: textEditingValue.text,
@@ -2480,6 +2521,7 @@ class EditableTextState extends State<EditableText>
if (selection.isCollapsed) {
return;
}
// bggRGjQaUbCoE cutSelection
final String text =
widget.controller.getSelectionText(selection) ??
selection.textInside(textEditingValue.text);
@@ -2528,12 +2570,7 @@ class EditableTextState extends State<EditableText>
selection.baseOffset,
selection.extentOffset,
);
// final TextEditingValue collapsedTextEditingValue =
// textEditingValue.copyWith(
// selection: TextSelection.collapsed(offset: lastSelectionIndex),
// );
// final newValue = collapsedTextEditingValue.replaced(selection, text);
// bggRGjQaUbCoE _pasteText
widget.controller.syncRichText(
selection.isCollapsed
? TextEditingDeltaInsertion(
@@ -2551,15 +2588,12 @@ class EditableTextState extends State<EditableText>
composing: TextRange.empty,
),
);
final newValue = _value.copyWith(
text: widget.controller.plainText,
selection: widget.controller.newSelection,
composing: TextRange.empty,
);
userUpdateTextEditingValue(newValue, cause);
if (cause == SelectionChangedCause.toolbar) {
// Schedule a call to bringIntoView() after renderEditable updates.
SchedulerBinding.instance.addPostFrameCallback((_) {
@@ -2579,7 +2613,6 @@ class EditableTextState extends State<EditableText>
// selecting it.
return;
}
userUpdateTextEditingValue(
textEditingValue.copyWith(
selection: TextSelection(
@@ -3368,6 +3401,19 @@ class EditableTextState extends State<EditableText>
// editing.
if (!_isMultiline) {
_finalizeEditing(action, shouldUnfocus: true);
} else if (HardwareKeyboard.instance.isControlPressed) {
final ctr = widget.controller;
final offset = ctr.selection.end;
// delete newline
ctr.syncRichText(
TextEditingDeltaDeletion(
composing: TextRange.empty,
selection: TextSelection.collapsed(offset: offset - 1),
deletedRange: TextRange(start: offset - 1, end: offset),
oldText: ctr.text,
),
);
_finalizeEditing(action, shouldUnfocus: true);
}
case TextInputAction.done:
case TextInputAction.go:
@@ -3520,7 +3566,9 @@ class EditableTextState extends State<EditableText>
);
case FloatingCursorDragState.End:
// Resume cursor blinking.
_startCursorBlink();
if (_hasFocus) {
_startCursorBlink();
}
// We skip animation if no update has happened.
if (_lastTextPosition != null && _lastBoundedOffset != null) {
_floatingCursorResetController!.value = 0.0;
@@ -4417,15 +4465,6 @@ class EditableTextState extends State<EditableText>
final bool textCommitted =
!oldValue.composing.isCollapsed && value.composing.isCollapsed;
final bool selectionChanged = oldValue.selection != value.selection;
// if (!textChanged && selectionChanged) {
// value = value.copyWith(
// selection: widget.controller.updateSelection(
// oldSelection: _value.selection,
// newSelection: value.selection,
// cause: cause,
// ),
// );
// }
if (textChanged || textCommitted) {
// Only apply input formatters if the text has changed (including uncommitted
@@ -4689,17 +4728,9 @@ class EditableTextState extends State<EditableText>
TextSelection? _adjustedSelectionWhenFocused() {
TextSelection? selection;
final bool isDesktop = switch (defaultTargetPlatform) {
TargetPlatform.android ||
TargetPlatform.iOS ||
TargetPlatform.fuchsia => false,
TargetPlatform.macOS ||
TargetPlatform.linux ||
TargetPlatform.windows => true,
};
final bool shouldSelectAll =
widget.selectAllOnFocus &&
widget.selectionEnabled &&
(kIsWeb || isDesktop) &&
!_isMultiline &&
!_nextFocusChangeIsInternal &&
!_justResumed;
@@ -5043,10 +5074,10 @@ class EditableTextState extends State<EditableText>
}
/// Shows the magnifier at the position given by `positionToShow`,
/// if there is no magnifier visible.
/// if no magnifier exists.
///
/// Updates the magnifier to the position given by `positionToShow`,
/// if there is a magnifier visible.
/// if a magnifier exits.
///
/// Does nothing if a magnifier couldn't be shown, such as when the selection
/// overlay does not currently exist.
@@ -5055,22 +5086,20 @@ class EditableTextState extends State<EditableText>
return;
}
if (_selectionOverlay!.magnifierIsVisible) {
if (_selectionOverlay!.magnifierExists) {
_selectionOverlay!.updateMagnifier(positionToShow);
} else {
_selectionOverlay!.showMagnifier(positionToShow);
}
}
/// Hides the magnifier if it is visible.
/// Hides the magnifier.
void hideMagnifier() {
if (_selectionOverlay == null) {
return;
}
if (_selectionOverlay!.magnifierIsVisible) {
_selectionOverlay!.hideMagnifier();
}
_selectionOverlay!.hideMagnifier();
}
// Tracks the location a [_ScribblePlaceholder] should be rendered in the
@@ -5161,6 +5190,7 @@ class EditableTextState extends State<EditableText>
allowedMimeTypes: widget.contentInsertionConfiguration == null
? const <String>[]
: widget.contentInsertionConfiguration!.allowedMimeTypes,
hintLocales: widget.hintLocales,
);
}
@@ -5357,10 +5387,7 @@ class EditableTextState extends State<EditableText>
void _replaceText(ReplaceTextIntent intent) {
final TextEditingValue oldValue = _value;
// final TextEditingValue newValue = intent.currentTextEditingValue.replaced(
// intent.replacementRange,
// intent.replacementText,
// );
// bggRGjQaUbCoE _replaceText
widget.controller.syncRichText(
intent.replacementText.isEmpty
? TextEditingDeltaDeletion(
@@ -5387,7 +5414,6 @@ class EditableTextState extends State<EditableText>
selection: widget.controller.newSelection,
composing: TextRange.empty,
);
userUpdateTextEditingValue(newValue, intent.cause);
// If there's no change in text and selection (e.g. when selecting and
@@ -5509,7 +5535,6 @@ class EditableTextState extends State<EditableText>
}
bringIntoView(nextSelection.extent);
userUpdateTextEditingValue(
_value.copyWith(selection: nextSelection),
SelectionChangedCause.keyboard,
@@ -5718,7 +5743,8 @@ class EditableTextState extends State<EditableText>
),
),
ScrollToDocumentBoundaryIntent: _makeOverridable(
CallbackAction<ScrollToDocumentBoundaryIntent>(
_WebComposingDisablingCallbackAction<ScrollToDocumentBoundaryIntent>(
this,
onInvoke: _scrollToDocumentBoundary,
),
),
@@ -5748,11 +5774,7 @@ class EditableTextState extends State<EditableText>
// Copy Paste
SelectAllTextIntent: _makeOverridable(_SelectAllAction(this)),
CopySelectionTextIntent: _makeOverridable(_CopySelectionAction(this)),
PasteTextIntent: _makeOverridable(
CallbackAction<PasteTextIntent>(
onInvoke: (PasteTextIntent intent) => pasteText(intent.cause),
),
),
PasteTextIntent: _makeOverridable(_PasteSelectionAction(this)),
TransposeCharactersIntent: _makeOverridable(_transposeCharactersAction),
EditableTextTapOutsideIntent: _makeOverridable(
@@ -5826,7 +5848,13 @@ class EditableTextState extends State<EditableText>
? AxisDirection.down
: AxisDirection.right,
controller: _scrollController,
physics: widget.scrollPhysics,
// On iOS a single-line TextField should not scroll.
physics:
widget.scrollPhysics ??
(!_isMultiline &&
defaultTargetPlatform == TargetPlatform.iOS
? const _NeverUserScrollableScrollPhysics()
: null),
dragStartBehavior: widget.dragStartBehavior,
restorationId: widget.restorationId,
// If a ScrollBehavior is not provided, only apply scrollbars when
@@ -5970,15 +5998,19 @@ class EditableTextState extends State<EditableText>
final int placeholderLocation = _value.text.length - _placeholderLocation;
if (_isMultiline) {
// The zero size placeholder here allows the line to break and keep the caret on the first line.
placeholders.add(
const _ScribblePlaceholder(child: SizedBox.shrink(), size: Size.zero),
);
placeholders.add(
_ScribblePlaceholder(
child: const SizedBox.shrink(),
size: Size(renderEditable.size.width, 0.0),
),
);
placeholders
..add(
const _ScribblePlaceholder(
child: SizedBox.shrink(),
size: Size.zero,
),
)
..add(
_ScribblePlaceholder(
child: const SizedBox.shrink(),
size: Size(renderEditable.size.width, 0.0),
),
);
} else {
placeholders.add(
const _ScribblePlaceholder(
@@ -6061,8 +6093,8 @@ class _Editable extends MultiChildRenderObjectWidget {
this.cursorRadius,
required this.cursorOffset,
required this.paintCursorAboveText,
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
ui.BoxHeightStyle? selectionHeightStyle,
ui.BoxWidthStyle? selectionWidthStyle,
this.enableInteractiveSelection = true,
required this.textSelectionDelegate,
required this.devicePixelRatio,
@@ -6070,7 +6102,11 @@ class _Editable extends MultiChildRenderObjectWidget {
this.promptRectColor,
required this.clipBehavior,
required this.controller,
}) : super(
}) : selectionHeightStyle =
selectionHeightStyle ?? EditableText.defaultSelectionHeightStyle,
selectionWidthStyle =
selectionWidthStyle ?? EditableText.defaultSelectionWidthStyle,
super(
children: WidgetSpan.extractFromInlineSpan(inlineSpan, textScaler),
);
@@ -6203,6 +6239,20 @@ class _Editable extends MultiChildRenderObjectWidget {
}
}
class _NeverUserScrollableScrollPhysics extends ScrollPhysics {
/// Creates a scroll physics that prevents scrolling with user input, for example
/// by dragging, but still allows for programmatic scrolling.
const _NeverUserScrollableScrollPhysics({super.parent});
@override
_NeverUserScrollableScrollPhysics applyTo(ScrollPhysics? ancestor) {
return _NeverUserScrollableScrollPhysics(parent: buildParent(ancestor));
}
@override
bool get allowUserScrolling => false;
}
@immutable
class _ScribbleCacheKey {
const _ScribbleCacheKey({
@@ -6454,11 +6504,7 @@ class _CodePointBoundary extends TextBoundary {
// ------------------------------- Text Actions -------------------------------
class _DeleteTextAction<T extends DirectionalTextEditingIntent>
extends ContextAction<T> {
_DeleteTextAction(
this.state,
this.getTextBoundary,
this._applyTextBoundary,
);
_DeleteTextAction(this.state, this.getTextBoundary, this._applyTextBoundary);
final EditableTextState state;
final TextBoundary Function() getTextBoundary;
@@ -6658,7 +6704,15 @@ class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent>
}
@override
bool get isActionEnabled => state._value.selection.isValid;
bool get isActionEnabled {
if (kIsWeb &&
state.widget.selectionEnabled &&
state._value.composing.isValid) {
return false;
}
return state._value.selection.isValid;
}
}
class _UpdateTextSelectionVerticallyAction<
@@ -6745,7 +6799,33 @@ class _UpdateTextSelectionVerticallyAction<
}
@override
bool get isActionEnabled => state._value.selection.isValid;
bool get isActionEnabled {
if (kIsWeb &&
state.widget.selectionEnabled &&
state._value.composing.isValid) {
return false;
}
return state._value.selection.isValid;
}
}
class _WebComposingDisablingCallbackAction<T extends Intent>
extends CallbackAction<T> {
_WebComposingDisablingCallbackAction(this.state, {required super.onInvoke});
final EditableTextState state;
@override
bool get isActionEnabled {
if (kIsWeb &&
state.widget.selectionEnabled &&
state._value.composing.isValid) {
return false;
}
return super.isActionEnabled;
}
}
class _SelectAllAction extends ContextAction<SelectAllTextIntent> {
@@ -6755,6 +6835,10 @@ class _SelectAllAction extends ContextAction<SelectAllTextIntent> {
@override
Object? invoke(SelectAllTextIntent intent, [BuildContext? context]) {
if (!state.widget.selectionEnabled) {
return null;
}
return Actions.invoke(
context!,
UpdateSelectionIntent(
@@ -6764,9 +6848,6 @@ class _SelectAllAction extends ContextAction<SelectAllTextIntent> {
),
);
}
@override
bool get isActionEnabled => state.widget.selectionEnabled;
}
class _CopySelectionAction extends ContextAction<CopySelectionTextIntent> {
@@ -6776,16 +6857,35 @@ class _CopySelectionAction extends ContextAction<CopySelectionTextIntent> {
@override
void invoke(CopySelectionTextIntent intent, [BuildContext? context]) {
if (!state._value.selection.isValid || state._value.selection.isCollapsed) {
return;
}
if (!state.widget.selectionEnabled) {
return;
}
if (intent.collapseSelection) {
state.cutSelection(intent.cause);
} else {
state.copySelection(intent.cause);
}
}
}
class _PasteSelectionAction extends ContextAction<PasteTextIntent> {
_PasteSelectionAction(this.state);
final EditableTextState state;
@override
bool get isActionEnabled =>
state._value.selection.isValid && !state._value.selection.isCollapsed;
void invoke(PasteTextIntent intent, [BuildContext? context]) {
if (!state.widget.selectionEnabled) {
return;
}
state.pasteText(intent.cause);
}
}
/// A [ClipboardStatusNotifier] whose [value] is hardcoded to

View File

@@ -0,0 +1,119 @@
// 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.
/// @docImport 'editable_text.dart';
library;
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart'
show EditableTextContextMenuBuilder;
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/services.dart' show SpellCheckService;
/// Controls how spell check is performed for text input.
///
/// This configuration determines the [SpellCheckService] used to fetch the
/// [List<SuggestionSpan>] spell check results and the [TextStyle] used to
/// mark misspelled words within text input.
@immutable
class SpellCheckConfiguration {
/// Creates a configuration that specifies the service and suggestions handler
/// for spell check.
const SpellCheckConfiguration({
this.spellCheckService,
this.misspelledSelectionColor,
this.misspelledTextStyle,
this.spellCheckSuggestionsToolbarBuilder,
}) : _spellCheckEnabled = true;
/// Creates a configuration that disables spell check.
const SpellCheckConfiguration.disabled()
: _spellCheckEnabled = false,
spellCheckService = null,
spellCheckSuggestionsToolbarBuilder = null,
misspelledTextStyle = null,
misspelledSelectionColor = null;
/// The service used to fetch spell check results for text input.
final SpellCheckService? spellCheckService;
/// The color the paint the selection highlight when spell check is showing
/// suggestions for a misspelled word.
///
/// For example, on iOS, the selection appears red while the spell check menu
/// is showing.
final Color? misspelledSelectionColor;
/// Style used to indicate misspelled words.
///
/// This is nullable to allow style-specific wrappers of [EditableText]
/// to infer this, but this must be specified if this configuration is
/// provided directly to [EditableText] or its construction will fail with an
/// assertion error.
final TextStyle? misspelledTextStyle;
/// Builds the toolbar used to display spell check suggestions for misspelled
/// words.
final EditableTextContextMenuBuilder? spellCheckSuggestionsToolbarBuilder;
final bool _spellCheckEnabled;
/// Whether or not the configuration should enable or disable spell check.
bool get spellCheckEnabled => _spellCheckEnabled;
/// Returns a copy of the current [SpellCheckConfiguration] instance with
/// specified overrides.
SpellCheckConfiguration copyWith({
SpellCheckService? spellCheckService,
Color? misspelledSelectionColor,
TextStyle? misspelledTextStyle,
EditableTextContextMenuBuilder? spellCheckSuggestionsToolbarBuilder,
}) {
if (!_spellCheckEnabled) {
// A new configuration should be constructed to enable spell check.
return const SpellCheckConfiguration.disabled();
}
return SpellCheckConfiguration(
spellCheckService: spellCheckService ?? this.spellCheckService,
misspelledSelectionColor:
misspelledSelectionColor ?? this.misspelledSelectionColor,
misspelledTextStyle: misspelledTextStyle ?? this.misspelledTextStyle,
spellCheckSuggestionsToolbarBuilder:
spellCheckSuggestionsToolbarBuilder ??
this.spellCheckSuggestionsToolbarBuilder,
);
}
@override
String toString() {
return '${objectRuntimeType(this, 'SpellCheckConfiguration')}('
'${_spellCheckEnabled ? 'enabled' : 'disabled'}, '
'service: $spellCheckService, '
'text style: $misspelledTextStyle, '
'toolbar builder: $spellCheckSuggestionsToolbarBuilder'
')';
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is SpellCheckConfiguration &&
other.spellCheckService == spellCheckService &&
other.misspelledTextStyle == misspelledTextStyle &&
other.spellCheckSuggestionsToolbarBuilder ==
spellCheckSuggestionsToolbarBuilder &&
other._spellCheckEnabled == _spellCheckEnabled;
}
@override
int get hashCode => Object.hash(
spellCheckService,
misspelledTextStyle,
spellCheckSuggestionsToolbarBuilder,
_spellCheckEnabled,
);
}

View File

@@ -2,9 +2,11 @@
// 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/adaptive_text_selection_toolbar.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/material.dart'
hide EditableText, EditableTextState, AdaptiveTextSelectionToolbar;
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'
show SelectionChangedCause, SuggestionSpan;

View File

@@ -0,0 +1,211 @@
// 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.
/// @docImport 'package:flutter/material.dart';
library;
import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide EditableText, EditableTextState;
import 'package:flutter/services.dart';
/// Displays the system context menu on top of the Flutter view.
///
/// Currently, only supports iOS 16.0 and above and displays nothing on other
/// platforms.
///
/// The context menu is the menu that appears, for example, when doing text
/// selection. Flutter typically draws this menu itself, but this class deals
/// with the platform-rendered context menu instead.
///
/// There can only be one system context menu visible at a time. Building this
/// widget when the system context menu is already visible will hide the old one
/// and display this one. A system context menu that is hidden is informed via
/// [onSystemHide].
///
/// Pass [items] to specify the buttons that will appear in the menu. Any items
/// without a title will be given a default title from [WidgetsLocalizations].
///
/// By default, [items] will be set to the result of [getDefaultItems]. This
/// method considers the state of the [EditableTextState] so that, for example,
/// it will only include [IOSSystemContextMenuItemCopy] if there is currently a
/// selection to copy.
///
/// To check if the current device supports showing the system context menu,
/// call [isSupported].
///
/// {@tool dartpad}
/// This example shows how to create a [TextField] that uses the system context
/// menu where supported and does not show a system notification when the user
/// presses the "Paste" button.
///
/// ** See code in examples/api/lib/widgets/system_context_menu/system_context_menu.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [SystemContextMenuController], which directly controls the hiding and
/// showing of the system context menu.
class SystemContextMenu extends StatefulWidget {
/// Creates an instance of [SystemContextMenu] that points to the given
/// [anchor].
const SystemContextMenu._({
super.key,
required this.anchor,
required this.items,
this.onSystemHide,
});
/// Creates an instance of [SystemContextMenu] for the field indicated by the
/// given [EditableTextState].
factory SystemContextMenu.editableText({
Key? key,
required EditableTextState editableTextState,
List<IOSSystemContextMenuItem>? items,
}) {
final (
startGlyphHeight: double startGlyphHeight,
endGlyphHeight: double endGlyphHeight,
) = editableTextState
.getGlyphHeights();
return SystemContextMenu._(
key: key,
anchor: TextSelectionToolbarAnchors.getSelectionRect(
editableTextState.renderEditable,
startGlyphHeight,
endGlyphHeight,
editableTextState.renderEditable.getEndpointsForSelection(
editableTextState.textEditingValue.selection,
),
),
items: items ?? getDefaultItems(editableTextState),
onSystemHide: () => editableTextState.hideToolbar(false),
);
}
/// The [Rect] that the context menu should point to.
final Rect anchor;
/// A list of the items to be displayed in the system context menu.
///
/// When passed, items will be shown regardless of the state of text input.
/// For example, [IOSSystemContextMenuItemCopy] will produce a copy button
/// even when there is no selection to copy. Use [EditableTextState] and/or
/// the result of [getDefaultItems] to add and remove items based on the state
/// of the input.
///
/// Defaults to the result of [getDefaultItems].
///
/// To add custom menu items, pass [IOSSystemContextMenuItemCustom] instances
/// in the [items] list. Each custom item requires a title and an onPressed callback.
///
/// See also:
///
/// * [IOSSystemContextMenuItemCustom], which creates custom menu items.
final List<IOSSystemContextMenuItem> items;
/// Called when the system hides this context menu.
///
/// For example, tapping outside of the context menu typically causes the
/// system to hide the menu.
///
/// This is not called when showing a new system context menu causes another
/// to be hidden.
final VoidCallback? onSystemHide;
/// Whether the current device supports showing the system context menu.
///
/// Currently, this is only supported on newer versions of iOS.
///
/// See also:
///
/// * [isSupportedByField], which uses this method and determines whether an
/// individual [EditableTextState] supports the system context menu.
static bool isSupported(BuildContext context) {
return defaultTargetPlatform == TargetPlatform.iOS &&
(MediaQuery.maybeSupportsShowingSystemContextMenu(context) ?? false);
}
/// Whether the given field supports showing the system context menu.
///
/// Currently [SystemContextMenu] is only supported with an active
/// [TextInputConnection]. In cases where this isn't possible, such as in a
/// read-only field, fall back to using a Flutter-rendered context menu like
/// [AdaptiveTextSelectionToolbar].
///
/// See also:
///
/// * [isSupported], which is used by this method and determines whether the
/// platform in general supports showing the system context menu.
static bool isSupportedByField(EditableTextState editableTextState) {
return !editableTextState.widget.readOnly &&
isSupported(editableTextState.context);
}
/// The default [items] for the given [EditableTextState].
///
/// For example, [IOSSystemContextMenuItemCopy] will only be included when the
/// field represented by the [EditableTextState] has a selection.
///
/// See also:
///
/// * [EditableTextState.contextMenuButtonItems], which provides the default
/// [ContextMenuButtonItem]s for the Flutter-rendered context menu.
static List<IOSSystemContextMenuItem> getDefaultItems(
EditableTextState editableTextState,
) {
return <IOSSystemContextMenuItem>[
if (editableTextState.copyEnabled) const IOSSystemContextMenuItemCopy(),
if (editableTextState.cutEnabled) const IOSSystemContextMenuItemCut(),
if (editableTextState.pasteEnabled) const IOSSystemContextMenuItemPaste(),
if (editableTextState.selectAllEnabled)
const IOSSystemContextMenuItemSelectAll(),
if (editableTextState.lookUpEnabled)
const IOSSystemContextMenuItemLookUp(),
if (editableTextState.searchWebEnabled)
const IOSSystemContextMenuItemSearchWeb(),
if (editableTextState.liveTextInputEnabled)
const IOSSystemContextMenuItemLiveText(),
];
}
@override
State<SystemContextMenu> createState() => _SystemContextMenuState();
}
class _SystemContextMenuState extends State<SystemContextMenu> {
late final SystemContextMenuController _systemContextMenuController;
@override
void initState() {
super.initState();
_systemContextMenuController = SystemContextMenuController(
onSystemHide: widget.onSystemHide,
);
}
@override
void dispose() {
_systemContextMenuController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
assert(SystemContextMenu.isSupported(context));
if (widget.items.isNotEmpty) {
final WidgetsLocalizations localizations = WidgetsLocalizations.of(
context,
);
final List<IOSSystemContextMenuItemData> itemDatas = widget.items
.map((IOSSystemContextMenuItem item) => item.getData(localizations))
.toList();
_systemContextMenuController.showWithItems(widget.anchor, itemDatas);
}
return const SizedBox.shrink();
}
}

View File

@@ -13,75 +13,44 @@ 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/spell_check_suggestions_toolbar.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/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,
EditableTextState,
CupertinoSpellCheckSuggestionsToolbar,
SystemContextMenu,
SpellCheckConfiguration,
EditableTextContextMenuBuilder,
buildTextSpanWithSpellCheckSuggestions,
SystemContextMenu,
CupertinoSpellCheckSuggestionsToolbar,
SpellCheckConfiguration,
CupertinoTextField,
TextSelectionGestureDetectorBuilderDelegate,
TextSelectionGestureDetectorBuilder,
TextSelectionOverlay;
TextSelectionOverlay,
TextSelectionGestureDetectorBuilderDelegate;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'
hide
EditableText,
EditableTextState,
SpellCheckSuggestionsToolbar,
EditableTextContextMenuBuilder,
AdaptiveTextSelectionToolbar,
SystemContextMenu,
SpellCheckSuggestionsToolbar,
SpellCheckConfiguration,
EditableTextContextMenuBuilder,
buildTextSpanWithSpellCheckSuggestions,
TextSelectionGestureDetectorBuilderDelegate,
TextSelectionGestureDetectorBuilder,
TextSelectionOverlay;
TextSelectionOverlay,
TextSelectionGestureDetectorBuilderDelegate;
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
export 'package:flutter/services.dart'
show
SmartDashesType,
SmartQuotesType,
TextCapitalization,
TextInputAction,
TextInputType;
// Examples can assume:
// late BuildContext context;
// late FocusNode myFocusNode;
/// Signature for the [RichTextField.buildCounter] callback.
typedef InputCounterWidgetBuilder =
Widget? Function(
/// The build context for the TextField.
BuildContext context, {
/// The length of the string currently in the input.
required int currentLength,
/// The maximum string length that can be entered into the TextField.
required int? maxLength,
/// Whether or not the TextField is currently focused. Mainly provided for
/// the [liveRegion] parameter in the [Semantics] widget for accessibility.
required bool isFocused,
});
class _TextFieldSelectionGestureDetectorBuilder
extends TextSelectionGestureDetectorBuilder {
_TextFieldSelectionGestureDetectorBuilder({
@@ -209,6 +178,14 @@ class _TextFieldSelectionGestureDetectorBuilder
/// [RichTextField] to ensure proper scroll coordination for [RichTextField] and its
/// components like [TextSelectionOverlay].
///
/// {@tool dartpad}
/// This sample demonstrates how to use the [Shortcuts] and [Actions] widgets
/// to create a custom `Shift+Enter` keyboard shortcut for inserting a new line
/// in a [RichTextField].
///
/// ** See code in examples/api/lib/material/text_field/text_field.3.dart **
/// {@end-tool}
///
/// See also:
///
/// * [TextFormField], which integrates with the [Form] widget.
@@ -262,7 +239,8 @@ class RichTextField extends StatefulWidget {
///
/// The [selectionHeightStyle] and [selectionWidthStyle] properties allow
/// changing the shape of the selection highlighting. These properties default
/// to [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight], respectively.
/// to [EditableText.defaultSelectionHeightStyle] and
/// [EditableText.defaultSelectionHeightStyle], respectively.
///
/// See also:
///
@@ -293,7 +271,7 @@ class RichTextField extends StatefulWidget {
this.statesController,
this.obscuringCharacter = '',
this.obscureText = false,
this.autocorrect = true,
this.autocorrect,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
this.enableSuggestions = true,
@@ -315,12 +293,13 @@ class RichTextField extends StatefulWidget {
this.cursorOpacityAnimates,
this.cursorColor,
this.cursorErrorColor,
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
this.selectionHeightStyle,
this.selectionWidthStyle,
this.keyboardAppearance,
this.scrollPadding = const EdgeInsets.all(20.0),
this.dragStartBehavior = DragStartBehavior.start,
bool? enableInteractiveSelection,
this.selectAllOnFocus,
this.selectionControls,
this.onTap,
this.onTapAlwaysCalled = false,
@@ -346,6 +325,7 @@ class RichTextField extends StatefulWidget {
this.canRequestFocus = true,
this.spellCheckConfiguration,
this.magnifierConfiguration,
this.hintLocales,
}) : assert(obscuringCharacter.length == 1),
smartDashesType =
smartDashesType ??
@@ -526,7 +506,7 @@ class RichTextField extends StatefulWidget {
final bool obscureText;
/// {@macro flutter.widgets.editableText.autocorrect}
final bool autocorrect;
final bool? autocorrect;
/// {@macro flutter.services.TextInputConfiguration.smartDashesType}
final SmartDashesType smartDashesType;
@@ -578,6 +558,8 @@ class RichTextField extends StatefulWidget {
/// field showing how many characters have been entered. If set to a number
/// greater than 0, it will also display the maximum number allowed. If set
/// to [RichTextField.noMaxLength] then only the current character count is displayed.
/// To remove the counter, set [InputDecoration.counterText] to an empty string or
/// return null from [RichTextField.buildCounter] callback.
///
/// After [maxLength] characters have been input, additional input
/// is ignored, unless [maxLengthEnforcement] is set to
@@ -642,6 +624,27 @@ class RichTextField extends StatefulWidget {
///
/// If non-null this property overrides the [decoration]'s
/// [InputDecoration.enabled] property.
///
/// When a text field is disabled, all of its children widgets are also
/// disabled, including the [InputDecoration.suffixIcon]. If you need to keep
/// the suffix icon interactive while disabling the text field, consider using
/// [readOnly] and [enableInteractiveSelection] instead:
///
/// ```dart
/// TextField(
/// enabled: true,
/// readOnly: true,
/// enableInteractiveSelection: false,
/// decoration: InputDecoration(
/// suffixIcon: IconButton(
/// onPressed: () {
/// // This will work because the TextField is enabled
/// },
/// icon: const Icon(Icons.edit_outlined),
/// ),
/// ),
/// )
/// ```
final bool? enabled;
/// Determines whether this widget ignores pointer events.
@@ -683,12 +686,12 @@ class RichTextField extends StatefulWidget {
/// Controls how tall the selection highlight boxes are computed to be.
///
/// See [ui.BoxHeightStyle] for details on available styles.
final ui.BoxHeightStyle selectionHeightStyle;
final ui.BoxHeightStyle? selectionHeightStyle;
/// Controls how wide the selection highlight boxes are computed to be.
///
/// See [ui.BoxWidthStyle] for details on available styles.
final ui.BoxWidthStyle selectionWidthStyle;
final ui.BoxWidthStyle? selectionWidthStyle;
/// The appearance of the keyboard.
///
@@ -703,6 +706,9 @@ class RichTextField extends StatefulWidget {
/// {@macro flutter.widgets.editableText.enableInteractiveSelection}
final bool enableInteractiveSelection;
/// {@macro flutter.widgets.editableText.selectAllOnFocus}
final bool? selectAllOnFocus;
/// {@macro flutter.widgets.editableText.selectionControls}
final TextSelectionControls? selectionControls;
@@ -837,7 +843,7 @@ class RichTextField extends StatefulWidget {
/// offset and - if no [controller] has been provided - the content of the
/// text field. If a [controller] has been provided, it is the responsibility
/// of the owner of that controller to persist and restore it, e.g. by using
/// a [RestorableTextEditingController].
/// a [RestorableRichTextEditingController].
///
/// The state of this widget is persisted in a [RestorationBucket] claimed
/// from the surrounding [RestorationScope] using the provided restoration ID.
@@ -883,12 +889,14 @@ class RichTextField extends StatefulWidget {
/// be possible to move the focus to the text field with tab key.
final bool canRequestFocus;
/// {@macro flutter.services.TextInputConfiguration.hintLocales}
final List<Locale>? hintLocales;
static Widget _defaultContextMenuBuilder(
BuildContext context,
EditableTextState editableTextState,
) {
if (defaultTargetPlatform == TargetPlatform.iOS &&
SystemContextMenu.isSupported(context)) {
if (SystemContextMenu.isSupportedByField(editableTextState)) {
return SystemContextMenu.editableText(
editableTextState: editableTextState,
);
@@ -991,7 +999,9 @@ class RichTextField extends StatefulWidget {
defaultValue: null,
),
)
..add(DiagnosticsProperty<bool>('enabled', enabled, defaultValue: null))
..add(
DiagnosticsProperty<bool>('enabled', enabled, defaultValue: null),
)
..add(
DiagnosticsProperty<InputDecoration>(
'decoration',
@@ -1006,7 +1016,9 @@ class RichTextField extends StatefulWidget {
defaultValue: TextInputType.text,
),
)
..add(DiagnosticsProperty<TextStyle>('style', style, defaultValue: null))
..add(
DiagnosticsProperty<TextStyle>('style', style, defaultValue: null),
)
..add(
DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false),
)
@@ -1028,7 +1040,7 @@ class RichTextField extends StatefulWidget {
DiagnosticsProperty<bool>(
'autocorrect',
autocorrect,
defaultValue: true,
defaultValue: null,
),
)
..add(
@@ -1058,7 +1070,9 @@ class RichTextField extends StatefulWidget {
)
..add(IntProperty('maxLines', maxLines, defaultValue: 1))
..add(IntProperty('minLines', minLines, defaultValue: null))
..add(DiagnosticsProperty<bool>('expands', expands, defaultValue: false))
..add(
DiagnosticsProperty<bool>('expands', expands, defaultValue: false),
)
..add(IntProperty('maxLength', maxLength, defaultValue: null))
..add(
EnumProperty<MaxLengthEnforcement>(
@@ -1102,8 +1116,12 @@ class RichTextField extends StatefulWidget {
defaultValue: null,
),
)
..add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0))
..add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null))
..add(
DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0),
)
..add(
DoubleProperty('cursorHeight', cursorHeight, defaultValue: null),
)
..add(
DiagnosticsProperty<Radius>(
'cursorRadius',
@@ -1118,7 +1136,9 @@ class RichTextField extends StatefulWidget {
defaultValue: null,
),
)
..add(ColorProperty('cursorColor', cursorColor, defaultValue: null))
..add(
ColorProperty('cursorColor', cursorColor, defaultValue: null),
)
..add(
ColorProperty('cursorErrorColor', cursorErrorColor, defaultValue: null),
)
@@ -1208,6 +1228,13 @@ class RichTextField extends StatefulWidget {
? const <String>[]
: kDefaultContentInsertionMimeTypes,
),
)
..add(
DiagnosticsProperty<List<Locale>?>(
'hintLocales',
hintLocales,
defaultValue: null,
),
);
}
}
@@ -1215,9 +1242,7 @@ class RichTextField extends StatefulWidget {
class RichTextFieldState extends State<RichTextField>
with RestorationMixin
implements TextSelectionGestureDetectorBuilderDelegate, AutofillClient {
// RestorableRichTextEditingController? _controller;
RichTextEditingController get _effectiveController => widget.controller;
// widget.controller ?? _controller!.value;
FocusNode? _focusNode;
FocusNode get _effectiveFocusNode =>
@@ -1260,13 +1285,7 @@ class RichTextFieldState extends State<RichTextField>
bool get _hasIntrinsicError =>
widget.maxLength != null &&
widget.maxLength! > 0 &&
(
// widget.controller == null
// ? !restorePending &&
// _effectiveController.value.text.characters.length >
// widget.maxLength!
// :
_effectiveController.value.text.characters.length > widget.maxLength!);
_effectiveController.value.text.characters.length > widget.maxLength!;
bool get _hasError =>
widget.decoration?.errorText != null ||
@@ -1288,7 +1307,10 @@ class RichTextFieldState extends State<RichTextField>
.applyDefaults(themeData.inputDecorationTheme)
.copyWith(
enabled: _isEnabled,
hintMaxLines: widget.decoration?.hintMaxLines ?? widget.maxLines,
hintMaxLines:
widget.decoration?.hintMaxLines ??
themeData.inputDecorationTheme.hintMaxLines ??
widget.maxLines,
);
// No need to build anything if counter or counterText were given directly.
@@ -1368,9 +1390,6 @@ class RichTextFieldState extends State<RichTextField>
state: this,
controller: widget.controller,
);
// if (widget.controller == null) {
// _createLocalController();
// }
_effectiveFocusNode.canRequestFocus = widget.canRequestFocus && _isEnabled;
_effectiveFocusNode.addListener(_handleFocusChanged);
_initStatesController();
@@ -1394,13 +1413,6 @@ class RichTextFieldState extends State<RichTextField>
@override
void didUpdateWidget(RichTextField oldWidget) {
super.didUpdateWidget(oldWidget);
// if (widget.controller == null && oldWidget.controller != null) {
// _createLocalController(oldWidget.controller!.value);
// } else if (widget.controller != null && oldWidget.controller == null) {
// unregisterFromRestoration(_controller!);
// _controller!.dispose();
// _controller = null;
// }
if (widget.focusNode != oldWidget.focusNode) {
(oldWidget.focusNode ?? _focusNode)?.removeListener(_handleFocusChanged);
@@ -1434,26 +1446,7 @@ class RichTextFieldState extends State<RichTextField>
}
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
// if (_controller != null) {
// _registerController();
// }
}
// void _registerController() {
// assert(_controller != null);
// registerForRestoration(_controller!, 'controller');
// }
// void _createLocalController([TextEditingValue? value]) {
// assert(_controller == null);
// _controller = value == null
// ? RestorableRichTextEditingController()
// : RestorableRichTextEditingController.fromValue(value);
// if (!restorePending) {
// _registerController();
// }
// }
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {}
@override
String? get restorationId => widget.restorationId;
@@ -1462,7 +1455,6 @@ class RichTextFieldState extends State<RichTextField>
void dispose() {
_effectiveFocusNode.removeListener(_handleFocusChanged);
_focusNode?.dispose();
// _controller?.dispose();
_statesController.removeListener(_handleStatesControllerChange);
_internalStatesController?.dispose();
super.dispose();
@@ -1476,8 +1468,9 @@ class RichTextFieldState extends State<RichTextField>
bool _shouldShowSelectionHandles(SelectionChangedCause? cause) {
// When the text field is activated by something that doesn't trigger the
// selection overlay, we shouldn't show the handles either.
if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) {
// selection toolbar, we shouldn't show the handles either.
if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar ||
!_selectionGestureDetectorBuilder.shouldShowSelectionHandles) {
return false;
}
@@ -1570,7 +1563,7 @@ class RichTextFieldState extends State<RichTextField>
WidgetStatesController? _internalStatesController;
void _handleStatesControllerChange() {
// Force a rebuild to resolve MaterialStateProperty properties.
// Force a rebuild to resolve WidgetStateProperty properties.
setState(() {});
}
@@ -1581,11 +1574,12 @@ class RichTextFieldState extends State<RichTextField>
if (widget.statesController == null) {
_internalStatesController = WidgetStatesController();
}
_statesController.update(WidgetState.disabled, !_isEnabled);
_statesController.update(WidgetState.hovered, _isHovering);
_statesController.update(WidgetState.focused, _effectiveFocusNode.hasFocus);
_statesController.update(WidgetState.error, _hasError);
_statesController.addListener(_handleStatesControllerChange);
_statesController
..update(WidgetState.disabled, !_isEnabled)
..update(WidgetState.hovered, _isHovering)
..update(WidgetState.focused, _effectiveFocusNode.hasFocus)
..update(WidgetState.error, _hasError)
..addListener(_handleStatesControllerChange);
}
// AutofillClient implementation start.
@@ -1716,7 +1710,7 @@ class RichTextFieldState extends State<RichTextField>
cupertinoTheme.primaryColor;
selectionColor =
selectionStyle.selectionColor ??
cupertinoTheme.primaryColor.withOpacity(0.40);
cupertinoTheme.primaryColor.withValues(alpha: 0.40);
cursorRadius ??= const Radius.circular(2.0);
cursorOffset = Offset(
iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context),
@@ -1737,7 +1731,7 @@ class RichTextFieldState extends State<RichTextField>
cupertinoTheme.primaryColor;
selectionColor =
selectionStyle.selectionColor ??
cupertinoTheme.primaryColor.withOpacity(0.40);
cupertinoTheme.primaryColor.withValues(alpha: 0.40);
cursorRadius ??= const Radius.circular(2.0);
cursorOffset = Offset(
iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context),
@@ -1767,7 +1761,7 @@ class RichTextFieldState extends State<RichTextField>
theme.colorScheme.primary;
selectionColor =
selectionStyle.selectionColor ??
theme.colorScheme.primary.withOpacity(0.40);
theme.colorScheme.primary.withValues(alpha: 0.40);
case TargetPlatform.linux:
forcePressEnabled = false;
@@ -1781,7 +1775,7 @@ class RichTextFieldState extends State<RichTextField>
theme.colorScheme.primary;
selectionColor =
selectionStyle.selectionColor ??
theme.colorScheme.primary.withOpacity(0.40);
theme.colorScheme.primary.withValues(alpha: 0.40);
handleDidGainAccessibilityFocus = () {
// Automatically activate the TextField when it receives accessibility focus.
if (!_effectiveFocusNode.hasFocus &&
@@ -1805,7 +1799,7 @@ class RichTextFieldState extends State<RichTextField>
theme.colorScheme.primary;
selectionColor =
selectionStyle.selectionColor ??
theme.colorScheme.primary.withOpacity(0.40);
theme.colorScheme.primary.withValues(alpha: 0.40);
handleDidGainAccessibilityFocus = () {
// Automatically activate the TextField when it receives accessibility focus.
if (!_effectiveFocusNode.hasFocus &&
@@ -1876,9 +1870,11 @@ class RichTextFieldState extends State<RichTextField>
scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance,
enableInteractiveSelection: widget.enableInteractiveSelection,
selectAllOnFocus: widget.selectAllOnFocus,
dragStartBehavior: widget.dragStartBehavior,
scrollController: widget.scrollController,
scrollPhysics: widget.scrollPhysics,
autofillHints: widget.autofillHints,
autofillClient: this,
autocorrectionTextRectColor: autocorrectionTextRectColor,
clipBehavior: widget.clipBehavior,
@@ -1892,6 +1888,7 @@ class RichTextFieldState extends State<RichTextField>
magnifierConfiguration:
widget.magnifierConfiguration ??
TextMagnifier.adaptiveMagnifierConfiguration,
hintLocales: widget.hintLocales,
),
),
);
@@ -2026,26 +2023,17 @@ TextStyle _m2CounterErrorStyle(BuildContext context) => Theme.of(
// dev/tools/gen_defaults/bin/gen_defaults.dart.
// dart format off
TextStyle? _m3StateInputStyle(BuildContext context) =>
WidgetStateTextStyle.resolveWith((Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return TextStyle(
color: Theme.of(context)
.textTheme
.bodyLarge!
.color
?.withOpacity(0.38));
}
return TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color);
});
TextStyle? _m3StateInputStyle(BuildContext context) => WidgetStateTextStyle.resolveWith((Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color?.withValues(alpha:0.38));
}
return TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color);
});
TextStyle _m3InputStyle(BuildContext context) =>
Theme.of(context).textTheme.bodyLarge!;
TextStyle _m3InputStyle(BuildContext context) => Theme.of(context).textTheme.bodyLarge!;
TextStyle _m3CounterErrorStyle(BuildContext context) => Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Theme.of(context).colorScheme.error);
TextStyle _m3CounterErrorStyle(BuildContext context) =>
Theme.of(context).textTheme.bodySmall!.copyWith(color: Theme.of(context).colorScheme.error);
// dart format on
// END GENERATED TOKEN PROPERTIES - TextField

View File

@@ -1,16 +1,37 @@
import 'dart:math' as math;
import 'dart:ui';
// 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 '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';
/// @docImport 'package:flutter/cupertino.dart';
/// @docImport 'package:flutter/material.dart';
library;
import 'dart:math' as math;
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;
import 'package:flutter/material.dart' hide EditableText, EditableTextState;
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart' hide EditableText, EditableTextState;
/// Delegate interface for the [TextSelectionGestureDetectorBuilder].
///
/// The interface is usually implemented by the [State] of text field
/// implementations wrapping [EditableText], so that they can use a
/// [TextSelectionGestureDetectorBuilder] to build a
/// [TextSelectionGestureDetector] for their [EditableText]. The delegate
/// provides the builder with information about the current state of the text
/// field. Based on that information, the builder adds the correct gesture
/// handlers to the gesture detector.
///
/// See also:
///
/// * [TextField], which implements this delegate for the Material text field.
/// * [CupertinoTextField], which implements this delegate for the Cupertino
/// text field.
abstract class TextSelectionGestureDetectorBuilderDelegate {
/// [GlobalKey] to the [EditableText] for which the
/// [TextSelectionGestureDetectorBuilder] will build a [TextSelectionGestureDetector].
@@ -83,6 +104,10 @@ class TextSelectionGestureDetectorBuilder {
// Hides the magnifier on supported platforms, currently only Android and iOS.
void _hideMagnifierIfSupportedByPlatform() {
if (!_isEditableTextMounted) {
return;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.iOS:
@@ -181,6 +206,7 @@ class TextSelectionGestureDetectorBuilder {
offset,
);
final TextSelection selection = renderEditable.selection!;
// bggRGjQaUbCoE on select
final TextSelection nextSelection = selection.copyWith(
extentOffset: controller.tapOffsetSimple(tappedPosition.offset),
);
@@ -193,12 +219,23 @@ class TextSelectionGestureDetectorBuilder {
/// Whether to show the selection toolbar.
///
/// It is based on the signal source when a [onTapDown] is called. This getter
/// will return true if current [onTapDown] event is triggered by a touch or
/// a stylus.
/// It is based on the signal source when [onTapDown], [onSecondaryTapDown],
/// [onDragSelectionStart], or [onForcePressStart] is called. This getter
/// will return true if the current [onTapDown], or [onDragSelectionStart] event
/// is triggered by a touch or a stylus. It will always return true for the
/// current [onSecondaryTapDown] or [onForcePressStart] event.
bool get shouldShowSelectionToolbar => _shouldShowSelectionToolbar;
bool _shouldShowSelectionToolbar = true;
/// Whether to show the selection handles.
///
/// It is based on the signal source when [onTapDown], [onSecondaryTapDown],
/// [onDragSelectionStart], is called. This getter will return true if the
/// current [onTapDown], [onSecondaryTapDown], or [onDragSelectionStart] event
/// is triggered by a touch or a stylus.
bool get shouldShowSelectionHandles => _shouldShowSelectionHandles;
bool _shouldShowSelectionHandles = true;
/// The [State] of the [EditableText] for which the builder will provide a
/// [TextSelectionGestureDetector].
@protected
@@ -209,6 +246,13 @@ class TextSelectionGestureDetectorBuilder {
@protected
RenderEditable get renderEditable => editableText.renderEditable;
/// Returns `true` if a widget with the global key [delegate.editableTextKey]
/// is in the tree and the widget is mounted.
///
/// Otherwise returns `false`.
bool get _isEditableTextMounted =>
delegate.editableTextKey.currentContext?.mounted ?? false;
/// Whether the Shift key was pressed when the most recent [PointerDownEvent]
/// was tracked by the [BaseTapAndDragGestureRecognizer].
bool _isShiftPressed = false;
@@ -311,6 +355,7 @@ class TextSelectionGestureDetectorBuilder {
kind == null ||
kind == PointerDeviceKind.touch ||
kind == PointerDeviceKind.stylus;
_shouldShowSelectionHandles = _shouldShowSelectionToolbar;
// It is impossible to extend the selection when the shift key is pressed, if the
// renderEditable.selection is invalid.
@@ -504,6 +549,7 @@ class TextSelectionGestureDetectorBuilder {
// Precise devices should place the cursor at a precise position if the
// word at the text position is not misspelled.
renderEditable.selectPosition(cause: SelectionChangedCause.tap);
editableText.hideToolbar();
case PointerDeviceKind.touch:
case PointerDeviceKind.unknown:
// If the word that was tapped is misspelled, select the word and show the spell check suggestions
@@ -719,22 +765,23 @@ class TextSelectionGestureDetectorBuilder {
/// callback.
@protected
void onSingleLongTapEnd(LongPressEndDetails details) {
_hideMagnifierIfSupportedByPlatform();
_onSingleLongTapEndOrCancel();
if (shouldShowSelectionToolbar) {
editableText.showToolbar();
}
_longPressStartedWithoutFocus = false;
_dragStartViewportOffset = 0.0;
_dragStartScrollOffset = 0.0;
if (defaultTargetPlatform == TargetPlatform.iOS &&
delegate.selectionEnabled &&
editableText.textEditingValue.selection.isCollapsed) {
// Update the floating cursor.
final RawFloatingCursorPoint cursorPoint = RawFloatingCursorPoint(
state: FloatingCursorDragState.End,
);
editableText.updateFloatingCursor(cursorPoint);
}
}
/// Handler for [TextSelectionGestureDetector.onSingleLongTapCancel].
///
/// By default, it hides the magnifier and the floating cursor if necessary.
///
/// See also:
///
/// * [TextSelectionGestureDetector.onSingleLongTapCancel], which triggers
/// this callback.
@protected
void onSingleLongTapCancel() {
_onSingleLongTapEndOrCancel();
}
/// Handler for [TextSelectionGestureDetector.onSecondaryTap].
@@ -785,6 +832,10 @@ class TextSelectionGestureDetectorBuilder {
TapDownDetails(globalPosition: details.globalPosition),
);
_shouldShowSelectionToolbar = true;
_shouldShowSelectionHandles =
details.kind == null ||
details.kind == PointerDeviceKind.touch ||
details.kind == PointerDeviceKind.stylus;
}
/// Handler for [TextSelectionGestureDetector.onDoubleTapDown].
@@ -806,6 +857,23 @@ class TextSelectionGestureDetectorBuilder {
}
}
void _onSingleLongTapEndOrCancel() {
_hideMagnifierIfSupportedByPlatform();
_longPressStartedWithoutFocus = false;
_dragStartViewportOffset = 0.0;
_dragStartScrollOffset = 0.0;
if (_isEditableTextMounted &&
defaultTargetPlatform == TargetPlatform.iOS &&
delegate.selectionEnabled &&
editableText.textEditingValue.selection.isCollapsed) {
// Update the floating cursor.
final RawFloatingCursorPoint cursorPoint = RawFloatingCursorPoint(
state: FloatingCursorDragState.End,
);
editableText.updateFloatingCursor(cursorPoint);
}
}
// Selects the set of paragraphs in a document that intersect a given range of
// global positions.
void _selectParagraphsInRange({
@@ -954,6 +1022,7 @@ class TextSelectionGestureDetectorBuilder {
kind == null ||
kind == PointerDeviceKind.touch ||
kind == PointerDeviceKind.stylus;
_shouldShowSelectionHandles = _shouldShowSelectionToolbar;
_dragStartSelection = renderEditable.selection;
_dragStartScrollOffset = _scrollPosition;
@@ -1300,6 +1369,7 @@ class TextSelectionGestureDetectorBuilder {
onSingleLongTapStart: onSingleLongTapStart,
onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate,
onSingleLongTapEnd: onSingleLongTapEnd,
onSingleLongTapCancel: onSingleLongTapCancel,
onDoubleTapDown: onDoubleTapDown,
onTripleTapDown: onTripleTapDown,
onDragSelectionStart: onDragSelectionStart,
@@ -1312,6 +1382,149 @@ class TextSelectionGestureDetectorBuilder {
}
}
/// A gesture detector to respond to non-exclusive event chains for a text field.
///
/// An ordinary [GestureDetector] configured to handle events like tap and
/// double tap will only recognize one or the other. This widget detects both:
/// the first tap and then any subsequent taps that occurs within a time limit
/// after the first.
///
/// See also:
///
/// * [TextField], a Material text field which uses this gesture detector.
/// * [CupertinoTextField], a Cupertino text field which uses this gesture
/// detector.
class TextSelectionGestureDetector extends StatefulWidget {
/// Create a [TextSelectionGestureDetector].
///
/// Multiple callbacks can be called for one sequence of input gesture.
const TextSelectionGestureDetector({
super.key,
this.onTapTrackStart,
this.onTapTrackReset,
this.onTapDown,
this.onForcePressStart,
this.onForcePressEnd,
this.onSecondaryTap,
this.onSecondaryTapDown,
this.onSingleTapUp,
this.onSingleTapCancel,
this.onUserTap,
this.onSingleLongTapStart,
this.onSingleLongTapMoveUpdate,
this.onSingleLongTapEnd,
this.onSingleLongTapCancel,
this.onDoubleTapDown,
this.onTripleTapDown,
this.onDragSelectionStart,
this.onDragSelectionUpdate,
this.onDragSelectionEnd,
this.onUserTapAlwaysCalled = false,
this.behavior,
required this.child,
});
/// {@template flutter.gestures.selectionrecognizers.TextSelectionGestureDetector.onTapTrackStart}
/// Callback used to indicate that a tap tracking has started upon
/// a [PointerDownEvent].
/// {@endtemplate}
final VoidCallback? onTapTrackStart;
/// {@template flutter.gestures.selectionrecognizers.TextSelectionGestureDetector.onTapTrackReset}
/// Callback used to indicate that a tap tracking has been reset which
/// happens on the next [PointerDownEvent] after the timer between two taps
/// elapses, the recognizer loses the arena, the gesture is cancelled or
/// the recognizer is disposed of.
/// {@endtemplate}
final VoidCallback? onTapTrackReset;
/// Called for every tap down including every tap down that's part of a
/// double click or a long press, except touches that include enough movement
/// to not qualify as taps (e.g. pans and flings).
final GestureTapDragDownCallback? onTapDown;
/// Called when a pointer has tapped down and the force of the pointer has
/// just become greater than [ForcePressGestureRecognizer.startPressure].
final GestureForcePressStartCallback? onForcePressStart;
/// Called when a pointer that had previously triggered [onForcePressStart] is
/// lifted off the screen.
final GestureForcePressEndCallback? onForcePressEnd;
/// Called for a tap event with the secondary mouse button.
final GestureTapCallback? onSecondaryTap;
/// Called for a tap down event with the secondary mouse button.
final GestureTapDownCallback? onSecondaryTapDown;
/// Called for the first tap in a series of taps, consecutive taps do not call
/// this method.
///
/// For example, if the detector was configured with [onTapDown] and
/// [onDoubleTapDown], three quick taps would be recognized as a single tap
/// down, followed by a tap up, then a double tap down, followed by a single tap down.
final GestureTapDragUpCallback? onSingleTapUp;
/// Called for each touch that becomes recognized as a gesture that is not a
/// short tap, such as a long tap or drag. It is called at the moment when
/// another gesture from the touch is recognized.
final GestureCancelCallback? onSingleTapCancel;
/// Called for the first tap in a series of taps when [onUserTapAlwaysCalled] is
/// disabled, which is the default behavior.
///
/// When [onUserTapAlwaysCalled] is enabled, this is called for every tap,
/// including consecutive taps.
final GestureTapCallback? onUserTap;
/// Called for a single long tap that's sustained for longer than
/// [kLongPressTimeout] but not necessarily lifted. Not called for a
/// double-tap-hold, which calls [onDoubleTapDown] instead.
final GestureLongPressStartCallback? onSingleLongTapStart;
/// Called after [onSingleLongTapStart] when the pointer is dragged.
final GestureLongPressMoveUpdateCallback? onSingleLongTapMoveUpdate;
/// Called after [onSingleLongTapStart] when the pointer is lifted.
final GestureLongPressEndCallback? onSingleLongTapEnd;
/// Called after [onSingleLongTapStart] when the pointer is canceled.
final GestureLongPressCancelCallback? onSingleLongTapCancel;
/// Called after a momentary hold or a short tap that is close in space and
/// time (within [kDoubleTapTimeout]) to a previous short tap.
final GestureTapDragDownCallback? onDoubleTapDown;
/// Called after a momentary hold or a short tap that is close in space and
/// time (within [kDoubleTapTimeout]) to a previous double-tap.
final GestureTapDragDownCallback? onTripleTapDown;
/// Called when a mouse starts dragging to select text.
final GestureTapDragStartCallback? onDragSelectionStart;
/// Called repeatedly as a mouse moves while dragging.
final GestureTapDragUpdateCallback? onDragSelectionUpdate;
/// Called when a mouse that was previously dragging is released.
final GestureTapDragEndCallback? onDragSelectionEnd;
/// Whether [onUserTap] will be called for all taps including consecutive taps.
///
/// Defaults to false, so [onUserTap] is only called for each distinct tap.
final bool onUserTapAlwaysCalled;
/// How this gesture detector should behave during hit testing.
///
/// This defaults to [HitTestBehavior.deferToChild].
final HitTestBehavior? behavior;
/// Child below this widget.
final Widget child;
@override
State<StatefulWidget> createState() => _TextSelectionGestureDetectorState();
}
class _TextSelectionGestureDetectorState
extends State<TextSelectionGestureDetector> {
// Converts the details.consecutiveTapCount from a TapAndDrag*Details object,
@@ -1411,21 +1624,19 @@ class _TextSelectionGestureDetectorState
}
void _handleLongPressStart(LongPressStartDetails details) {
if (widget.onSingleLongTapStart != null) {
widget.onSingleLongTapStart!(details);
}
widget.onSingleLongTapStart?.call(details);
}
void _handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) {
if (widget.onSingleLongTapMoveUpdate != null) {
widget.onSingleLongTapMoveUpdate!(details);
}
widget.onSingleLongTapMoveUpdate?.call(details);
}
void _handleLongPressEnd(LongPressEndDetails details) {
if (widget.onSingleLongTapEnd != null) {
widget.onSingleLongTapEnd!(details);
}
widget.onSingleLongTapEnd?.call(details);
}
void _handleLongPressCancel() {
widget.onSingleLongTapCancel?.call();
}
@override
@@ -1445,7 +1656,8 @@ class _TextSelectionGestureDetectorState
if (widget.onSingleLongTapStart != null ||
widget.onSingleLongTapMoveUpdate != null ||
widget.onSingleLongTapEnd != null) {
widget.onSingleLongTapEnd != null ||
widget.onSingleLongTapCancel != null) {
gestures[LongPressGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer(
@@ -1456,7 +1668,8 @@ class _TextSelectionGestureDetectorState
instance
..onLongPressStart = _handleLongPressStart
..onLongPressMoveUpdate = _handleLongPressMoveUpdate
..onLongPressEnd = _handleLongPressEnd;
..onLongPressEnd = _handleLongPressEnd
..onLongPressCancel = _handleLongPressCancel;
},
);
}
@@ -1824,8 +2037,11 @@ class TextSelectionOverlay {
/// specifically is visible.
bool get toolbarIsVisible => _selectionOverlay.toolbarIsVisible;
/// Whether the magnifier is currently visible.
bool get magnifierIsVisible => _selectionOverlay._magnifierController.shown;
/// {@macro flutter.widgets.SelectionOverlay.magnifierIsVisible}
bool get magnifierIsVisible => _selectionOverlay.magnifierIsVisible;
/// {@macro flutter.widgets.SelectionOverlay.magnifierExists}
bool get magnifierExists => _selectionOverlay.magnifierExists;
/// Whether the spell check menu is currently visible.
///
@@ -1965,6 +2181,16 @@ class TextSelectionOverlay {
late double _endHandleDragTarget;
// The initial selection when a selection handle drag has started.
//
// This is used on Apple platforms to:
//
// 1. Preserve a collapsed selection: if the selection was collapsed when the drag
// began, then it should remain collapsed throughout the entire drag.
// 2. Anchor the non-dragged end of a non-collapsed selection: On Apple platforms,
// the dragged handle always defines the selection's new extent. The drag start
// selection provides the original position for the selection's new base. This
// allows the selection handles to correctly swap their logical order (invert)
// during the drag.
TextSelection? _dragStartSelection;
void _handleSelectionEndHandleDragStart(DragStartDetails details) {
@@ -1990,7 +2216,12 @@ class TextSelectionOverlay {
final TextPosition position = renderObject.getPositionForPoint(
Offset(details.globalPosition.dx, centerOfLineGlobal),
);
_dragStartSelection ??= _selection;
// The drag start selection is only utilized on Apple platforms.
if (defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS) {
_dragStartSelection ??= _selection;
}
_selectionOverlay.showMagnifier(
_buildMagnifier(
@@ -2031,7 +2262,6 @@ class TextSelectionOverlay {
if (!renderObject.attached) {
return;
}
assert(_dragStartSelection != null);
// This is NOT the same as details.localPosition. That is relative to the
// selection handle, whereas this is relative to the RenderEditable.
@@ -2059,27 +2289,27 @@ class TextSelectionOverlay {
// bggRGjQaUbCoE right drag
position = controller.dragOffset(position);
if (_dragStartSelection!.isCollapsed) {
_selectionOverlay.updateMagnifier(
_buildMagnifier(
currentTextPosition: position,
globalGesturePosition: details.globalPosition,
renderEditable: renderObject,
),
);
final TextSelection currentSelection = TextSelection.fromPosition(
position,
);
_handleSelectionHandleChanged(currentSelection);
return;
}
final TextSelection newSelection;
switch (defaultTargetPlatform) {
// On Apple platforms, dragging the base handle makes it the extent.
case TargetPlatform.iOS:
case TargetPlatform.macOS:
assert(_dragStartSelection != null);
if (_dragStartSelection!.isCollapsed) {
_selectionOverlay.updateMagnifier(
_buildMagnifier(
currentTextPosition: position,
globalGesturePosition: details.globalPosition,
renderEditable: renderObject,
),
);
final TextSelection currentSelection = TextSelection.fromPosition(
position,
);
_handleSelectionHandleChanged(currentSelection);
return;
}
// Use this instead of _dragStartSelection.isNormalized because TextRange.isNormalized
// always returns true for a TextSelection.
final bool dragStartSelectionNormalized =
@@ -2095,6 +2325,21 @@ class TextSelectionOverlay {
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
if (_selection.isCollapsed) {
_selectionOverlay.updateMagnifier(
_buildMagnifier(
currentTextPosition: position,
globalGesturePosition: details.globalPosition,
renderEditable: renderObject,
),
);
final TextSelection currentSelection = TextSelection.fromPosition(
position,
);
_handleSelectionHandleChanged(currentSelection);
return;
}
newSelection = TextSelection(
baseOffset: _selection.baseOffset,
extentOffset: position.offset,
@@ -2146,7 +2391,12 @@ class TextSelectionOverlay {
final TextPosition position = renderObject.getPositionForPoint(
Offset(details.globalPosition.dx, centerOfLineGlobal),
);
_dragStartSelection ??= _selection;
// The drag start selection is only utilized on Apple platforms.
if (defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS) {
_dragStartSelection ??= _selection;
}
_selectionOverlay.showMagnifier(
_buildMagnifier(
@@ -2161,7 +2411,6 @@ class TextSelectionOverlay {
if (!renderObject.attached) {
return;
}
assert(_dragStartSelection != null);
// This is NOT the same as details.localPosition. That is relative to the
// selection handle, whereas this is relative to the RenderEditable.
@@ -2186,27 +2435,27 @@ class TextSelectionOverlay {
// bggRGjQaUbCoE single drag, left drag
position = controller.dragOffset(position);
if (_dragStartSelection!.isCollapsed) {
_selectionOverlay.updateMagnifier(
_buildMagnifier(
currentTextPosition: position,
globalGesturePosition: details.globalPosition,
renderEditable: renderObject,
),
);
final TextSelection currentSelection = TextSelection.fromPosition(
position,
);
_handleSelectionHandleChanged(currentSelection);
return;
}
final TextSelection newSelection;
switch (defaultTargetPlatform) {
// On Apple platforms, dragging the base handle makes it the extent.
case TargetPlatform.iOS:
case TargetPlatform.macOS:
assert(_dragStartSelection != null);
if (_dragStartSelection!.isCollapsed) {
_selectionOverlay.updateMagnifier(
_buildMagnifier(
currentTextPosition: position,
globalGesturePosition: details.globalPosition,
renderEditable: renderObject,
),
);
final TextSelection currentSelection = TextSelection.fromPosition(
position,
);
_handleSelectionHandleChanged(currentSelection);
return;
}
// Use this instead of _dragStartSelection.isNormalized because TextRange.isNormalized
// always returns true for a TextSelection.
final bool dragStartSelectionNormalized =
@@ -2222,6 +2471,21 @@ class TextSelectionOverlay {
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
if (_selection.isCollapsed) {
_selectionOverlay.updateMagnifier(
_buildMagnifier(
currentTextPosition: position,
globalGesturePosition: details.globalPosition,
renderEditable: renderObject,
),
);
final TextSelection currentSelection = TextSelection.fromPosition(
position,
);
_handleSelectionHandleChanged(currentSelection);
return;
}
newSelection = TextSelection(
baseOffset: position.offset,
extentOffset: _selection.extentOffset,
@@ -2250,19 +2514,26 @@ class TextSelectionOverlay {
return;
}
_dragStartSelection = null;
final bool draggingHandles =
_selectionOverlay.isDraggingStartHandle ||
_selectionOverlay.isDraggingEndHandle;
if (selectionControls is! TextSelectionHandleControls) {
_selectionOverlay.hideMagnifier();
if (!_selection.isCollapsed) {
_selectionOverlay.showToolbar();
if (!draggingHandles) {
_selectionOverlay.hideMagnifier();
if (!_selection.isCollapsed) {
_selectionOverlay.showToolbar();
}
}
return;
}
_selectionOverlay.hideMagnifier();
if (!_selection.isCollapsed) {
_selectionOverlay.showToolbar(
context: context,
contextMenuBuilder: contextMenuBuilder,
);
if (!draggingHandles) {
_selectionOverlay.hideMagnifier();
if (!_selection.isCollapsed) {
_selectionOverlay.showToolbar(
context: context,
contextMenuBuilder: contextMenuBuilder,
);
}
}
}
@@ -2375,6 +2646,19 @@ class SelectionOverlay {
: _toolbar != null || _spellCheckToolbarController.isShown;
}
/// {@template flutter.widgets.SelectionOverlay.magnifierIsVisible}
/// Whether the magnifier is currently visible.
/// {@endtemplate}
bool get magnifierIsVisible => _magnifierController.shown;
/// {@template flutter.widgets.SelectionOverlay.magnifierExists}
/// Whether the magnifier currently exists.
///
/// This differs from [magnifierIsVisible] in that the magnifier may exist
/// in the overlay, but not be shown.
/// {@endtemplate}
bool get magnifierExists => _magnifierController.overlayEntry != null;
/// {@template flutter.widgets.SelectionOverlay.showMagnifier}
/// Shows the magnifier, and hides the toolbar if it was showing when [showMagnifier]
/// was called. This is safe to call on platforms not mobile, since
@@ -2386,6 +2670,10 @@ class SelectionOverlay {
/// [MagnifierController.shown].
/// {@endtemplate}
void showMagnifier(MagnifierInfo initialMagnifierInfo) {
// Do not show the magnifier if one already exists.
if (_magnifierController.overlayEntry != null) {
return;
}
if (toolbarIsVisible) {
hideToolbar();
}
@@ -2459,8 +2747,26 @@ class SelectionOverlay {
markNeedsBuild();
}
// Whether a drag is in progress on the start handle. This differs from
// `_isDraggingStartHandle` in that it is not blocked by `_canDragStartHandle`.
bool _startHandleDragInProgress = false;
/// Whether the selection start handle is currently being dragged.
bool get isDraggingStartHandle =>
_isDraggingStartHandle || _startHandleDragInProgress;
bool _isDraggingStartHandle = false;
// Whether the start handle can be dragged.
//
// On Apple and web platforms only one selection handle can be dragged
// at a time, so when the end handle is being dragged on these platforms
// the the start handle cannot be dragged.
bool get _canDragStartHandle =>
!_isDraggingEndHandle ||
(defaultTargetPlatform != TargetPlatform.iOS &&
defaultTargetPlatform != TargetPlatform.macOS &&
!kIsWeb);
/// Whether the start handle is visible.
///
/// If the value changes, the start handle uses [FadeTransition] to transition
@@ -2480,6 +2786,10 @@ class SelectionOverlay {
_isDraggingStartHandle = false;
return;
}
_startHandleDragInProgress = true;
if (!_canDragStartHandle) {
return;
}
_isDraggingStartHandle = details.kind == PointerDeviceKind.touch;
onStartHandleDragStart?.call(details);
}
@@ -2491,6 +2801,22 @@ class SelectionOverlay {
_isDraggingStartHandle = false;
return;
}
if (!_canDragStartHandle) {
return;
}
// The handle drag may have been blocked before on Apple platforms and the web
// while the opposite handle was being dragged. Ensure that any logic that was
// meant to be run in onStartHandleDragStart is still run.
if (!_isDraggingStartHandle) {
_isDraggingStartHandle = details.kind == PointerDeviceKind.touch;
final DragStartDetails startDetails = DragStartDetails(
globalPosition: details.globalPosition,
localPosition: details.localPosition,
sourceTimeStamp: details.sourceTimeStamp,
kind: details.kind,
);
onStartHandleDragStart?.call(startDetails);
}
onStartHandleDragUpdate?.call(details);
}
@@ -2508,6 +2834,10 @@ class SelectionOverlay {
if (_handles == null) {
return;
}
_startHandleDragInProgress = false;
if (!_canDragStartHandle) {
return;
}
onStartHandleDragEnd?.call(details);
}
@@ -2539,8 +2869,26 @@ class SelectionOverlay {
markNeedsBuild();
}
// Whether a drag is in progress on the start handle. This differs from
// `_isDraggingEndHandle` in that it is not blocked by `_canDragEndHandle`.
bool _endHandleDragInProgress = false;
/// Whether the selection end handle is currently being dragged.
bool get isDraggingEndHandle =>
_isDraggingEndHandle || _endHandleDragInProgress;
bool _isDraggingEndHandle = false;
// Whether the end handle can be dragged.
//
// On Apple and web platforms only one selection handle can be dragged
// at a time, so when the start handle is being dragged on these platforms
// the the end handle cannot be dragged.
bool get _canDragEndHandle =>
!_isDraggingStartHandle ||
(defaultTargetPlatform != TargetPlatform.iOS &&
defaultTargetPlatform != TargetPlatform.macOS &&
!kIsWeb);
/// Whether the end handle is visible.
///
/// If the value changes, the end handle uses [FadeTransition] to transition
@@ -2560,6 +2908,10 @@ class SelectionOverlay {
_isDraggingEndHandle = false;
return;
}
_endHandleDragInProgress = true;
if (!_canDragEndHandle) {
return;
}
_isDraggingEndHandle = details.kind == PointerDeviceKind.touch;
onEndHandleDragStart?.call(details);
}
@@ -2571,6 +2923,22 @@ class SelectionOverlay {
_isDraggingEndHandle = false;
return;
}
if (!_canDragEndHandle) {
return;
}
// The handle drag may have been blocked before on Apple platforms and the web
// while the opposite handle was being dragged. Ensure that any logic that was
// meant to be run in onStartHandleDragStart is still run.
if (!_isDraggingEndHandle) {
_isDraggingEndHandle = details.kind == PointerDeviceKind.touch;
final DragStartDetails startDetails = DragStartDetails(
globalPosition: details.globalPosition,
localPosition: details.localPosition,
sourceTimeStamp: details.sourceTimeStamp,
kind: details.kind,
);
onEndHandleDragStart?.call(startDetails);
}
onEndHandleDragUpdate?.call(details);
}
@@ -2588,6 +2956,10 @@ class SelectionOverlay {
if (_handles == null) {
return;
}
_endHandleDragInProgress = false;
if (!_canDragEndHandle) {
return;
}
onEndHandleDragEnd?.call(details);
}

View File

@@ -8,7 +8,7 @@ class ImmediateTapGestureRecognizer extends OneSequenceGestureRecognizer {
super.allowedButtonsFilter,
this.onTapDown,
required this.onTapUp,
required this.onTapCancel,
this.onTapCancel,
this.onTap,
});
@@ -16,7 +16,7 @@ class ImmediateTapGestureRecognizer extends OneSequenceGestureRecognizer {
final GestureTapUpCallback onTapUp;
final GestureTapCancelCallback onTapCancel;
final GestureTapCancelCallback? onTapCancel;
final GestureTapCallback? onTap;
@@ -100,8 +100,8 @@ class ImmediateTapGestureRecognizer extends OneSequenceGestureRecognizer {
}
void _cancelGesture(String reason) {
if (_sentTapDown) {
invokeCallback<void>('onTapCancel: $reason', onTapCancel);
if (_sentTapDown && onTapCancel != null) {
invokeCallback<void>('onTapCancel: $reason', onTapCancel!);
}
_reset();
}

View File

@@ -36,9 +36,9 @@ class MouseInteractiveViewer extends StatefulWidget {
this.transformationController,
this.alignment,
this.trackpadScrollCausesScale = false,
required this.childKey,
required this.child,
required this.onTranslate,
}) : assert(minScale > 0),
assert(interactionEndFrictionCoefficient > 0),
assert(maxScale > 0),
@@ -66,6 +66,7 @@ class MouseInteractiveViewer extends StatefulWidget {
final GestureScaleUpdateCallback? onInteractionUpdate;
final TransformationController? transformationController;
final GlobalKey childKey;
final VoidCallback onTranslate;
static const double _kDrag = 0.0000135;
@@ -80,7 +81,9 @@ class _MouseInteractiveViewerState extends State<MouseInteractiveViewer>
final GlobalKey _parentKey = GlobalKey();
Animation<Offset>? _animation;
CurvedAnimation? _curvedAnimation;
Animation<double>? _scaleAnimation;
CurvedAnimation? _curvedScaleAnimation;
late Offset _scaleAnimationFocalPoint;
late AnimationController _controller;
late AnimationController _scaleController;
@@ -95,17 +98,7 @@ class _MouseInteractiveViewerState extends State<MouseInteractiveViewer>
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;
late final ScaleGestureRecognizer _scaleGestureRecognizer;
final bool _rotateEnabled = false;
@@ -450,7 +443,10 @@ class _MouseInteractiveViewerState extends State<MouseInteractiveViewer>
frictionSimulationY.finalX,
),
).animate(
CurvedAnimation(parent: _controller, curve: Curves.decelerate),
_curvedAnimation ??= CurvedAnimation(
parent: _controller,
curve: Curves.decelerate,
),
)
..addListener(_handleInertiaAnimation);
_controller
@@ -477,7 +473,7 @@ class _MouseInteractiveViewerState extends State<MouseInteractiveViewer>
begin: scale,
end: frictionSimulation.x(tFinal),
).animate(
CurvedAnimation(
_curvedScaleAnimation ??= CurvedAnimation(
parent: _scaleController,
curve: Curves.decelerate,
),
@@ -576,16 +572,16 @@ class _MouseInteractiveViewerState extends State<MouseInteractiveViewer>
final Offset global = event.position;
if (_gestureIsSupported(_GestureType.scale)) {
late final shift = HardwareKeyboard.instance.isShiftPressed;
if (HardwareKeyboard.instance.isControlPressed) {
_handleMouseWheelScale(event, local, global);
return;
} else if (shift || HardwareKeyboard.instance.isAltPressed) {
}
final shift = HardwareKeyboard.instance.isShiftPressed;
if (shift || HardwareKeyboard.instance.isAltPressed) {
_handleMouseWheelPanAsScale(event, local, global, shift);
return;
} else {
widget.pointerSignalFallback?.call(event);
}
widget.pointerSignalFallback?.call(event);
}
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
@@ -641,6 +637,8 @@ class _MouseInteractiveViewerState extends State<MouseInteractiveViewer>
_transformer.value,
newFocalPointScene - focalPointScene,
);
widget.onTranslate();
}
void _handleInertiaAnimation() {
@@ -697,6 +695,17 @@ class _MouseInteractiveViewerState extends State<MouseInteractiveViewer>
@override
void initState() {
super.initState();
_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;
_controller = AnimationController(vsync: this);
_scaleController = AnimationController(vsync: this);
@@ -723,7 +732,9 @@ class _MouseInteractiveViewerState extends State<MouseInteractiveViewer>
@override
void dispose() {
_scaleGestureRecognizer.dispose();
_curvedAnimation?.dispose();
_controller.dispose();
_curvedScaleAnimation?.dispose();
_scaleController.dispose();
_transformer.removeListener(_handleTransformation);
if (widget.transformationController == null) {

View File

@@ -124,12 +124,10 @@ class _CachedNetworkSVGImageState extends State<CachedNetworkSVGImage> {
Future<void> _loadImage() async {
try {
var file = (await widget._cacheManager.getFileFromCache(_cacheKey))?.file;
file ??= await widget._cacheManager.getSingleFile(
final file = await widget._cacheManager.getSingleFile(
widget._url,
key: _cacheKey,
headers: widget._headers ?? {},
headers: widget._headers ?? const {},
);
final svg = await file.readAsString();
_svgString = svg;

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